File Coverage

blib/lib/Lemplate/Directive.pm
Criterion Covered Total %
statement 16 218 7.3
branch 0 68 0.0
condition 0 27 0.0
subroutine 6 42 14.2
pod 0 35 0.0
total 22 390 5.6


line stmt bran cond sub pod time code
1             package Lemplate::Directive;
2 8     8   73 use strict;
  8         19  
  8         244  
3 8     8   42 use warnings;
  8         19  
  8         16546  
4              
5             our $VERSION = '0.15';
6              
7             our $OUTPUT = 'i = i + 1 output[i] =';
8             our $WHILE_MAX = 1000;
9              
10             # parser state variable
11             # only true when inside JAVASCRIPT blocks
12             our $INJAVASCRIPT = 0;
13              
14             sub new {
15 0     0 0   my $class = shift;
16              
17 0           return bless {}, $class
18             }
19              
20             sub template {
21 0     0 0   my ($class, $block) = @_;
22              
23 0 0         return "function() return '' end" unless $block =~ /\S/;
24              
25 0           return <<"...";
26             function (context)
27             if not context then
28             return error("Lemplate function called without context\\n")
29             end
30             local stash = context.stash
31             local output = {}
32             local i = 0
33              
34             $block
35              
36             return output
37             end
38             ...
39             }
40              
41             # Try to do 1 .. 10 expansions
42             sub _attempt_range_expand_val ($) {
43 0     0     my $val = shift;
44 0 0         return $val unless
45             my ( $from, $to ) = $val =~ m/\s*\[\s*(\S+)\s*\.\.\s*(\S+)\s*\]/;
46              
47 0 0 0       die "Range expansion is current supported for positive/negative integer values only (e.g. [ 1 .. 10 ])\nCannot expand: $val" unless $from =~ m/^-?\d+$/ && $to =~ m/^-?\d+$/;
48              
49 0           return join '', '[', join( ',', $from .. $to ), ']';
50             }
51              
52             #------------------------------------------------------------------------
53             # textblock($text)
54             #------------------------------------------------------------------------
55              
56             sub textblock {
57 0     0 0   my ($class, $text) = @_;
58 0 0         return $text if $INJAVASCRIPT;
59 0           return "$OUTPUT " . $class->text($text);
60             }
61              
62             #------------------------------------------------------------------------
63             # text($text)
64             #------------------------------------------------------------------------
65              
66             sub text {
67 0     0 0   my ($class, $text) = @_;
68 0           for ($text) {
69 0           s/([\'\\])/\\$1/g;
70 0           s/\n/\\n/g;
71 0           s/\r/\\r/g;
72             }
73 0           return "'" . $text . "'";
74             }
75              
76             #------------------------------------------------------------------------
77             # ident(\@ident) foo.bar(baz)
78             #------------------------------------------------------------------------
79              
80             sub ident {
81 0     0 0   my ($class, $ident) = @_;
82 0 0         return "''" unless @$ident;
83 0           my $ns;
84              
85             # does the first element of the identifier have a NAMESPACE
86             # handler defined?
87 0 0 0       if (ref $class && @$ident > 2 && ($ns = $class->{ NAMESPACE })) {
      0        
88 0           my $key = $ident->[0];
89 0           $key =~ s/^'(.+)'$/$1/s;
90 0 0         if ($ns = $ns->{ $key }) {
91 0           return $ns->ident($ident);
92             }
93             }
94              
95 0 0 0       if (scalar @$ident <= 2 && ! $ident->[1]) {
96 0           $ident = $ident->[0];
97             }
98             else {
99 0           $ident = '{' . join(', ', @$ident) . '}';
100             }
101 0           return "stash_get(stash, $ident)";
102             }
103              
104              
105             #------------------------------------------------------------------------
106             # assign(\@ident, $value, $default) foo = bar
107             #------------------------------------------------------------------------
108              
109             sub assign {
110 0     0 0   my ($class, $var, $val, $default) = @_;
111              
112 0 0         if (ref $var) {
113 0 0 0       if (scalar @$var == 2 && ! $var->[1]) {
114 0           $var = $var->[0];
115             }
116             else {
117 0           $var = '{' . join(', ', @$var) . '}';
118             }
119             }
120 0           $val = _attempt_range_expand_val $val;
121 0 0         $val .= ', 1' if $default;
122 0           return "stash_set(stash, $var, $val)";
123             }
124              
125              
126             #------------------------------------------------------------------------
127             # args(\@args) foo, bar, baz = qux
128             #------------------------------------------------------------------------
129              
130             sub args {
131 0     0 0   my ($class, $args) = @_;
132 0           my $hash = shift @$args;
133 0 0         push(@$args, '{ ' . join(', ', @$hash) . ' }')
134             if @$hash;
135              
136 0 0         return '{}' unless @$args;
137 0           return '{ ' . join(', ', @$args) . ' }';
138             }
139              
140              
141             #------------------------------------------------------------------------
142             # filenames(\@names)
143             #------------------------------------------------------------------------
144              
145             sub filenames {
146 0     0 0   my ($class, $names) = @_;
147 0 0         if (@$names > 1) {
148 0           $names = '[ ' . join(', ', @$names) . ' ]';
149             }
150             else {
151 0           $names = shift @$names;
152             }
153 0           return $names;
154             }
155              
156              
157             #------------------------------------------------------------------------
158             # get($expr) [% foo %]
159             #------------------------------------------------------------------------
160              
161             sub get {
162 0     0 0   my ($class, $expr) = @_;
163 0           return "$OUTPUT $expr";
164             }
165              
166             sub block {
167 0     0 0   my ($class, $block) = @_;
168             return join "\n", map {
169 0           s/^#(?=line \d+)/-- /gm;
170 0           $_;
171 0 0         } @{ $block || [] };
  0            
172             }
173              
174             #------------------------------------------------------------------------
175             # call($expr) [% CALL bar %]
176             #------------------------------------------------------------------------
177              
178             sub call {
179 0     0 0   my ($class, $expr) = @_;
180 0           $expr .= ';';
181 0           return $expr;
182             }
183              
184              
185             #------------------------------------------------------------------------
186             # set(\@setlist) [% foo = bar, baz = qux %]
187             #------------------------------------------------------------------------
188              
189             sub set {
190 0     0 0   my ($class, $setlist) = @_;
191 0           my $output;
192 0           while (my ($var, $val) = splice(@$setlist, 0, 2)) {
193 0           $output .= $class->assign($var, $val) . ";\n";
194             }
195 0           chomp $output;
196 0           return $output;
197             }
198              
199              
200             #------------------------------------------------------------------------
201             # default(\@setlist) [% DEFAULT foo = bar, baz = qux %]
202             #------------------------------------------------------------------------
203              
204             sub default {
205 0     0 0   my ($class, $setlist) = @_;
206 0           my $output;
207 0           while (my ($var, $val) = splice(@$setlist, 0, 2)) {
208 0           $output .= &assign($class, $var, $val, 1) . ";\n";
209             }
210 0           chomp $output;
211 0           return $output;
212             }
213              
214              
215             #------------------------------------------------------------------------
216             # include(\@nameargs) [% INCLUDE template foo = bar %]
217             # # => [ [ $file, ... ], \@args ]
218             #------------------------------------------------------------------------
219              
220             sub include {
221 0     0 0   my ($class, $nameargs) = @_;
222 0           my ($file, $args) = @$nameargs;
223 0           my $hash = shift @$args;
224 0           $file = $class->filenames($file);
225 0           (my $raw_file = $file) =~ s/^'|'$//g;
226 0           $Lemplate::ExtraTemplates{$raw_file} = 1;
227 0           my $file2 = "'$Lemplate::TemplateName/$raw_file'";
228 0 0         my $str_args = (@$hash ? ', { ' . join(', ', @$hash) . ' }' : '');
229 0           return "$OUTPUT context.include(context, template_map['$Lemplate::TemplateName/$raw_file'] and $file2 or $file$str_args)";
230             }
231              
232              
233             #------------------------------------------------------------------------
234             # process(\@nameargs) [% PROCESS template foo = bar %]
235             # # => [ [ $file, ... ], \@args ]
236             #------------------------------------------------------------------------
237              
238             sub process {
239 0     0 0   my ($class, $nameargs) = @_;
240 0           my ($file, $args) = @$nameargs;
241 0           my $hash = shift @$args;
242 0           $file = $class->filenames($file);
243 0           (my $raw_file = $file) =~ s/^'|'$//g;
244 0           $Lemplate::ExtraTemplates{$raw_file} = 1;
245 0 0         $file .= @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
246 0           return "$OUTPUT context.process(context, $file)";
247             }
248              
249              
250             #------------------------------------------------------------------------
251             # if($expr, $block, $else) [% IF foo < bar %]
252             # ...
253             # [% ELSE %]
254             # ...
255             # [% END %]
256             #------------------------------------------------------------------------
257              
258             sub if {
259 0     0 0   my ($class, $expr, $block, $else) = @_;
260 0 0         my @else = $else ? @$else : ();
261 0           $else = pop @else;
262              
263 0           my $output = "if tt2_true($expr) then\n$block\n";
264              
265 0           foreach my $elsif (@else) {
266 0           ($expr, $block) = @$elsif;
267 0           $output .= "elseif tt2_true($expr) then\n$block\n";
268             }
269 0 0         if (defined $else) {
270 0           $output .= "else\n$else\nend\n";
271             } else {
272 0           $output .= "end\n";
273             }
274              
275 0           return $output;
276             }
277              
278             #------------------------------------------------------------------------
279             # foreach($target, $list, $args, $block) [% FOREACH x = [ foo bar ] %]
280             # ...
281             # [% END %]
282             #------------------------------------------------------------------------
283              
284             sub foreach {
285 0     0 0   my ($class, $target, $list, $args, $block) = @_;
286 0           $args = shift @$args;
287 0 0         $args = @$args ? ', { ' . join(', ', @$args) . ' }' : '';
288              
289 0           my ($loop_save, $loop_set, $loop_restore, $setiter);
290 0 0         if ($target) {
291 0           $loop_save =
292             'local oldloop = ' . $class->ident(["'loop'"]);
293 0           $loop_set = "stash['$target'] = value";
294 0           $loop_restore = "stash_set(stash, 'loop', oldloop)";
295             }
296             else {
297 0           die "XXX - Not supported yet";
298 0           $loop_save = 'stash = context.localise()';
299 0           $loop_set =
300             "stash.get(['import', [value]]) if typeof(value) == 'object'";
301 0           $loop_restore = 'stash = context.delocalise()';
302             }
303              
304 0           $list = _attempt_range_expand_val $list;
305              
306 0           return <
307              
308             -- FOREACH
309             do
310             local list = $list
311             local iterator
312             if list.list then
313             iterator = list
314             list = list.list
315             end
316             $loop_save
317             local count
318             if not iterator then
319             count = table_maxn(list)
320             iterator = { count = 1, max = count - 1, index = 0, size = count, first = true, last = false, prev = "" }
321             else
322             count = iterator.size
323             end
324             stash.loop = iterator
325             for idx, value in ipairs(list) do
326             if idx == count then
327             iterator.last = true
328             end
329             iterator.index = idx - 1
330             iterator.count = idx
331             iterator.next = list[idx + 1]
332             $loop_set
333             $block
334             iterator.first = false
335             iterator.prev = value
336             end
337             $loop_restore
338             end
339             EOF
340             }
341              
342              
343             #------------------------------------------------------------------------
344             # next() [% NEXT %]
345             #
346             # Next iteration of a FOREACH loop (experimental)
347             #------------------------------------------------------------------------
348              
349             sub next {
350 0     0 0   return <
351             return error("NEXT not implemented yet")
352             EOF
353             }
354              
355             #------------------------------------------------------------------------
356             # wrapper(\@nameargs, $block) [% WRAPPER template foo = bar %]
357             # # => [ [$file,...], \@args ]
358             #------------------------------------------------------------------------
359             sub wrapper {
360 0     0 0   my ($class, $nameargs, $block) = @_;
361 0           my ($file, $args) = @$nameargs;
362 0           my $hash = shift @$args;
363              
364 0           s/ => /: / for @$hash;
365 0 0         return $class->multi_wrapper($file, $hash, $block)
366             if @$file > 1;
367 0           $file = shift @$file;
368 0           push(@$hash, "'content': output");
369 0 0         $file .= @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
370              
371 0           return <
372              
373             // WRAPPER
374             $OUTPUT (function() {
375             var output = '';
376             $block;
377             return context.include($file);
378             })();
379             EOF
380             }
381              
382             sub multi_wrapper {
383 0     0 0   my ($class, $file, $hash, $block) = @_;
384              
385 0           push(@$hash, "'content': output");
386 0 0         $hash = @$hash ? ', { ' . join(', ', @$hash) . ' }' : '';
387              
388 0           $file = join(', ', reverse @$file);
389             # print STDERR "multi wrapper: $file\n";
390              
391 0           return <
392              
393             // WRAPPER
394             $OUTPUT (function() {
395             var output = '';
396             $block;
397             var files = new Array($file);
398             for (var i = 0; i < files.length; i++) {
399             output = context.include(files[i]$hash);
400             }
401             return output;
402             })();
403             EOF
404             }
405              
406              
407             #------------------------------------------------------------------------
408             # while($expr, $block) [% WHILE x < 10 %]
409             # ...
410             # [% END %]
411             #------------------------------------------------------------------------
412              
413             sub while {
414 0     0 0   my ($class, $expr, $block) = @_;
415              
416 0           return <
417              
418             -- WHILE
419             do
420             local failsafe = $WHILE_MAX;
421             while $expr do
422             failsafe = failsafe - 1
423             if failsafe <= 0 then
424             break
425             end
426             $block
427             end
428             if not failsafe then
429             return error("WHILE loop terminated (> $WHILE_MAX iterations)\\n")
430             end
431             end
432             EOF
433             }
434              
435             #------------------------------------------------------------------------
436             # javascript($script) [% JAVASCRIPT %]
437             # ...
438             # [% END %]
439             #------------------------------------------------------------------------
440             sub javascript {
441 0     0 0   my ( $class, $javascript ) = @_;
442 0           return $javascript;
443             }
444              
445             sub no_javascript {
446 0     0 0   my ( $class ) = @_;
447 0           die "EVAL_JAVASCRIPT has not been enabled, cannot process [% JAVASCRIPT %] blocks";
448             }
449              
450             #------------------------------------------------------------------------
451             # switch($expr, \@case) [% SWITCH %]
452             # [% CASE foo %]
453             # ...
454             # [% END %]
455             #------------------------------------------------------------------------
456              
457             sub switch {
458 0     0 0   my ($class, $expr, $case) = @_;
459 0           my @case = @$case;
460 0           my ($match, $block, $default);
461 0           my $caseblock = '';
462              
463 0           $default = pop @case;
464              
465 0           foreach $case (@case) {
466 0           $match = $case->[0];
467 0           $block = $case->[1];
468             # $block = pad($block, 1) if $PRETTY;
469 0           $caseblock .= <
470             case $match:
471             $block
472             break;
473              
474             EOF
475             }
476              
477 0 0         if (defined $default) {
478 0           $caseblock .= <
479             default:
480             $default
481             break;
482             EOF
483             }
484             # $caseblock = pad($caseblock, 2) if $PRETTY;
485              
486 0           return <
487              
488             switch($expr) {
489             $caseblock
490             }
491              
492             EOF
493             }
494              
495              
496             #------------------------------------------------------------------------
497             # throw(\@nameargs) [% THROW foo "bar error" %]
498             # # => [ [$type], \@args ]
499             #------------------------------------------------------------------------
500              
501             sub throw {
502 0     0 0   my ($class, $nameargs) = @_;
503 0           my ($type, $args) = @$nameargs;
504 0           my $hash = shift(@$args);
505 0           my $info = shift(@$args);
506 0           $type = shift @$type;
507              
508 0           return qq{return error({$type, $info})};
509             }
510              
511              
512             #------------------------------------------------------------------------
513             # clear() [% CLEAR %]
514             #
515             # NOTE: this is redundant, being hard-coded (for now) into Parser.yp
516             #------------------------------------------------------------------------
517              
518             sub clear {
519 0     0 0   return "output = {}";
520             }
521              
522              
523             #------------------------------------------------------------------------
524             # break() [% BREAK %]
525             #
526             # NOTE: this is redundant, being hard-coded (for now) into Parser.yp
527             #------------------------------------------------------------------------
528              
529             sub break {
530 0     0 0   return 'break';
531             }
532              
533             #------------------------------------------------------------------------
534             # return() [% RETURN %]
535             #------------------------------------------------------------------------
536              
537             sub return {
538 0     0 0   return "return output"
539             }
540              
541              
542             #------------------------------------------------------------------------
543             # stop() [% STOP %]
544             #------------------------------------------------------------------------
545              
546             sub stop {
547 0     0 0   return "return error('Lemplate.STOP\\n' .. concat(output))";
548             }
549              
550              
551             #------------------------------------------------------------------------
552             # use(\@lnameargs) [% USE alias = plugin(args) %]
553             # # => [ [$file, ...], \@args, $alias ]
554             #------------------------------------------------------------------------
555              
556             sub use {
557 0     0 0   my ($class, $lnameargs) = @_;
558 0           my ($file, $args, $alias) = @$lnameargs;
559 0           $file = shift @$file; # same production rule as INCLUDE
560 0   0       $alias ||= $file;
561 0           $args = &args($class, $args);
562 0 0         $file .= ", $args" if $args;
563 0           return "-- USE\n"
564             . "stash_set(stash, $alias, context.plugin(context, $file))";
565             }
566              
567              
568             #------------------------------------------------------------------------
569             # raw(\@lnameargs) [% RAW alias = plugin(args) %]
570             # # => [ [$file, ...], \@args, $alias ]
571             #------------------------------------------------------------------------
572              
573             sub raw {
574 0     0 0   my ($class, $lnameargs) = @_;
575 0           my ($file, $args, $alias) = @$lnameargs;
576 0           $file = shift @$file; # same production rule as INCLUDE
577 0   0       $alias ||= $file;
578 0           $args = &args($class, $args);
579             # $file .= ", $args" if $args;
580 0           $file =~ s/'|"//g;
581 0           return "// RAW\n"
582             . "stash_set(stash, $alias, $file)";
583             }
584              
585              
586             #------------------------------------------------------------------------
587             # stubs() [% STOP %]
588             #------------------------------------------------------------------------
589              
590             sub filter {
591 0     0 0   my ($class, $lnameargs, $block) = @_;
592 0           my ($name, $args, $alias) = @$lnameargs;
593 0           $name = shift @$name;
594 0           $args = &args($class, $args);
595 0 0         $args = $args ? "$args, $alias" : ", null, $alias"
    0          
596             if $alias;
597 0 0         $name .= ", $args" if $args;
598 0           return <
599              
600             -- FILTER
601             local value
602             do
603             local output = {}
604             local i = 0
605              
606             $block
607              
608             value = context.filter(output, $name)
609             end
610             $OUTPUT value
611             EOF
612             }
613              
614             sub quoted {
615 0     0 0   my $class = shift;
616 0 0 0       if ( @_ && ref($_[0]) ) {
617 0           return join( " .. ", @{$_[0]} );
  0            
618             }
619 0           return "return error('QUOTED called with unknown arguments in Lemplate')";
620             }
621              
622             #------------------------------------------------------------------------
623             # macro($name, $block, \@args)
624             #------------------------------------------------------------------------
625              
626             sub macro {
627 0     0 0   my ($class, $ident, $block, $args) = @_;
628              
629 0 0         if ($args) {
630 0           $args = join(';', map { "args['$_'] = fargs.shift()" } @$args);
  0            
631              
632 0           return <
633              
634             //MACRO
635             stash.set('$ident', function () {
636             var output = '';
637             var args = {};
638             var fargs = Array.prototype.slice.call(arguments);
639             $args;
640             args.arguments = Array.prototype.slice.call(arguments);
641              
642             var params = fargs.shift() || {};
643              
644             for (var key in params) {
645             args[key] = params[key];
646             }
647              
648             context.stash.clone(args);
649             try {
650             $block
651             }
652             catch(e) {
653             var error = context.set_error(e, output);
654             throw(error);
655             }
656              
657             context.stash.declone();
658             return output;
659             });
660              
661             EOF
662              
663             }
664             else {
665 0           return <
666              
667             //MACRO
668              
669             stash.set('$ident', function () {
670             var output = '';
671             var args = {};
672              
673             var fargs = Array.prototype.slice.call(arguments);
674             args.arguments = Array.prototype.slice.call(arguments);
675              
676             if (typeof arguments[0] == 'object') args = arguments[0];
677              
678             context.stash.clone(args);
679             try {
680             $block
681             }
682             catch(e) {
683             var error = context.set_error(e, output);
684             throw(error);
685             }
686              
687             context.stash.declone();
688             return output;});
689              
690             EOF
691             }
692             }
693              
694             sub capture {
695 0     0 0   my ($class, $name, $block) = @_;
696              
697 0 0         if (ref $name) {
698 0 0 0       if (scalar @$name == 2 && ! $name->[1]) {
699 0           $name = $name->[0];
700             }
701             else {
702 0           $name = '[' . join(', ', @$name) . ']';
703             }
704             }
705              
706 0           return <
707              
708             // CAPTURE
709             (function() {
710             var output = '';
711             $block
712             stash.set($name, output);
713             })();
714             EOF
715              
716             }
717              
718             BEGIN {
719 8     8   170 return; # Comment out this line to get callback traces
720 8     8   65 no strict 'refs';
  8         16  
  8         393  
721 0           my $pkg = __PACKAGE__ . '::';
722 0           my $stash = \ %$pkg;
723 8     8   40 use strict 'refs';
  8         20  
  8         388  
724 0           for my $name (keys %$stash) {
725 0           my $glob = $stash->{$name};
726 0 0         if (*$glob{CODE}) {
727 0           my $code = *$glob{CODE};
728 8     8   47 no warnings 'redefine';
  8         16  
  8         714  
729             $stash->{$name} = sub {
730 0           warn "Calling $name(@_)\n";
731 0           &$code(@_);
732 0           };
733             }
734             }
735             }
736              
737              
738             1;
739              
740             __END__