Mason - A Template system for us Perl programmers

Post on 19-Jun-2015

1.107 views 1 download

Tags:

description

Templating Modules are a bit like editors. Every web application developer has a favourite one. And every template system is someone's favorite. Mine is Mason. But not because the Perl MVC tutorials are full of examples using Mason. Not because it's the fastest (use xSlate if you want to trade speed for flexibility). Not because it's the most popular. It's my favourite for a very plain reason: It makes me more productive and allows me to develop web GUIs using all the powerful features a programmer should biterly miss if they are taken away. That includes writing Perl code :P I never heard of a business that went down because they didn't have fast enough CPUs, so I'm not fussy about trading a few CPU cycles for elegance, ease and speed of development.

Transcript of Mason - A Template system for us Perl programmers

MasonA Template System for us programmers

March 2013 - http://www.eteve.net

A bit of myth busting

● Mason is old● Mason is a viewcontroller● Mason is too complex● Mason is slow● Mason uses embedded Perl

Mason is old

Mason 1 is old (HTML::Mason)

Mason 2 released in 02/2011

Complete Moosified rewrite

Mason is a viewtroller

Kind of true for Mason 1

But that was 10 years ago

Mason 2 is a pure templating system

The controller bit has been moved to Poet

Mason is too complex

AugmentationInheritanceCompositionMethod modifiersDynamic filters

Mason is too complex

AugmentationInheritanceCompositionMethod modifiersDynamic filters

But wait..

We are programmersSo it should be just fine

Mason is slow

Hum, well...

Compared to TT (as complex as TT can take benchmark)

Rate Mason TTMason 909/s -- -53%TT 1923/s 112% --

Mason is slow - but

Power comes with a price tag

Experience shows it scales very well with complexity

Mason is slow - but

Power comes with a price tag

Experience shows it scales very well with complexity

The benchmark I wrote is not bigfat.Have a look:

https://bitbucket.org/jeteve/mason-pres/

Mason is slow - but honestly

Do we use Perl because it's faster than X?

Mason uses embedded Perl

Prefer PHP?

Mason uses embedded Perl

Or worse, a mini-language ?

Mason uses embedded Perl

Perl is just fine no?

use <Anything you like>;

The basics - Embedded code

