File Coverage

blib/lib/Jemplate/Directive.pm
Criterion Covered Total %
statement 16 210 7.6
branch 0 68 0.0
condition 0 27 0.0
subroutine 6 41 14.6
pod 0 34 0.0
total 22 380 5.7


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