File Coverage

blib/lib/Jemplate.pm
Criterion Covered Total %
statement 30 179 16.7
branch 5 132 3.7
condition 0 44 0.0
subroutine 9 20 45.0
pod 4 13 30.7
total 48 388 12.3


line stmt bran cond sub pod time code
1             package Jemplate;
2 22     22   11866 use 5.006001;
  22         93  
  22         819  
3 22     22   87 use strict;
  22         24  
  22         528  
4 22     22   79 use warnings;
  22         26  
  22         485  
5 22     22   12053 use Template 2.14;
  22         80566  
  22         612  
6 22     22   15496 use Getopt::Long;
  22         198083  
  22         113  
7              
8             =head1 VERSION
9              
10             Version 0.25_2
11              
12             =cut
13            
14             our $VERSION = '0.25_2';
15              
16 22     22   11403 use Jemplate::Parser;
  22         62  
  22         38974  
17              
18             #-------------------------------------------------------------------------------
19             sub usage {
20 0     0 0 0 <<'...';
21             Usage:
22              
23             jemplate --runtime [runtime-opt]
24              
25             jemplate --compile [compile-opt]
26              
27             jemplate --runtime [runtime-opt] --compile [compile-opt]
28              
29             jemplate --list
30              
31             Where "--runtime" and "runtime-opt" can include:
32              
33             --runtime Equivalent to --ajax=ilinsky --json=json2
34             --runtime=standard
35              
36             --runtime=lite Same as --ajax=none --json=none
37             --runtime=jquery Same as --ajax=jquery --json=none
38             --runtime=yui Same as --ajax=yui --json=yui
39             --runtime=legacy Same as --ajax=gregory --json=json2
40              
41             --json By itself, equivalent to --json=json2
42             --json=json2 Include http://www.json.org/json2.js for parsing/stringifying
43             --json=yui Use YUI: YAHOO.lang.JSON (requires external YUI)
44             --json=none Doesn't provide any JSON functionality except a warning
45            
46             --ajax By itself, equivalent to --ajax=xhr
47             --ajax=jquery Use jQuery for Ajax get and post (requires external jQuery)
48             --ajax=yui Use YUI: yui/connection/connection.js (requires external YUI)
49             --ajax=xhr Use XMLHttpRequest (will automatically use --xhr=ilinsky if --xhr is not set)
50             --ajax=none Doesn't provide any Ajax functionality except a warning
51              
52             --xhr By itself, equivalent to --xhr=ilinsky
53             --xhr=ilinsky Include http://code.google.com/p/xmlhttprequest/
54             --xhr=gregory Include http://www.scss.com.au/family/andrew/webdesign/xmlhttprequest/
55              
56             --xxx Include XXX and JJJ helper functions
57              
58             --compact Use the YUICompressor compacted version of the runtime
59              
60             Where "compile-opt" can include:
61              
62             --start-tag
63             --end-tag
64             --pre-chomp
65             --post-chomp
66             --trim
67             --any-case
68             --eval
69             --noeval
70             -s, --source
71              
72             For more information use:
73             perldoc jemplate
74             ...
75             }
76              
77             sub main {
78 0     0 0 0 my $class = shift;
79              
80 0         0 my @argv = @_;
81              
82 0         0 my ($template_options, $jemplate_options) = get_options(@argv);
83 0         0 my ($runtime, $compile, $list) = @$jemplate_options{qw/runtime compile list/};
84              
85 0 0       0 if ($runtime) {
86 0         0 print runtime_source_code(@$jemplate_options{qw/runtime ajax json xhr xxx compact/});
87 0 0       0 return unless $compile;
88             }
89              
90 0         0 my $templates = make_file_list(@argv);
91 0 0       0 print_usage_and_exit() unless @$templates;
92              
93 0 0       0 if ($list) {
94 0         0 foreach (@$templates) {
95 0         0 print STDOUT $_->{short} . "\n";
96             }
97 0         0 return;
98             }
99              
100 0 0       0 if ($compile) {
101 0         0 my $jemplate = Jemplate->new(%$template_options);
102 0         0 print STDOUT $jemplate->_preamble;
103 0         0 foreach my $template (@$templates) {
104 0         0 my $content = slurp($template->{full});
105 0 0       0 if ($content) {
106 0         0 print STDOUT $jemplate->compile_template_content(
107             $content,
108             $template->{short},
109             );
110             }
111             }
112 0         0 return;
113             }
114              
115 0         0 print_usage_and_exit();
116             }
117              
118             sub get_options {
119 0     0 0 0 local @ARGV = @_;
120              
121 0         0 my $runtime;
122 0         0 my $compile = 0;
123 0         0 my $list = 0;
124              
125 0 0       0 my $start_tag = exists $ENV{JEMPLATE_START_TAG}
126             ? $ENV{JEMPLATE_START_TAG}
127             : undef;
128 0 0       0 my $end_tag = exists $ENV{JEMPLATE_END_TAG}
129             ? $ENV{JEMPLATE_END_TAG}
130             : undef;
131 0 0       0 my $pre_chomp = exists $ENV{JEMPLATE_PRE_CHOMP}
132             ? $ENV{JEMPLATE_PRE_CHOMP}
133             : undef;
134 0 0       0 my $post_chomp = exists $ENV{JEMPLATE_POST_CHOMP}
135             ? $ENV{JEMPLATE_POST_CHOMP}
136             : undef;
137 0 0       0 my $trim = exists $ENV{JEMPLATE_TRIM}
138             ? $ENV{JEMPLATE_TRIM}
139             : undef;
140 0 0       0 my $anycase = exists $ENV{JEMPLATE_ANYCASE}
141             ? $ENV{JEMPLATE_ANYCASE}
142             : undef;
143 0 0       0 my $eval_javascript = exists $ENV{JEMPLATE_EVAL_JAVASCRIPT}
144             ? $ENV{JEMPLATE_EVAL_JAVASCRIPT}
145             : 1;
146              
147 0         0 my $source = 0;
148 0         0 my ($ajax, $json, $xxx, $xhr, $compact, $minify);
149              
150 0         0 my $help = 0;
151              
152 0 0       0 GetOptions(
153             "compile|c" => \$compile,
154             "list|l" => \$list,
155             "runtime|r:s" => \$runtime,
156              
157             "start-tag=s" => \$start_tag,
158             "end-tag=s" => \$end_tag,
159             "trim=s" => \$trim,
160             "pre-chomp" => \$pre_chomp,
161             "post-chomp" => \$post_chomp,
162             "any-case" => \$anycase,
163             "eval!" => \$eval_javascript,
164              
165             "source|s" => \$source,
166              
167             "ajax:s" => \$ajax,
168             "json:s" => \$json,
169             "xxx" => \$xxx,
170             "xhr:s" => \$xhr,
171              
172             "compact" => \$compact,
173             "minify:s" => \$minify,
174              
175             "help|?" => \$help,
176             ) or print_usage_and_exit();
177              
178 0 0       0 if ($help) {
179 0         0 print_usage_and_exit();
180             }
181              
182 0 0 0     0 ($runtime, $ajax, $json, $xxx, $xhr, $minify) = map { defined $_ && ! length $_ ? 1 : $_ } ($runtime, $ajax, $json, $xxx, $xhr, $minify);
  0         0  
183 0 0 0     0 $runtime = "standard" if $runtime && $runtime eq 1;
184              
185 0 0 0     0 print_usage_and_exit("Don't understand '--runtime $runtime'") if defined $runtime && ! grep { $runtime =~ m/$_/ } qw/standard lite jquery yui legacy/;
  0         0  
186 0 0 0     0 print_usage_and_exit("Can't specify --list with a --runtime and/or the --compile option") if $list && ($runtime || $compile);
      0        
187 0 0 0     0 print_usage_and_exit() unless $list || $runtime || $compile;
      0        
188              
189 0 0       0 my $command =
    0          
    0          
190             $runtime ? 'runtime' :
191             $compile ? 'compile' :
192             $list ? 'list' :
193             print_usage_and_exit();
194              
195 0         0 my $options = {};
196 0 0       0 $options->{START_TAG} = $start_tag if defined $start_tag;
197 0 0       0 $options->{END_TAG} = $end_tag if defined $end_tag;
198 0 0       0 $options->{PRE_CHOMP} = $pre_chomp if defined $pre_chomp;
199 0 0       0 $options->{POST_CHOMP} = $post_chomp if defined $post_chomp;
200 0 0       0 $options->{TRIM} = $trim if defined $trim;
201 0 0       0 $options->{ANYCASE} = $anycase if defined $anycase;
202 0 0       0 $options->{EVAL_JAVASCRIPT} = $eval_javascript if defined $eval_javascript;
203              
204             return (
205 0         0 $options,
206             { compile => $compile, runtime => $runtime, list => $list,
207             source => $source,
208             ajax => $ajax, json => $json, xxx => $xxx, xhr => $xhr,
209             compact => $compact, minify => $minify },
210             );
211             }
212              
213              
214             sub slurp {
215 0     0 0 0 my $filepath = shift;
216 0 0       0 open(F, '<', $filepath) or die "Can't open '$filepath' for input:\n$!";
217 0         0 my $contents = do {local $/; };
  0         0  
  0         0  
218 0         0 close(F);
219 0         0 return $contents;
220             }
221              
222             sub recurse_dir {
223 1     1 0 610 require File::Find::Rule;
224              
225 1         6415 my $dir = shift;
226 1         2 my @files;
227 1         26 foreach ( File::Find::Rule->file->in( $dir ) ) {
228 3 100       831 if ( m{/\.[^\.]+} ) {} # Skip ".hidden" files or directories
229             else {
230 1         2 push @files, $_;
231             }
232             }
233 1         10 return @files;
234             }
235              
236             sub make_file_list {
237 0     0 0 0 my @args = @_;
238              
239 0         0 my @list;
240              
241 0         0 foreach my $arg (@args) {
242 0 0       0 unless (-e $arg) { next; } # file exists
  0         0  
243 0 0 0     0 unless (-s $arg or -d $arg) { next; } # file size > 0 or directory (for Win platform)
  0         0  
244              
245 0 0       0 if (-d $arg) {
246 0         0 foreach my $full ( recurse_dir($arg) ) {
247 0         0 $full =~ /$arg(\/|)(.*)/;
248 0         0 my $short = $2;
249 0         0 push(@list, {full=>$full, short=>$short} );
250             }
251             }
252             else {
253 0         0 my $full = $arg;
254 0         0 my $short = $full;
255 0         0 $short =~ s/.*[\/\\]//;
256 0         0 push(@list, {full=>$arg, short=>$short} );
257             }
258             }
259              
260 0         0 return [ sort { $a->{short} cmp $b->{short} } @list ];
  0         0  
261             }
262              
263             sub print_usage_and_exit {
264 0 0   0 0 0 print STDOUT join "\n", "", @_, "Aborting!", "\n" if @_;
265 0         0 print STDOUT usage();
266 0         0 exit;
267             }
268              
269             sub runtime_source_code {
270 0     0 0 0 require Jemplate::Runtime;
271 0         0 require Jemplate::Runtime::Compact;
272              
273 0 0       0 unshift @_, "standard" unless @_;
274              
275 0 0       0 my ($runtime, $ajax, $json, $xhr, $xxx, $compact) = map { defined $_ ? lc $_ : "" } @_[0 .. 5];
  0         0  
276              
277 0 0       0 my $Jemplate_Runtime = $compact ? "Jemplate::Runtime::Compact" : "Jemplate::Runtime";
278              
279 0 0       0 if ($runtime eq "standard") {
    0          
    0          
    0          
    0          
280 0   0     0 $ajax ||= "xhr";
281 0   0     0 $json ||= "json2";
282 0   0     0 $xhr ||= "ilinsky";
283             }
284             elsif ($runtime eq "jquery") {
285 0   0     0 $ajax ||= "jquery";
286             }
287             elsif ($runtime eq "yui") {
288 0   0     0 $ajax ||= "yui";
289 0   0     0 $json ||= "yui";
290             }
291             elsif ($runtime eq "legacy") {
292 0   0     0 $ajax ||= "xhr";
293 0   0     0 $json ||= "json2";
294 0   0     0 $xhr ||= "gregory";
295 0         0 $xxx = 1;
296             }
297             elsif ($runtime eq "lite") {
298             }
299              
300 0 0       0 $ajax = "xhr" if $ajax eq 1;
301 0 0 0     0 $xhr ||= 1 if $ajax eq "xhr";
302 0 0       0 $json = "json2" if $json eq 1;
303 0 0       0 $xhr = "ilinsky" if $xhr eq 1;
304              
305 0         0 my @runtime;
306              
307 0 0       0 push @runtime, $Jemplate_Runtime->kernel if $runtime;
308              
309 0 0       0 push @runtime, $Jemplate_Runtime->json2 if $json =~ m/^json2?$/i;
310            
311 0 0       0 push @runtime, $Jemplate_Runtime->ajax_xhr if $ajax eq "xhr";
312 0 0       0 push @runtime, $Jemplate_Runtime->ajax_jquery if $ajax eq "jquery";
313 0 0       0 push @runtime, $Jemplate_Runtime->ajax_yui if $ajax eq "yui";
314              
315 0 0       0 push @runtime, $Jemplate_Runtime->json_json2 if $json =~ m/^json2?$/i;
316 0 0       0 push @runtime, $Jemplate_Runtime->json_json2_internal if $json =~ m/^json2?[_-]?internal$/i;
317 0 0       0 push @runtime, $Jemplate_Runtime->json_yui if $json eq "yui";
318              
319 0 0       0 push @runtime, $Jemplate_Runtime->xhr_ilinsky if $xhr eq "ilinsky";
320 0 0       0 push @runtime, $Jemplate_Runtime->xhr_gregory if $xhr eq "gregory";
321              
322 0 0       0 push @runtime, $Jemplate_Runtime->xxx if $xxx;
323              
324 0         0 return join ";", @runtime;
325             }
326              
327             #-------------------------------------------------------------------------------
328              
329             sub new {
330 1     1 0 13 my $class = shift;
331 1         6 return bless { @_ }, $class;
332             }
333              
334             sub compile_module {
335 0     0 1 0 my ($self, $module_path, $template_file_paths) = @_;
336 0 0       0 my $result = $self->compile_template_files(@$template_file_paths)
337             or return;
338 0 0       0 open MODULE, "> $module_path"
339             or die "Can't open '$module_path' for output:\n$!";
340 0         0 print MODULE $result;
341 0         0 close MODULE;
342 0         0 return 1;
343             }
344              
345             sub compile_module_cached {
346 0     0 1 0 my ($self, $module_path, $template_file_paths) = @_;
347 0         0 my $m = -M $module_path;
348 0 0       0 return 0 unless grep { -M($_) < $m } @$template_file_paths;
  0         0  
349 0         0 return $self->compile_module($module_path, $template_file_paths);
350             }
351              
352             sub compile_template_files {
353 0     0 1 0 my $self = shift;
354 0         0 my $output = $self->_preamble;
355 0         0 for my $filepath (@_) {
356 0         0 my $filename = $filepath;
357 0         0 $filename =~ s/.*[\/\\]//;
358 0 0       0 open FILE, $filepath
359             or die "Can't open '$filepath' for input:\n$!";
360 0         0 my $template_input = do {local $/; };
  0         0  
  0         0  
361 0         0 close FILE;
362 0         0 $output .=
363             $self->compile_template_content($template_input, $filename);
364             }
365 0         0 return $output;
366             }
367              
368             sub compile_template_content {
369 5 50   5 1 68 die "Invalid arguments in call to Jemplate->compile_template_content"
370             unless @_ == 3;
371 5         13 my ($self, $template_content, $template_name) = @_;
372 5 100       56 my $parser = Jemplate::Parser->new( ref($self) ? %$self : () );
373 0 0         my $parse_tree = $parser->parse(
374             $template_content, {name => $template_name}
375             ) or die $parser->error;
376 0           my $output =
377             "Jemplate.templateMap['$template_name'] = " .
378             $parse_tree->{BLOCK} .
379             "\n";
380 0           for my $function_name (sort keys %{$parse_tree->{DEFBLOCKS}}) {
  0            
381 0           $output .=
382             "Jemplate.templateMap['$function_name'] = " .
383             $parse_tree->{DEFBLOCKS}{$function_name} .
384             "\n";
385             }
386 0           return $output;
387             }
388              
389             sub _preamble {
390 0     0     return <<'...';
391             /*
392             This JavaScript code was generated by Jemplate, the JavaScript
393             Template Toolkit. Any changes made to this file will be lost the next
394             time the templates are compiled.
395              
396             Copyright 2006-2008 - Ingy döt Net - All rights reserved.
397             */
398              
399             if (typeof(Jemplate) == 'undefined')
400             throw('Jemplate.js must be loaded before any Jemplate template files');
401              
402             ...
403             }
404              
405             1;
406              
407             =head1 NAME
408              
409             Jemplate - JavaScript Templating with Template Toolkit
410              
411             =head1 SYNOPSIS
412              
413             var data = Ajax.get('url/data.json');
414             var elem = document.getElementById('some-div');
415             elem.innerHTML = Jemplate.process('my-template.html', data);
416              
417             or:
418              
419             var data = Ajax.get('url/data.json');
420             var elem = document.getElementById('some-div');
421             Jemplate.process('my-template.html', data, elem);
422              
423             or simply:
424              
425             Jemplate.process('my-template.html', 'url/data.json', '#some-div');
426              
427             or, with jQuery.js:
428              
429             jQuery.getJSON("url/data.json", function(data) {
430             Jemplate.process('my-template.html', data, '#some-div');
431             });
432              
433             From the commandline:
434              
435             jemplate --runtime --compile path/to/jemplate/directory/ > jemplate.js
436              
437             =head1 DESCRIPTION
438              
439             Jemplate is a templating framework for JavaScript that is built over
440             Perl's Template Toolkit (TT2).
441              
442             Jemplate parses TT2 templates using the TT2 Perl framework, but with a
443             twist. Instead of compiling the templates into Perl code, it compiles
444             them into JavaScript.
445              
446             Jemplate then provides a JavaScript runtime module for processing
447             the template code. Presto, we have full featured JavaScript
448             templating language!
449              
450             Combined with JSON and xmlHttpRequest, Jemplate provides a really simple
451             and powerful way to do Ajax stuff.
452              
453             =head1 HOWTO
454              
455             Jemplate comes with a command line tool call C that you use to
456             precompile your templates into a JavaScript file. For example if you have
457             a template directory called C that contains:
458              
459             > ls templates/
460             body.html
461             footer.html
462             header.html
463              
464             You might run this command:
465              
466             > jemplate --compile template/* > js/jemplates.js
467              
468             This will compile all the templates into one JavaScript file.
469              
470             You also need to generate the Jemplate runtime.
471              
472             > jemplate --runtime > js/Jemplate.js
473              
474             Now all you need to do is include these two files in your HTML:
475              
476            
477            
478              
479             Now you have Jemplate support for these templates in your HTML document.
480              
481             =head1 PUBLIC API
482              
483             The Jemplate.js JavaScript runtime module has the following API method:
484              
485             =over
486              
487             =item Jemplate.process(template-name, data, target);
488              
489             The C is a string like C<'body.html'> that is the name of
490             the top level template that you wish to process.
491              
492             The optional C specififies the data object to be used by the
493             templates. It can be an object, a function or a url. If it is an object,
494             it is used directly. If it is a function, the function is called and the
495             returned object is used. If it is a url, an asynchronous is
496             performed. The result is expected to be a JSON string, which gets turned
497             into an object.
498              
499             The optional C can be an HTMLElement reference, a function or a
500             string beginning with a C<#> char. If the target is omitted, the
501             template result is returned. If it is a function, the function is called
502             with the result. If it is a string, the string is used as an id to find
503             an HTMLElement.
504              
505             If an HTMLElement is used (by id or directly) then the innerHTML
506             property is set to the template processing result.
507              
508             =back
509              
510             The Jemplate.pm Perl module has the following public class methods,
511             although you won't likely need to use them directly. Normally, you just
512             use the C command line tool.
513              
514             =over
515              
516             =item Jemplate->compile_template_files(@template_file_paths);
517              
518             Take a list of template file paths and compile them into a module of
519             functions. Returns the text of the module.
520              
521             =item Jemplate->compile_template_content($content, $template_name);
522              
523             Compile one template whose content is in memory. You must provide a
524             unique template name. Returns the JavaScript text result of the
525             compilation.
526              
527             =item Jemplate->compile_module($module_path, \@template_file_paths);
528              
529             Similar to `compile_template_files`, but prints to result to the
530             $module_path. Returns 1 if successful, undef if error.
531              
532             =item Jemplate->compile_module_cached($module_path, \@template_file_paths);
533              
534             Similar to `compile_module`, but only compiles if one of the templates
535             is newer than the module. Returns 1 if sucessful compile, 0 if no
536             compile due to cache, undef if error.
537              
538             =back
539              
540             =head1 AJAX AND JSON METHODS
541              
542             Jemplate comes with builtin Ajax and JSON support.
543              
544             =over
545              
546             =item Ajax.get(url, [callback]);
547              
548             Does a GET operation to the url.
549              
550             If a callback is provided, the operation is asynchronous, and the data
551             is passed to the callback. Otherwise, the operation is synchronous and
552             the data is returned.
553              
554             =item Ajax.post(url, data, [callback]);
555              
556             Does a POST operation to the url.
557              
558             Same callback rules as C apply.
559              
560             =item JSON.stringify(object);
561              
562             Return the JSON serialization of an object.
563              
564             =item JSON.parse(jsonString);
565              
566             Turns a JSON string into an object and returns the object.
567              
568             =back
569              
570             =head1 CURRENT SUPPORT
571              
572             The goal of Jemplate is to support all of the Template Toolkit features
573             that can possibly be supported.
574              
575             Jemplate now supports almost all the TT directives, including:
576              
577             * Plain text
578             * [% [GET] variable %]
579             * [% CALL variable %]
580             * [% [SET] variable = value %]
581             * [% DEFAULT variable = value ... %]
582             * [% INCLUDE [arguments] %]
583             * [% PROCESS [arguments] %]
584             * [% BLOCK name %]
585             * [% FILTER filter %] text... [% END %]
586             * [% JAVASCRIPT %] code... [% END %]
587             * [% WRAPPER template [variable = value ...] %]
588             * [% IF condition %]
589             * [% ELSIF condition %]
590             * [% ELSE %]
591             * [% SWITCH variable %]
592             * [% CASE [{value|DEFAULT}] %]
593             * [% FOR x = y %]
594             * [% WHILE expression %]
595             * [% RETURN %]
596             * [% THROW type message %]
597             * [% STOP %]
598             * [% NEXT %]
599             * [% LAST %]
600             * [% CLEAR %]
601             * [%# this is a comment %]
602             * [% MACRO name(param1, param2) BLOCK %] ... [% END %]
603              
604             ALL of the string virtual functions are supported.
605              
606             ALL of the array virtual functions are supported:
607              
608             ALL of the hash virtual functions are supported:
609              
610             MANY of the standard filters are implemented.
611              
612             The remaining features will be added very soon. See the DESIGN document
613             in the distro for a list of all features and their progress.
614              
615             =head1 BROWSER SUPPORT
616              
617             Tested successfully in:
618              
619             * Firefox Mac/Win32/Linux
620             * IE 6.0
621             * Safari
622             * Opera
623             * Konqueror
624              
625             All tests run 100% successful in the above browsers.
626              
627             =head1 DEVELOPMENT
628              
629             The bleeding edge code is available via Git at
630             git://github.com/ingydotnet/jemplate.git
631              
632             You can run the runtime tests directly from
633             http://svn.jemplate.net/repo/trunk/tests/run/index.html or from the
634             corresponding CPAN or JSAN directories.
635              
636             Jemplate development is being discussed at irc://irc.freenode.net/#jemplate
637              
638             If you want a committer bit, just ask ingy on the irc channel.
639              
640             =head1 CREDIT
641              
642             This module is only possible because of Andy Wardley's mighty Template
643             Toolkit. Thanks Andy. I will gladly give you half of any beers I
644             receive for this work. (As long as you are in the same room when I'm
645             drinking them ;)
646              
647             =head1 AUTHORS
648              
649             Ingy döt Net
650              
651             (Note: I had to list myself first so that this line would go into META.yml)
652              
653             Jemplate is truly a community authored project:
654              
655             Ingy döt Net
656              
657             Tatsuhiko Miyagawa
658              
659             Yann Kerherve
660              
661             David Davis
662              
663             Cory Bennett
664              
665             Cees Hek
666              
667             Christian Hansen
668              
669             David A. Coffey
670              
671             Robert Krimen
672              
673             Nickolay Platonov
674              
675             =head1 COPYRIGHT
676              
677             Copyright (c) 2006-2008. Ingy döt Net.
678              
679             This program is free software; you can redistribute it and/or modify it
680             under the same terms as Perl itself.
681              
682             See L
683              
684             =cut