File Coverage

blib/lib/Dancer2/Template/Implementation/ForkedTiny.pm
Criterion Covered Total %
statement 64 68 94.1
branch 21 26 80.7
condition 5 6 83.3
subroutine 11 11 100.0
pod 2 3 66.6
total 103 114 90.3


line stmt bran cond sub pod time code
1             package Dancer2::Template::Implementation::ForkedTiny;
2             $Dancer2::Template::Implementation::ForkedTiny::VERSION = '1.0.0';
3             # ABSTRACT: Dancer2 own implementation of Template::Tiny
4              
5 115     115   206715 use 5.00503;
  115         617  
6 115     115   836 use strict;
  115         356  
  115         2843  
7 115     115   737 no warnings;
  115         368  
  115         4783  
8 115     115   2970 use Ref::Util qw<is_arrayref is_coderef is_plain_hashref>;
  115         6986  
  115         140780  
9              
10             # Evaluatable expression
11             my $EXPR = qr/ [a-z_][\w.]* /xs;
12              
13             sub new {
14 22     22 1 29043 my $self = bless {
15             start_tag => '[%',
16             end_tag => '%]',
17             @_[ 1 .. $#_ ]
18             },
19             $_[0];
20              
21             # Opening tag including whitespace chomping rules
22 22         619 my $LEFT = $self->{LEFT} = qr/
23             (?:
24             (?: (?:^|\n) [ \t]* )? \Q$self->{start_tag}\E\-
25             |
26             \Q$self->{start_tag}\E \+?
27             ) \s*
28             /xs;
29              
30             # Closing %] tag including whitespace chomping rules
31 22         386 my $RIGHT = $self->{RIGHT} = qr/
32             \s* (?:
33             \+? \Q$self->{end_tag}\E
34             |
35             \-\Q$self->{end_tag}\E (?: [ \t]* \n )?
36             )
37             /xs;
38              
39             # Preparsing run for nesting tags
40 22         1572 $self->{PREPARSE} = qr/
41             $LEFT ( IF | UNLESS | FOREACH ) \s+
42             (
43             (?: \S+ \s+ IN \s+ )?
44             \S+ )
45             $RIGHT
46             (?!
47             .*?
48             $LEFT (?: IF | UNLESS | FOREACH ) \b
49             )
50             ( .*? )
51             (?:
52             $LEFT ELSE $RIGHT
53             (?!
54             .*?
55             $LEFT (?: IF | UNLESS | FOREACH ) \b
56             )
57             ( .+? )
58             )?
59             $LEFT END $RIGHT
60             /xs;
61              
62 22         1301 $self->{CONDITION} = qr/
63             \Q$self->{start_tag}\E\s
64             ( ([IUF])\d+ ) \s+
65             (?:
66             ([a-z]\w*) \s+ IN \s+
67             )?
68             ( $EXPR )
69             \s\Q$self->{end_tag}\E
70             ( .*? )
71             (?:
72             \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
73             ( .+? )
74             )?
75             \Q$self->{start_tag}\E\s \1 \s\Q$self->{end_tag}\E
76             /xs;
77              
78 22         319 $self;
79             }
80              
81             # Copy and modify
82             sub preprocess {
83 3     3 0 12 my $self = shift;
84 3         6 my $text = shift;
85 3         10 $self->_preprocess( \$text );
86 3         13 return $text;
87             }
88              
89             sub process {
90 31     31 1 4919 my $self = shift;
91 31         70 my $copy = ${ shift() };
  31         1932  
92 31   50     115 my $stash = shift || {};
93              
94 31         73 local $@ = '';
95 31         286 local $^W = 0;
96              
97             # Preprocess to establish unique matching tag sets
98 31         142 $self->_preprocess( \$copy );
99              
100             # Process down the nested tree of conditions
101 31         139 my $result = $self->_process( $stash, $copy );
102 31 50       122 if (@_) {
    0          
103 31         59 ${ $_[0] } = $result;
  31         180  
104             }
105             elsif ( defined wantarray ) {
106 0         0 require Carp;
107 0         0 Carp::carp(
108             'Returning of template results is deprecated in Template::Tiny 0.11'
109             );
110 0         0 return $result;
111             }
112             else {
113 0         0 print $result;
114             }
115             }
116              
117              
118             ######################################################################
119             # Support Methods
120              
121             # The only reason this is a standalone is so we can
122             # do more in-depth testing.
123             sub _preprocess {
124 34     34   67 my $self = shift;
125 34         63 my $copy = shift;
126              
127             # Preprocess to establish unique matching tag sets
128 34         66 my $id = 0;
129 34         2002 1 while $$copy =~ s/
130             $self->{PREPARSE}
131             /
132 20         76 my $tag = substr($1, 0, 1) . ++$id;
133 20 100       1324 "\[\% $tag $2 \%\]$3\[\% $tag \%\]"
134             . (defined($4) ? "$4\[\% $tag \%\]" : '');
135             /sex;
136             }
137              
138             sub _process {
139 51     51   160 my ( $self, $stash, $text ) = @_;
140              
141 51         1192 $text =~ s/
142             $self->{CONDITION}
143             /
144             ($2 eq 'F')
145             ? $self->_foreach($stash, $3, $4, $5)
146 18 100       61 : eval {
    100          
147 16   100     38 $2 eq 'U'
148             xor
149             !! # Force boolification
150             $self->_expression($stash, $4)
151             }
152             ? $self->_process($stash, $5)
153             : $self->_process($stash, $6)
154             /gsex;
155              
156             # Resolve expressions
157 51         1310 $text =~ s/
158             $self->{LEFT} ( $EXPR ) $self->{RIGHT}
159             /
160 62         148 eval {
161 62         191 $self->_expression($stash, $1)
162             . '' # Force stringification
163             }
164             /gsex;
165              
166             # Trim the document
167 51 100       173 $text =~ s/^\s*(.+?)\s*\z/$1/s if $self->{TRIM};
168              
169 51         310 return $text;
170             }
171              
172             # Special handling for foreach
173             sub _foreach {
174 2     2   11 my ( $self, $stash, $term, $expr, $text ) = @_;
175              
176             # Resolve the expression
177 2         7 my $list = $self->_expression( $stash, $expr );
178 2 50       7 is_arrayref($list) or return '';
179              
180             # Iterate
181             return join '',
182 2         16 map { $self->_process( { %$stash, $term => $_ }, $text ) } @$list;
  4         21  
183             }
184              
185             # Evaluates a stash expression
186             sub _expression {
187 80     80   134 my $cursor = $_[1];
188 80         302 my @path = split /\./, $_[2];
189 80         205 foreach (@path) {
190              
191             # Support for private keys
192 107 100       286 return if substr( $_, 0, 1 ) eq '_';
193              
194             # Split by data type
195 105 100       244 ref $cursor or return '';
196 103 100       265 if ( is_arrayref($cursor) ) {
    100          
197 5 100       26 return '' unless /^(?:0|[0-9]\d*)\z/;
198 4         10 $cursor = $cursor->[$_];
199             }
200             elsif ( is_plain_hashref($cursor) ) {
201 96         233 $cursor = $cursor->{$_};
202             }
203             else {
204 2         6 $cursor = $cursor->$_();
205             }
206             }
207              
208             # If the last expression is a CodeRef, execute it
209 75 50       170 is_coderef($cursor)
210             and $cursor = $cursor->();
211 75         560 return $cursor;
212             }
213              
214             1;
215              
216             __END__
217              
218             =pod
219              
220             =encoding UTF-8
221              
222             =head1 NAME
223              
224             Dancer2::Template::Implementation::ForkedTiny - Dancer2 own implementation of Template::Tiny
225              
226             =head1 VERSION
227              
228             version 1.0.0
229              
230             =head1 SYNOPSIS
231              
232             my $template = Dancer2::Template::Implementation::ForkedTiny->new(
233             TRIM => 1,
234             );
235              
236             # Print the template results to STDOUT
237             $template->process( <<'END_TEMPLATE', { foo => 'World' } );
238             Hello [% foo %]!
239             END_TEMPLATE
240              
241             =head1 DESCRIPTION
242              
243             B<Dancer2::Template::Implementation::ForkedTiny> is a reimplementation of a subset of the functionality from
244             L<Template> Toolkit in as few lines of code as possible.
245              
246             It is intended for use in light-usage, low-memory, or low-cpu templating
247             situations, where you may need to upgrade to the full feature set in the
248             future, or if you want the retain the familiarity of TT-style templates.
249              
250             For the subset of functionality it implements, it has fully-compatible template
251             and stash API. All templates used with B<Dancer2::Template::Implementation::ForkedTiny> should be able to be
252             transparently upgraded to full Template Toolkit.
253              
254             Unlike Template Toolkit, B<Dancer2::Template::Implementation::ForkedTiny> will process templates without a
255             compile phase (but despite this is still quicker, owing to heavy use of
256             the Perl regular expression engine.
257              
258             =head2 SUPPORTED USAGE
259              
260             By default, the C<[% %]> tag style is used. You can change the start tag and
261             end tag by specifying them at object creation :
262              
263             my $template = Dancer2::Template::Implementation::ForkedTiny->new(
264             start_tag => '<%',
265             end_tag => '%>,
266             );
267              
268             In the rest of the documentation, C<[% %]> will be used, but it can be of
269             course your specified start / end tags.
270              
271             Both the C<[%+ +%]> style explicit whitespace and the C<[%- -%]> style
272             explicit chomp B<are> support, although the C<[%+ +%]> version is unneeded
273             in practice as B<Dancer2::Template::Implementation::ForkedTiny> does not support default-enabled C<PRE_CHOMP>
274             or C<POST_CHOMP>.
275              
276             Variable expressions in the form C<[% foo.bar.baz %]> B<are> supported.
277              
278             Appropriate simple behaviours for C<ARRAY> references, C<HASH> references and
279             objects are supported. "VMethods" such as [% array.length %] are B<not>
280             supported at this time.
281              
282             If the resulting expression is a CodeRef, it'll be evaluated.
283              
284             C<IF>, C<ELSE> and C<UNLESS> conditional blocks B<are> supported, but only with
285             simple C<[% foo.bar.baz %]> conditions.
286              
287             Support for looping (or rather iteration) is available in simple
288             C<[% FOREACH item IN list %]> form B<is> supported. Other loop structures are
289             B<not> supported. Because support for arbitrary or infinite looping is not
290             available, B<Dancer2::Template::Implementation::ForkedTiny> templates are not turing complete. This is
291             intentional.
292              
293             All of the four supported control structures C<IF>/C<ELSE>/C<UNLESS>/C<FOREACH>
294             can be nested to arbitrary depth.
295              
296             The treatment of C<_private> hash and method keys is compatible with
297             L<Template> Toolkit, returning null or false rather than the actual content
298             of the hash key or method.
299              
300             Anything beyond the above is currently out of scope.
301              
302             =head1 NAME
303              
304             Dancer2::Template::Implementation::ForkedTiny - Template Toolkit reimplemented in as little code as possible, forked from Template::Tiny
305              
306             =head1 METHODS
307              
308             =head2 new
309              
310             my $template = Dancer2::Template::Implementation::ForkedTiny->new(
311             TRIM => 1,
312             );
313              
314             The C<new> constructor is provided for compatibility with Template Toolkit.
315              
316             The only parameter it currently supports is C<TRIM> (which removes leading
317             and trailing whitespace from processed templates).
318              
319             Additional parameters can be provided without error, but will be ignored.
320              
321             =head2 process
322              
323             # DEPRECATED: Return template results (emits a warning)
324             my $text = $template->process( \$input, $vars );
325              
326             # Print template results to STDOUT
327             $template->process( \$input, $vars );
328              
329             # Generate template results into a variable
330             my $output = '';
331             $template->process( \$input, $vars, \$output );
332              
333             The C<process> method is called to process a template.
334              
335             The first parameter is a reference to a text string containing the template
336             text. A reference to a hash may be passed as the second parameter containing
337             definitions of template variables.
338              
339             If a third parameter is provided, it must be a scalar reference to be
340             populated with the output of the template.
341              
342             For a limited amount of time, the old deprecated interface will continue to
343             be supported. If C<process> is called without a third parameter, and in
344             scalar or list contest, the template results will be returned to the caller.
345              
346             If C<process> is called without a third parameter, and in void context, the
347             template results will be C<print()>ed to the currently selected file handle
348             (probably C<STDOUT>) for compatibility with L<Template>.
349              
350             =head1 SUPPORT
351              
352             Bugs should be reported via the CPAN bug tracker at
353              
354             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Tiny>
355              
356             For other issues, or commercial enhancement or support, contact the author.
357              
358             =head1 AUTHOR
359              
360             Adam Kennedy E<lt>adamk@cpan.orgE<gt>
361              
362             Forked and improved by Damien Krotkine E<lt>dams@cpan.orgE<gt>
363              
364             =head1 SEE ALSO
365              
366             L<Config::Tiny>, L<CSS::Tiny>, L<YAML::Tiny>
367              
368             =head1 COPYRIGHT
369              
370             Copyright 2009 - 2011 Adam Kennedy.
371             Copyright 2012 Damien Krotkine.
372              
373             This program is free software; you can redistribute
374             it and/or modify it under the same terms as Perl itself.
375              
376             The full text of the license can be found in the
377             LICENSE file included with this module.
378              
379             =head1 AUTHOR
380              
381             Dancer Core Developers
382              
383             =head1 COPYRIGHT AND LICENSE
384              
385             This software is copyright (c) 2023 by Alexis Sukrieh.
386              
387             This is free software; you can redistribute it and/or modify it under
388             the same terms as the Perl 5 programming language system itself.
389              
390             =cut