File Coverage

blib/lib/Jemplate/Directive.pm
Criterion Covered Total %
statement 155 212 73.1
branch 35 68 51.4
condition 10 27 37.0
subroutine 37 42 88.1
pod 0 35 0.0
total 237 384 61.7


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