Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

RFC: Templating without a System

by shmem (Chancellor)
on Jun 17, 2006 at 13:04 UTC ( #555958=perlmeditation: print w/replies, xml ) Need Help??

Hello monks,

reading various nodes here I begun thinking again about perl vs. PHP and such. Bottom line for random web stuff:

  • 1. grab PHP and just do it, drop your code in some webspace and done
  • 2. use perl and expect a steeper learning curve, but be rewarded with a bike (and drop your training wheels later).

One day somebody of our staff walked up to me, saying:

"We need a web interface for our SAP ticket system, here are the specs. Here's the link to the HTML prototype files. You have five days, the service is already sold to customer X. Be sure to deliver it as a package in format $pkg_foo. If you need additional software packages for this, them as well. Ah, and don't change those HTML files. It's likely they get revised after the app is finished."

I had a look at web related perl modules for the next two hours. Oh yes, HTML::Template, Template Toolkit, Mason and so on.. uhm, complete frameworks, oodles of additional modules.

No way - as those are "not invented here" I would spend too much time getting a grip on them. I know I'm slow at learning new things, sometimes.

Besides, HTML::Template is almost rabid forbidding perl code in templates. Template::Toolkt has an own mini-language, hmm. Mason works with non- standard <% %> tags. Ah well.

So, what basics would I expect of some template processing?

  • interpolate some perl program state into some foreign formatting code
  • separate code & logic from representation & design
  • ease reusability - don't repeat yourself
  • lazy loading - only use the templates you need at some specific point
  • inclusion, pipelining and callbacks

Am I missing something?
The perl builtin for templates are formats, but they don't cover points 3, 4 and 5 above. Ok, templating toolkits out there provide much more - besides chaining, inclusion, session management, fancy restrictions resembling the strive between strong and loosely typed languages etc - but as far as I can see there is no "generic" solution yet adressing the five points above in perl, i.e. templating without a templating system.

By necessity, I hacked something maybe close to that goal dealing with that web app.

First I inserted comments in those files in places where I need control statements (loops, if-elses and such). I duplicated sample data blocks and marked one of each with start/end comments, the other got variables. The files looked the same in the browser. Values in input elements to be filled in via servlet showed up as perl variables, though.

Then I wrote a simple parser which turned the file inside out. Three substitutions to free the perl code from its comments, wrap all non-perl stuff in print statements (here-document syntax) and enclose it all in a sub.

Where should I put them? There is AutoSplit and AutoLoader with its nifty *.al files providing some standard. I only use AutoLoader, since the autosplit equivalent is dones with my converting sub.

Each template rendering sub is now stuffed into auto/__PACKAGE__/template.al, of course My::Special::View gets it's stuff from auto/My/Special/View. Insert "use Servlet qw(AUTOLOAD)" in view packages and call your views by name. The package global Servlet::dir is '.' by default an can be set prior or after importing the module to some value, e.g. the path in a "use lib $path" statement.

Differences to AutoSplit *.al files: each sub is written as an anonymous sub, and require'ing Servlet *.al files returns not just 'some true value' but a reference to the sub required. That way the function name doesn't show up in the caller's symbol table, if Servlet is used in OO fashion.

By default, 'use strict' is written to the AutoLoader files. Variables in @_ to the servlet functions are passed into the servlet constructor via an arrayref of variables in the style of 'use vars'.

Back to the app - the controller (as per MVC) was hand-crufted via CGI; CGI::Prototype is a good thing, but then I am supposed to understand Class::Prototyped - see above. Sometimes I'm just lazy at the wrong time.

Done that, I could begin fiddling with getting data from SAP - the "Model". Later, I could give back my templates saying "the HTML files are almost the same - I inserted only comments. Don't touch the comments which have 'perl' in them or the app fails."

Upon delivery, the templates source files could be removed and the *.al files be bundled with the calling packages *.pm after the __END__token - to be processed with AutoSplit upon installation, if AutoSplit had some way of dealing with anonymous subs.

Below are an example, the module, which I called 'Servlet' for now - that name surprisingly is not used at CPAN, and the subs are just that - a bunch of print statements, with a bit of flow control around, and variables inside, wrapped into a subroutine to be autoloaded by its package or called in either a functional or OO fashion.

The servlet pages are created follwing these rules:

All Perl code within source HTML files has to be embedded into standard HTML comment tags of one of the following forms: <!-- perl --> This tag allows arbitrary perl code to be included between the opening '<!-- perl' and the closing '-->' tags. The current implementation uses a non-greedy pattern matching, so HTML comment tags inside the perl comment tags will produce unexpected results. <!-- =perl $var --> This form allows the interpolation of a single variable between the opening '<!-- perl' and the closing '-->' tags. These tags don't break up a print statement. <!-- =perl -->Placeholder text<!-- /perl ---> As above, additionally "Placeholder text" and the closing tags are removed <!-- perl dummy start --> <!-- perl dummy end --> These tags and all characters in between are weeded out.
Here is a sample servlet template.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/> <meta name="GENERATOR" content="vim (X11; I; OpenBSD 3.5 i386) [UNIX]"/> <title> <!-- =perl $title -->page title<!-- /perl --> </title> </head> <body> <h1><!-- =perl $header1 -->Caption<!-- /perl --></h1> <!-- perl unless($ref) { --> <!-- =perl No data provided.<br> --><!-- /perl --> <!-- perl } else { --> <table> <!-- perl foreach my $ary(@$ref) { --> <tr> <!-- perl map { --> <td><!-- =perl $_ --><!-- /perl --> <!-- perl } @$ary; } --> </table> <!-- perl } --> <!-- perl dummy start --> <table> <tr><td>1<td>foo <tr><td>2<td>bar </table> <!-- perl dummy end --> </body> </html>
Make it a servlet via
#!/usr/bin/perl package My::Special::View; use Servlet qw(servlet); my $s = servlet( in => "example.html", # nostrict => 1, # ah, no, never :-) args => [ qw($title $header1 $arrayref) ], ); $s->( "sample servlet page", "Servlet Heading for a table", [[1,a],[2,b]] );
or OO-style
#!/usr/bin/perl package My::Special::View; use Servlet; my $s = Servlet->new( in => "example.html", args => [ qw($title $header1 $ref) ], ); $s->render("page","heading",[['foo',1,2],[bar,3,4],[quux,5,6]]); my @lines = $s->output; # or # @lines = $s->render->output; # then postproess, whatever.. # and finally print them print @lines; # direkt render & print $s->display("page","heading",[['foo',1,2],[bar,3,4],[quux,5,6]]);
Once the servlet is created, it can be used via AUTOLOAD as well:
#!/usr/bin/perl package My::Special::View; use Servlet qw(AUTOLOAD); example("sample servlet page","Servlet Heading for a table", [[1,a],[2,b]]);
The servlet is created as auto/My/Special/View/example.al as follows:
#!/usr/bin/perl package My::Special::View; use strict; # File generated by Servlet 0.01 at Sat Jun 17 13:55:02 2006 # from source: example.html # WARNING: changes made here will be overwritten upon re-creation. use File::Temp qw( tempfile ); # required once in My::Special::View return sub { my ($title, $header1, $ref) = @_;; my ($___example_fh,$___example_fn,$___example_oldfh); my $wantarray = wantarray(); if(wantarray) { ($___example_fh,$___example_fn) = tempfile() or die "can't tem +pfile: $!\n"; $___example_oldfh = select ($___example_fh); $| = 1; } print <<"E0000"; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "xhtml1 +-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-88 +59-1"> <meta name="GENERATOR" content="vim (X11; I; OpenBSD 3.5 i386) [UN +IX]"> <title> $title </title> </head> <body> <h1>$header1</h1> E0000 unless($ref) { print <<"E0001"; No data provided.<br> E0001 } else { print <<"E0002"; <table> E0002 foreach my $ary(@$ref) { print <<"E0003"; <tr> E0003 map { print <<"E0004"; <td>$_ E0004 } @$ary; } print <<"E0005"; </table> E0005 } print <<"E0006"; </body> </html> E0006 if($wantarray) { local *I; open(I,"<",$___example_fn) or warn "My::Special::View::example +: Can't open $___example_fn for reading: $!\n"; my @text = <I>; close(I); unlink($___example_fn); select($___example_oldfh); return @text; } };
Here's the output:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "xhtml1 +-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-88 +59-1"> <meta name="GENERATOR" content="vim (X11; I; OpenBSD 3.5 i386) [UN +IX]"> <title> sample servlet page </title> </head> <body> <h1>Servlet Heading for a table</h1> <table> <tr> <td>1 <td>a <tr> <td>2 <td>b </table> </body> </html>
Here's the module. The AUTOLOAD is borrowed from AutoLoader.
For now the processing only covers HTML, but I'll move that part into a subclass and leave only a stub in the module.
package Servlet; use Carp; use strict; use 5.006_001; our($VERSION, $AUTOLOAD, $exported,$debug); my $is_dosish; my $is_epoc; my $is_vms; my $is_macos; BEGIN { $is_dosish = $^O eq 'dos' || $^O eq 'os2' || $^O eq 'MSWin32' || $ +^O eq 'NetWare'; $is_epoc = $^O eq 'epoc'; $is_vms = $^O eq 'VMS'; $is_macos = $^O eq 'MacOS'; $VERSION = '0.01'; } $Servlet::dir ||= ''; my @export_ok = qw(AUTOLOAD servlet); my %T; # timestamps my %S; # subs - our own %INC... sub new { my $class = shift; my $caller = caller; bless { 'sub' => servlet($caller,@_) }, $class; } # Output the servlet. # At this point, the timestamps of the servlets source and its # sub could be checked, and the sub recreated and reloaded. sub display { my $self = shift; if($self->{'output'}) { print @{$self->{'output'}}; } else { $self->{'sub'}->(@_); } $self; } # get the output from the servlet. sub output { @{$_[0]->{'output'}}; } # just render the servlet, store it's output. # we use the value of wantarray() in the servlet function to # determine if the output should be printed or returned. sub render { my $self = shift; my @l = $self->{'sub'}->(@_); $self->{'output'} = [@l]; $self; } # get the servlet's stored subroutine, # require the servlet.al file # or create it and require then sub servlet { my $callpkg = caller; my $class = $callpkg eq __PACKAGE__ ? shift : $callpkg; confess __PACKAGE__.' class wants no servlet! -' if $class eq __PACKAGE__; my %a = @_; $a{'in'} or $a{'code'} or confess __PACKAGE__.'::servlet: ' ."provide at least one of argument for 'in' and 'code'\n"; -f $a{'in'} or confess "Can't find file '$a{in}' - $!\n" if define +d($a{'in'}); # if we have 'in' and no 'code', process into standard path auto/_ +_PACKAGE__ # if we have 'in' and 'code', name function & file after 'code'. # if we have 'code' and no 'in', load that file, function is named + after 'code'. my ($sub, $function, $outfile); if($a{'in'}) { my $infile = $a{'code'} || $a{'in'}; ($outfile = $infile ) =~ s/\.\w+$//; (my $classpath = $class ) =~ s#::#/#g; my $dir = "auto/$classpath"; substr($dir,0,0) = $servlet::dir .'/' if $Servlet::dir; _mkdir_p($dir) unless -d $dir; $outfile =~ s#.*/##; substr($outfile,0,0) = $dir.'/'; $outfile .= '.al'; } else { $outfile = $a{'code'}; } ($function = $outfile) =~ s#^.*/|\.\w+$##g; if( ($function eq 'servlet') && $exported) { carp "function for '$a{in}' resolves to servlet,\n" ."which is exported from ".__PACKAGE__."\n" .'pointing gun at left foot. shooting...'; } # check time of source & servlet.al if(_time_ok($a{'in'},$outfile) || (!$a{'in'} && -f $outfile)) { return $S{$outfile} if $S{$outfile}; # already known # we use eval EXPR because we need to switch package $S{$outfile} = $sub = eval "package $class; require '$outfile' +;"; } else { # get rid of the old stuff, so we can require again. delete $INC{$outfile}; local *O; # we could use Symbol and such, but what for? local $/; # slurp local *I; open(I,"<$a{in}") or confess "can't read '$a{in}': $!"; open(O,">$outfile") or confess "Can't write $outfile: $!"; my $oldfh = select O; local $\; # reset -l switch locally my $text = <I>; # first weed out sample content $text =~ s/<!-- perl dummy start -->.*?<!-- perl dummy end --> +//gs; # then strip variables of enclosing tags $text =~ s/<\!-- =perl\s*(.+?)\s*-->(.*?<\!--\s*\/perl\s*-->)? +/$1/g; # then, convert all into print <<"Exxxx" statements # and leave perl code as is my $t = "E0000"; # tag for here-document print statements my $s = ''; $text =~ s/(.+?)?<\!-- perl\s*(.*?)?\s*-->|(.+)/ my $a = $1 if $1; $a .= $3 if $3; my $b = $2 || ''; $a =~ s#[\s*\n]*$#\n#; ($s = "\nprint <<\"$t\";\n".$a.$t++."\n".$b)=~s#\n+#\n#g; $s /ges; # The output of the servlet is delivered straight to the # selected filehandle or returned to the caller. # We use wantarray() to determine the current context. # If wantarray() returnes true, the output is captured into # a temporary file which filehandle we select(). Thus all # print() without explicit filehandle ends up in the tempfile # and we can return its content as a list of lines. # # This mess is necessary because servlets may be chained, and # we don't want output going unadverted into STDOUT or such. my $strict = 'use strict;' unless $a{'nostrict'}; my $args; $args = 'my ('.join(', ',@{$a{'args'}}).') = @_;' if ($a{'args +'}); my $pkg = __PACKAGE__; my $t = scalar(localtime(time)); print <<"EOH"; #!$^X package $class; $strict # File generated by $pkg $VERSION at $t # from source: $a{in} # WARNING: changes made here will be overwritten upon re-creation. use File::Temp qw( tempfile ); # required once in $class return sub { $args; my (\$___${function}_fh,\$___${function}_fn,\$___${function}_oldfh +); my \$wantarray = wantarray(); if(wantarray) { (\$___${function}_fh,\$___${function}_fn) = tempfile() or die +"can't tempfile: \$!\\n"; \$___${function}_oldfh = select (\$___${function}_fh); \$| = 1 +; } $text if(\$wantarray) { local *I; open(I,"<",\$___${function}_fn) or warn "$class\::$function: C +an't open \$___${function}_fn for reading: \$!\\n"; my \@text = <I>; close(I); unlink(\$___${function}_fn); select(\$___${function}_oldfh); return \@text; } }; EOH close(O); select $oldfh; $sub = eval "package $class; require '$outfile';"; confess $@ if $@; $S{$outfile} = $sub; } no strict 'refs'; if ($a{'import'}) { *{$class.'::'.$function} = $sub; } $sub; } # ==================================================================== +========== # private subs # ==================================================================== +========== # timestamp sub _time_ok { if ($_[0] and $_[1]) { (stat($_[0]))[9] <= (stat($_[1]))[9] if -f $_[0] and -f $_[1]; } } # mkdir -p sub _mkdir_p { my @l = split(/\//,$_[0]); my $dir = shift(@l); mkdir($dir,0755) unless -d $dir; foreach my $d(@l) { $dir .= "/$d"; unless(-d $dir) { mkdir($dir,0755) or confess "huh? - mkdir($dir,0755): $!\n +"; } } } 1; # From here on slightly modified AutoLoader.pm AUTOLOAD { my $sub = shift; if ($sub =~ /.*::servlet$/) { require Carp; Carp::confess("servlet may not be autoloaded"); } my $filename; # Braces used to preserve $1 et al. { # [ AutoLoader comments deleted ] my ($pkg,$func) = ($sub =~ /(.*)::([^:]+)$/); $pkg =~ s#::#/#g; if (defined($filename = $INC{"$pkg.pm"})) { if ($is_macos) { $pkg =~ tr#/#:#; $filename =~ s#^(.*)$pkg\.pm\z#$1auto:$pkg:$func.al#s; } else { $filename =~ s#^(.*)$pkg\.pm\z#$1auto/$pkg/$func.al#s; } # # [ AutoLoader comments deleted ] if (-r $filename) { unless ($filename =~ m|^/|s) { if ($is_dosish) { unless ($filename =~ m{^([a-z]:)?[\\/]}is) { if ($^O ne 'NetWare') { $filename = "./$filename"; } else { $filename = "$filename"; } } } elsif ($is_epoc) { unless ($filename =~ m{^([a-z?]:)?[\\/]}is) { $filename = "./$filename"; } } elsif ($is_vms) { # XXX todo by VMSmiths $filename = "./$filename"; } elsif (!$is_macos) { $filename = "./$filename"; } } } else { $filename = undef; } } unless (defined $filename) { # let C<require> do the searching $filename = "auto/$sub.al"; $filename =~ s#::#/#g; } } my $save = $@; local $!; # Do not munge the value. # BEGIN changes my $ref; $ref = eval { local $SIG{__DIE__}; require $filename }; # END changes if ($@) { if (substr($sub,-9) eq '::DESTROY') { no strict 'refs'; *$sub = sub {}; $@ = undef; } elsif ($@ =~ /^Can't locate/) { # [ AutoLoader comments deleted ] if ($filename =~ s/(\w{12,})\.al$/substr($1,0,11).".al"/e) +{ $ref = eval { local $SIG{__DIE__}; require $filename } +; } } if ($@){ # $@ =~ s/ at .*\n//; # why??? my $error = $@; require Carp; Carp::croak($error); } } $@ = $save; goto ref($ref) eq 'CODE' ? $ref : &$sub; } sub import { my $pkg = shift; my $callpkg = caller; # # Export symbols, but not by accident of inheritance. # my %a; @a{@_} = @_; if ($pkg eq 'Servlet') { no strict 'refs'; foreach my $symbol(@export_ok){ *{ $callpkg . '::' . $symbol } = \&$symbol if $a{$symbol}; $exported++ if $symbol eq 'servlet'; } } # I've left out the autosplit searching. } sub unimport { my $callpkg = caller; no strict 'refs'; my $symname = $callpkg . '::AUTOLOAD'; undef *{ $symname } if \&{ $symname } == \&AUTOLOAD; *{ $symname } = \&{ $symname }; } 1; __END__
Update: changed title to include RFC; cleanup

Would be nice to know wether this makes (non)sense to you. Any critics and enhancement sugesstions are greatly welcome.

regards,

shmem

Replies are listed 'Best First'.
Re: RFC: Templating without a System
by perrin (Chancellor) on Jun 17, 2006 at 16:14 UTC
    I think you should have done more research. HTML::Template supports using HTML comments for your template tags. TT has configurable tags and supports in-line Perl. Mason has configurable tags. Other options like Petal let you work directly on valid HTML documents without even adding comments.

    In short, there was no need to do this. However, your real motivation seems to be something about the difficulty of using CPAN modules at your job. I'd suggest working on fixing that, rather than duplicating solutions for problems as well-covered as templating.

Re: RFC: Templating without a System
by chromatic (Archbishop) on Jun 18, 2006 at 05:39 UTC

    Instead of using a well-documented, well-tested, well-deployed system, you created your own. Now you have to maintain it, update it, fix bugs, add features, and explain it to everyone who wants to use it.

    To me, this is a false economy that just increased your Technical Debt, just as avoiding non-core modules does. (Really, copying and pasting the source code of AutoLoader? It's core!)

      Instead of using a well-documented, well-tested, well-deployed system, you created your own. Now you have to maintain it, update it, fix bugs, add features, and explain it to everyone who wants to use it.

      That was the problem - which one? I had to delay that decision. If I was familiar enough with any of them I wouldn't even have thought of writing that module.

      (Really, copying and pasting the source code of AutoLoader? It's core!)

      AutoLoader doesn't support loading autosplit.al files which return an anonymous sub on require. If AutoLoader.pm were patched with the following

      --- AutoLoader.pm.orig 2006-06-18 16:18:30.000000000 +0200 +++ AutoLoader.pm 2006-06-18 16:20:10.000000000 +0200 @@ -89,7 +89,7 @@ } my $save = $@; local $!; # Do not munge the value. - eval { local $SIG{__DIE__}; require $filename }; + my $ref; $ref = eval { local $SIG{__DIE__}; require $filename }; if ($@) { if (substr($sub,-9) eq '::DESTROY') { no strict 'refs'; @@ -102,7 +102,7 @@ # There is a slight risk that we could pick up the wrong f +ile here # but autosplit should have warned about that when splitti +ng. if ($filename =~ s/(\w{12,})\.al$/substr($1,0,11).".al"/e) +{ - eval { local $SIG{__DIE__}; require $filename }; + $ref = eval { local $SIG{__DIE__}; require $filename } +; } } if ($@){ @@ -113,6 +113,7 @@ } } $@ = $save; + goto $ref if ref($ref) && ref($ref) eq 'CODE'; goto &$sub; }
      there would have been no need to borrow it's code other than by use. I could have written my own autoload sub, but copy & paste was quicker...

      thanks for the TechnicalDebt link.
      --shmem

        That was the problem - which one?

        It doesn't really matter, as long as it:

        • Meets your immediate needs and appears to have a reasonable chance of meeting your future needs.
        • Seems reasonably stable.
        • Meets your maintenance budget (that is, it's less work to maintain than if you had to do it yourself).
        • Has sufficient documentation.
        • Runs on your target and development platforms.
        • Reasonably compatible with existing deployed systems.

        A day's worth of research on templating systems would have revealed a lot. Now maybe writing this for your own organization was the right approach -- I don't and can't know that. Yet I do want to bring up the risks involved, as I would if someone in my organization were to do the same thing.

Re: RFC: Templating without a System
by davidrw (Prior) on Jun 17, 2006 at 14:30 UTC
    don't have a chance at the moment to review in full, but a few phrases in your intro caught my attention:
    Template::Toolkt has an own mini-language, hmm.
    The Template::Toolkit language is pretty easy to pick up (and decent docs at http://template-toolkit.org). Also, you can ember striaght perl with the [% PERL %]...[% END %] directive (see "Directives" section of the Manual).
    Mason works with non- standard <% %> tags. Ah well.
    I assume you can configure those (you can in TT), though i don't know offhand..

    Seems like TT covers your 5 pts.. but if you're under such a time crunch, you're pretty much forced to go w/whatever works best (read:fastest) for you, which is probably your custom-tailored solution..

      The Template::Toolkit language is pretty easy to pick up

      I just see no sense in introducing a mini-language for templates. Perl has all things necessary ready for that, and with use strict you're on the safe side. Mini-languages just introduce an abstraction and ask you to believe they are reliably coded. TT claims to "Do The Right Thing" with its glob-like variables. How can I know?

      Seems like TT covers your 5 pts.. but if you're under such a time crunch, you're pretty much forced to go w/whatever works best (read:fastest) for you, which is probably your custom-tailored solution..
      The point is, every templating system covers the 5 points, but it's about templating - with no system - in the most standard perl way possible.

      greetings,
      --shmen
        It makes very little sense to introduce a mini-language if your primary HTML-whackers are also already Perl coders.

        However, in many jobs shops, the HTML-whacking is done by people who specialize in that, and are generally better at driving DreamWeaver and Photoshop than they are at driving Emacs (or even vi).

        Many people have reported that they can comfortably teach-by-example to these designers the mini-language of TT far faster than trying to explain why it's @foo in one place, but $foo[3] somewhere else.

        So, while a mini-language may not make sense for you, it makes a great deal of sense in these shops.

        Also, I find that having a mini-language keeps me honest about MVC. About the time it starts getting hard to write in TT, I realize that I'm actually writing control or model code, and that code belongs away from the view code. So, I rip that out and put it in Perl or SQL where it belongs.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

Re: RFC: Templating without a System
by xdg (Monsignor) on Jun 17, 2006 at 14:50 UTC
    Mason works with non- standard <% %> tags.

    From what I understand of HTML::Mason, I think it does most/all of what you need. Apart from the tags, what about it doesn't work for you? It seems overkill to write yet another template system just because you prefer <!-- --> to <% %>.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      From what I understand of HTML::Mason, I think it does most/all of what you need. Apart from the tags, what about it doesn't work for you?

      Additional Modules, which I am supposed to package, additionally, to my own stuff. Make sure they work on the target system. Dependecies beyond the perl core.
      It seems overkill to write yet another template system just because you prefer <!-- --> to <% %>.
      Embedding all my alterations into the source files as standard W3C compliant comments is essential to cross-work with the designers. And, again, it's not a system, it's about dealing with templates only with the perl core modules.

      greetings,
      --shmem
        Additional Modules, which I am supposed to package, additionally, to my own stuff. Make sure they work on the target system. Dependecies beyond the perl core.
        sorry, but you say you do "Templating without a System". What is a "system" for you?

        HTML::Template (or HTML::Template::Compiled, to mention my own module) are just a couple of files containing perl-code (also called pure-perl modules). You don't want to be dependant on these, but you write your own Servlet.pm? (and you consider the mentioned modules "systems", but not your Servlet.pm?)

        I can't see what the above mentioned modules couldn't do for you. Only parameter passing is a bit more work, but I consider it to be more clean to pass parameters explicitly than to refer to real perl variables in a template.

Re: RFC: Templating without a System
by wazoox (Prior) on Jun 18, 2006 at 08:14 UTC
    Looks like you forgot to try PLP ( http://plp.juerd.nl/ ). PLP has most of the advantages of PHP (quick and dirty, etc) BUT it has the tremendous advantage to be Perl :)
Re: RFC: Templating without a System
by tilly (Archbishop) on Jun 21, 2006 at 02:29 UTC
    Advice: get a better job.

    I'm serious. There is fundamentally wrong with a company that doesn't even talk to an engineeer before selling a contract committing itself to developing a kind of software it has never done before within 5 days. I don't care how simple the project winds up being, sales people should not make that judgement call.

    Given the time constraints and your lack of relevant experience, I can understand the choice to roll your own. After all rolling your own is a project of predictable length and complexity. Evaluating mature systems is not. With a 5 day time limit, you can't risk the possibility of deciding on a course after day 1, then finding out on day 3 that the existing tool forces you to do something differently than you were planning on, and you need to redo a day and a half of work.

    However the result is that your product will be crappy and inflexible. You'll make a bunch of mistakes that existing templating systems figured out are mistakes. Plus it will be impossible to find anyone else with expertise in what you did.

    If this is standard for your employer, then you'll have a lot of poorly designed crap to deal with. After a while everything that you do will be pain and frustration. Better to burn out early, before you're really pissed off, and take a new job than to stick it out in an environment that is guaranteed to make you unhappy.

      Glad that you raise that issue. But it wasn't as harsh as it may seem. I mentioned the kickoff episode to have an intro, but took those words out of context to get a short intro which would defend somehow my case of instigating a discussion about templating, forseeing that I would be bashed with do your homework or don't reinvent the wheel or choose Foo::Bar, it does the job or such.

      The truth is, those words were uttered likewise, but theres's more about it. As the inhouse SAP administrator at that time I was asked to program the fallback for the applocation our 'Java Witnesses' had been struggling with for more than a year. It was a changing team, mostly students, and they ended up with a myriad of classes they couldn't handle any more. As they went along, I often asked "why don't you do that in perl?", and as the final deadline drew near they gave in and confessed they couldn't, alas, a bit too late.

      I left away the question "Can you do that?" and my answer "I don't know, but I assume yes. I'll try". I did, not in 5 but in 8 days. I omitted this conversation to avoid getting posts saying you fool, you agreed having no clue.

      Yes, the application is crappy, but only in what concerns the SAP part - a plain nightmare, but that's SAP. The web stuff is straight forward, plain CGI as well, with a dispatch table resembling CGI::Application (which I just oversaw at that moment) with heavy use of AutoLoader.

      After all, this situation was non-standard, but a great raise in esteem for perl and your humble poster. The company is more than OK in its standards, generally. Otherwise, I wouldn't be there any more.

      At the end, it showed again that "one perl is often more worth than a sack of java beans", just to quote myself ;-)

      greetings,
      --shmem

      _($_=" "x(1<<5)."?\n".q/)Oo.  G\        /
                                    /\_/(q    /
      ----------------------------  \__(m.====.(_("always off the crowd"))."
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: RFC: Templating without a System (your system)
by tye (Sage) on Jul 01, 2006 at 16:07 UTC

    The final requirement that you were given: Don't modify the HTML files. The first step of your solution: Modify the HTML files. Any questions?

    The reason for the first requirement is that someone besides you is maintaining the HTML files. More changes will come after your system is deployed. Are you going to teach whoever has the job of writing HTML not just all of Perl (since you don't want any restrictions on using the power of Perl) but also your system of embedding Perl into HTML?

    As for the benefits or drawbacks of your system, I don't have any comments as I don't understand how your system glues the disjointed chunks of Perl together. You didn't aviod creating a system, you just created one that seemed obvious to you and one that you don't yet see the long-term benefits and gotchas of.

    - tye        

      Any questions?
      Perhaps this one: why do you just stick to words and don't get the meaning. "Don't change" means in this context "don't change them in a way they show up different than now in a HTML editor/viewer". No more questions.
      As for the benefits or drawbacks of your system, I don't have any comments as I don't understand how your system glues the disjointed chunks of Perl together.
      Simple. I turn the files inside out outside in, and lo! the disjoint chunks are not longer disjoint.
      You didn't aviod creating a system, you just created one that seemed obvious to you and one that you don't yet see the long-term benefits and gotchas of.
      From miniperlmain.c:
      /* * "The Road goes ever on and on, down from the door where it began." */
      ...and wither then? I cannot say.

      Most likely I wouldn't adventure anything if seeing the "long-term benefits and gotchas" were crucial criteria for my actions.

      --shmem

      _($_=" "x(1<<5)."?\n".q/)Oo.  G\        /
                                    /\_/(q    /
      ----------------------------  \__(m.====.(_("always off the crowd"))."
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

        Yeah, you didn't get my points in the other thread either.

        "Don't change" means in this context "don't change them in a way they show up different than now in a HTML editor/viewer".

        and yet

        Ah, and don't change those HTML files. It's likely they get revised after the app is finished.

        And what good does it do to add only HTML comments when the person who is doing the revising has no idea how to keep those HTML comments so that they actually work with your system? Are you assuming that revisions will be so tiny that your HTML comments will still work?

        Most likely I wouldn't adventure anything if seeing the "long-term benefits and gotchas" were crucial criteria for my actions.

        Yeah, not even close to my point. *shrug* Sorry you felt I didn't get your points. Sorry it appears that I failed to get my points across. Good luck anyway.

        - tye        

Re: RFC: Templating without a System
by jdtoronto (Prior) on Jun 20, 2006 at 06:10 UTC
    No, it doesn't make a lot of sense because so much of what you did was done for your own personal reasons not for reasons of logical dissent. OK, so some of the existing systems had features you didn't like. But it is as obvious to me as it is to others that you didn't do a whole lot of research, after all, you admit to two hours research and... oh... how many hours designing and coding?

    Your comment on "not written here" is somewhat indicative. If you didn't write it you can't understand it. Fine, don't try and understand anything anyone else has written.

    It seems you have come up with a more difficult to understand templating system because you didn't want to understand the existing templating systems. I only needed about three hours to explain the fundamentals of HTML::Template to the designers who produce the HTML and CSS here. Anything they don't understand how to do they give us the HTML and wrap it in quotes so we know they were having a problem, usually only takes a Perl coder 3 or 4 minutes to fix the problem.

    Good luck! I found your templates hard to read, so I am certain your HTML coders who know no Perl at all will find it a snap.

    jdtoronto

      But it is as obvious to me as it is to others that you didn't do a whole lot of research, after all, you admit to two hours research and... oh... how many hours designing and coding?
      Something around four hours until I had something that kept me going.
      Your comment on "not written here" is somewhat indicative. If you didn't write it you can't understand it. Fine, don't try and understand anything anyone else has written.
      Thanks for the bad advice. I usually try to (and then do) understand what is written and said, it's not that I am ignorant because I want to (but then, Exporter::Heavy.pm is still puzzling me, and I don't really want to grok it). I only decided to defer the decision for a toolkit (if any) until I had time. I thought I had made that point clear. As someone said, "those who don't understand UNIX are doomed to reinvent it, poorly", but this is not the case here. And the sheer complexity of the evolved toolkits is probably a reason why so many stick with PHP.

      _($_=" "x(1<<5)."?\n".q/)Oo.  G\        /
                                    /\_/(q    /
      ----------------------------  \__(m.====.(_("always off the crowd"))."
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      
Re: RFC: Templating without a System
by metaperl (Curate) on Jun 20, 2006 at 16:27 UTC
    HTML::Seamstress was designed exactly for type of requirements you have. Please RSVP with your comments on it. Thanks.
      Not exactly if I look at speed.

      DOM parsing is costly, I have considered that approach, but I would do it only in the design phase of the application and deployment, or after changes made to some template, its to say I wouldn't use it as the page generating core.

      I ran benchmark on a 5649 Byte html file with HTML::Seamstress and Servlet.

      The only changes were uncomment use warnings because it clutters up the test with warnings about unitialized variables, and changed the warn "PROCESS_TREE: ", $tree->as_HTML; to a print statement.
      No replace_content at all.

      bench.pl:

      #!/usr/bin/perl use lib "."; use Benchmark qw(:all) ; use Servlet qw(servlet); use example; # the spkg.pl generated package open(O,">/dev/null"); select O; # initialize some empty references I need in example.al my $freelist = []; my $list = []; my $in = {}; my $r = example->new; my $s = servlet( in => 'example.html', args => [ qw($nm $list $em8 $error $in $scopename $ref $res +ult $em26 $link $freelist $networkname $em14 $em9 %oui) ], ); my $count = 10000; my $results = timethese($count, { 'Seamstress' => sub { $r->process() }, 'Servlet' => sub { $s->($nm, $list, $em8, $error, $in, $scopena +me, $ref, $result, $em26, $link, $freelist, $networkname, $em14, $em9 +, %oui) }, }); select STDOUT; cmpthese($results);
      gave the following results:
      Rate Seamstress Servlet Seamstress 242/s -- -99% Servlet 47619/s 19548% --
      I would replace_content with variable stubs and turn the resulting file inside out, as with my code.

      greetings,
      --shmem

      _($_=" "x(1<<5)."?\n".q/)Oo.  G\        /
                                    /\_/(q    /
      ----------------------------  \__(m.====.(_("always off the crowd"))."
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
        Thank you for giving HTML::Seamstress serious consideration. I too was worried about performance at production level... what I did was pre-parse the HTML file before each web request.

        I looked around on CPAN for a singleton caching solution, but nothing really fit, so what I did was call Webpage::PackageName->new() and store the results in a cache key ahead of time. The module for caching is Class::Cache. Like I said, I didn't want to write it but it was necessary.

Re: RFC: Templating without a System
by Aristotle (Chancellor) on Jul 24, 2006 at 23:26 UTC

    Using comments to hide templating stuff is pretty cruddy. If you need to stick values in attributes, youll be writing invalid markup, which HTML editing tools that dont know your template syntax will complain about. And if you have loops building the output, its likely that the markup around those areas will be both invalid as well as very different from the final output. Even if the markup were perfectly sensible, theres no default content in your template, so if it is rendered unprocessed the result will make little sense. Also, it looks as though you make no attempt to HTML-escape anything, so if you use your system on a public site youll have to spend a lot of effort making very sure its not vulnerable to cross-site scripting attacks.

    The best templating system I have found is Petal, an implementation of TAL, which addresses all of these points by keeping template-related stuff in special attributes. (Actually, Kid is better yet, with a TAL-inspired but better syntax but its a Python lib, not a Perl module.)

    I havent looked at the implementation in much detail (only at your example template and output) because there is way too much in there for a templating system thats supposed to be simple. You appear to be mixing together concerns wildly instead of separating them.

    Makeshifts last the longest.

      Hmm. Please explain why stuffing templating stuff into comments is cruddy to your view. I used that approach because comments are ignored by browsers, and sensible behaving editors don't mess them up. Of course, if I have an odd number of, say, $foo-- occurrences, I break the rules, but other than that comments are just fine. Pages look for me way more ugly if a browser displays [% foo.bar %] or <TMPL_VAR baz>, which really break design.

      But for loops and stuff - I had all that nicely stuffed into comments, and yes, I had default content marked up with <!-- perl dummy -->default stuff here<!-- perl dumy end --> which are entirely weeded out.

      The only places where perl variables shine through is in, yes, attributes, which having invalid values distort the output; and in content /values of form elements.

      I solved that with a bunch of regexes to insert the perl stubs during the function generation; indeed using a real html/xml parser would be much better and less error prone.

      Next, HTML-escaping is not the templating engine's business, but of the caller which validates input and provides content anyways - it should be done there.

      Petal? I've looked at it; it's an approach with full xml / html validation as is HTML::Seamstress, and there's too much overhead in it. Doing the same tasks over and over again, as I understand them. And the special attribute syntax is -- well, special, and too little perl related to my liking.

      The bunch of code I published in this thread is lousy and buggy, and I'm cleaning that up. Have you read the follow-up? I think there I've laid out my ideas more precisely, and I'll have it all made into a module by the end of this week. I appreciate your comments and would be glad if you had a look at it.

      greetings,
      --shmem

      _($_=" "x(1<<5)."?\n".q/)Oo.  G\        /
                                    /\_/(q    /
      ----------------------------  \__(m.====.(_("always off the crowd"))."
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

        The point is, you can just as well hide directives in special attributes, and then youre guaranteed not to break HTML syntax rules and you can provide default content without the massive verbosity of bracketing it with special comments on both sides.

        HTML-escaping is definitely the engines business. Why do you think are cross-site scripting attacks so common? Because everyone eventually forgets to escape something; or is just lazy. But only a vanishing minority of cases ever requires output to be printed unescaped. Good defensive programming sense then demands that escaping should be the default.

        As for Petal and others doing the same tasks over and over, how is that relevant? You dont have to choose to follow their implementation if you follow their language design.

        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://555958]
Approved by randyk
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (5)
As of 2017-12-16 01:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    What programming language do you hate the most?




















    Results (447 votes). Check out past polls.

    Notices?