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 = '0.400001';
3             # ABSTRACT: Dancer2 own implementation of Template::Tiny
4              
5 114     114   164035 use 5.00503;
  114         568  
6 114     114   733 use strict;
  114         288  
  114         2632  
7 114     114   646 no warnings;
  114         298  
  114         4625  
8 114     114   2302 use Ref::Util qw<is_arrayref is_coderef is_plain_hashref>;
  114         5567  
  114         129671  
9              
10             # Evaluatable expression
11             my $EXPR = qr/ [a-z_][\w.]* /xs;
12              
13             sub new {
14 21     21 1 23135 my $self = bless {
15             start_tag => '[%',
16             end_tag => '%]',
17             @_[ 1 .. $#_ ]
18             },
19             $_[0];
20              
21             # Opening tag including whitespace chomping rules
22 21         773 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 21         316 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 21         1362 $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 21         964 $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 21         260 $self;
79             }
80              
81             # Copy and modify
82             sub preprocess {
83 3     3 0 5 my $self = shift;
84 3         4 my $text = shift;
85 3         10 $self->_preprocess( \$text );
86 3         10 return $text;
87             }
88              
89             sub process {
90 30     30 1 4246 my $self = shift;
91 30         59 my $copy = ${ shift() };
  30         71  
92 30   50     99 my $stash = shift || {};
93              
94 30         84 local $@ = '';
95 30         115 local $^W = 0;
96              
97             # Preprocess to establish unique matching tag sets
98 30         287 $self->_preprocess( \$copy );
99              
100             # Process down the nested tree of conditions
101 30         120 my $result = $self->_process( $stash, $copy );
102 30 50       91 if (@_) {
    0          
103 30         53 ${ $_[0] } = $result;
  30         160  
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 33     33   67 my $self = shift;
125 33         52 my $copy = shift;
126              
127             # Preprocess to establish unique matching tag sets
128 33         59 my $id = 0;
129 33         1655 1 while $$copy =~ s/
130             $self->{PREPARSE}
131             /
132 20         61 my $tag = substr($1, 0, 1) . ++$id;
133 20 100       1051 "\[\% $tag $2 \%\]$3\[\% $tag \%\]"
134             . (defined($4) ? "$4\[\% $tag \%\]" : '');
135             /sex;
136             }
137              
138             sub _process {
139 50     50   130 my ( $self, $stash, $text ) = @_;
140              
141 50         1009 $text =~ s/
142             $self->{CONDITION}
143             /
144             ($2 eq 'F')
145             ? $self->_foreach($stash, $3, $4, $5)
146 18 100       45 : eval {
    100          
147 16   100     32 $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 50         1169 $text =~ s/
158             $self->{LEFT} ( $EXPR ) $self->{RIGHT}
159             /
160 59         159 eval {
161 59         161 $self->_expression($stash, $1)
162             . '' # Force stringification
163             }
164             /gsex;
165              
166             # Trim the document
167 50 100       160 $text =~ s/^\s*(.+?)\s*\z/$1/s if $self->{TRIM};
168              
169 50         238 return $text;
170             }
171              
172             # Special handling for foreach
173             sub _foreach {
174 2     2   8 my ( $self, $stash, $term, $expr, $text ) = @_;
175              
176             # Resolve the expression
177 2         6 my $list = $self->_expression( $stash, $expr );
178 2 50       5 is_arrayref($list) or return '';
179              
180             # Iterate
181             return join '',
182 2         5 map { $self->_process( { %$stash, $term => $_ }, $text ) } @$list;
  4         15  
183             }
184              
185             # Evaluates a stash expression
186             sub _expression {
187 77     77   109 my $cursor = $_[1];
188 77         214 my @path = split /\./, $_[2];
189 77         154 foreach (@path) {
190              
191             # Support for private keys
192 104 100       243 return if substr( $_, 0, 1 ) eq '_';
193              
194             # Split by data type
195 102 100       217 ref $cursor or return '';
196 100 100       212 if ( is_arrayref($cursor) ) {
    100          
197 5 100       21 return '' unless /^(?:0|[0-9]\d*)\z/;
198 4         8 $cursor = $cursor->[$_];
199             }
200             elsif ( is_plain_hashref($cursor) ) {
201 93         196 $cursor = $cursor->{$_};
202             }
203             else {
204 2         8 $cursor = $cursor->$_();
205             }
206             }
207              
208             # If the last expression is a CodeRef, execute it
209 72 50       143 is_coderef($cursor)
210             and $cursor = $cursor->();
211 72         423 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 0.400001
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