Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

The history of a templating engine

by jimt (Chaplain)
on Sep 11, 2006 at 21:19 UTC ( [id://572402]=perlmeditation: print w/replies, xml ) Need Help??

This is a long read, and may or may not be interesting. But I figured I'd write it up anyway since someone else may find it interesting or informative.

4 1/2 years ago I bought the domain JimAndKoka.com for my wife as a birthday present. I then proceeded to let it rot for another 6 months before I finally did anything with it.

I had "homepages" set up back when I was in college, because that was the hip thing to do back then. Alas, I was also a putz in college and my programming exposure was pascal with a little C++. I had minimal graphics skills, nominal javascript, and a passable amount of HTML. In short, my pages sucked. There was a fair bit of content (a lot of it cobbled together off of the rest of the internet), but managing it was a bitch.

This time, this time would be different. I was a professional perl programmer, I'd built all sorts of big, interactive websites and apps, I knew databases. I could build an awesome, dynamic website with easy to manage and update content. It'd rock. Chicks would flock to me if the domain didn't make it blatantly obvious I was married.

Naturally, there are some technologies needed for an interactive website. I already had the nascent components of Basset formed, so of course I was going to use that. That was the M part of my MVC website, now I just needed the other two. For the controller layer, I just used simple CGI scripts, with the acknowledgement that I'd eventually move to something better (I finally did last year). For the view layer, I needed a templating system. I originally looked at and used HTML::Template, but it didn't work quite the way I wanted. I tried to go with HTML::Mason, but it looked like a big pain in the ass to install and run in a non-mod_perl environment. Besides, I'd gotten burned a few times by the volatile API back then, so I was wary.

Instead, in a fit of hubris and laziness, I decided to write my own system, which is what turned into Basset::Template. Right now it's fast and robust and extremely useable. This is that story, as best as I can remember it. I consider it a success story, but I wanted to re-tell it to give the budding template engineers out there an idea of what they're getting in to.

This project and its development history

The running joke in web circles is that sooner or later everybody writes their own templating system. The progression is normally along these lines:

  1. I need to embed variables. I'll use a special syntax like %%VARNAME%%. That's all I need to do.
  2. Oops, I sometimes need to vary the display content. It sucks to do this in my CGI script, so I'll add a conditional construct.
  3. Oops, building tables sucks, since I need to do it all in my CGI. I'll add in some simple looping constructs.
  4. Oops. My lexically scoped loop index variable clobbers the global. err, I'll add in a new syntax for it "%%%VARNAME%%%" for variables in a different scope.
  5. Oops. I need to embed a template. I'll add in a new INCLUDE directive.
  6. Oops. The preprocessor clobbers the scope of the embedded template variables so I'll add in a new directive ">>%VARNAME%<<" for variables specifically inside embedded templates.
  7. Repeat ad nauseum.

If you're lucky, you'll end up with something like Template::Toolkit at the end. If you're not, you'll end up with one of the myriad homebrew solutions littering so many websites. The ones where the dev staff grits their teeth when they talk about it and swear that they'll get rid of it someday, if they can, but rarely do because the cost is so high.

Instead of that route to madness, I took a different approach and simply opted to embed perl into my templates. I really liked Mason's syntax, but had already opted not to go with it. HTML::Template and Text::Template but they rubbed me the wrong way. Mason made sense, so I wanted something like that.

I initially waffled about it, since I didn't really want to go to the hassle of building a templating system and building a parser and all that other crap that goes into it. So I stuck with HTML::Template for a few months before I had my great flash of inspiration.

A template is just a program, but you quote the code, not the output.

For example, Template: <table> %% foreach my $row (@rows) { <tr> %% foreach my $cell (@$row) { <td><% $cell %></td> %% } </tr> %% } </table> CGI: print "<table>\n"; foreach my $row (@rows) { print "\t\t<tr>\n"; foreach my $cell (@$row) { print "\t\t\t<td>$cell</td>"; } print "\t\t</tr>\n"; } print "</table>\n";

So all you needed to do was reverse the quotes. Easy!

Well, not easy. There were a few additional caveats I had off the bat. Such as, some snippets of code needed to output a value, some just needed to execute code (such as a loop directive).

Okay, that was easy. I added two sets of syntax to it, <% $value %> (standard ASP or Mason or etc variable embed tags) and % at the start of a line to indicate something that needed to be executed, but had no output (similar to Mason's % at the start of a line, except mine allowed leading whitespace and Mason's didn't (at least not at the time, I hadn't kept up on it)). So all I needed to do was add an additional pre-processor to turn the <% % > tags into % tags to redirect to STDOUT.

That was keen, but it immediately introduced another issue - the template always output to STDOUT, I couldn't capture the output and spit it out somewhere. Yes, I could've redirected STDOUT to something and capture it, but that would've been a nuisance. I also wasn't sure if it would be error prone, maybe with threads or other output or somethingI also sure didn't want to require someone else to do the capture and redirect, so I needed a different approach.

I opted for a different filehandle, which I just called OUT. I'd then capture and redirect its output as desired.

Next issue I had was how to get variables into it. My templates needed data to display, after all. But...what's "into" the template? The template doesn't exist as executable code, so where should it live?

I didn't want to pull it into the calling package's namespace, because I wasn't sure if the variables would stomp on each other. In fact, that quickly expanded to not wanting to pull it into any calling package, for fear of clobbering something.

So I built up a complicated internal method to generate what I assumed to be a reasonably safe namespace for the template. It's multiple namespaces deep in the symbol table and uses the name of the file to construct it, with the hopes of having something unique that other templates couldn't clobber, or the user having managed to use himself.

And while I was at it, I used a similar approach to generate an arbitrary scalar value to toss the template data into. Now I didn't need to worry about the filehandle at all.

It was then just a simple matter of writing an importer to import passed variables into the new namespace of the template. All said, I think I reached this point in development after around a week or so. It was pretty powerful and fast enough, and only took a week. That's a quick ROI, and no doubt one of the reasons that homegrown systems are so popular. Why spend a week learning something when you can spend a week writing your own?

Next, just reverse the quotes. Go through and change all <% %> quoting to %% quoting, then flipflop all of those so the code was unquoted and the output was. It's fairly easy. That then gives you a big string with a program in it, living in a particular namespace. Just eval it and look at the magic scalar for the output and you're done.

Except it wasn't. There are all sorts of oddball little edge cases that pop up.

  1. What if you have "<% $variable %>"? You need to add a trailing semicolon when you print it.
  2. What if you have "% $x++ ?" You need to add a trailing semicolon.
  3. What if you want to do extra processing in a <% %> tag? Say something like: <% $text =~ /^(.{0,$length}\w)\b/s; $1 || $text > (yes, the example may be a bit contrived, but I had it for a while. I wanted to truncate some things but didn't have a good place to put the code)

Yeah, yeah, I could've just made demands on the code to require you to add semi-colons or only have the value you're going to output in the code, but I didn't like that. So I added in code to add semicolons and allow additional text before my return value.

The initial pass broke horribly because I forgot that there were cases where you didn't want to add a semicolon (such as %% foreach my $val (@array) {), so I wrote up a more advanced parser to deal with those cases.

I then ran it as is for a few months.

The first stumbling block I ran into was when I wanted to use a sprintf in my code. Whoops. The pre-processor was looking for any % anywhere on the line as the start of a code block, so it was goofing up royally. I hastily changed my default code delimiter to be '%%', knowing that it would break on that string (such as a literal % in a sprintf statement), but it was less likely to occur, so an acceptable risk. A few months later I further tweaked it so that %%s weren't an issue at all.

The next problem was that I ran into the (simplified) case <% ";" %> Well, my simple little preprocessor would assume that you wanted to execute the code "; and then output the code ". Obviously, Perl disagreed, so I needed to beef up my parser. Incidentally, that version of the parser is the one that's on the current release, but it only handles the cases of ";" and ';' and ignores q{;}, qq{;}, qw{;} and the like. It's fixed internally, but more on that later.

So it was running fine, but was pretty slow. I needed to speed it up. The big bottleneck was that pre-processing step to flip the quotes. But, I realized, the pre-processing should always be the same (unless the template has changed), so I can just cache it to disk. I implemented a caching scheme to store it on disk and then as the first step of pre-processing, look to see if a valid one already exists. If it does, great - use it. if not, then pre-process and re-cache. Come to think of it, this may have been when I implemented the deep nested packages approach, so I could have something easily written to disk that would always come out of the cache the same way, but this was years ago, so I'm foggy on the details.

I now was caching stuff and running along very fast. Awesome.

But it really sucked to have large code blocks in my templates. Since I'd chosen to use %% to start code and "\n" to end code, I had to prepend large codeblocks with strings of %%s. This sucked. So I added an additional set of tags - <code> and </code> to delimit large blocks of code.

Next I wanted to embed templates. By this point, I had large blocks of common code duplicated all over my website, and I wanted to get rid of that. So I needed to embed them.

I opted for <& &> for my embed syntax and quickly determined that I wanted two different approaches - sometimes it'd be keen to hand in additional variables to the subtemplate, and sometimes it'd be keen to just have it inside the calling template's environment and inherit everything currently in scope. I'm sure I had a really really good reason for it at the time, but I can't quite remember it now.

<& /path/to/other/template.tpl { 'arg1' => \'val1', 'arg2' => \'val2' } &> or <& /path/to/other/template.tpl &>

So that was created with different rules to allow for the two different approaches. It also unearthed a nuisance - passing in variables always had to be done by reference. It had always been there, but hidden down a few layers so I rarely encountered it. The second issue was the key value was a simple string without a sigil. So I couldn't pass $msg and @msg into the same namespace, it wouldn't allow it, I'd have to alias one.

This lead to me allowing simple scalar values to not be passed by reference and allowing an optioanl sigil on the variable key.

It also introduced the problem of needing to know exactly where these templates live. Previously, with just a single template, only its cgi needed to know where it was, and it was usually in the same directory. But now? Templates could talk to templates in other directories? How does it find them? Relative paths? Relative to what? Where does this template exist, anyway?

This lead to the addition of a template root to allow for absolute paths that weren't server dependent (paths from the server's root would've been horrible, since I needed it to work in different directory locations on my machine vs. my host). That way, I could always use absolute paths and have them work, but not worry about portability.

As my templates were getting more complex, around this time I also added in template comments <# comment #> to allow for better documentation. They're better than HTML comments because the processor would strip them out before display to the user.

As a further attempt to optimize, I added in an optional flag to compress whitespace, since it's not necessary in HTML anyway.

Since this was adding in a lot of additional stuff to the template, I added in a simple debugging syntax as well, above and beyond doing %% print STDERR...;, especially since it theoretically decoupled the debugger from STDERR w/o resorting to redirection of the handle. Note that I've never implemented anything like that in practice, it always just goes to STDERR.

It then ran and worked for a while.

I introduced another optimization and allowed embedded templates to have their pre-processed value stored in the pre-processed parent - <<%+ /path/to/template.tpl +&>. This skipped the step of firing up another template object, looking for a pre-cached copy, and returning and executing it. It just all ran inline in the parent as if it was always there. But, it lost the ability to look for changes in the subtemplate. So if you changed the subtemplate, the supertemplate wouldn't automatically reflect. You needed to blow away the cache or flat the supertemplate as changed. Naturally, this is an optimization to be used sparingly - either when speed really really really counts or when the embedded template rarely (if ever) changes.

But there was a lot of stuff that I ended up duplicating a lot. Such as HTML escapes or URL escapes. This was done by exposing the template object in the template to call methods on it. <% $self->html_escape($value) %>. But I didn't like having to put it in all the time, so I finally diverged from my "pure perl" approach by adding in pipes.

The pipe syntax is easy - just end off a variable with a pipe and a pre-defined token (extensible via subclasses) to internally translate into a method call. Instead, I'd now just type <% $value | h %>. Much simpler, but I never really liked the break from being true perl. I justified it to myself figuring that the | h was part of the template quoting not the perl, but never really convinced myself.

Recently (within the last week - not yet released)), I started needing a few additional things. First of all, I finally fixed the bug with the <% q{;} %> (ed- fixed typo) syntax. I spent about a day mulling over it and trying to think about how to fix it with a fancy parser or something, but wasn't convinced I could catch all cases. Instead, I opted to wrap up everything in those tags inside an anonymous subroutine, execute it, and display the results. This naturally takes advantage of perl's functions not requiring the "return" keyword.

update - I took advantage of Jenda's awesome suggestion down below and updated the internal build to use do {} blocks instead of anonymous subs. It loses the ability to type <% return $val %>, but I can live with it, it's a heck of an improvement otherwise.

I also had another problem - and that was that I had no way to generate snippets of text and pass them around in the template. I'd need to do that in the CGI layer with an extra method then hand in the pre-processed data to the template. But this didn't scale well (for example, loops in the template). Besides, I now had my output coming from two different places.

So this called for anoter divergence from perl syntax as I added in an embed redirect - <& /path/to/template.tpl >> $variable &> to instead stuff the output into $variable, which could then be handed around as desired.

This brings us up to current, where my "simple little template package" stands at 1,345 lines (counting documentation) and has been actively worked on for 4 years.

Things to take away from this project.

  • I actively work on and develop this thing because it's a hobby for me. I enjoy working on it, I have a fair bit of infrastructure invested in it. But, there has been some percentage of my time for the past 4 years that I've spent developing it. Anyone else that looks at it would need to be trained on how it works. Any bugs that pop up in it or new features I need are my responsibility to take care of.

    If you go off and build your own templating system, you could very well end up in the same situation. And it may not be cost effective for you. Joel Spolsky wrote in one of his columns that by all means you should build your own copy of a piece of software if it will give you a competitive advantage. If it doesn't give you an advantage, you shouldn't bother - use something off the shelf and spend your time on something that will give you a competitive advantage.

    Do I think this gave me a competitive advantage? Tough to say, since I have no competitors in the "Jim's homepage" arena that I'm aware of. But, it does work exactly the way I want and does exactly what I want. In the cases where it doesn't do what I want, I can usually change and update it. So maybe it's kept me happier and more interested in working on my webpage than I would've been otherwise.
  • If you're going to build your own templating system, treat it like production code, since it is. Further, treat it as if it's code that's going to be released and work on it the way you'd expect other open source things to be worked on. Don't treat it as your internal toy that can be modified and hacked as desired just to get one thing in - you've got to really invest time and effort into designing, re-factoring, and developing to make sure it's consistent and easy to work with. If you don't, it will quickly buckle under its own weight.
  • Even if you can justify building your own system by the productivity argument up above, remember that you are spending additional time and resources on a brand new internal project that won't directly make you money. Unless you get rid of it, the development will never go away. It may tone down or go on hiatus, but eventually it'll need work done on it. This will extend deadlines and keep you away from software that makes you money. It's a great hobby for my free time. I wouldn't have done this for work. Note that I would be comfortable using Basset::Template for work now, but I wouldn't have started developing it for a job.
  • If the guy that wrote your templating system leaves, you're in trouble. I don't care how easy it is (please, make it easy!), there will be more of a learning curve for new people than with a standard template. Plus, if the original developers leave, you may learn that it's difficult to support and extend the code, which increases your cost. Note that I didn't need to worry about this anyway, since I'm a one man shop on this "project".
  • I do consider this module a success. It's fast, easy to implement, easy to extend and enhance, and powerful in what it does. If I had to do it over again, I would've done it the same way. Your mileage may vary.
  • I'm not trying to advertise my own template. Use whatever you want. Other stuff on CPAN rocks. Use what you're comfortable with. Remember, there's always a cost involved in switching templating engines, so jump technologies sparingly.
  • I'm not trying to downplay someone from writing their own template, just trying to paint a picture of what you may be getting into if you try. I got lucky and my simple little template system caught my interest and has kept me working on it over the years, and I've been willing to devote the time to working on it. It could have easily gone the other way. Just be aware of what you're getting into.

Replies are listed 'Best First'.
Re: The history of a templating engine
by graff (Chancellor) on Sep 12, 2006 at 02:14 UTC
    Wow, that turned out to be a very engaging read -- thanks.

    I think my strongest reaction involved this point, and all the places you went as you pursued it:

    A template is just a program, but you quote the code, not the output.

    Well, not exactly. Based on the established wisdom of separating data/content from code, a template is a way of representing the content in a manner that is compatible with, yet still separable from, the code. A lot of stuff is needed in the code that really just does not belong in the template, just as there's a lot of stuff in a template that does not belong in code.

    Your various gyrations in templating syntax, to support a dizzying assortment of coding constructs within the template data, all end up (from the sound of it) as an elaborate way to obfuscate the displayable content of the template files. But if it cooks for you, hey, who am I to knock it? ("++" in any case)

    I realize you've only given a very cursory description of some of the details, but I had to wonder what the documentation must be like for such a system, and how long it would take someone besides you to grok it.

      I'll agree with your statements about separation of display from other things and stuff being in templates not in controllers and vice versa. I simply meant that conceptually, reversing the quotes is an easy way to make it work.

      And yes, as another reply commented, this is about Basset::Template, which is pretty extensively documented (I think), and actually a pretty simple system, despite all the gyrations I've rattled off here. So it should be easy for other people to pick up, if you were curious and wanted to look at the docs. But, again, the only one using it is me, so I don't know how easy it is to understand in the real world.

      It's the old line about how in theory, things are the same in theory and in practice, in practice they aren't.

      I had to wonder what the documentation must be like for such a system
      Isn't this Basset::Template?
Re: The history of a templating engine
by ruoso (Curate) on Sep 12, 2006 at 08:46 UTC

    I don't know, but maybe Template Engine is something like Office Software... before OpenOffice we had a lot initiatives in developing word processors and spreadsheet software, but after that there are just OpenOffice and some few-featured word processors... It's hard to think about creating a new Word Processor from scratch...

    Maybe Templating systems reached the same state of art, we have Template::Toolkit as the full-featured-all-included suite for template management and some few-featured Template Processors like HTML::Template or Text::Template... In fact, if someone considers starting a new Template processing engine from scratch nowadays, this one would give a lot of work for a psicologyst...

    daniel
Re: The history of a templating engine
by Jenda (Abbot) on Sep 12, 2006 at 07:54 UTC
    First of all, I finally fixed the bug with the <% q; %> syntax. I spent about a day mulling over it and trying to think about how to fix it with a fancy parser or something, but wasn't convinced I could catch all cases. Instead, I opted to wrap up everything in those tags inside an anonymous subroutine, execute it, and display the results. This naturally takes advantage of perl's functions not requiring the "return" keyword.

    Maybe I misunderstood, but this looks like your templates will spend lots of time calling those anonymous subroutines, whould not

    <% some; code; y %> => print OUT do {some; code; y};
    suffice?

Re: The history of a templating engine
by powerman (Friar) on Sep 12, 2006 at 20:42 UTC
    Very nice article, but I must say you overcomplicate things. I've developed similar template system, used by group of freelance programmers in last 5 years. For me it looks like you make few wrong design decisions and thus result in lot of additional work and problems.

    Please don't take my comment as "I'm rock, you suck", I'm just wanna show design differences between our systems to prove this task can be solved much ease.

    My system has same features as yours, except 'include template' feature and 'caching' feature (my parser is small and fast so I just don't need caching for now).

    My template syntax was designed to work well in WYSIWYG-editors like Dreamweaver. For code which should be executed without outputing anything I've used <!--& ... --> constuct, so it looks like html-comments for such editors. For code which should insert something into html I've used three different construct: @~...~@, ^~...~^ and #~...~#, so web designer is able to see: here will be inserted some text.
    Only difference between them is escaping: @~~@ add html-escaping, ^~~^ add uri-escaping and #~~# leave text as is for case I need to generate html tags from my code (bad idea usually - because make web designer work too hard - and used rarely).
    That's all, only 4 special tags!

    I've also used eperl idea "A template is just a program, but you quote the code, not the output.", but for solving ';' issue I've used do{}, as Jenda proposed in previous comment.

    My module has many other features in addition to template processing (outputing die() message to browser, sending files to browser, sending redirects, reloading changed files from disk (for mod_perl environment), supporting correct line numbers for error messages).
    And all these tasks solved in 150 lines of code (parser itself is 30 lines), less than 6KB.

    Some other notes:

    • namespace issue I've solved by using no global variables (as mod_perl recommend), this way everything works fine in 'main::'
    • instead of using 'print' I've added text into some variable (accumulator), this solve STDOUT/OUT issue
    • no problem with non-scalar variables and references because I can use any my() scalars/arrays/hashes
    • to comment my perl code I use usual #-comments inside <--& ... --> block, no need for special tag because that perl block will not be included in generated html anyway
    I'll not include here code examples, this comment already too long, but you can download my POWER::iCGI-2.73 module from my site http://powerman.asdfgroup.com/Projects/-POWER-ARCHIVE-/ and check it code.

      Just so there isn't any confusion, I'll hit a few of these points...

      For code which should insert something into html I've used three different construct: @~...~@, ^~...~^ and #~...~#, so web designer is able to see: here will be inserted some text. Only difference between them is escaping: @~~@ add html-escaping, ^~~^ add uri-escaping and #~~#

      See, this is what I would consider to be a bad design decision. This completely ties the templating system into HTML and with different syntax for different output. What happens if you want to use your template for something other than HTML and need a different type of escaping? What if you want to escape your HTML in different ways?

      For example, internally on my site, I have a blogger style syntax processor. So I can type ~some text~ and it'll translate to some text automatically for me. I hate typing HTML all over the place. With your approach, to do something similar, you'd need to add in some sort of new embed syntax, or wrapper up a new function. With my approach, it uses pipes (ala unix) for the same effect - <% "~some text~" | blog %>. Sure, you define a new function, but you don't need new syntax to add new functionality.

      Incidentally, I also made all of my template tags user configurable with no speed hit. So if you want to use something else, it's simply changing a value in a config file.

      That's all, only 4 special tags!

      Basset::Template has 7 special tags. 3 of are unquestionably syntactic sugar (the comment tags, the "big eval" tags, and the debug tags), leaving the return tags, eval tags, and include tags (and the cached include tags, but that's an optimization syntactic sugar). And I'm sure you don't have a problem with sugar, since the 3 different insert styles you have are arguably just sugar as well. :-)

      I've used do{}, as Jenda proposed in previous comment.

      This was an awesome suggestion (thanks Jenda!) and I have added it to the internal builds. Admittedly, it does prevent the user from typing in <% return $value %>, but I figured that it was a worthwhile modification to get rid of the anonymous subs.

      150 lines of code (parser itself is 30 lines), less than 6KB.

      Even if I rip out the documentation, examples, sugar, and unique features, i still only get down to 405 lines, so I'll give this one to you.

      • namespace issue I've solved by using no global variables (as mod_perl recommend), this way everything works fine in 'main::'

        The globals are used for importing into the package that the template appears in. Otherwise, I didn't have a way to send variables into the template. I wouldn't mind hearing a non-global approach.
      • instead of using 'print' I've added text into some variable (accumulator), this solve STDOUT/OUT issue

        As said in my original post, the filehandles are long gone, instead accumulating into a scalar. Though "print OUT" still exists for backwards compatability (not that I use it).
      • no problem with non-scalar variables and references because I can use any my() scalars/arrays/hashes

        I have no problem importing non-scalars or refs, you just need to hand in a reference to what you want to the processing engine. I'm willing to bet you need to do that, too.
      • to comment my perl code I use usual #-comments inside <--& ... --> block, no need for special tag because that perl block will not be included in generated html anyway

        Basset::Template could have comments as:
        %% # this is a comment or <% # this is a comment %> or <# this is a comment #>
        it's all pure sugar.
        See, this is what I would consider to be a bad design decision. This completely ties the templating system into HTML and with different syntax for different output. What happens if you want to use your template for something other than HTML and need a different type of escaping? What if you want to escape your HTML in different ways?

        Agreed. I've in TODO list this feature:
        - move parser from POWER::iCGI module into separate POWER::Template module and make it configurable a-la (shown interface is just an example of needed features, not proposed module interface):

        use POWER::Template; my $t = POWER::Template->new(); # example configuration to simulate POWER::iCGI: $t->eval('<!--&', '-->'); $t->text('@~', '~@', \&Enc); $t->text('^~', '~^', \&EncUri); $t->text('#~', '~#', undef);
        This way parser become 100% configurable, will have same 30 lines of code or even less, and my templates will use "tags" and escaping/converting features suitable for each format - one in html template, other in email templates, etc.

        UPDATE: And don't forget, I don't write these comments to compare my template system to yours, I just wanna prove this task is much simpler than you affirm.

        I'm looking at rolling my own template system as well. I had that "switch the quotes" moment as well, but without actually coming up with words for it.

        Anyhow, here's how I intend to handle the data thwomping problem, as well as STDOUT. Templates are to be mangled into:

        use UrsusTemplate::tmpl; $tmpl_sub{$tmpl_path} = sub { my $out = ''; <i>some Perl</i> $out .= "<i>some HTML</i>"; return $out; }
        Which gets evaled. As long as your code uses my (or the template engine inserts it), you have data safety. Imported via UrsusTemplate::tmpl, we have:
        sub run { my $tmpl_path = shift; generate($tmpl_path) unless $tmpl_sub{$tmpl_path}; return $tmpl_sub{$tmpl_path}->(@_); #goto &$tmpl_sub{$tmpl_path}; #if you want to scare the other devs ;-) } sub emit { print $site_head; print run(@_); print $site_tail; }
        Now we have an easy way to do includes, plus an API for the controller. If we keep the timestamp from the source file, we can easily implement caching as well.

        If anyone can sanity-check this for me, I'd love that.

Re: The history of a templating engine
by mreece (Friar) on Sep 13, 2006 at 03:11 UTC
    i like this node, very educational. you may have saved many hours of work if you had first come across Text::MicroMason, but only you know if that would have made the journey to build "Jim's homepage" more or less pleasant. ;)

      I thought Text::MicroMason was awesome until I had a cascade of painful run-ins with its horrible error reporting. That basically puts it out of the race, since the quality of error reporting is a prime usability factor for programming environments.

      Makeshifts last the longest.

Re: The history of a templating engine
by Aristotle (Chancellor) on Sep 26, 2006 at 01:27 UTC

    Thanks for this writeup. I have been planning to embark on writing a templating system myself, so it was enjoyable to get to read a war story.

    The first writeup I ever posted to PerlMonks was about a templating system I thought I should write, and had I embarked on it then, I would have followed the same journey as you have, and certainly with less success. I thought I should because all these template systems I found very so complex and full of features and so much slower than something like

    s{ %% (\w+) (?: \s* || \s? \s*? (.+?) )? %% } { defined $var{ $1 } ? $var{ $1 } : $2 || '' }g;

    (which is roughly what I was using – simple variable substitution with optional default values). I can only chuckle at my naïvete now… teehee. Of course the systems are all “bloated” – they start as simple schemes that can cover only stiflingly modest needs and invariably follow the same progression towards comprehensiveness.

    Now, several years down the line, I know a lot more. It’s reassuring to read your account and find that I’ve contemplated every single point you mentioned. (It goes without saying that my design decisions differ greatly from what you describe, and also from everything found on CPAN – else why would I be thinking about it?) I might still be too lazy to do it; which is OK, in the big picture: CPAN isn’t terribly in need of another one of those… :-)

    Makeshifts last the longest.

Re: The history of a templating engine
by Anonymous Monk on Sep 26, 2006 at 15:43 UTC

    Hi jimt, i liked your article, i'm just another guy who wrote his own template system.

    I have to say that in the process i experienced many of the same problems than you. It has been very enrichment to read the mental chewing behind the scenes.

    Sadly i also believe that "there's more than one way to do it" is a bare and a bone, i can't complaint about it, because i did it (i built my own template system).

    What's good: "There are many ways to do it”.

    What's Wrong: "There are many ways to do it”:

    • Which one ?
    • What's the more popular ?
    • What are the benefits of this over the other ?
    • etc.

    It's similar to what happens with people trying to get into Linux, the first “and complex” question they face is: ¿ What distro to use ?

    People new to Develop Web Applications with Perl get confused on the large number of tools at different levels available.

    I don't pretend to criticize Perl Tools Developers, what i mention here is a real fact that probably don't have any solution.

    Hans Poo

    Edited by planetscape - removed style tags

Re: The history of a templating engine
by Anonymous Monk on May 14, 2008 at 18:03 UTC
    Little late to the party, but I couldn't help myself. I'm rock, you suck! This task can be solved much ease!

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://572402]
Approved by friedo
Front-paged by BrowserUk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (4)
As of 2024-03-19 10:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found