File Coverage

blib/lib/Lemplate.pm
Criterion Covered Total %
statement 15 164 9.1
branch 0 96 0.0
condition 0 27 0.0
subroutine 5 19 26.3
pod 0 13 0.0
total 20 319 6.2


line stmt bran cond sub pod time code
1             # ToDo:
2             # - Use TT:Simple in Makefiles
3              
4             # ABSTRACT: compiles Perl TT2 templates to standalone Lua modules for OpenResty
5              
6             package Lemplate;
7              
8 8     8   211569 use strict;
  8         18  
  8         203  
9 8     8   37 use warnings;
  8         17  
  8         235  
10 8     8   3197 use Template 2.14;
  8         22202  
  8         200  
11 8     8   4836 use Getopt::Long;
  8         61338  
  8         37  
12              
13             our $VERSION = '0.15';
14              
15 8     8   3747 use Lemplate::Parser;
  8         30  
  8         12112  
16              
17             #-------------------------------------------------------------------------------
18              
19             our %ExtraTemplates;
20             our %ProcessedTemplates;
21             our $TemplateName;
22              
23             sub usage {
24 0     0 0   <<'...';
25             Usage:
26              
27             lemplate --runtime [runtime-opt]
28              
29             lemplate --compile [compile-opt]
30              
31             lemplate --runtime [runtime-opt] --compile [compile-opt]
32              
33             lemplate --list
34              
35             Where "--runtime" and "runtime-opt" can include:
36              
37             --runtime Equivalent to --ajax=ilinsky --json=json2
38             --runtime=standard
39              
40             --runtime=lite Same as --ajax=none --json=none
41             --runtime=jquery Same as --ajax=jquery --json=none
42             --runtime=yui Same as --ajax=yui --json=yui
43             --runtime=legacy Same as --ajax=gregory --json=json2
44              
45             --json By itself, equivalent to --json=json2
46             --json=json2 Include http://www.json.org/json2.js for parsing/stringifying
47             --json=yui Use YUI: YAHOO.lang.JSON (requires external YUI)
48             --json=none Doesn't provide any JSON functionality except a warning
49              
50             --ajax By itself, equivalent to --ajax=xhr
51             --ajax=jquery Use jQuery for Ajax get and post (requires external jQuery)
52             --ajax=yui Use YUI: yui/connection/connection.js (requires external YUI)
53             --ajax=xhr Use XMLHttpRequest (will automatically use --xhr=ilinsky if --xhr is not set)
54             --ajax=none Doesn't provide any Ajax functionality except a warning
55              
56             --xhr By itself, equivalent to --xhr=ilinsky
57             --xhr=ilinsky Include http://code.google.com/p/xmlhttprequest/
58             --xhr=gregory Include http://www.scss.com.au/family/andrew/webdesign/xmlhttprequest/
59              
60             --xxx Include XXX and JJJ helper functions
61              
62             --compact Use the YUICompressor compacted version of the runtime
63              
64             Where "compile-opt" can include:
65              
66             --include_path=DIR Add directory to INCLUDE_PATH
67              
68             --start-tag
69             --end-tag
70             --pre-chomp
71             --post-chomp
72             --trim
73             --any-case
74             --eval
75             --noeval
76             -s, --source
77             --exclude
78              
79             For more information use:
80             perldoc lemplate
81             ...
82             }
83              
84             sub main {
85 0     0 0   my $class = shift;
86              
87 0           my @argv = @_;
88              
89 0           my ($template_options, $lemplate_options) = get_options(@argv);
90 0           my ($runtime, $compile, $list) = @$lemplate_options{qw/runtime compile list/};
91              
92 0 0         if ($runtime) {
93 0           print runtime_source_code(@$lemplate_options{qw/runtime ajax json xhr xxx compact/});
94 0 0         return unless $compile;
95             }
96              
97 0           my $templates = make_file_list($lemplate_options->{exclude}, @argv);
98 0 0         print_usage_and_exit() unless @$templates;
99              
100 0 0         if ($list) {
101 0           foreach (@$templates) {
102 0           print STDOUT $_->{short} . "\n";
103             }
104 0           return;
105             }
106              
107 0 0         if ($compile) {
108 0           my $lemplate = Lemplate->new(%$template_options);
109 0           print STDOUT $lemplate->_preamble;
110 0           for (my $i = 0; $i < @$templates; $i++) {
111 0           my $template = $templates->[$i];
112             #warn "processing $template->{short}";
113 0           my $content = slurp($template->{full});
114 0 0         if ($content) {
115 0           %ExtraTemplates = ();
116             print STDOUT $lemplate->compile_template_content(
117             $content,
118             $template->{short}
119 0           );
120 0           my @new_files;
121 0           for my $new_template (keys %ExtraTemplates) {
122 0 0         if (!$ProcessedTemplates{$new_template}) {
123 0 0         if (!-f $new_template) {
124 0           $new_template = "t/data/" . $new_template;
125             }
126             #warn $new_template;
127 0 0         if (-f $new_template) {
128             #warn "adding new template $new_template";
129 0           push @new_files, $new_template;
130             }
131             }
132             }
133 0           push @$templates, @{ make_file_list({}, @new_files) };
  0            
134             }
135             }
136 0           print STDOUT "return _M\n";
137 0           return;
138             }
139              
140 0           print_usage_and_exit();
141             }
142              
143             sub get_options {
144 0     0 0   local @ARGV = @_;
145              
146 0           my $runtime;
147 0           my $compile = 0;
148 0           my $list = 0;
149              
150             my $start_tag = exists $ENV{LEMPLATE_START_TAG}
151             ? $ENV{LEMPLATE_START_TAG}
152 0 0         : undef;
153             my $end_tag = exists $ENV{LEMPLATE_END_TAG}
154             ? $ENV{LEMPLATE_END_TAG}
155 0 0         : undef;
156             my $pre_chomp = exists $ENV{LEMPLATE_PRE_CHOMP}
157             ? $ENV{LEMPLATE_PRE_CHOMP}
158 0 0         : undef;
159             my $post_chomp = exists $ENV{LEMPLATE_POST_CHOMP}
160             ? $ENV{LEMPLATE_POST_CHOMP}
161 0 0         : undef;
162             my $trim = exists $ENV{LEMPLATE_TRIM}
163             ? $ENV{LEMPLATE_TRIM}
164 0 0         : undef;
165             my $anycase = exists $ENV{LEMPLATE_ANYCASE}
166             ? $ENV{LEMPLATE_ANYCASE}
167 0 0         : undef;
168             my $eval_javascript = exists $ENV{LEMPLATE_EVAL_JAVASCRIPT}
169             ? $ENV{LEMPLATE_EVAL_JAVASCRIPT}
170 0 0         : 1;
171              
172 0           my $source = 0;
173 0           my $exclude = 0;
174 0           my ($ajax, $json, $xxx, $xhr, $compact, $minify);
175              
176 0           my $help = 0;
177 0           my @include_paths;
178              
179 0 0         GetOptions(
180             "compile|c" => \$compile,
181             "list|l" => \$list,
182             "runtime|r:s" => \$runtime,
183              
184             "start-tag=s" => \$start_tag,
185             "end-tag=s" => \$end_tag,
186             "trim=s" => \$trim,
187             "pre-chomp" => \$pre_chomp,
188             "post-chomp" => \$post_chomp,
189             "any-case" => \$anycase,
190             "eval!" => \$eval_javascript,
191              
192             "source|s" => \$source,
193             "exclude=s" => \$exclude,
194              
195             "ajax:s" => \$ajax,
196             "json:s" => \$json,
197             "xxx" => \$xxx,
198             "xhr:s" => \$xhr,
199              
200             "include_path" => \@include_paths,
201             "compact" => \$compact,
202             "minify:s" => \$minify,
203              
204             "help|?" => \$help,
205             ) or print_usage_and_exit();
206              
207 0 0         if ($help) {
208 0           print_usage_and_exit();
209             }
210              
211 0 0 0       ($runtime, $ajax, $json, $xxx, $xhr, $minify) = map { defined $_ && ! length $_ ? 1 : $_ } ($runtime, $ajax, $json, $xxx, $xhr, $minify);
  0            
212 0 0 0       $runtime = "standard" if $runtime && $runtime eq 1;
213              
214 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            
215 0 0 0       print_usage_and_exit("Can't specify --list with a --runtime and/or the --compile option") if $list && ($runtime || $compile);
      0        
216 0 0 0       print_usage_and_exit() unless $list || $runtime || $compile;
      0        
217              
218 0 0         my $command =
    0          
    0          
219             $runtime ? 'runtime' :
220             $compile ? 'compile' :
221             $list ? 'list' :
222             print_usage_and_exit();
223              
224 0           my $options = {};
225 0 0         $options->{START_TAG} = $start_tag if defined $start_tag;
226 0 0         $options->{END_TAG} = $end_tag if defined $end_tag;
227 0 0         $options->{PRE_CHOMP} = $pre_chomp if defined $pre_chomp;
228 0 0         $options->{POST_CHOMP} = $post_chomp if defined $post_chomp;
229 0 0         $options->{TRIM} = $trim if defined $trim;
230 0 0         $options->{ANYCASE} = $anycase if defined $anycase;
231 0 0         $options->{EVAL_JAVASCRIPT} = $eval_javascript if defined $eval_javascript;
232 0           $options->{INCLUDE_PATH} = \@include_paths;
233              
234             return (
235 0           $options,
236             { compile => $compile, runtime => $runtime, list => $list,
237             source => $source,
238             exclude => $exclude,
239             ajax => $ajax, json => $json, xxx => $xxx, xhr => $xhr,
240             compact => $compact, minify => $minify },
241             );
242             }
243              
244              
245             sub slurp {
246 0     0 0   my $filepath = shift;
247 0 0         open(F, '<', $filepath) or die "Can't open '$filepath' for input:\n$!";
248 0           my $contents = do {local $/; };
  0            
  0            
249 0           close(F);
250 0           return $contents;
251             }
252              
253             sub recurse_dir {
254 0     0 0   require File::Find::Rule;
255              
256 0           my $dir = shift;
257 0           my @files;
258 0           foreach ( File::Find::Rule->file->in( $dir ) ) {
259 0 0         if ( m{/\.[^\.]+} ) {} # Skip ".hidden" files or directories
260             else {
261 0           push @files, $_;
262             }
263             }
264 0           return @files;
265             }
266              
267             sub make_file_list {
268 0     0 0   my ($exclude, @args) = @_;
269              
270 0           my @list;
271              
272 0           foreach my $arg (@args) {
273 0 0         unless (-e $arg) { next; } # file exists
  0            
274 0 0 0       unless (-s $arg or -d $arg) { next; } # file size > 0 or directory (for Win platform)
  0            
275 0 0 0       if ($exclude and $arg =~ m/$exclude/) { next; } # file matches exclude regex
  0            
276              
277 0 0         if (-d $arg) {
278 0           foreach my $full ( recurse_dir($arg) ) {
279 0           $full =~ /$arg(\/|)(.*)/;
280 0           my $short = $2;
281 0           push(@list, {full=>$full, short=>$short} );
282             }
283             }
284             else {
285 0           my $full = $arg;
286 0           my $short = $full;
287 0           $short =~ s/.*[\/\\]//;
288 0           push(@list, {full=>$arg, short=>$short} );
289             }
290             }
291              
292 0           return [ sort { $a->{short} cmp $b->{short} } @list ];
  0            
293             }
294              
295             sub print_usage_and_exit {
296 0 0   0 0   print STDOUT join "\n", "", @_, "Aborting!", "\n" if @_;
297 0           print STDOUT usage();
298 0           exit;
299             }
300              
301             sub runtime_source_code {
302 0     0 0   die "generating a separate runtime not supported yet";
303             }
304              
305             #-------------------------------------------------------------------------------
306              
307             sub new {
308 0     0 0   my $class = shift;
309 0           return bless { @_ }, $class;
310             }
311              
312             sub compile_module {
313 0     0 0   my ($self, $module_path, $template_file_paths) = @_;
314 0 0         my $result = $self->compile_template_files(@$template_file_paths)
315             or return;
316 0 0         open MODULE, "> $module_path"
317             or die "Can't open '$module_path' for output:\n$!";
318 0           print MODULE $result;
319 0           close MODULE;
320 0           return 1;
321             }
322              
323             sub compile_module_cached {
324 0     0 0   my ($self, $module_path, $template_file_paths) = @_;
325 0           my $m = -M $module_path;
326 0 0         return 0 unless grep { -M($_) < $m } @$template_file_paths;
  0            
327 0           return $self->compile_module($module_path, $template_file_paths);
328             }
329              
330             sub compile_template_files {
331 0     0 0   my $self = shift;
332 0           my $output = $self->_preamble;
333 0           for my $filepath (@_) {
334 0           my $filename = $filepath;
335 0           $filename =~ s/.*[\/\\]//;
336 0 0         open FILE, $filepath
337             or die "Can't open '$filepath' for input:\n$!";
338 0           my $template_input = do {local $/; };
  0            
  0            
339 0           close FILE;
340 0           $output .=
341             $self->compile_template_content($template_input, $filename);
342             }
343 0           return $output;
344             }
345              
346             sub compile_template_content {
347 0 0   0 0   die "Invalid arguments in call to Lemplate->compile_template_content"
348             unless @_ == 3;
349 0           my ($self, $template_content, $template_name) = @_;
350 0           $TemplateName = $template_name;
351 0 0         my $parser = Lemplate::Parser->new( ref($self) ? %$self : () );
352 0 0         my $parse_tree = $parser->parse(
353             $template_content, {name => $template_name}
354             ) or die $parser->error;
355             my $output =
356             "-- $template_name\n" .
357             "template_map['$template_name'] = " .
358             $parse_tree->{BLOCK} .
359 0           "\n";
360 0           for my $function_name (sort keys %{$parse_tree->{DEFBLOCKS}}) {
  0            
361 0           my $name = "$template_name/$function_name";
362 0 0         next if $ProcessedTemplates{$name};
363             #warn "seen $name";
364 0           $ProcessedTemplates{$name} = 1;
365             $output .=
366             "template_map['$name'] = " .
367 0           $parse_tree->{DEFBLOCKS}{$function_name} .
368             "\n";
369             }
370 0           return $output;
371             }
372              
373             sub _preamble {
374 0     0     return <<'...';
375             --[[
376             This Lua code was generated by Lemplate, the Lua
377             Template Toolkit. Any changes made to this file will be lost the next
378             time the templates are compiled.
379              
380             Copyright 2016 - Yichun Zhang (agentzh) - All rights reserved.
381              
382             Copyright 2006-2014 - Ingy döt Net - All rights reserved.
383             ]]
384              
385             local gsub = ngx.re.gsub
386             local concat = table.concat
387             local type = type
388             local math_floor = math.floor
389             local table_maxn = table.maxn
390              
391             local _M = {
392             version = '0.07'
393             }
394              
395             local template_map = {}
396              
397             local function tt2_true(v)
398             return v and v ~= 0 and v ~= "" and v ~= '0'
399             end
400              
401             local function tt2_not(v)
402             return not v or v == 0 or v == "" or v == '0'
403             end
404              
405             local context_meta = {}
406              
407             function context_meta.plugin(context, name, args)
408             if name == "iterator" then
409             local list = args[1]
410             local count = table_maxn(list)
411             return { list = list, count = 1, max = count - 1, index = 0, size = count, first = true, last = false, prev = "" }
412             else
413             return error("unknown iterator: " .. name)
414             end
415             end
416              
417             function context_meta.process(context, file)
418             local f = template_map[file]
419             if not f then
420             return error("file error - " .. file .. ": not found")
421             end
422             return f(context)
423             end
424              
425             function context_meta.include(context, file)
426             local f = template_map[file]
427             if not f then
428             return error("file error - " .. file .. ": not found")
429             end
430             return f(context)
431             end
432              
433             context_meta = { __index = context_meta }
434              
435             -- XXX debugging function:
436             -- local function xxx(data)
437             -- io.stderr:write("\n" .. require("cjson").encode(data) .. "\n")
438             -- end
439              
440             local function stash_get(stash, expr)
441             local result
442              
443             if type(expr) ~= "table" then
444             result = stash[expr]
445             if type(result) == "function" then
446             return result()
447             end
448             return result or ''
449             end
450              
451             result = stash
452             for i = 1, #expr, 2 do
453             local key = expr[i]
454             if type(key) == "number" and key == math_floor(key) and key >= 0 then
455             key = key + 1
456             end
457             local val = result[key]
458             local args = expr[i + 1]
459             if args == 0 then
460             args = {}
461             end
462              
463             if val == nil then
464             if not _M.vmethods[key] then
465             if type(expr[i + 1]) == "table" then
466             return error("virtual method " .. key .. " not supported")
467             end
468             return ''
469             end
470             val = _M.vmethods[key]
471             args = {result, unpack(args)}
472             end
473              
474             if type(val) == "function" then
475             val = val(unpack(args))
476             end
477              
478             result = val
479             end
480              
481             return result
482             end
483              
484             local function stash_set(stash, k, v, default)
485             if default then
486             local old = stash[k]
487             if old == nil then
488             stash[k] = v
489             end
490             else
491             stash[k] = v
492             end
493             end
494              
495             _M.vmethods = {
496             join = function (list, delim)
497             delim = delim or ' '
498             local out = {}
499             local size = #list
500             for i = 1, size, 1 do
501             out[i * 2 - 1] = list[i]
502             if i ~= size then
503             out[i * 2] = delim
504             end
505             end
506             return concat(out)
507             end,
508              
509             first = function (list)
510             return list[1]
511             end,
512              
513             keys = function (list)
514             local out = {}
515             i = 1
516             for key in pairs(list) do
517             out[i] = key
518             i = i + 1
519             end
520             return out
521             end,
522              
523             last = function (list)
524             return list[#list]
525             end,
526              
527             push = function(list, ...)
528             local n = select("#", ...)
529             local m = #list
530             for i = 1, n do
531             list[m + i] = select(i, ...)
532             end
533             return ''
534             end,
535              
536             size = function (list)
537             if type(list) == "table" then
538             return #list
539             else
540             return 1
541             end
542             end,
543              
544             sort = function (list)
545             local out = { unpack(list) }
546             table.sort(out)
547             return out
548             end,
549              
550             split = function (str, delim)
551             delim = delim or ' '
552             local out = {}
553             local start = 1
554             local sub = string.sub
555             local find = string.find
556             local sstart, send = find(str, delim, start)
557             local i = 1
558             while sstart do
559             out[i] = sub(str, start, sstart-1)
560             i = i + 1
561             start = send + 1
562             sstart, send = find(str, delim, start)
563             end
564             out[i] = sub(str, start)
565             return out
566             end,
567             }
568              
569             _M.filters = {
570             html = function (s, args)
571             s = gsub(s, "&", '&', "jo")
572             s = gsub(s, "<", '<', "jo");
573             s = gsub(s, ">", '>', "jo");
574             s = gsub(s, '"', '"', "jo"); -- " end quote for emacs
575             return s
576             end,
577              
578             lower = function (s, args)
579             return string.lower(s)
580             end,
581              
582             upper = function (s, args)
583             return string.upper(s)
584             end,
585             }
586              
587             function _M.process(file, params)
588             local stash = params
589             local context = {
590             stash = stash,
591             filter = function (bits, name, params)
592             local s = concat(bits)
593             local f = _M.filters[name]
594             if f then
595             return f(s, params)
596             end
597             return error("filter '" .. name .. "' not found")
598             end
599             }
600             context = setmetatable(context, context_meta)
601             local f = template_map[file]
602             if not f then
603             return error("file error - " .. file .. ": not found")
604             end
605             return f(context)
606             end
607             ...
608             }
609              
610             1;
611              
612             __END__