% while( my $product = $products->next() ){ <p>Buy our great <% $product->name() | H %>. It's only <% $product->price() | H %> </p>% }

With DefaultFilter set to H:<% $product->name() %>

And now, some cool Mason stuff

Header

Main content

Footer

Augmentation - A typical page

Augmentation - Non Mason-ish

/index.mc<& /comp/header.mi &>Welcome to index!<& /comp/footer.mi &>

Augmentation - The Mason Base.mc

/Base.mc<%augment wrap> <html><head><title>Site</title></head> <body> <% inner() %> <body></html></%augment>

Augmentation - a page

/index.mc<h1>Welcome</h1>

Augmentation - a page

Evaluates to <html><head><title>Site</title></head> <body> <h1>Welcome</h1> <body></html>

Augmentation - another page?

/products.mc<ul> <li>Lathe</li> <li>Piano</li></ul>

Augmentation - another page?

Evaluates to ..

Well you get the idea

Augmentation - Become specific

/products/Base.mc<%augment wrap> <div class="product"> <% inner() %> </div></%product>

Augmentation - Become specific

/products/lathe.mc<h1>Lathe</h1>

Augmentation - Become specific

Evaluates to<html> <head><title>Site</title></head> <body> <div class="product"> <h1>Lathe</h1> </div> <body></html>

Augmentation - Wanna go bare?

/products/baremetal.mc<%flags> extends => undef</%flags><h1>Some bare content</h1>

Evaluates to<h1>Some bare content</h1>

A typical layout

/index

Header

Menu

Content

Footer

A typical layout

/products/lathe.htmlHeader

Menu

Share BoxProduct description

Footer

A typical layout

/errors/404.htmlHeader

Menu

Error description

Footer

Augmentation - Layout control

Typical hierarchy/layout/Base.mc/layout/withmenu.mc/Base.mc/index.mc/products/Base.mc/products/lathe.mc/errors/Base.mc/errors/404.mc

Augmentation - Layout control

/layout/Base.mc<%flags> extends => undef</%flags><%augment wrap><html> <head><title>My site</title></head> <body><% inner() %></body></html></%augment>

Augmentation - Layout control

/layout/withmenu.mc<%flags> extends => undef</%flags><%augment wrap> <div class="menu">...</div> <% inner() %></%augment>

Augmentation - Layout control

/Base.mc<%flags> extends => '/layout/withmenu.mc'</%flags>

Augmentation - Layout control

/index.mc<h1>This is index</h1>

Augmentation - Layout control

/products/Base.mc<%augment wrap> <div class="share">...</div> <div class="product"> <% inner() %> </div></%augment>

Augmentation - Layout control

/products/lathe.mc<h1>This is a lathe</h1>

Augmentation - Layout control

/errors/Base.mc<%flags> extends => '/layout/Base.mc'</%flags><%augment wrap><div class="error"><% inner() %></div></%augment>

Augmentation - Layout control

/errors/404.mc<h1>Sorry, nothing here</h1>

Actually, I want the menu in the error pages

No problem:/errors/Base.mc<%flags> extends => '/layout/Base.mc'</%flags><%augment wrap><div class="error"><% inner() %></div></%augment>

And now, inheritanceAnd method modifiers

Let's speak about page titles

Remember /layout/Base.mc ?<%flags> inherit => undef</%flags><%method title>Site</%method><%augment wrap> ... <title><% $.title() %></title> ...</%augment>

Now the index title

<%method title>Welcome to Site!</%method><h1>This is index</h1>

The lathe.mc

<%after title> - Lathe</%method><h1>This is a Lathe</h1>

Renders as:<title>Site - Lathe</title>

a_product.mc

<%class> has 'product' => ( isa => 'My::Product', required => 1 );</%class><%after title> - <% $.product->title() %></%after><h1>This is a <% $.product->title() %></h1>

a_product.mc

In Catalyst

$c->stash()->{product} = $product;

a_product.mc instance is built with what's on the stash

a_product.mc

Trivial unit testing

my $mason = Mason->new();

my $p_page = $mason->run('/products/a_product', product => $p )->output;

ok_contains($p_page , $p->name() );

CompositionAnd a bit more

Classic composition

/comp/share.mi<%class> has 'stuff' => ( does => 'Sharable' );</%class><div class="share">Share <% $.stuff->share_name() %> on blabla</div>

Composition - New /products/Base.mc

/products/Base.mc<%class> has 'product' => ( isa => 'Product' );<%augment wrap> <& /comp/share.mi , stuff => $.product &> <div class="product"> <% inner() %> </div></%augment>

Internal components are components

So inheritance works

/comp/share/advanced.mi<%flags> extends => '/comp/share.mi'</%flags>Advanced share <% $.stuff->share_name() %>

Internal components - Unit testing

my $m = $mason->_make_request();my $m_text = $m->scomp('/comp/share.mi', p => 'Banana' );

ok_contains($m_text, "Share Banana");

FiltersMore powerful than it sounds

A Simple filter

% $.NoBlankLines {{Hello

World% }}Renders:HelloWorld

With argument(s)

% my $i = 1;% $.Repeat(3) {{<% $i++ %> \% }}

Renders:1 2 3

With parametric content

% my $nums = [ 1 , 2 , 3 ];% $.Loop($nums) {{% my ( $num ) = @_;<% $num %>, \% }}

Renders:1, 2, 3,

Filters - Make your own

package My::Mason::Filters;use Mason::PluginRole;method Iterate($on){ Mason::DynamicFilter->new( filter => sub{ my ($yield) = @_;my $txt = ''; while( my $next = $on->next() ){ $txt .= $yield->($next); } return $txt; });}

Filters - Make your own

/Base.mc<%class> with My::Mason::Filter;</%class>/anywhere% $.Iterate($resultset){{% my ($result) = @_; This is result <% $result->name() %>!%}}

Filters - As component

/comp/pager.mi<%class> has 'on' => ( isa => 'DBIx::Class::Resulset' ); has 'page' => ( isa => 'Int' , default => 1 ); has 'yield';</%class>% my $rs=$.on->search({} , {page => $.page});<div class="pager">.. Do stuff with $rs->page() ..</div>

Filters - As component

/comp/pager.mi .. Continued

%# Some paging was output% $.Iterate($rs){{% my $stuff = $_[0]; <% $.yield->($stuff) %>% }}%# Some other paging maybe?

Let's use our pager

/products/index.mc<$class> has 'products' => ( isa => 'DBIx::Resultset' );</$class>% $.CompCall('/comp/page.mi',on=>$.products){{% my ( $product ) = @_; Product <% product->name() %>% }}

Filters can be curried<%class> has 'lang'; has 'Translate' ( lazy_build => 1 ); sub _build_Translate{ my ($self) = @_; $self->TranslateIn($self->lang));}</%class>% $.Translate(){{Some Text% }}

Filters - One more nice one% $.Cache($key , '10 minute') {{ <div class="sluggish"> .... </div>% }}

Combine with currying for cleaner code :)

ConclusionsIn case you're not convinced yet

Mason helps writing clean code

Strict by default

Enforce type consistency a-la Moose

Enforce stash content (With Catalyst)

Minimal global variables

Easy unit testing

Mason is powerful

Code the view like a programmer with inheritance, augmentation, method modifiers, dynamic filters, currying ..

No change of mindset required

Changes are simple and consistent

Writing a new pages is trivial

Mason is extensible

Base class injection.

my $mason = Mason->new(base_*_class => 'MyApp::Mason::*Subclass' );

Or Role based Plugins (and Plugins Bundles)

For instance: Mason::Plugin::Cache

Reference

In order of preference● MasonHQ: http://www.masonhq.com/● Cpan: Mason● Cpan: Catalyst::View::Mason2● Mailing list● Jerome :P

Give it a go

$ sudo apt-get install libmason-perl$ echo "Hello world" > mason/index.mc$ mason.pl mason/index.mc

$ ## In catalyst$ sudo cpan -i Catalyst::View::Mason2

Thanks for your watching!

Questions?