24 6 / 2014

Template::Mustache Module in Perl 6

November Yak Shaving

I started Template::Mustache as a quick little hack. I have a goal of reviving the November wiki, relying as much as possible on the growing modules.perl6.org ecosystem. I started working with Web::App::Ballet, and got distracted by the supported template options, none of which I’m fond of. I thought the Mustache format would be perfect, and decided to throw it together. It’s logic-less, and should be pretty simple, right?

Actually, one of the great things about Mustache, which I was completely unaware of when I started, is its thorough spec test suite. It makes test-driven development a piece of cake when all the tests are already written! On the other hand, I was unwilling to implement a stripped-down version that wouldn’t pass all the tests, so the final project became more ambitious that I’d originally planned. It’s a good thing, because it has forced me to learn a bunch more about Perl 6, and I hope I can pass some of that on to you here. Yak shaving at its best.

Template::Mustache

Usage is best described in the mustache(5) man page, the Template::Mustache README, and the above-mentioned spec tests.

Here’s a sample:

use Template::Mustache;

my @people =
    { :name('James T. Kirk'), :title<Captain> },
    { :name('Wesley'), :title('Dread Pirate'), :emcee },
    { :name('Dana Scully'), :title('Special Agent') },
    ;
my %data =
    event => 'Masters of the Universe Convention',
    :@people,
    ;
my %partials =
    welcome =>
        q:b{Welcome to the {{event}}! We’re pleased to have you here.\n},
    ;

Template::Mustache.render(q:to/EOF/,
        {{> welcome}}

        {{> roster}}

            Dinner at 7PM in the Grand Ballroom. Bring a chair!
        EOF
    %data,
    :from([%partials, './views'])
).say;

Combined with the rosters.mustache template, this produces:

Welcome to the Masters of the Universe Convention! We’re pleased to have you here.

Our esteemed guests:
- The honorable James T. Kirk (Captain)
- The honorable Wesley (Dread Pirate) *TONIGHT’S EMCEE*
- The honorable Dana Scully (Special Agent)

Enjoy!

    Dinner at 7PM in the Grand Ballroom. Bring a chair!

While the module is mostly complete at this point, and passes all of the spec tests, there may yet be a change to the calling convention once I implement getting data directly from any objects, not just hashes. In particular, I think I may convert Template::Mustache itself into a role, which can easily be added to any object or class.

Lessons Learned

I won’t get to them all in a single post, but I want to share a few lessons learned through this. The first one is, spend a bit of time with the rakudo Perl6 Grammar (and associated Actions). There are so many good tricks used there, and it helped me a lot to have concrete, full-scale examples of pretty much any Grammar features I could want to use.

At first a lot of it felt too complicated and I didn’t understand what the bits were doing. But going back and forth between Synopsis 05, Grammar.pm and Actions.pm quickly cleared most of it up.

One feature that is new to me, which I really like, is the proto regex. It allows me to write separate, specific rules to match different kinds of template construct, but then treat them all similarly elsewhere in the code.

I think I am not quite understanding backtracking control sufficiently. I really don’t like how the <linething> regex is defined and used, for example. It parses all the way through it’s sub-thing, and then if it finds a non-whitespace it fails, returns to where linething started, and will re-parse as a normal thing. I haven’t traced it, but I think this is double-parsing every {{foo}} thing that starts a line! Clearly there must be a better way, but I haven’t thought of it yet. (Other than doing .lines() on the template at the beginning and parsing one line at a time, but I want to avoid that if I can.) Despite this, it works, and is fast enough for now – thanks, Rakudo! Caching will help (not yet implemented), too, for long-running processes.

Documentation

I decided to not shave another yak by writing Pod::To::Markdown.pm, so I generate the docs as plain text. It’s a little sloppy, and a little boring, but it gets the job done. It seems that much of the Pod handling is incomplete, so this is an area where someone relatively new could come in and make some good improvements. I’d actually like to see Pod::To::Text generate Markdown (or RsT, asciidoc, or whatever) directly. Markdown is probably the simplest and most like plain text. Why create an ad-hoc write-only format instead of using something general?

Well, that’s all for now, I hope this has been interesting. Let me know if there’s anything you’d like to know more about in my next post.