X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FTemplate%2FSimple.pm;h=02bf39da81525280dfc9aa99e2698d2c3cff0e65;hb=a5b978a49c8f4c22e28d19c5eb9e333b8abaefe6;hp=1b603d0d0e1bcffe847bd4f0c6afeea6336a0d31;hpb=060b866c8fcd6f0db48be9db01e7e9137ca860cc;p=urisagit%2FTemplate-Simple.git diff --git a/lib/Template/Simple.pm b/lib/Template/Simple.pm index 1b603d0..02bf39d 100644 --- a/lib/Template/Simple.pm +++ b/lib/Template/Simple.pm @@ -1,24 +1,24 @@ -package Template::Simple; +package Template::Simple ; -use warnings; -use strict; +use warnings ; +use strict ; use Carp ; -use Scalar::Util qw( reftype ) ; -use File::Slurp ; - use Data::Dumper ; +use Scalar::Util qw( reftype blessed ) ; +use File::Slurp ; -our $VERSION = '0.03'; +our $VERSION = '0.06'; my %opt_defaults = ( pre_delim => qr/\[%/, post_delim => qr/%\]/, + token_re => qr/\w+?/, greedy_chunk => 0, # upper_case => 0, # lower_case => 0, - include_paths => [ qw( templates ) ], + search_dirs => [ qw( templates ) ], ) ; sub new { @@ -29,12 +29,19 @@ sub new { # get all the options or defaults into the object +# support the old name 'include_paths' ; + + $opts{search_dirs} ||= delete $opts{include_paths} ; + while( my( $name, $default ) = each %opt_defaults ) { $self->{$name} = defined( $opts{$name} ) ? $opts{$name} : $default ; } + croak "search_dirs is not an ARRAY reference" unless + ref $self->{search_dirs} eq 'ARRAY' ; + # make up the regexes to parse the markup from templates # this matches scalar markups and grabs the name @@ -42,7 +49,7 @@ sub new { $self->{scalar_re} = qr{ $self->{pre_delim} \s* # optional leading whitespace - (\w+?) # grab scalar name + ($self->{token_re}) # grab scalar name \s* # optional trailing whitespace $self->{post_delim} }xi ; # case insensitive @@ -60,7 +67,7 @@ sub new { \s* # optional leading whitespace START # required START token \s+ # required whitespace - (\w+?) # grab the chunk name + ($self->{token_re}) # grab the chunk name \s* # optional trailing whitespace $self->{post_delim} ($chunk_body) # grab the chunk body @@ -82,7 +89,7 @@ sub new { \s* # optional leading whitespace INCLUDE # required INCLUDE token \s+ # required whitespace - (\w+?) # grab the included template name + ($self->{token_re}) # grab the included template name \s* # optional trailing whitespace $self->{post_delim} }xi ; # case insensitive @@ -102,126 +109,155 @@ sub compile { $self->_get_template( $template_name ) ; } ; +#print Dumper $self ; + croak "Template::Simple $@" if $@ ; -# compile a copy of the template as it will be destroyed + my $included = $self->_render_includes( $tmpl_ref ) ; - my $code_body = $self->_compile_chunk( '', "${$tmpl_ref}", "\t" ) ; +# compile a copy of the template as it will be destroyed - $self->{source} = <_compile_chunk( '', "${$included}", "\t" ) ; + my $source = <{source_cache}{$template_name} = $self->{source} ; - print $self->{source} ; +#print $source ; - my $code_ref = eval $self->{source} ; + my $code_ref = eval $source ; -die $@ if $@ ; +#print $@ if $@ ; $self->{compiled_cache}{$template_name} = $code_ref ; + $self->{source_cache}{$template_name} = $source ; } - sub _compile_chunk { my( $self, $chunk_name, $template, $indent ) = @_ ; return '' unless length $template ; - $indent .= "\t" ; - - my @parts ; +# generate a lookup in data for this chunk name (unless it is the top +# level). this descends down the data tree during rendering -# loop all nested chunks and the text separating them + my $data_init = $chunk_name ? "\$data->{$chunk_name}" : '$data' ; - while( $template =~ m{$self->{chunk_re}}g ) { + my $code = <_compile_scalars( - substr( $template, 0, $-[0] ) ) ; +CODE -# compile the nested chunk and save its parts + $indent .= "\t" ; - push @parts, $self->_compile_chunk( $1, $2, $indent ) ; +# loop all nested chunks and the text separating them -# chop off the pre-chunk and chunk + while( my( $parsed_name, $parsed_body ) = + $template =~ m{$self->{chunk_re}} ) { - substr( $template, 0, $+[0], '' ) ; - } + my $chunk_left_index = $-[0] ; + my $chunk_right_index = $+[0] ; -# compile trailing text for scalars and save all of its parts +# get the pre-match text and compile its scalars and text. append to the code - push @parts, $self->_compile_scalars( $template ) ; + $code .= $self->_compile_scalars( + substr( $template, 0, $chunk_left_index ), $indent ) ; -# generate the code for this chunk +# print "CHUNK: [$1] BODY [$2]\n\n" ; +# print "TRUNC: [", substr( $template, 0, $chunk_right_index ), "]\n\n" ; +# print "PRE: [", substr( $template, 0, $chunk_left_index ), "]\n\n" ; -# start it with a do{} block open +# chop off the pre-match and the chunk - my $code = "do {\n$indent" ; + substr( $template, 0, $chunk_right_index, '' ) ; -# generate a lookup in data for this chunk name (unless it is the top -# level). this descends down the data tree during rendering +# print "REMAIN: [$template]\n\n" ; - $code .= <{$chunk_name} ; -CODE +# compile the nested chunk and append to the code -# now generate the code to output all the parts of this chunk. they -# are all concatentated by the . operator + $code .= $self->_compile_chunk( + $parsed_name, $parsed_body, $indent + ) ; + } - $code .= join( "\n$indent.\n$indent", @parts ) ; +# compile trailing text for scalars and append to the code -# now we close the do block + $code .= $self->_compile_scalars( $template, $indent ) ; chop $indent ; - $code .= "\n$indent}" ; + +# now we end the loop for this chunk + $code .= <{scalar_re}}g ) { -# keep the text before the scalar markup and the code to access the scalar +# get the pre-match text before the scalar markup and generate code to +# access the scalar push( @parts, - dump_text( substr( $template, 0, $-[0] ) ), + _dump_text( substr( $template, 0, $-[0] ) ), "\$data->{$1}" ) ; + +# truncate the matched text so the next match starts at begining of string + substr( $template, 0, $+[0], '' ) ; } # keep any trailing text part - push @parts, dump_text( $template ) ; + push @parts, _dump_text( $template ) ; + + my $parts_code = join( "\n$indent.\n$indent", @parts ) ; - return @parts ; + return <{source_cache}{$template_name} ; +} + sub render { my( $self, $template_name, $data ) = @_ ; -# render with cached code if we precompiled this template + my $tmpl_ref = ref $template_name eq 'SCALAR' ? $template_name : '' ; - if ( my $compiled = $self->{compiled_cache}{$template_name} ) { + unless( $tmpl_ref ) { - return $compiled->($data) ; - } - -# TODO: look for template by name +# render with cached code and return if we precompiled this template - my $template = eval{ $self->_get_template($1) } ; + if ( my $compiled = $self->{compiled_cache}{$template_name} ) { -print "GOT [$template]\n" ; + return $compiled->($data) ; + } -# force the template into a ref +# not compiled so try to get this template by name or +# assume the template name are is the actual template - my $tmpl_ref = ref $template eq 'SCALAR' ? $template : \$template ; + $tmpl_ref = + eval{ $self->_get_template( $template_name ) } || + \$template_name ; + } my $rendered = $self->_render_includes( $tmpl_ref ) ; @@ -282,20 +327,19 @@ sub _render_includes { # loop until we can render no more include markups 1 while $rendered =~ - s{$self->{include_re}} - { ${ $self->_get_template($1) } - }e ; + s{$self->{include_re}}{ ${ $self->_get_template($1) }}e ; return \$rendered ; } my %renderers = ( + SCALAR => sub { return $_[2] }, + '' => sub { return \$_[2] }, HASH => \&_render_hash, ARRAY => \&_render_array, CODE => \&_render_code, # if no ref then data is a scalar so replace the template with just the data - '' => sub { \$_[2] }, ) ; @@ -310,13 +354,31 @@ sub _render_chunk { return \'' unless defined $data ; +# get the type of this data. handle blessed types + + my $reftype = blessed( $data ) ; + +#print "REF $reftype\n" ; + +# handle the case of a qr// which blessed returns as Regexp + + if ( $reftype ) { + + $reftype = reftype $data unless $reftype eq 'Regexp' ; + } + else { + $reftype = ref $data ; + } + +#print "REF2 $reftype\n" ; + # now render this chunk based on the type of data - my $renderer = $renderers{reftype $data || ''} ; + my $renderer = $renderers{ $reftype || ''} ; -#print "EXP $renderer\nREF ", reftype $data, "\n" ; +#print "EXP $renderer\nREF $reftype\n" ; - die "unknown template data type '$data'\n" unless defined $renderer ; + croak "unknown template data type '$data'\n" unless defined $renderer ; return $self->$renderer( $tmpl_ref, $data ) ; } @@ -331,14 +393,13 @@ sub _render_hash { my $rendered = ${$tmpl_ref} ; - # recursively render all top level chunks in this chunk $rendered =~ s{$self->{chunk_re}} { # print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ; - print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ; - print "pre CHUNK [$`]\n" ; +# print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ; +# print "pre CHUNK [$`]\n" ; ${ $self->_render_chunk( \"$2", $href->{$1} ) } }gex ; @@ -378,7 +439,7 @@ sub _render_code { my $rendered = $cref->( $tmpl_ref ) ; - die <{tmpl_cache}}{ keys %{$tmpls} } = - map ref $_ eq 'SCALAR' ? \"${$_}" : \"$_", values %{$tmpls} ; + + while( my( $name, $tmpl ) = each %{$tmpls} ) { + + defined $tmpl or croak "undefined template value for '$name'" ; + +# cache the a scalar ref of the template + + $self->{tmpl_cache}{$name} = ref $tmpl eq 'SCALAR' ? + \"${$tmpl}" : \"$tmpl" + } #print Dumper $self->{tmpl_cache} ; @@ -413,6 +481,7 @@ sub delete_templates { @names = keys %{$self->{tmpl_cache}} unless @names ; +#print "NAMES @names\n" ; # clear out all the caches # TODO: reorg these into a hash per name @@ -452,11 +521,14 @@ sub _find_template { my( $self, $tmpl_name ) = @_ ; - foreach my $dir ( @{$self->{include_paths}} ) { +#print "FIND $tmpl_name\n" ; + foreach my $dir ( @{$self->{search_dirs}} ) { my $tmpl_path = "$dir/$tmpl_name.tmpl" ; #print "PATH: $tmpl_path\n" ; + + next if $tmpl_path =~ /\n/ ; next unless -r $tmpl_path ; # cache the path to this template @@ -465,11 +537,15 @@ sub _find_template { # slurp in the template file and return it as a scalar ref - return scalar read_file( $tmpl_path, scalar_ref => 1 ) ; +#print "FOUND $tmpl_name\n" ; + + return read_file( $tmpl_path, scalar_ref => 1 ) ; } - die <{include_paths}}' +#print "CAN'T FIND $tmpl_name\n" ; + + croak <{search_dirs}}' DIE } @@ -480,7 +556,7 @@ __END__ =head1 NAME -Template::Simple - A simple and fast template module +Template::Simple - A simple and very fast template module =head1 VERSION @@ -492,7 +568,10 @@ Version 0.03 my $tmpl = Template::Simple->new(); - my $template = < { date => 'Jan 1, 2008', @@ -520,65 +605,40 @@ TMPL }, } ; - my $rendered = $tmpl->render( $template, $data ) ; - -=head1 DESCRIPTION - -Template::Simple has these goals: - -=over 4 - -=item * Support most common template operations - -It can recursively include other templates, replace tokens (scalars), -recursively render nested chunks of text and render lists. By using -simple idioms you can get conditional renderings. - -=item * Complete isolation of template from program code - -This is very important as template design can be done by different -people than the program logic. It is rare that one person is well -skilled in both template design and also programming. - -=item * Very simple template markup (only 4 markups) + # this call renders the template with the data tree -The only markups are C, C, C and C. See -MARKUP for more. + my $rendered = $tmpl->render( \$template_text, $data ) ; -=item * Easy to follow rendering rules + # here we add the template to the cache and give it a name -Rendering of templates and chunks is driven from a data tree. The type -of the data element used in an rendering controls how the rendering -happens. The data element can be a scalar or scalar reference or an -array, hash or code reference. + $tmpl->add_templates( { demo => $template_text } ) ; -=item * Efficient template rendering + # this compiles and then renders that template with the same data + # but is much faster -Rendering is very simple and uses Perl's regular expressions -efficiently. Because the markup is so simple less processing is needed -than many other templaters. Precompiling templates is not supported -yet but that optimization is on the TODO list. + $tmpl->compile( 'demo' ) ; + my $rendered = $tmpl->render( 'demo', $data ) ; -=item * Easy user extensions -User code can be called during an rendering so you can do custom -renderings and plugins. Closures can be used so the code can have its -own private data for use in rendering its template chunk. +=head1 DESCRIPTION -=back +Template::Simple is a very fast template rendering module with a +simple markup. It can do almost any templating task and is extendable +with user callbacks. It can render templates directly or compile them +for more speed. -=head2 new() +=head1 CONSTRUCTOR +=head2 new + You create a Template::Simple by calling the class method new: my $tmpl = Template::Simple->new() ; All the arguments to C are key/value options that change how -the object will do renderings. - -=over 4 +the object will render templates. -=item pre_delim +=head2 pre_delim This option sets the string or regex that is the starting delimiter for all markups. You can use a plain string or a qr// but you need to @@ -591,7 +651,7 @@ chars. The default is qr/\[%/. my $rendered = $tmpl->render( '<%FOO%]', 'bar' ) ; -=item post_delim +=head2 post_delim This option sets the string or regex that is the ending delimiter for all markups. You can use a plain string or a qr// but you need to @@ -604,26 +664,35 @@ chars. The default is qr/%]/. my $rendered = $tmpl->render( '[%FOO%>', 'bar' ) ; -=item greedy_chunk +=head2 token_re + +This option overrides the regular expression that is used match a +token or name in the markup. It should be a qr// and you may need to +escape (with \Q or \) any regex metachars if you want them to be plain +chars. The default is qr/\w+?/. + + my $tmpl = Template::Simple->new( + token_re => qr/[\w-]+?/, + ); + + my $rendered = $tmpl->render( + '[% id-with-hyphens %]', + { 'id-with-hyphens' => 'bar' } + ) ; + +=head2 greedy_chunk This boolean option will cause the regex that grabs a chunk of text between the C markups to become greedy (.+). The default is a not-greedy grab of the chunk text. (UNTESTED) -=item templates +=head2 templates This option lets you load templates directly into the cache of the -Template::Simple object. This cache will be searched by the C -markup which will be replaced by the template if found. The option -value is a hash reference which has template names (the name in the -C markup) for keys and their template text as their -values. You can delete or clear templates from the object cache with -the C method. - +Template::Simple object. See