Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

restoring the layout template feature and the utility methods are aga…

…in available for templates. also in the process of completely documenting the code
  • Loading branch information...
commit 359aa430bb65ff467727e3486bffd6f22e8cfe94 1 parent d327009
@ido50 authored
View
9 .gitignore
@@ -3,3 +3,12 @@ inc/*
Makefile
Makefile.old
pm_to_blib
+Build
+Build.bat
+_build*
+pm_to_blib*
+*.tar.gz
+.lwpcookies
+cover_db
+pod2htm*.tmp
+Tenjin-*
View
7 Changes
@@ -1,5 +1,12 @@
Revision history for Perl extension Tenjin
+0.06 2010-02-24 00:00:00
+ - Restored the layout template feature which went MIA on 0.05
+ - Broadened the documentation for the layout template feature
+ - Made the utility functions available in Tenjin::Context again
+ - Added documentation for all modules and methods
+ - Fixed bug when preprocessing templates
+
0.052 2010-02-19 01:06:00
- Pointless update to remove git files from the release
View
10 MANIFEST
@@ -17,10 +17,12 @@ lib/Tenjin/Template.pm
lib/Tenjin/Util.pm
Makefile.PL
MANIFEST This list of files
-META.yml
README
-t/01use.t
-t/02pod.t
-t/03podcoverage.t
+t/00-load.t
+t/manifest.t
+t/pod.t
+t/pod-coverage.t
t/test_tmpl.pl
t/test_tmpl.html
+t/test_layout_tmpl.html
+t/test_layout_tmpl_2.html
View
1  MANIFEST.SKIP
@@ -0,0 +1 @@
+.gitignore
View
24 META.yml
@@ -1,24 +0,0 @@
----
-abstract: 'Fast templating engine with support for embedded Perl.'
-author:
- - 'Makoto Kuwata'
- - 'Ido Perelmutter <ido@ido50.net>'
-build_requires:
- Test::More: 0
-distribution_type: module
-generated_by: 'Module::Install version 0.77'
-license: MIT
-meta-spec:
- url: http://module-build.sourceforge.net/META-spec-v1.4.html
- version: 1.4
-name: Tenjin
-no_index:
- directory:
- - inc
- - t
-requires:
- perl: 5.8.0
- Encode: 0
-resources:
- license: http://www.opensource.org/licenses/mit-license.php
-version: 0.052
View
94 README
@@ -20,7 +20,7 @@ SYNOPSIS
print $output;
VERSION
- 0.052
+ 0.06
DESCRIPTION
Tenjin is a very fast and full-featured templating engine, implemented
@@ -57,7 +57,7 @@ DESCRIPTION
of this module is NOT backwards compatible with previous versions.
METHODS
- new \%options
+ new( \%options )
This creates a new instant of Tenjin. "\%options" is a hash-ref
containing Tenjin's configuration options:
@@ -72,7 +72,8 @@ METHODS
include the dot, such as '.html'. Empty by default.
* cache - If set to 1 (the default), compiled templates will be cached
- on the filesystem.
+ on the filesystem (this means the template's code will be cached,
+ not the completed rendered output).
* preprocess - Enable template preprocessing (turned off by default).
Only use if you're actually using any preprocessed Perl code in your
@@ -88,7 +89,7 @@ METHODS
* encoding - Another way to set the encoding of your template files
(set to utf8 by default).
- render( $tmpl_name, [\%context, $layout] )
+ render( $tmpl_name, [\%context, $use_layout] )
Renders a template whose name is identified by $tmpl_name. Remember that
a prefix and a postfix might be added if they where set when creating
the Tenjin instance.
@@ -98,13 +99,71 @@ METHODS
{ message => 'Hi there }, then you can use $message inside your
templates.
- $layout is a flag denoting whether or not to render this template into
- the layout template there was set when creating the Tenjin instance.
-
- Please note that file templates are cached on disk (with a '.cache')
- extension. Tenjin automatically deprecates these cache files every 10
- seconds. If you find this value is too low, you can override the
- $Tenjin::TIMESTAMP_INTERVAL variable with your preferred value.
+ $use_layout is a flag denoting whether or not to render this template
+ into a layout template (when doing so, the template will be rendered,
+ then the rendered output will be added to the context hash-ref as
+ '_content', and finally the layout template will be rendered with the
+ revised context and returned. If $use_layout is 1, than Tenjin will use
+ the layout template that was set when creating the Tenjin instance (via
+ the 'layout' configuration option). If you want to use a different
+ layout template (or if you haven't defined a layout template when
+ creating the Tenjin instance), then you must add the layout template's
+ name to the context as '_layout'. You can also just pass the layout
+ template's name as $use_layout, which has precendence over
+ "$context->{_layout}". If $use_layout is 0 or undefined, then a layout
+ template will not be used, even if "$context->{_layout}" is defined.
+
+ Please note that by default file templates are cached on disk (with a
+ '.cache') extension. Tenjin automatically deprecates these cache files
+ every 10 seconds. If you find this value is too low, you can override
+ the $Tenjin::TIMESTAMP_INTERVAL variable with your preferred value.
+
+INTERNAL METHODS
+ register_template( $template_name, $template )
+ Receives the name of a template and its Tenjin::Template object and
+ stores it in memory for usage by the engine.
+
+ get_template( $template_name, $context )
+ Receives the name of a template and the context object and tries to find
+ that template in the engine's memory. If it's not there, it will try to
+ find it in the file system (the cache file might be loaded, if present).
+ Returns the templates Tenjin::Template object.
+
+ to_filename( $template_name )
+ Receives a template name and returns the proper file name to be searched
+ in the file system, which will only be different than $template_name if
+ it begins with ':', in which case the prefix and postfix configuration
+ options will be appended and prepended to the template name (minus the
+ ':'), respectively.
+
+ find_template_file( $filename )
+ Receives a template filename and searches for it in the path defined in
+ the configuration options (or, if a path was not set, in the current
+ working directory). Returns the absolute path to the file.
+
+ read_template_file( $template, $filename, $context )
+ Receives a template object and its absolute file path and reads that
+ file. If preprocessing is on, preprocessing will take place using the
+ provided context object.
+
+ cachename( $filename )
+ Receives a template filename and returns its standard cache filename
+ (which will simply be $filename with '.cache' appended to it.
+
+ store_cachefile( $cachename, $template )
+ Receives the name of a template cache file and the corrasponding
+ template object, and creates the cache file on disk.
+
+ load_cachefile( $cachename, $template )
+ Receives the name of a template cache file and the corrasponding
+ template object, reads the cache file and stores it in the template
+ object (as 'script').
+
+ create_template( $filename, $context )
+ Receives an absolute path to a template file and the context object,
+ reads the file, processes it (which may involve loading the template's
+ cache file or creating the template's cache file), compiles it and
+ returns the template object.
SEE ALSO
The original Tenjin website is located at
@@ -120,18 +179,18 @@ SEE ALSO
using a .plhtml extension for the templates instead of .html (this is
entirely your choice).
- Tenjin::Template, Catalyst::View::Tenjin.
+ Tenjin::Template, Catalyst::View::Tenjin, Dancer::Template::Tenjin.
CHANGES
- This version of Tenjin breaks backwards compatibility with previous
+ Version 0.05 of this module broke backwards compatibility with previous
versions. In particular, the Tenjin::Engine module does not exist any
more and is instead integrated into this one. Templates are also
rendered entirely different (as per changes in the original tenjin)
which provides much faster rendering.
- Upon upgrading to this version, you MUST perform the following changes
- for your applications (or, if you're using Catalyst, you must also
- upgrade Catalyst::View::Tenjin):
+ Upon upgrading to versions 0.05 and above, you MUST perform the
+ following changes for your applications (or, if you're using Catalyst,
+ you must also upgrade Catalyst::View::Tenjin):
* "use Tenjin" as your normally would, but to get an instance of
Tenjin you must call "Tenjin->new()" instead of the old method of
@@ -142,6 +201,9 @@ CHANGES
templates structure and WILL cause your application to fail if
present.
+ Version 0.06 (this version) restored the layout template feature which
+ was accidentaly missing in version 0.05.
+
TODO
* Expand pod documentation and properly document the code, which is
hard to understand as it is.
View
440 lib/Tenjin.pm
@@ -3,12 +3,11 @@ package Tenjin;
use Tenjin::Context;
use Tenjin::Template;
use Tenjin::Preprocessor;
-use Tenjin::Util;
use strict;
use warnings;
-our $VERSION = 0.052;
+our $VERSION = 0.06;
our $USE_STRICT = 0;
our $ENCODING = 'utf8';
our $BYPASS_TAINT = 1; # unset if you like taint mode
@@ -17,6 +16,109 @@ our $CONTEXT_CLASS = 'Tenjin::Context';
our $PREPROCESSOR_CLASS = 'Tenjin::Preprocessor';
our $TIMESTAMP_INTERVAL = 10;
+=head1 NAME
+
+Tenjin - Fast templating engine with support for embedded Perl.
+
+=head1 SYNOPSIS
+
+ use Tenjin;
+
+ $Tenjin::USE_STRICT = 1; # use strict in the embedded Perl inside
+ # your templates. Recommended, but not used
+ # by default.
+
+ $Tenjin::ENCODING = "utf8"; # set the encoding of your template files
+ # to utf8. This is the default encoding used
+ # so there's no need to do this if your
+ # templates really are utf8.
+
+ my $engine = Tenjin->new(\%options);
+ my $context = { title => 'Tenjin Example', items => [qw/AAA BBB CCC/] };
+ my $filename = 'file.html';
+ my $output = $engine->render($filename, $context);
+ print $output;
+
+=head1 VERSION
+
+0.06
+
+=head1 DESCRIPTION
+
+Tenjin is a very fast and full-featured templating engine, implemented in several programming languages, among them Perl.
+
+The Perl version of Tenjin supports embedded Perl code, nestable layout template,
+inclusion of other templates inside a template, capturing parts of or the entire
+template output, file and memory caching, template arguments and preprocessing.
+
+The original version of Tenjin is developed by Makoto Kuwata. This CPAN
+version is developed by Ido Perlmuter and differs from the original in a
+few key aspects:
+
+=over
+
+=item * Code is entirely revised, packages are separated into modules, with
+a smaller number of packages than the original version. In particular, the
+Tenjin::Engine module no longer exists, and is now instead just the Tenjin
+module (i.e. this one).
+
+=item * Support for rendering templates from non-files sources (such as
+a database) is added.
+
+=item * Ability to set the encoding of your templates is added.
+
+=item * HTML is encoded and decoded using the L<HTML::Entities> module,
+instead of internally.
+
+=item * The C<pltenjin> script is not provided, at least for now.
+
+=back
+
+To make it clear, this version of Tenjin might somehow divert from the
+original Tenjin's roadmap. Although my aim is to be as compatible as
+possible (and this version is always updated with features and changes
+from the original), I cannot guarantee it. Please note that version 0.05
+of this module is NOT backwards compatible with previous versions.
+
+=head1 METHODS
+
+=head2 new( \%options )
+
+This creates a new instant of Tenjin. C<\%options> is a hash-ref
+containing Tenjin's configuration options:
+
+=over
+
+=item * B<path> - Array-ref of filesystem paths where templates will be searched
+
+=item * B<prefix> - A string that will be automatically prepended to template names
+when searching for them in the path. Empty by default.
+
+=item * B<postfix> - The default extension to be automtically appended to template names
+when searching for them in the path. Don't forget to include the
+dot, such as '.html'. Empty by default.
+
+=item * B<cache> - If set to 1 (the default), compiled templates will be cached on the
+filesystem (this means the template's code will be cached, not the completed rendered
+output).
+
+=item * B<preprocess> - Enable template preprocessing (turned off by default). Only
+use if you're actually using any preprocessed Perl code in your templates.
+
+=item * B<layout> - Name of a layout template that can be optionally used. If set,
+templates will be automatically inserted into the layout template,
+in the location where you use C<[== $_content ==]>.
+
+=item * B<strict> - Another way to make Tenjin use strict on embedded Perl code (turned
+off by default).
+
+=item * B<encoding> - Another way to set the encoding of your template files (set to utf8
+by default).
+
+=back
+
+=cut
+
sub new {
my ($class, $options) = @_;
@@ -37,38 +139,78 @@ sub new {
$Tenjin::USE_STRICT = $self->{strict};
}
- $self->{utils} = Tenjin::Util->new();
-
return bless $self, $class;
}
-sub to_filename {
- my ($self, $template_name) = @_;
+=head2 render( $tmpl_name, [\%context, $use_layout] )
- if (substr($template_name, 0, 1) eq ':') {
- return $self->{prefix} . substr($template_name, 1) . $self->{postfix};
- }
+Renders a template whose name is identified by C<$tmpl_name>. Remember that a prefix
+and a postfix might be added if they where set when creating the Tenjin instance.
- return $template_name;
-}
+C<$context> is a hash-ref containing the variables that will be available for usage inside
+the templates. So, for example, if your C<\%context> is { message => 'Hi there }, then
+you can use C<$message> inside your templates.
-sub find_template_file {
- my ($self, $filename) = @_;
+C<$use_layout> is a flag denoting whether or not to render this template into a layout
+template (when doing so, the template will be rendered, then the rendered output will be
+added to the context hash-ref as '_content', and finally the layout template will be rendered
+with the revised context and returned. If C<$use_layout> is 1, than Tenjin will use the
+layout template that was set when creating the Tenjin instance (via the 'layout' configuration
+option). If you want to use a different layout template (or if you haven't defined a layout
+template when creating the Tenjin instance), then you must add the layout template's name
+to the context as '_layout'. You can also just pass the layout template's name as C<$use_layout>,
+which has precendence over C<< $context->{_layout} >>. If C<$use_layout> is 0 or undefined,
+then a layout template will not be used, even if C<< $context->{_layout} >> is defined.
+
+Please note that by default file templates are cached on disk (with a '.cache') extension.
+Tenjin automatically deprecates these cache files every 10 seconds. If you
+find this value is too low, you can override the C<$Tenjin::TIMESTAMP_INTERVAL>
+variable with your preferred value.
- my $path = $self->{path};
- if ($path) {
- my $sep = $^O eq 'MSWin32' ? '\\\\' : '/';
- foreach my $dirname (@$path) {
- my $filepath = $dirname . $sep . $filename;
- return $filepath if -f $filepath;
- }
- } else {
- return $filename if -f $filename;
+=cut
+
+sub render {
+ my ($self, $template_name, $context, $use_layout) = @_;
+
+ $context ||= {};
+ $context->{'_engine'} = $self;
+
+ my $template = $self->get_template($template_name, $context); # pass $context only for preprocessing
+ my $output = $template->_render($context);
+ die("*** ERROR: $template->{filename}\n", $@) if $@;
+
+ # should we render inside a layout template?
+ if ($use_layout) {
+ # was a layout template name passed, or should we use the layout defined
+ # in when creating the engine instance?
+ my $layout_tmpl = $use_layout =~ m/^1$/ ? $self->{layout} : $use_layout;
+ $layout_tmpl ||= $context->{_layout};
+
+ # make sure we have a layout template to render
+ return $output unless $layout_tmpl;
+
+ # add the output of the rendered template to the context as '_content'
+ # and remove the reference to the layout from the context (if present)
+ $context->{_content} = $output;
+ delete $context->{_layout};
+
+ # render the layout template
+ $output = $self->get_template($layout_tmpl, $context)->_render($context);
+ die("*** ERROR: $layout_tmpl\n", $@) if $@;
}
- my $s = $path ? ("['" . join("','", @$path) . "']") : '[]';
- die "Tenjin::Engine: \"$filename not found (path=$s)\".";
+
+ return $output;
}
+=head1 INTERNAL METHODS
+
+=head2 register_template( $template_name, $template )
+
+Receives the name of a template and its L<Tenjin::Template> object
+and stores it in memory for usage by the engine.
+
+=cut
+
sub register_template {
my ($self, $template_name, $template) = @_;
@@ -76,6 +218,15 @@ sub register_template {
$self->{templates}->{$template_name} = $template;
}
+=head2 get_template( $template_name, $context )
+
+Receives the name of a template and the context object and tries to find
+that template in the engine's memory. If it's not there, it will try to find
+it in the file system (the cache file might be loaded, if present). Returns
+the templates L<Tenjin::Template> object.
+
+=cut
+
sub get_template {
my ($self, $template_name, $context) = @_;
@@ -96,6 +247,59 @@ sub get_template {
return $template;
}
+=head2 to_filename( $template_name )
+
+Receives a template name and returns the proper file name to be searched
+in the file system, which will only be different than C<$template_name>
+if it begins with ':', in which case the prefix and postfix configuration
+options will be appended and prepended to the template name (minus the ':'),
+respectively.
+
+=cut
+
+sub to_filename {
+ my ($self, $template_name) = @_;
+
+ if (substr($template_name, 0, 1) eq ':') {
+ return $self->{prefix} . substr($template_name, 1) . $self->{postfix};
+ }
+
+ return $template_name;
+}
+
+=head2 find_template_file( $filename )
+
+Receives a template filename and searches for it in the path defined in
+the configuration options (or, if a path was not set, in the current
+working directory). Returns the absolute path to the file.
+
+=cut
+
+sub find_template_file {
+ my ($self, $filename) = @_;
+
+ my $path = $self->{path};
+ if ($path) {
+ my $sep = $^O eq 'MSWin32' ? '\\\\' : '/';
+ foreach my $dirname (@$path) {
+ my $filepath = $dirname . $sep . $filename;
+ return $filepath if -f $filepath;
+ }
+ } else {
+ return $filename if -f $filename;
+ }
+ my $s = $path ? ("['" . join("','", @$path) . "']") : '[]';
+ die "Tenjin::Engine: \"$filename not found (path=$s)\".";
+}
+
+=head2 read_template_file( $template, $filename, $context )
+
+Receives a template object and its absolute file path and reads that file.
+If preprocessing is on, preprocessing will take place using the provided
+context object.
+
+=cut
+
sub read_template_file {
my ($self, $template, $filename, $context) = @_;
@@ -105,13 +309,33 @@ sub read_template_file {
$context->{'_engine'} = $self;
}
my $pp = $Tenjin::PREPROCESSOR_CLASS->new();
- $pp->convert($self->_read_file($filename));
+ $pp->convert($template->_read_file($filename));
return $pp->render($context);
}
- return $self->{utils}->read_file($filename, 1);
+ return $template->_read_file($filename, 1);
}
+=head2 cachename( $filename )
+
+Receives a template filename and returns its standard cache filename (which
+will simply be C<$filename> with '.cache' appended to it.
+
+=cut
+
+sub cachename {
+ my ($self, $filename) = @_;
+
+ return $filename . '.cache';
+}
+
+=head2 store_cachefile( $cachename, $template )
+
+Receives the name of a template cache file and the corrasponding template
+object, and creates the cache file on disk.
+
+=cut
+
sub store_cachefile {
my ($self, $cachename, $template) = @_;
@@ -120,13 +344,20 @@ sub store_cachefile {
my $args = $template->{args};
$cache = "\#\@ARGS " . join(',', @$args) . "\n" . $cache;
}
- $self->{utils}->write_file($cachename, $cache, 1);
+ $template->_write_file($cachename, $cache, 1);
}
+=head2 load_cachefile( $cachename, $template )
+
+Receives the name of a template cache file and the corrasponding template
+object, reads the cache file and stores it in the template object (as 'script').
+
+=cut
+
sub load_cachefile {
my ($self, $cachename, $template) = @_;
- my $cache = $self->{utils}->read_file($cachename, 1);
+ my $cache = $template->_read_file($cachename, 1);
if ($cache =~ s/\A\#\@ARGS (.*)\r?\n//) {
my $argstr = $1;
$argstr =~ s/\A\s+|\s+\Z//g;
@@ -136,11 +367,14 @@ sub load_cachefile {
$template->{script} = $cache;
}
-sub cachename {
- my ($self, $filename) = @_;
+=head2 create_template( $filename, $context )
- return $filename . '.cache';
-}
+Receives an absolute path to a template file and the context object, reads
+the file, processes it (which may involve loading the template's cache file
+or creating the template's cache file), compiles it and returns the template
+object.
+
+=cut
sub create_template {
my ($self, $filename, $context) = @_;
@@ -164,143 +398,10 @@ sub create_template {
return $template;
}
-sub render {
- my ($self, $template_name, $context) = @_;
-
- $context ||= {};
- $context->{'_engine'} = $self;
-
- my $template = $self->get_template($template_name, $context); # pass $context only for preprocessing
- my $output = $template->_render($context);
- die("*** ERROR: $template->{filename}\n", $@) if $@;
-
- return $output;
-}
-
__PACKAGE__;
__END__
-=pod
-
-=head1 NAME
-
-Tenjin - Fast templating engine with support for embedded Perl.
-
-=head1 SYNOPSIS
-
- use Tenjin;
-
- $Tenjin::USE_STRICT = 1; # use strict in the embedded Perl inside
- # your templates. Recommended, but not used
- # by default.
-
- $Tenjin::ENCODING = "utf8"; # set the encoding of your template files
- # to utf8. This is the default encoding used
- # so there's no need to do this if your
- # templates really are utf8.
-
- my $engine = Tenjin->new(\%options);
- my $context = { title => 'Tenjin Example', items => [qw/AAA BBB CCC/] };
- my $filename = 'file.html';
- my $output = $engine->render($filename, $context);
- print $output;
-
-=head1 VERSION
-
-0.052
-
-=head1 DESCRIPTION
-
-Tenjin is a very fast and full-featured templating engine, implemented in several programming languages, among them Perl.
-
-The Perl version of Tenjin supports embedded Perl code, nestable layout template,
-inclusion of other templates inside a template, capturing parts of or the entire
-template output, file and memory caching, template arguments and preprocessing.
-
-The original version of Tenjin is developed by Makoto Kuwata. This CPAN
-version is developed by Ido Perlmuter and differs from the original in a
-few key aspects:
-
-=over
-
-=item * Code is entirely revised, packages are separated into modules, with
-a smaller number of packages than the original version. In particular, the
-Tenjin::Engine module no longer exists, and is now instead just the Tenjin
-module (i.e. this one).
-
-=item * Support for rendering templates from non-files sources (such as
-a database) is added.
-
-=item * Ability to set the encoding of your templates is added.
-
-=item * HTML is encoded and decoded using the L<HTML::Entities> module,
-instead of internally.
-
-=item * The C<pltenjin> script is not provided, at least for now.
-
-=back
-
-To make it clear, this version of Tenjin might somehow divert from the
-original Tenjin's roadmap. Although my aim is to be as compatible as
-possible (and this version is always updated with features and changes
-from the original), I cannot guarantee it. Please note that version 0.05
-of this module is NOT backwards compatible with previous versions.
-
-=head1 METHODS
-
-=head2 new \%options
-
-This creates a new instant of Tenjin. C<\%options> is a hash-ref
-containing Tenjin's configuration options:
-
-=over
-
-=item * B<path> - Array-ref of filesystem paths where templates will be searched
-
-=item * B<prefix> - A string that will be automatically prepended to template names
- when searching for them in the path. Empty by default.
-
-=item * B<postfix> - The default extension to be automtically appended to template names
- when searching for them in the path. Don't forget to include the
- dot, such as '.html'. Empty by default.
-
-=item * B<cache> - If set to 1 (the default), compiled templates will be cached on the
- filesystem.
-
-=item * B<preprocess> - Enable template preprocessing (turned off by default). Only
- use if you're actually using any preprocessed Perl code in
- your templates.
-
-=item * B<layout> - Name of a layout template that can be optionally used. If set,
- templates will be automatically inserted into the layout template,
- in the location where you use C<[== $_content ==]>.
-
-=item * B<strict> - Another way to make Tenjin use strict on embedded Perl code (turned
- off by default).
-
-=item * B<encoding> - Another way to set the encoding of your template files (set to utf8
- by default).
-
-=back
-
-=head2 render( $tmpl_name, [\%context, $layout] )
-
-Renders a template whose name is identified by C<$tmpl_name>. Remember that a prefix
-and a postfix might be added if they where set when creating the Tenjin instance.
-
-C<$context> is a hash-ref containing the variables that will be available for usage inside
-the templates. So, for example, if your C<\%context> is { message => 'Hi there }, then
-you can use C<$message> inside your templates.
-
-C<$layout> is a flag denoting whether or not to render this template into the layout template
-there was set when creating the Tenjin instance.
-
-Please note that file templates are cached on disk (with a '.cache') extension.
-Tenjin automatically deprecates these cache files every 10 seconds. If you
-find this value is too low, you can override the C<$Tenjin::TIMESTAMP_INTERVAL>
-variable with your preferred value.
-
=head1 SEE ALSO
The original Tenjin website is located at L<http://www.kuwata-lab.com/tenjin/>. In there check out
@@ -312,17 +413,17 @@ Note that the Perl version of Tenjin is refered to as plTenjin on the Tenjin web
and that, as oppose to this module, the website suggests using a .plhtml extension
for the templates instead of .html (this is entirely your choice).
-L<Tenjin::Template>, L<Catalyst::View::Tenjin>.
+L<Tenjin::Template>, L<Catalyst::View::Tenjin>, L<Dancer::Template::Tenjin>.
=head1 CHANGES
-This version of Tenjin breaks backwards compatibility with previous versions.
+Version 0.05 of this module broke backwards compatibility with previous versions.
In particular, the Tenjin::Engine module does not exist any more and is
instead integrated into this one. Templates are also rendered entirely
different (as per changes in the original tenjin) which provides much
faster rendering.
-Upon upgrading to this version, you MUST perform the following changes
+Upon upgrading to versions 0.05 and above, you MUST perform the following changes
for your applications (or, if you're using Catalyst, you must also upgrade
L<Catalyst::View::Tenjin>):
@@ -338,6 +439,9 @@ templates structure and WILL cause your application to fail if present.
=back
+Version 0.06 (this version) restored the layout template feature which was
+accidentaly missing in version 0.05.
+
=head1 TODO
=over
View
108 lib/Tenjin/Context.pm
@@ -2,14 +2,75 @@ package Tenjin::Context;
use strict;
use warnings;
+use Tenjin::Util;
+
+=head1 NAME
+
+Tenjin::Context - In charge of managing variables passed to Tenjin templates.
+
+=head1 SYNOPSIS
+
+ # this module is used internally, but if you insist, it is
+ # in charge of the context object:
+
+ # in your templates (unnecessary, for illustration purposes):
+ <title>[== $context->{title} =]</title>
+ # instead use:
+ <title>[== $title =]</title>
+
+=head1 DESCRIPTION
+
+This module is in charge of managing Perl variables that are passed to
+templates upon rendering for direct usage. The context object is simply
+a hash-ref of key-value pairs, which are made available for templates
+as "standalone variables" named for each key in the hash-ref.
+
+This module is also in charge of the actual rendering of the templates,
+or more correctly, for evaluating the Perl code created from the templates,
+first integrating the context variables to them, and returning the rendered
+output.
+
+=head1 INTERNAL METHODS
+
+=head2 new( [\%vars] )
+
+Constructs a new context object, which is basically a hash-ref of key-value pairs
+which are passed to templates as variables. If a C<$vars> hash-ref is passed
+to the constructor, it will be augmented into the created object.
+
+To illustrate the context object, suppose it looks like so:
+
+ {
+ scalar => 'I am a scalar',
+ arrayref => [qw/I am an array/],
+ hashref => { i => 'am', a => 'hash-ref' },
+ }
+
+Then the variables C<$scalar>, C<$arrayref> and C<$hashref> will be available for
+direct usage inside your templates, and you can dereference the variables
+normally (i.e. C<@$arrayref> and C<%$hashref>).
+
+=cut
sub new {
my ($class, $self) = @_;
$self ||= {};
+
return bless $self, $class;
}
+=head2 evaluate( $script, [$filename] )
+
+This method receives a compiled template and actually performes the evaluation
+the renders it, then returning the rendered output. If Tenjin is configured
+to C<use strict>, the script will be C<eval>ed under C<use strict>.
+
+If the rendered template's filename is passed, a Perl comment noting that filename
+will be appended to the script prior to its evaluation.
+
+=cut
+
sub evaluate {
my ($self, $script, $filename) = @_;
@@ -30,6 +91,14 @@ sub evaluate {
return $ret;
}
+=head2 to_func( $script, [$filename] )
+
+This method receives the script created when reading a template and wraps
+it in a subroutine, C<eval>s it and returns the rendered output. This method
+is called when compiling the template.
+
+=cut
+
sub to_func {
my ($self, $script, $filename) = @_;
@@ -49,6 +118,16 @@ sub to_func {
return $ret;
}
+=head2 _build_decl()
+
+This method is in charge of making all the key-value pairs of the context
+object available to templates directly by the key names. This is simply done
+by traversing the key-value pairs of the context object and adding an
+assignment line between a scalar variable named as the key and its appropriate
+value.
+
+=cut
+
sub _build_decl {
my $self = shift;
@@ -60,23 +139,30 @@ sub _build_decl {
return $s;
}
+# this makes the Tenjin utility methods available to templates 'natively'
+*_p = *Tenjin::Util::_p;
+*_P = *Tenjin::Util::_P;
+*escape = *Tenjin::Util::escape_xml;
+*escape_xml = *Tenjin::Util::escape_xml;
+*unescape_xml = *Tenjin::Util::unescape_xml;
+*encode_url = *Tenjin::Util::encode_url;
+*decode_url = *Tenjin::Util::decode_url;
+*checked = *Tenjin::Util::checked;
+*selected = *Tenjin::Util::selected;
+*disabled = *Tenjin::Util::disabled;
+*nl2br = *Tenjin::Util::nl2br;
+*text2html = *Tenjin::Util::text2html;
+*tagattr = *Tenjin::Util::tagattr;
+*tagattrs = *Tenjin::Util::tagattrs;
+*new_cycle = *Tenjin::Util::new_cycle;
+
__PACKAGE__;
__END__
-=pod
-
-=head1 NAME
-
-Tenjin::Context
-
-=head1 SYNOPSIS
-
- used internally.
-
=head1 SEE ALSO
-L<Tenjin>.
+L<Tenjin>, L<Tenjin::Template>.
=head1 AUTHOR
View
40 lib/Tenjin/Preprocessor.pm
@@ -5,18 +5,44 @@ use warnings;
our @ISA = ('Tenjin::Template');
+=head1 NAME
+
+Tenjin::Preprocessor - Preprocessing Tenjin templates
+
+=head1 SYNOPSIS
+
+ used internally.
+
+=head1 DESCRIPTION
+
+This module provides some methods needed for preprocessing templates.
+
+=head1 INTERNAL METHODS
+
+=head2 stmt_pattern()
+
+=cut
+
sub stmt_pattern {
return shift->SUPER::compile_stmt_pattern('PL');
}
+=head2 expr_pattern()
+
+=cut
+
sub expr_pattern {
return qr/\[\*=(=?)(.*?)(=?)=\*\]/s;
}
+=head2 add_expr()
+
+=cut
+
sub add_expr {
my ($self, $bufref, $expr, $flag_escape) = @_;
- $expr = "$self->{utils}->_decode_params($expr)";
+ $expr = "decode_params($expr)";
$self->SUPER::add_expr($bufref, $expr, $flag_escape);
}
@@ -24,19 +50,9 @@ __PACKAGE__;
__END__
-=pod
-
-=head1 NAME
-
-Tenjin::Preprocessor - The Tenjin preprocessor
-
-=head1 SYNOPSIS
-
- used internally.
-
=head1 SEE ALSO
-L<Tenjin>.
+L<Tenjin>, L<Tenjin::Template>.
=head1 AUTHOR
View
51 lib/Tenjin/Template.pm
@@ -2,7 +2,7 @@ package Tenjin::Template;
use strict;
use warnings;
-use Tenjin::Util;
+use Fcntl qw/:flock/;
our $MACRO_HANDLER_TABLE = {
'include' => sub { my $arg = shift;
@@ -39,8 +39,6 @@ sub new {
'timestamp' => undef,
'args' => undef,
}, $class;
-
- $self->{utils} = Tenjin::Util->new();
$self->convert_file($filename) if $filename;
@@ -78,7 +76,7 @@ sub render {
sub convert_file {
my ($self, $filename) = @_;
- return $self->convert($self->{utils}->read_file($filename, 1), $filename);
+ return $self->convert($self->_read_file($filename, 1), $filename);
}
sub convert {
@@ -274,9 +272,50 @@ sub escaped_expr {
return "$self->{escapefunc}($expr)" if $self->{escapefunc};
- return "(ref(\$_V = ($expr)) eq '$self->{rawclass}' ? \$_V->{str} : \$_engine->{utils}->escape_xml($expr)" if $self->{rawclass};
+ return "(ref(\$_V = ($expr)) eq '$self->{rawclass}' ? \$_V->{str} : escape_xml($expr)" if $self->{rawclass};
+
+ return "escape_xml($expr)";
+}
+
+=head2 _read_file( $filename, [$lock_required] )
+
+Receives an absolute path to a template file, reads its content and
+returns it. If C<$lock_required> is passed (and has a true value), the
+file will be locked for reading.
+
+=cut
+
+sub _read_file {
+ my ($self, $filename, $lock_required) = @_;
+
+ open(IN, $filename) or die("Tenjin::Template: Can't open $filename for reading: $!");
+ binmode(IN);
+ flock(IN, LOCK_SH) if $lock_required;
+
+ read(IN, my $content, -s $filename);
+
+ close(IN);
+
+ return $content;
+}
+
+=head2 _write_file( $filename, $content, [$lock_required] )
+
+Receives an absolute path to a template file and the templates contents,
+and creates the file (or truncates it, if existing) with that contents.
+If C<$lock_required> is passed (and has a true value), the file will be
+locked exclusively when writing.
+
+=cut
+
+sub _write_file {
+ my ($self, $filename, $content, $lock_required) = @_;
- return "\$_engine->{utils}->escape_xml($expr)";
+ open(OUT, ">$filename") or die("Tenjin::Template: \"Can't open $filename for writing: $!\"");
+ binmode(OUT);
+ flock(OUT, LOCK_EX) if $lock_required;
+ print OUT $content;
+ close(OUT);
}
__PACKAGE__;
View
248 lib/Tenjin/Util.pm
@@ -2,43 +2,46 @@ package Tenjin::Util;
use strict;
use warnings;
-
-use Fcntl qw/:flock/;
use Encode;
use HTML::Entities;
-sub new {
- bless {}, shift;
-}
+=head1 NAME
-sub read_file {
- my ($self, $filename, $lock_required) = @_;
+Tenjin::Util - Utility methods for Tenjin.
- open(IN, $filename) or die("Tenjin::Util: Can't open $filename for reading: $!");
- binmode(IN);
- flock(IN, LOCK_SH) if ($lock_required);
+=head1 SYNOPSIS
- read(IN, my $content, -s $filename);
+ # in your templates:
+
+ # encode a URL
+ [== encode_url('http://www.google.com/search?q=tenjin&ie=utf-8&oe=utf-8&aq=t') =]
+ # returns http%3A//www.google.com/search%3Fq%3Dtenjin%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt
- close(IN);
+ # escape a string of lines of HTML code
+ <?pl my $string = '<h1>You & Me</h1>\n<h2>Me & You</h2>'; ?>
+ [== text2html($string) =]
+ # returns &lt;h1&gt;You &amp; Me&lt;/h1&gt;<br />\n&lt;h2&gt;Me &amp; You&lt;/h2&gt;
- return $content;
-}
+=head1 DESCRIPTION
-sub write_file {
- my ($self, $filename, $content, $lock_required) = @_;
+This module provides a few utility functions which can be used in your
+templates for your convenience. These include functions to (un)escape
+and (en/de)code URLs.
- open(OUT, ">$filename") or die("Tenjin::Util: \"Can't open $filename for writing: $!\"");
- binmode(OUT);
- flock(OUT, LOCK_EX) if $lock_required;
- print OUT $content;
- close(OUT);
-}
+=head1 METHODS
+
+=head2 expand_tabs( $str, [$tabwidth] )
+
+Receives a string that might contain tabs in it, and replaces those
+tabs with spaces, each tab with the number of spaces defined by C<$tabwidth>,
+or, if C<$tabwidth> was not passed, with 8 spaces.
+
+=cut
sub expand_tabs {
- my ($self, $str, $tabwidth) = @_;
+ my ($str, $tabwidth) = @_;
- $tabwidth = 8 unless defined($tabwidth);
+ $tabwidth ||= 8;
my $s = '';
my $pos = 0;
while ($str =~ /.*?\t/sg) { # /(.*?)\t/ may be slow
@@ -54,128 +57,213 @@ sub expand_tabs {
return $s;
}
+=head2 escape_xml( $str )
-sub _p {
- "<`\#$_[0]\#`>";
-}
+Receives a string of XML (or (x)HTML) code and converts the characters
+<>&\' to HTML entities. This is the method that is invoked when you use
+[= $expression =] in your templates.
+=cut
-sub _P {
- "<`\$$_[0]\$`>";
+sub escape_xml {
+ encode_entities($_[0], '<>&"\'');
}
+=head2 unescape_xml( $str )
-sub _decode_params {
- my ($self, $s) = @_;
+Receives a string of escaped XML (or (x)HTML) code (for example, a string
+that was escaped with the L<escape_xml()|escape_xml( $str )> function,
+and 'unescapes' all HTML entities back to their actual characters.
- return '' unless $s;
-
- $s =~ s/%3C%60%23(.*?)%23%60%3E/'[=='.$self->decode_url($1).'=]'/ge;
- $s =~ s/%3C%60%24(.*?)%24%60%3E/'[='.$self->decode_url($1).'=]'/ge;
- $s =~ s/&lt;`\#(.*?)\#`&gt;/'[=='.$self->unescape_xml($1).'=]'/ge;
- $s =~ s/&lt;`\$(.*?)\$`&gt;/'[='.$self->unescape_xml($1).'=]'/ge;
- $s =~ s/<`\#(.*?)\#`>/[==$1=]/g;
- $s =~ s/<`\$(.*?)\$`>/[=$1=]/g;
+=cut
- return $s;
+sub unescape_xml {
+ decode_entities($_[0]);
}
-sub escape_xml {
- encode_entities($_[1], '<>&"\'');
-}
+=head2 encode_url( $url )
-sub unescape_xml {
- decode_entities($_[1]);
-}
+Receives a URL and encodes it by escaping 'non-standard' characters.
+
+=cut
sub encode_url {
- my ($self, $s) = @_;
+ my $url = shift;
- $s =~ s/([^-A-Za-z0-9_.\/])/sprintf("%%%02X", ord($1))/sge;
- $s =~ tr/ /+/;
- return $s;
+ $url =~ s/([^-A-Za-z0-9_.\/])/sprintf("%%%02X", ord($1))/sge;
+ $url =~ tr/ /+/;
+ return $url;
}
+=head2 decode_url( $url )
+
+Does the opposite of L<encode_url()|encode_url( $url )>.
+
+=cut
+
sub decode_url {
- my ($self, $s) = @_;
+ my $url = shift;
- $s =~ s/\%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/sge;
- return $s;
+ $url =~ s/\%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/sge;
+ return $url;
}
+=head2 checked( $val )
+
+Receives a value of some sort, and if it is a true value, returns the string
+' checked="checked"' which can be appended to HTML checkboxes.
+
+=cut
+
sub checked {
- $_[1] ? ' checked="checked"' : '';
+ $_[0] ? ' checked="checked"' : '';
}
+=head2 selected( $val )
+
+Receives a value of some sort, and if it is a true value, returns the string
+' selected="selected"' which can be used in an option in an HTML select box.
+
+=cut
+
sub selected {
- $_[1] ? ' selected="selected"' : '';
+ $_[0] ? ' selected="selected"' : '';
}
+=head2 disabled( $val )
+
+Receives a value of some sort, and if it is a true value, returns the string
+' disabled="disabled"' which can be used in an HTML input.
+
+=cut
+
sub disabled {
- $_[1] ? ' disabled="disabled"' : '';
+ $_[0] ? ' disabled="disabled"' : '';
}
+=head2 nl2br( $text )
+
+Receives a string of text containing lines delimited by newline characters
+(\n, or possibly \r\n) and appends an HTML line break (<br />) to every
+line (the newline character is left untouched).
+
+=cut
+
sub nl2br {
- my ($self, $text) = @_;
+ my $text = shift;
$text =~ s/(\r?\n)/<br \/>$1/g;
return $text;
}
-sub text2html {
- my ($self, $text) = @_;
+=head2 text2html( $text )
+
+Receives a string of text containing lines delimited by newline characters,
+and possibly some XML (or (x)HTML) code, escapes that code with
+L<escape_xml()|escape_xml( $str )> and then appends an HTML line break
+to every line with L<nl2br()|nl2br( $text )>.
- $self->nl2br($self->escape_xml($text));
+=cut
+
+sub text2html {
+ nl2br(escape_xml($_[0]));
}
+=head2 tagattr( $name, $expr, [$value] )
+
+=cut
+
sub tagattr {
- my ($self, $name, $expr, $value) = @_;
+ my ($name, $expr, $value) = @_;
return '' unless $expr;
- $value = $expr unless defined($value);
+ $value = $expr unless defined $value;
return " $name=\"$value\"";
}
+=head2 tagattrs( %attrs )
+
+=cut
+
sub tagattrs {
- my ($self, %attrs) = @_;
+ my (%attrs) = @_;
my $s = '';
while (my ($k, $v) = each %attrs) {
- $s .= " $k=\"".$self->escape_xml($v)."\"" if defined $v;
+ $s .= " $k=\"".escape_xml($v)."\"" if defined $v;
}
return $s;
}
-## ex.
-## my $cycle = new_cycle('red', 'blue');
-## print $cycle->(); #=> 'red'
-## print $cycle->(); #=> 'blue'
-## print $cycle->(); #=> 'red'
-## print $cycle->(); #=> 'blue'
-sub new_cycle {
- my $self = shift;
+=head2 new_cycle( @items )
+
+Creates a subroutine reference that can be used for cycling through the
+items of the C<@items> array. So, for example, you can:
+ my $cycle = new_cycle(qw/red green blue/);
+ print $cycle->(); # prints 'red'
+ print $cycle->(); # prints 'green'
+ print $cycle->(); # prints 'blue'
+ print $cycle->(); # prints 'red' again
+
+=cut
+
+sub new_cycle {
my $i = 0;
sub { $_[$i++ % scalar @_] }; # returns
}
-__PACKAGE__;
+=head1 INTERNAL(?) METHODS
-__END__
+=head2 _p( $expression )
-=pod
+Wraps a Perl expression in a customized wrapper which will be processed
+by the Tenjin preprocessor and replaced with the standard [== $expression =].
-=head1 NAME
+=cut
-Tenjin::Util - Utility methods for Tenjin.
+sub _p {
+ "<`\#$_[0]\#`>";
+}
-=head1 SYNOPSIS
+=head2 _P( $expression )
- used internally.
+Wrap a Perl expression in a customized wrapper which will be processed
+by the Tenjin preprocessor and replaced with the standard [= $expression =],
+which means the expression will be escaped.
+
+=cut
+
+sub _P {
+ "<`\$$_[0]\$`>";
+}
+
+=head2 _decode_params( $s )
+
+=cut
+
+sub _decode_params {
+ my $s = shift;
+
+ return '' unless $s;
+
+ $s =~ s/%3C%60%23(.*?)%23%60%3E/'[=='.decode_url($1).'=]'/ge;
+ $s =~ s/%3C%60%24(.*?)%24%60%3E/'[='.decode_url($1).'=]'/ge;
+ $s =~ s/&lt;`\#(.*?)\#`&gt;/'[=='.unescape_xml($1).'=]'/ge;
+ $s =~ s/&lt;`\$(.*?)\$`&gt;/'[='.unescape_xml($1).'=]'/ge;
+ $s =~ s/<`\#(.*?)\#`>/[==$1=]/g;
+ $s =~ s/<`\$(.*?)\$`>/[=$1=]/g;
+
+ return $s;
+}
+
+__PACKAGE__;
+
+__END__
=head1 SEE ALSO
-L<Tenjin>.
+L<Tenjin>, L<Tenjin::Template>, L<Tenjin::Context>.
=head1 AUTHOR
View
10 t/00-load.t
@@ -0,0 +1,10 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+ use_ok( 'Tenjin' ) || print "Bail out!
+";
+}
+
+diag( "Testing Tenjin $Tenjin::VERSION, Perl $], $^X" );
View
3  t/01use.t
@@ -1,3 +0,0 @@
-use Test::More tests => 1;
-
-use_ok('Tenjin');
View
7 t/02pod.t
@@ -1,7 +0,0 @@
-use Test::More;
-
-eval "use Test::Pod 1.14";
-plan skip_all => 'Test::Pod 1.14 required' if $@;
-plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
-
-all_pod_files_ok();
View
7 t/03podcoverage.t
@@ -1,7 +0,0 @@
-use Test::More;
-
-eval "use Test::Pod::Coverage 1.04";
-plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@;
-plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
-
-all_pod_coverage_ok();
View
13 t/manifest.t
@@ -0,0 +1,13 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+ plan( skip_all => "Author tests not required for installation" );
+}
+
+eval "use Test::CheckManifest 0.9";
+plan skip_all => "Test::CheckManifest 0.9 required" if $@;
+ok_manifest( { filter => [ qr/\.git/ ] } );
View
18 t/pod-coverage.t
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+ if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+ if $@;
+
+all_pod_coverage_ok();
View
12 t/pod.t
@@ -0,0 +1,12 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();
View
8 t/test_layout_tmpl.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Tenjin Test</title>
+</head>
+<body>
+ [== $_content =]
+</body>
+</html>
View
9 t/test_layout_tmpl_2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+ <title>Tenjin Test 2</title>
+</head>
+<body>
+ <!-- This will be encoded -->
+ [= $_content =]
+</body>
+</html>
View
13 t/test_tmpl.pl
@@ -4,8 +4,9 @@
use warnings;
use Tenjin;
-my $t = Tenjin->new({ path => ['./'], postfix => '.html' });
-print $t->render('test_tmpl.html', {
+my $t = Tenjin->new({ path => ['./'], postfix => '.html', layout => 'test_layout_tmpl.html' });
+
+my $context = {
scalar_variable => 'hello',
hash_variable => { hash_value_key => 'sensible' },
array_variable => [ undef, undef, 'world' ],
@@ -20,4 +21,10 @@
variable_expression_a => 2,
variable_expression_b => 5,
variable_function_arg => 'asf asdf asdfff',
-});
+};
+
+print "Standalone rendering of test_tmpl.html:\n", $t->render('test_tmpl.html', $context);
+print "\n\n\nRendered inside test_layout_tmpl.html:\n", $t->render('test_tmpl.html', $context, 1);
+print "\n\n\nRendered inside test_layout_tmpl_2.html:\n", $t->render('test_tmpl.html', $context, 'test_layout_tmpl_2.html');
+print "\n\n\nUsing Tenjin Utility methods:\n", $t->render('test_util.html', $context), "\n";
+
View
6 t/test_util.html
@@ -0,0 +1,6 @@
+_p => [== _p("whaddup?") =]
+_P => [== _P("encode & me") =]
+escape_xml => [== escape_xml("<a href=\"http://localhost:3000/?key=value&value=key\">test\"test</a>") =]
+unescape_xml => [== unescape_xml("<a href=\"http://localhost:3000/?key=value&amp;value=key\">test&quot;test</a>") =]
+encode_url => [== encode_url("http://www.google.com/search?q=tenjin&ie=utf-8&oe=utf-8&aq=t") =]
+text2html => [== text2html("<h1>You & Me</h1>\n<h2>Me & You</h2>") =]
Please sign in to comment.
Something went wrong with that request. Please try again.