| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Tenjin::Template; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 8 |  |  | 8 |  | 29 | use strict; | 
|  | 8 |  |  |  |  | 11 |  | 
|  | 8 |  |  |  |  | 193 |  | 
| 4 | 8 |  |  | 8 |  | 28 | use warnings; | 
|  | 8 |  |  |  |  | 9 |  | 
|  | 8 |  |  |  |  | 239 |  | 
| 5 | 8 |  |  | 8 |  | 37 | use Fcntl qw/:flock/; | 
|  | 8 |  |  |  |  | 6 |  | 
|  | 8 |  |  |  |  | 1037 |  | 
| 6 | 8 |  |  | 8 |  | 35 | use Carp; | 
|  | 8 |  |  |  |  | 15 |  | 
|  | 8 |  |  |  |  | 15039 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | our $VERSION = "1.000000"; | 
| 9 |  |  |  |  |  |  | $VERSION = eval $VERSION; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | =head1 NAME | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | Tenjin::Template - A Tenjin template object, either built from a file or from memory. | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | # mostly used internally, but you can manipulate | 
| 18 |  |  |  |  |  |  | # templates like so | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | my $template = Tenjin::Template->new('/path/to/templates/template.html'); | 
| 21 |  |  |  |  |  |  | my $context = { scalar => 'scalar', arrayref => ['one', 2, "3"] }; | 
| 22 |  |  |  |  |  |  | $template->render($context); | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | This module is in charge of the task of compiling Tenjin templates. | 
| 27 |  |  |  |  |  |  | Templates in Tenjin are compiled into standard Perl code (combined with | 
| 28 |  |  |  |  |  |  | any Perl code used inside the templates themselves). Rendering a template | 
| 29 |  |  |  |  |  |  | means Cuating that Perl code and returning its output. | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | The Tenjin engine reads a template file or a template string, and creates | 
| 32 |  |  |  |  |  |  | a Template object from it. Then the object compiles itself by traversing | 
| 33 |  |  |  |  |  |  | the template, parsing Tenjin macros like 'include' and 'start_capture', | 
| 34 |  |  |  |  |  |  | replaces Tenjin expressions (i.e. C<[== $expr =]> or C<[= $expr =]>) with the | 
| 35 |  |  |  |  |  |  | appropriate Perl code, etc. This module ties a template object with | 
| 36 |  |  |  |  |  |  | a context object, but all context manipulation (and the actual Cuation | 
| 37 |  |  |  |  |  |  | of the Perl code) is done by L. | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | If you're planning on using this module by itself (i.e. without the L | 
| 40 |  |  |  |  |  |  | engine), keep in mind that template caching and layout templates are not | 
| 41 |  |  |  |  |  |  | handled by this module. | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =cut | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | our $MACRO_HANDLER_TABLE = { | 
| 46 |  |  |  |  |  |  | 'include' => sub { my $arg = shift; | 
| 47 |  |  |  |  |  |  | " \$_buf .= \$_context->{'_engine'}->render($arg, \$_context, 0);"; | 
| 48 |  |  |  |  |  |  | }, | 
| 49 |  |  |  |  |  |  | 'start_capture' => sub { my $arg = shift; | 
| 50 |  |  |  |  |  |  | " my \$_buf_bkup=\$_buf; \$_buf=''; my \$_capture_varname=$arg;"; | 
| 51 |  |  |  |  |  |  | }, | 
| 52 |  |  |  |  |  |  | 'stop_capture' => sub { my $arg = shift; | 
| 53 |  |  |  |  |  |  | " \$_context->{\$_capture_varname}=\$_buf; \$_buf=\$_buf_bkup;"; | 
| 54 |  |  |  |  |  |  | }, | 
| 55 |  |  |  |  |  |  | 'start_placeholder' => sub { my $arg = shift; | 
| 56 |  |  |  |  |  |  | " if (\$_context->{$arg}) { \$_buf .= \$_context->{$arg}; } else {"; | 
| 57 |  |  |  |  |  |  | }, | 
| 58 |  |  |  |  |  |  | 'stop_placeholder' => sub { my $arg = shift; | 
| 59 |  |  |  |  |  |  | " }"; | 
| 60 |  |  |  |  |  |  | }, | 
| 61 |  |  |  |  |  |  | 'echo' => sub { my $arg = shift; | 
| 62 |  |  |  |  |  |  | " \$_buf .= $arg;"; | 
| 63 |  |  |  |  |  |  | }, | 
| 64 |  |  |  |  |  |  | }; | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =head1 METHODS | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | =head2 new( [$filename, \%opts] ) | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | Creates a new Tenjin::Template object, possibly from a file on the file | 
| 71 |  |  |  |  |  |  | system (in which case C<$filename> must be provided and be an absolute | 
| 72 |  |  |  |  |  |  | path to a template file). Optionally, a hash-ref of options can be | 
| 73 |  |  |  |  |  |  | passed to set some customizations. Available options are 'escapefunc', | 
| 74 |  |  |  |  |  |  | which will be in charge of escaping expressions (from C<[= $expr =]>) instead | 
| 75 |  |  |  |  |  |  | of the internal method (which uses L); and 'rawclass', | 
| 76 |  |  |  |  |  |  | which can be used to prevent variables and objects of a certain class | 
| 77 |  |  |  |  |  |  | from being escaped, in which case the variable must be a hash-ref | 
| 78 |  |  |  |  |  |  | that has a key named 'str', which will be used instead. So, for example, | 
| 79 |  |  |  |  |  |  | if you have a variable named C<$var> which is a hash-ref, and 'rawclass' | 
| 80 |  |  |  |  |  |  | is set as 'HASH', then writing C<[= $var =]> on your templates will replace | 
| 81 |  |  |  |  |  |  | C<$var> with C<< $var->{str} >>. | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | =cut | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | sub new { | 
| 86 | 18 |  |  | 18 | 1 | 32 | my ($class, $filename, $template_name, $opts) = @_; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 18 | 50 | 33 |  |  | 86 | my $escapefunc = defined($opts) && exists($opts->{escapefunc}) ? $opts->{escapefunc} : undef; | 
| 89 | 18 | 50 | 33 |  |  | 69 | my $rawclass   = defined($opts) && exists($opts->{rawclass}) ? $opts->{rawclass} : undef; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 18 |  |  |  |  | 91 | my $self = bless { | 
| 92 |  |  |  |  |  |  | 'filename'   => $filename, | 
| 93 |  |  |  |  |  |  | 'name'       => $template_name, | 
| 94 |  |  |  |  |  |  | 'script'     => undef, | 
| 95 |  |  |  |  |  |  | 'escapefunc' => $escapefunc, | 
| 96 |  |  |  |  |  |  | 'rawclass'   => $rawclass, | 
| 97 |  |  |  |  |  |  | 'timestamp'  => undef, | 
| 98 |  |  |  |  |  |  | 'args'       => undef, | 
| 99 |  |  |  |  |  |  | }, $class; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 18 | 50 |  |  |  | 44 | $self->convert_file($filename) if $filename; | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 18 |  |  |  |  | 43 | return $self; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | =head2 render( [$_context] ) | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | Renders the template, possibly with a context hash-ref, and returns the | 
| 109 |  |  |  |  |  |  | rendered output. If errors have occured when rendering the template (which | 
| 110 |  |  |  |  |  |  | might happen since templates have and are Perl code), then this method | 
| 111 |  |  |  |  |  |  | will croak. | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | =cut | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub render { | 
| 116 | 23 |  |  | 23 | 1 | 27 | my ($self, $_context) = @_; | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 23 |  | 50 |  |  | 46 | $_context ||= {}; | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 23 | 50 |  |  |  | 55 | if ($self->{func}) { | 
| 121 | 0 |  |  |  |  | 0 | return $self->{func}->($_context); | 
| 122 |  |  |  |  |  |  | } else { | 
| 123 | 23 | 100 |  |  |  | 141 | $_context = $Tenjin::CONTEXT_CLASS->new($_context) if ref $_context eq 'HASH'; | 
| 124 |  |  |  |  |  |  |  | 
| 125 | 23 |  |  |  |  | 40 | my $script = $self->{script}; | 
| 126 | 23 | 50 |  |  |  | 93 | $script = $_context->_build_decl() . $script unless $self->{args}; | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # rendering is actually done inside the context object | 
| 129 |  |  |  |  |  |  | # with the evaluate method. We pass either the name of | 
| 130 |  |  |  |  |  |  | # the template or the filename of the template for debug | 
| 131 |  |  |  |  |  |  | # purposes | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 23 |  | 33 |  |  | 105 | return $_context->evaluate($script, $self->{filename} || $self->{name}); | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  | } | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | =head1 INTERNAL METHODS | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | =head2 convert_file( $filename ) | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | Receives an absolute path to a template file, converts that file | 
| 142 |  |  |  |  |  |  | to Perl code by calling L and | 
| 143 |  |  |  |  |  |  | returns that code. | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | =cut | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | sub convert_file { | 
| 148 | 0 |  |  | 0 | 1 | 0 | my ($self, $filename) = @_; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 0 |  |  |  |  | 0 | return $self->convert($self->_read_file($filename, 1), $filename); | 
| 151 |  |  |  |  |  |  | } | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | =head2 convert( $input, [$filename] ) | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | Receives a text of a template (i.e. the template itself) and possibly | 
| 156 |  |  |  |  |  |  | an absolute path to the template file (if the template comes from a file), | 
| 157 |  |  |  |  |  |  | and converts the template into Perl code, which is later Cuated | 
| 158 |  |  |  |  |  |  | for rendering. Conversion is done by parsing the statements in the | 
| 159 |  |  |  |  |  |  | template (see L). | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | =cut | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | sub convert { | 
| 164 | 18 |  |  | 18 | 1 | 54 | my ($self, $input, $filename) = @_; | 
| 165 |  |  |  |  |  |  |  | 
| 166 | 18 |  |  |  |  | 55 | $self->{filename} = $filename; | 
| 167 | 18 |  |  |  |  | 36 | my @buf = ('my $_buf = ""; my $_V; ', ); | 
| 168 | 18 |  |  |  |  | 48 | $self->parse_stmt(\@buf, $input); | 
| 169 |  |  |  |  |  |  |  | 
| 170 | 18 |  |  |  |  | 209 | return $self->{script} = $buf[0] . " \$_buf;\n"; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | =head2 compile_stmt_pattern( $pl ) | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | Receives a string which denotes the Perl code delimiter which is used | 
| 176 |  |  |  |  |  |  | inside templates. Tenjin uses 'C<<  >>' and 'C<<  >>' | 
| 177 |  |  |  |  |  |  | (the latter for preprocessing), so C<$pl> will be 'pl'. This method | 
| 178 |  |  |  |  |  |  | returns a tranlsation regular expression which will be used for reading | 
| 179 |  |  |  |  |  |  | embedded Perl code. | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | =cut | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | sub compile_stmt_pattern { | 
| 184 | 18 |  |  | 18 | 1 | 23 | my $pl = shift; | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 18 |  |  |  |  | 36 | my $pat = '((^[ \t]*)?<\?'.$pl.'( |\t|\r?\n)(.*?) ?\?>([ \t]*\r?\n)?)'; | 
| 187 | 18 |  |  |  |  | 321 | return qr/$pat/sm; | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | =head2 stmt_pattern | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | Returns the default pattern (which uses 'pl') with the | 
| 193 |  |  |  |  |  |  | L. | 
| 194 |  |  |  |  |  |  |  | 
| 195 |  |  |  |  |  |  | =cut | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | sub stmt_pattern { | 
| 198 | 18 |  |  | 18 | 1 | 39 | return compile_stmt_pattern('pl'); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | =head2 expr_pattern() | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | Defines how expressions are written in Tenjin templates (C<[== $expr =]> | 
| 204 |  |  |  |  |  |  | and C<[= $expr =]>). | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | =cut | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | sub expr_pattern { | 
| 209 | 23 |  |  | 23 | 1 | 52 | return qr/\[=(=?)(.*?)(=?)=\]/s; | 
| 210 |  |  |  |  |  |  | } | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =head2 parse_stmt( $bufref, $input ) | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | Receives a buffer which is used for saving a template's expressions | 
| 215 |  |  |  |  |  |  | and the template's text, parses all expressions in the templates and | 
| 216 |  |  |  |  |  |  | pushes them to the buffer. | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | =cut | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | sub parse_stmt { | 
| 221 | 18 |  |  | 18 | 1 | 35 | my ($self, $bufref, $input) = @_; | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 18 |  |  |  |  | 32 | my $pos = 0; | 
| 224 | 18 |  |  |  |  | 40 | my $pat = $self->stmt_pattern(); | 
| 225 | 18 |  |  |  |  | 159 | while ($input =~ /$pat/g) { | 
| 226 | 14 |  |  |  |  | 42 | my ($pi, $lspace, $mspace, $stmt, $rspace) = ($1, $2, $3, $4, $5); | 
| 227 | 14 |  |  |  |  | 37 | my $start = $-[0]; | 
| 228 | 14 |  |  |  |  | 50 | my $text = substr($input, $pos, $start - $pos); | 
| 229 | 14 |  |  |  |  | 21 | $pos = $start + length($pi); | 
| 230 | 14 | 100 |  |  |  | 34 | $self->parse_expr($bufref, $text) if $text; | 
| 231 | 14 | 100 |  |  |  | 35 | $mspace = '' if $mspace eq ' '; | 
| 232 | 14 |  |  |  |  | 25 | $stmt = $self->hook_stmt($stmt); | 
| 233 | 14 | 100 |  |  |  | 31 | $stmt .= $rspace if $rspace; | 
| 234 | 14 | 100 |  |  |  | 25 | $stmt = $mspace . $stmt if $mspace; | 
| 235 | 14 | 50 |  |  |  | 20 | $stmt = $lspace . $stmt if $lspace; | 
| 236 | 14 |  |  |  |  | 23 | $self->add_stmt($bufref, $stmt); | 
| 237 |  |  |  |  |  |  | } | 
| 238 | 18 | 100 |  |  |  | 63 | my $rest = $pos == 0 ? $input : substr($input, $pos); | 
| 239 | 18 | 100 |  |  |  | 85 | $self->parse_expr($bufref, $rest) if $rest; | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | =head2 hook_stmt( $stmt ) | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | =cut | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | sub hook_stmt { | 
| 247 | 14 |  |  | 14 | 1 | 17 | my ($self, $stmt) = @_; | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | ## macro expantion | 
| 250 | 14 | 100 |  |  |  | 45 | if ($stmt =~ /\A(\s*)(\w+)\((.*?)\);?(\s*)\Z/) { | 
| 251 | 7 |  |  |  |  | 16 | my ($lspace, $funcname, $arg, $rspace) = ($1, $2, $3, $4); | 
| 252 | 7 |  |  |  |  | 14 | my $s = $self->expand_macro($funcname, $arg); | 
| 253 | 7 | 100 |  |  |  | 24 | return $lspace . $s . $rspace if defined($s); | 
| 254 |  |  |  |  |  |  | } | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | ## template arguments | 
| 257 | 8 | 50 |  |  |  | 23 | unless ($self->{args}) { | 
| 258 | 8 | 50 |  |  |  | 24 | if ($stmt =~ m/\A(\s*)\#\@ARGS\s+(.*)(\s*)\Z/) { | 
| 259 | 0 |  |  |  |  | 0 | my ($lspace, $argstr, $rspace) = ($1, $2, $3); | 
| 260 | 0 |  |  |  |  | 0 | my @args = (); | 
| 261 | 0 |  |  |  |  | 0 | my @declares = (); | 
| 262 | 0 |  |  |  |  | 0 | foreach my $arg (split(/,/, $argstr)) { | 
| 263 | 0 |  |  |  |  | 0 | $arg =~ s/(^\s+|\s+$)//g; | 
| 264 | 0 | 0 |  |  |  | 0 | next unless $arg; | 
| 265 | 0 | 0 |  |  |  | 0 | $arg =~ m/\A([\$\@\%])?([a-zA-Z_]\w*)\Z/ or croak "[Tenjin] $arg: invalid template argument."; | 
| 266 | 0 | 0 | 0 |  |  | 0 | croak "[Tenjin] $arg: only '\$var' is available for template argument." unless (!$1 || $1 eq '$'); | 
| 267 | 0 |  |  |  |  | 0 | my $name = $2; | 
| 268 | 0 |  |  |  |  | 0 | push(@args, $name); | 
| 269 | 0 |  |  |  |  | 0 | push(@declares, "my \$$name = \$_context->{$name}; "); | 
| 270 |  |  |  |  |  |  | } | 
| 271 | 0 |  |  |  |  | 0 | $self->{args} = \@args; | 
| 272 | 0 |  |  |  |  | 0 | return $lspace . join('', @declares) . $rspace; | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  | } | 
| 275 |  |  |  |  |  |  |  | 
| 276 | 8 |  |  |  |  | 13 | return $stmt; | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | =head2 expand_macro( $funcname, $arg ) | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | This method is in charge of invoking macro functions which might be used | 
| 282 |  |  |  |  |  |  | inside templates. The following macros are available: | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | =over | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | =item * C | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | Includes another template, whose name is C<$filename>, inside the current | 
| 289 |  |  |  |  |  |  | template. The included template will be placed inside the template as if | 
| 290 |  |  |  |  |  |  | they were one unit, so the context variable applies to both. | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | =item * C and C | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | Tells Tenjin to capture the output of the rendered template from the point | 
| 295 |  |  |  |  |  |  | where C was called to the point where C | 
| 296 |  |  |  |  |  |  | was called. You must provide a name for the captured portion, which will be | 
| 297 |  |  |  |  |  |  | made available in the context as C<< $_context->{$name} >> for immediate | 
| 298 |  |  |  |  |  |  | usage. Note that the captured portion will not be printed unless you do | 
| 299 |  |  |  |  |  |  | so explicilty with C<< $_context->{$name} >>. | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | =item * C and C | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | This is a special method which can be used for making your templates a bit | 
| 304 |  |  |  |  |  |  | cleaner. Suppose your context might have a variable whose name is defined | 
| 305 |  |  |  |  |  |  | in C<$var>. If that variable exists in the context, you simply want to print | 
| 306 |  |  |  |  |  |  | it, but if it's not, you want to print and/or perform other things. In that | 
| 307 |  |  |  |  |  |  | case you can call C with the name of the context | 
| 308 |  |  |  |  |  |  | variable you want printed, and if it's not, anything you do between | 
| 309 |  |  |  |  |  |  | C and C will be printed instead. | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | =item * echo( $exr ) | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | Just prints the provided expression. You might want to use it if you're | 
| 314 |  |  |  |  |  |  | a little too comfortable with PHP. | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | =back | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | =cut | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | sub expand_macro { | 
| 321 | 7 |  |  | 7 | 1 | 9 | my ($self, $funcname, $arg) = @_; | 
| 322 |  |  |  |  |  |  |  | 
| 323 | 7 |  |  |  |  | 13 | my $handler = $MACRO_HANDLER_TABLE->{$funcname}; | 
| 324 | 7 | 100 |  |  |  | 16 | return $handler ? $handler->($arg) : undef; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | =head2 get_expr_and_escapeflag( $not_escape, $expr, $delete_newline ) | 
| 328 |  |  |  |  |  |  |  | 
| 329 |  |  |  |  |  |  | =cut | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | ## ex. get_expr_and_escapeflag('=', '$item->{name}', '')  => 1, '$item->{name}', 0 | 
| 332 |  |  |  |  |  |  | sub get_expr_and_escapeflag { | 
| 333 | 17 |  |  | 17 | 1 | 60 | my ($self, $not_escape, $expr, $delete_newline) = @_; | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 17 |  |  |  |  | 48 | return $expr, $not_escape eq '', $delete_newline eq '='; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | =head2 parse_expr( $bufref, $input ) | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | =cut | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | sub parse_expr { | 
| 343 | 23 |  |  | 23 | 1 | 42 | my ($self, $bufref, $input) = @_; | 
| 344 |  |  |  |  |  |  |  | 
| 345 | 23 |  |  |  |  | 28 | my $pos = 0; | 
| 346 | 23 |  |  |  |  | 47 | $self->start_text_part($bufref); | 
| 347 | 23 |  |  |  |  | 59 | my $pat = $self->expr_pattern(); | 
| 348 | 23 |  |  |  |  | 158 | while ($input =~ /$pat/g) { | 
| 349 | 17 |  |  |  |  | 32 | my $start = $-[0]; | 
| 350 | 17 |  |  |  |  | 51 | my $text = substr($input, $pos, $start - $pos); | 
| 351 | 17 |  |  |  |  | 35 | my ($expr, $flag_escape, $delete_newline) = $self->get_expr_and_escapeflag($1, $2, $3); | 
| 352 | 17 |  |  |  |  | 35 | $pos = $+[0]; | 
| 353 | 17 | 100 |  |  |  | 52 | $self->add_text($bufref, $text) if $text; | 
| 354 | 17 | 50 |  |  |  | 51 | $self->add_expr($bufref, $expr, $flag_escape) if $expr; | 
| 355 | 17 | 100 |  |  |  | 95 | if ($delete_newline) { | 
| 356 | 4 |  |  |  |  | 7 | my $end = $+[0]; | 
| 357 | 4 | 100 |  |  |  | 28 | if (substr($input, $end, 1) eq "\n") { | 
| 358 | 1 |  |  |  |  | 2 | $bufref->[0] .= "\n"; | 
| 359 | 1 |  |  |  |  | 6 | $pos++; | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  | } | 
| 362 |  |  |  |  |  |  | } | 
| 363 | 23 | 100 |  |  |  | 93 | my $rest = $pos == 0 ? $input : substr($input, $pos); | 
| 364 | 23 |  |  |  |  | 45 | $self->add_text($bufref, $rest); | 
| 365 | 23 |  |  |  |  | 40 | $self->stop_text_part($bufref); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =head2 start_text_part( $bufref ) | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | =cut | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | sub start_text_part { | 
| 373 | 23 |  |  | 23 | 1 | 25 | my ($self, $bufref) = @_; | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 23 |  |  |  |  | 50 | $bufref->[0] .= ' $_buf .= '; | 
| 376 |  |  |  |  |  |  | } | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | =head2 stop_text_part( $bufref ) | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | =cut | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub stop_text_part { | 
| 383 | 23 |  |  | 23 | 1 | 28 | my ($self, $bufref) = @_; | 
| 384 |  |  |  |  |  |  |  | 
| 385 | 23 |  |  |  |  | 100 | $bufref->[0] .= '; '; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | =head2 add_text( $bufref, $text ) | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | =cut | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | sub add_text { | 
| 393 | 34 |  |  | 34 | 1 | 53 | my ($self, $bufref, $text) = @_; | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 34 | 100 |  |  |  | 78 | return unless $text; | 
| 396 | 30 |  |  |  |  | 54 | $text =~ s/([`\\])/\\$1/g; | 
| 397 | 30 |  |  |  |  | 77 | my $is_start = $bufref->[0] =~ / \$_buf \.= \Z/; | 
| 398 | 30 | 100 |  |  |  | 153 | $bufref->[0] .= $is_start ? "q`$text`" : " . q`$text`"; | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | =head2 add_stmt( $bufref, $stmt ) | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | =cut | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | sub add_stmt { | 
| 406 | 14 |  |  | 14 | 1 | 17 | my ($self, $bufref, $stmt) = @_; | 
| 407 |  |  |  |  |  |  |  | 
| 408 | 14 |  |  |  |  | 120 | $bufref->[0] .= $stmt; | 
| 409 |  |  |  |  |  |  | } | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | =head2 add_expr( $bufref, $expr, $flag_escape ) | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | =cut | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | sub add_expr { | 
| 416 | 17 |  |  | 17 | 1 | 21 | my ($self, $bufref, $expr, $flag_escape) = @_; | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 17 | 100 |  |  |  | 55 | my $dot = $bufref->[0] =~ / \$_buf \.= \Z/ ? '' : ' . '; | 
| 419 | 17 | 50 |  |  |  | 73 | $bufref->[0] .= $dot . ($flag_escape ? $self->escaped_expr($expr) : "($expr)"); | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | =head2 defun( $funcname, @args ) | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | =cut | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | sub defun {   ## (experimental) | 
| 427 | 0 |  |  | 0 | 1 | 0 | my ($self, $funcname, @args) = @_; | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 0 | 0 |  |  |  | 0 | unless ($funcname) { | 
| 430 | 0 |  |  |  |  | 0 | my $funcname = $self->{filename}; | 
| 431 | 0 | 0 |  |  |  | 0 | if ($funcname) { | 
| 432 | 0 |  |  |  |  | 0 | $funcname =~ s/\.\w+$//; | 
| 433 | 0 |  |  |  |  | 0 | $funcname =~ s/[^\w]/_/g; | 
| 434 |  |  |  |  |  |  | } | 
| 435 | 0 |  |  |  |  | 0 | $funcname = 'render_' . $funcname; | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 | 0 |  |  |  |  | 0 | my $str = "sub $funcname { my (\$_context) = \@_; "; | 
| 439 | 0 |  |  |  |  | 0 | foreach (@args) { | 
| 440 | 0 |  |  |  |  | 0 | $str .= "my \$$_ = \$_context->{'$_'}; "; | 
| 441 |  |  |  |  |  |  | } | 
| 442 | 0 |  |  |  |  | 0 | $str .= $self->{script}; | 
| 443 | 0 |  |  |  |  | 0 | $str .= "}\n"; | 
| 444 |  |  |  |  |  |  |  | 
| 445 | 0 |  |  |  |  | 0 | return $str; | 
| 446 |  |  |  |  |  |  | } | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | =head2 compile() | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | =cut | 
| 451 |  |  |  |  |  |  |  | 
| 452 |  |  |  |  |  |  | ## compile $self->{script} into closure. | 
| 453 |  |  |  |  |  |  | sub compile { | 
| 454 | 18 |  |  | 18 | 1 | 28 | my $self = shift; | 
| 455 |  |  |  |  |  |  |  | 
| 456 | 18 | 50 |  |  |  | 50 | if ($self->{args}) { | 
| 457 | 0 |  |  |  |  | 0 | $self->{func} = $Tenjin::CONTEXT_CLASS->to_func($self->{script}, $self->{name}); | 
| 458 | 0 |  |  |  |  | 0 | return $self->{func}; | 
| 459 |  |  |  |  |  |  | } | 
| 460 | 18 |  |  |  |  | 31 | return; | 
| 461 |  |  |  |  |  |  | } | 
| 462 |  |  |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | =head2 escaped_expr( $expr ) | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | Receives a Perl expression (from C<[= $expr =]>) and escapes it. This will | 
| 466 |  |  |  |  |  |  | happen in one of three ways: with the escape function defined in | 
| 467 |  |  |  |  |  |  | C<< $opts->{escapefunc} >> (if defined), with a scalar string (if | 
| 468 |  |  |  |  |  |  | C<< $opts->{rawclass} >> is defined), or with C from | 
| 469 |  |  |  |  |  |  | L, which uses L. | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | =cut | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | sub escaped_expr { | 
| 474 | 0 |  |  | 0 | 1 | 0 | my ($self, $expr) = @_; | 
| 475 |  |  |  |  |  |  |  | 
| 476 | 0 | 0 |  |  |  | 0 | return "$self->{escapefunc}($expr)" if $self->{escapefunc}; | 
| 477 |  |  |  |  |  |  |  | 
| 478 | 0 | 0 |  |  |  | 0 | return "(ref(\$_V = ($expr)) eq '$self->{rawclass}' ? \$_V->{str} : escape_xml($expr)" if $self->{rawclass}; | 
| 479 |  |  |  |  |  |  |  | 
| 480 | 0 |  |  |  |  | 0 | return "escape_xml($expr)"; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | =head2 _read_file( $filename, [$lock_required] ) | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | Receives an absolute path to a template file, reads its content and | 
| 486 |  |  |  |  |  |  | returns it. If C<$lock_required> is passed (and has a true value), the | 
| 487 |  |  |  |  |  |  | file will be locked for reading. | 
| 488 |  |  |  |  |  |  |  | 
| 489 |  |  |  |  |  |  | =cut | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | sub _read_file { | 
| 492 | 18 |  |  | 18 |  | 27 | my ($self, $filename, $lock_required) = @_; | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 18 | 50 |  | 7 |  | 513 | open(IN, "<:encoding($Tenjin::ENCODING)", $filename) | 
|  | 7 |  |  |  |  | 52 |  | 
|  | 7 |  |  |  |  | 11 |  | 
|  | 7 |  |  |  |  | 42 |  | 
| 495 |  |  |  |  |  |  | or croak "[Tenjin] Can't open $filename for reading: $!"; | 
| 496 | 18 | 50 |  |  |  | 62267 | flock(IN, LOCK_SH) if $lock_required; | 
| 497 |  |  |  |  |  |  |  | 
| 498 | 18 |  |  |  |  | 442 | read(IN, my $content, -s $filename); | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 18 |  |  |  |  | 327 | close(IN); | 
| 501 |  |  |  |  |  |  |  | 
| 502 | 18 |  |  |  |  | 115 | return $content; | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | =head2 _write_file( $filename, $content, [$lock_required] ) | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | Receives an absolute path to a template file and the templates contents, | 
| 508 |  |  |  |  |  |  | and creates the file (or truncates it, if existing) with that contents. | 
| 509 |  |  |  |  |  |  | If C<$lock_required> is passed (and has a true value), the file will be | 
| 510 |  |  |  |  |  |  | locked exclusively when writing. | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | =cut | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | sub _write_file { | 
| 515 | 16 |  |  | 16 |  | 27 | my ($self, $filename, $content, $lock_required) = @_; | 
| 516 |  |  |  |  |  |  |  | 
| 517 | 16 | 50 |  |  |  | 43 | my $enc = $Tenjin::ENCODING eq 'UTF-8' ? '>:utf8' : ">:encoding($Tenjin::ENCODING)"; | 
| 518 |  |  |  |  |  |  |  | 
| 519 | 16 | 50 |  |  |  | 1070 | open(OUT, $enc, $filename) | 
| 520 |  |  |  |  |  |  | or croak "[Tenjin] Can't open $filename for writing: $!"; | 
| 521 | 16 | 50 |  |  |  | 81 | flock(OUT, LOCK_EX) if $lock_required; | 
| 522 | 16 |  |  |  |  | 130 | print OUT $content; | 
| 523 | 16 |  |  |  |  | 548 | close(OUT); | 
| 524 |  |  |  |  |  |  | } | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | 1; | 
| 527 |  |  |  |  |  |  |  | 
| 528 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | L. | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | =head1 AUTHOR, LICENSE AND COPYRIGHT | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | See L. | 
| 535 |  |  |  |  |  |  |  | 
| 536 |  |  |  |  |  |  | =cut |