File Coverage

blib/lib/EntityModel/Template.pm
Criterion Covered Total %
statement 28 173 16.1
branch 0 60 0.0
condition 0 19 0.0
subroutine 8 27 29.6
pod 12 14 85.7
total 48 293 16.3


line stmt bran cond sub pod time code
1             package EntityModel::Template;
2             {
3             $EntityModel::Template::VERSION = '0.102';
4             }
5             use EntityModel::Class {
6 1         9 include_path => { type => 'array', subclass => 'string' }
7 1     1   25102 };
  1         75287  
8              
9             =head1 NAME
10              
11             EntityModel::Template - template handling for L
12              
13             =head1 VERSION
14              
15             version 0.102
16              
17             =head1 SYNOPSIS
18              
19             =head1 DESCRIPTION
20              
21             =cut
22              
23 1     1   2834 use Template;
  1         28071  
  1         28  
24 1     1   1599 use Template::Stash;
  1         15120  
  1         33  
25 1     1   12 use File::Basename;
  1         2  
  1         108  
26 1     1   972 use Tie::Cache::LRU;
  1         10725  
  1         32  
27 1     1   1224 use DateTime;
  1         196575  
  1         41  
28 1     1   9 use POSIX qw/floor/;
  1         1  
  1         8  
29              
30             tie my %LONG_DATE_HASH, 'Tie::Cache::LRU', 5000;
31             tie my %SHORT_DATE_HASH, 'Tie::Cache::LRU', 5000;
32              
33             our $BasePath = '.';
34              
35             BEGIN {
36             # Convenience functions so we can do something.arrayref and be sure to get back something FOREACH-suitable
37             $Template::Stash::LIST_OPS->{ arrayref } = sub {
38 0         0 my $list = shift;
39 0         0 return $list;
40 1     1   383 };
41             $Template::Stash::HASH_OPS->{ arrayref } = sub {
42 0         0 my $hash = shift;
43 0         0 return [ $hash ];
44 1         7 };
45             # hashops since we have datetime object... in theory.
46             #$Template::Stash::HASH_OPS->{ msDuration } = sub {
47             # my $v = shift;
48             # return DateTime::Format::Duration->new(pattern => '%H:%M:%S.%3N')->format_duration($v);
49             #};
50             $Template::Stash::HASH_OPS->{ from_now } = sub {
51 0         0 my $v = shift;
52 0         0 return from_now($v);
53 1         4 };
54             $Template::Stash::HASH_OPS->{ 'ref' } = sub {
55 0         0 my $scalar = shift;
56 0         0 return ref $scalar;
57 1         13 };
58             $Template::Stash::SCALAR_OPS->{ arrayref } = sub {
59 0         0 my $scalar = shift;
60 0         0 return [ $scalar ];
61 1         4 };
62             $Template::Stash::SCALAR_OPS->{ trim } = sub {
63 0         0 my $scalar = shift;
64 0         0 $scalar =~ s/^\s+//ms;
65 0         0 $scalar =~ s/\s+$//ms;
66 0         0 return $scalar;
67 1         9 };
68             $Template::Stash::SCALAR_OPS->{ js } = sub {
69 0           my $str = join('', @_);
70 0           $str =~ s/"/\\"/ms;
71 0           return '"' . $str . '"';
72 1         2097 };
73             }
74              
75             sub new {
76 0     0 1   my $class = shift;
77 0           my $self = bless { data => { } }, $class;
78 0           my %args = @_;
79 0 0         if(defined(my $include = delete $args{include_path})) {
80 0 0         $include = [ $include ] unless ref $include;
81 0           $self->include_path->push($_) for @$include;
82             }
83              
84             # We want access to _ methods, such as _view, so disable this.
85 0           undef $Template::Stash::PRIVATE;
86              
87             my %cfg = (
88             INCLUDE_PATH => [ $self->include_path->list ],
89             INTERPOLATE => 0,
90             ABSOLUTE => 0,
91             RELATIVE => 0,
92             RECURSION => 1,
93             AUTO_RESET => 0,
94             STAT_TTL => 15,
95             COMPILE_EXT => '.ttc',
96             COMPILE_DIR => '/tmp/ttc',
97             CACHE_SIZE => 4096,
98             PRE_DEFINE => {
99             # cfg => \%EntityModel::Config::Current,
100             # imageHost => 'http://' . EntityModel::Config::ImageHost,
101             # scriptHost => 'http://' . EntityModel::Config::ScriptHost,
102             },
103             FILTERS => {
104             long_date => [
105             sub {
106 0     0     my ($context, @args) = @_;
107             return sub {
108 0           return long_date(shift, @args);
109             }
110 0           }, 1
111             ],
112             short_date => [
113             sub {
114 0     0     my ($context, @args) = @_;
115             return sub {
116 0           return short_date(shift, @args);
117             }
118 0           }, 1
119             ],
120             ymd_date => [
121             sub {
122 0     0     my ($context, @args) = @_;
123             return sub {
124 0           return ymd_date(shift, @args);
125             }
126 0           }, 1
127             ],
128             tidy_ymd => [
129             sub {
130 0     0     my ($context, @args) = @_;
131             return sub {
132 0           return tidy_ymd(shift, @args);
133             }
134 0           }, 1
135             ],
136             from_now => [
137             sub {
138 0     0     my ($context, @args) = @_;
139             return sub {
140 0           return from_now(shift, @args);
141             }
142 0           }, 1
  0            
143             ],
144             #as_duration => [
145             # sub {
146             # my ($context, @args) = @_;
147             # return sub {
148             # return as_duration(shift, @args);
149             # }
150             # }, 1
151             #],
152             },
153             );
154             #$cfg{CONTEXT} = new Template::Timer(%cfg) if EntityModel::Config::Debug;
155 0 0         my $tmpl = Template->new(%cfg) or die Template->error;
156 0           $self->{ template } = $tmpl;
157 0           return $self;
158             }
159              
160             =head2 from_now
161              
162             Duration from/since now
163              
164             =cut
165              
166             sub from_now {
167 0     0 1   my $v = shift;
168 0 0         return ' ' unless $v;
169              
170 0 0 0       $v = DateTime->from_epoch(epoch => $1) if !ref($v) && $v =~ /^(\d+(?:\.\d*))$/;
171 0           my $delta = $v->epoch - time;
172 0           my $neg;
173 0 0         if($delta < 0) {
174 0           $neg = 1;
175 0           $delta = -$delta;
176             }
177 0           my @p;
178 0           my @match = (
179             second => 60,
180             minute => 60,
181             hour => 24,
182             day => 30,
183             month => 12,
184             year => 0
185             );
186 0   0       while($delta && @match) {
187 0           my $k = shift @match;
188 0           my $m = shift @match;
189 0 0         my $unit = $m ? ($delta % $m) : $delta;
190 0 0         $delta = floor($delta / $m) if $m;
191 0 0         unshift @p, "$unit $k" . ($unit != 1 ? 's' : '');
192             }
193              
194             # Don't show too much resolution
195 0 0         @p = @p[0..1] if @p > 2;
196 0           my $pattern = join(', ', @p);
197              
198 0 0         return $pattern . ($neg ? ' ago' : ' from now');
199             }
200              
201             =head2 long_date
202              
203             Long date format filter.
204              
205             =cut
206              
207             sub long_date {
208 0     0 1   my ($v, $fmt) = @_;
209 0 0         return ' ' unless $v;
210 0 0         unless ($LONG_DATE_HASH{$v}) {
211 0           my $dt;
212 0 0         if($v =~ m/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/) {
213 0           my ($year, $month, $day, $hour, $minute, $second, $us) = ($1, $2, $3, $4, $5, $6, $7);
214 0   0       $dt = DateTime->new(
215             year => $year,
216             month => $month,
217             day => $day,
218             hour => $hour,
219             minute => $minute,
220             second => $second,
221             nanosecond => 1000 * ($us // 0)
222             );
223             } else {
224 0           $dt = DateTime->from_epoch(epoch => $v);
225             }
226 0           $LONG_DATE_HASH{$v} = $dt->strftime('%e %b %Y, %l:%M %P');
227             }
228 0           return $LONG_DATE_HASH{$v};
229             }
230              
231             =head2 short_date
232              
233             Short date format filter.
234              
235             =cut
236              
237             sub short_date {
238 0     0 1   my ($v, $fmt) = @_;
239 0 0         return ' ' unless $v;
240 0 0         unless ($SHORT_DATE_HASH{$v}) {
241 0           my $dt;
242 0 0         if($v =~ m/^(\d+)-(\d+)-(\d+)/) {
243 0           my ($year, $month, $day) = ($1, $2, $3);
244 0           $dt = DateTime->new(
245             year => $year,
246             month => $month,
247             day => $day,
248             );
249             } else {
250 0           $dt = DateTime->from_epoch(epoch => $v);
251             }
252 0           my $suffix = 'th';
253 0 0 0       if(($dt->day % 10) == 1 && ($dt->day != 11)) {
    0 0        
    0 0        
254 0           $suffix = 'st';
255             } elsif(($dt->day % 10) == 2 && ($dt->day != 12)) {
256 0           $suffix = 'nd';
257             } elsif(($dt->day % 10) == 3 && ($dt->day != 13)) {
258 0           $suffix = 'rd';
259             }
260 0           $SHORT_DATE_HASH{$v} = $dt->strftime("%d$suffix %b");
261             }
262 0           return $SHORT_DATE_HASH{$v};
263             }
264              
265             =head2 ymd_date
266              
267             YMD date filter
268              
269             =cut
270              
271             sub ymd_date {
272 0     0 1   my ($v, $fmt) = @_;
273 0 0         return ' ' unless $v;
274 0           my $dt;
275 0 0         if($v =~ m/^(\d+)-(\d+)-(\d+)/) {
276 0           my ($year, $month, $day) = ($1, $2, $3);
277 0           return sprintf("%04d-%02d-%02d", $year, $month, $day);
278             } else {
279 0           $dt = DateTime->from_epoch(epoch => $v);
280             }
281 0           return $dt->strftime('%Y-%m-%d');
282             }
283              
284             =head2 tidy_ymd
285              
286             YMD date filter
287              
288             =cut
289              
290             sub tidy_ymd {
291 0     0 1   my ($v, $fmt) = @_;
292 0 0         return ' ' unless $v;
293 0           my $dt;
294 0 0         if($v =~ m/^(\d+)-(\d+)-(\d+)/) {
295 0           my ($year, $month, $day) = ($1, $2, $3);
296 0           return sprintf("%04d-%02d-%02d", $year, $month, $day);
297             } else {
298 0           $dt = DateTime->from_epoch(epoch => $v);
299 0           return $dt->strftime('%Y-%m-%d');
300             }
301             }
302              
303             =head2 as_duration
304              
305             Convert duration to MM:SS representation.
306              
307             =cut
308              
309             sub as_duration {
310 0     0 1   my ($v, $fmt) = @_;
311 0 0         return ' ' unless $v;
312              
313 0           return sprintf('%02d:%02d', int($v / 60), int($v % 60));
314             }
315              
316             =head2 template
317              
318             Return the TT2 object, created as necessary.
319              
320             =cut
321              
322 0     0 1   sub template { shift->{template} }
323              
324             =head2 as_text
325              
326             Return template output as text.
327              
328             =cut
329              
330             sub as_text {
331 0     0 1   my ($self, $template, $newData) = @_;
332 0   0       $newData ||= {};
333 0           my %data = ( %{ $self->{data} }, %$newData );
  0            
334 0           my $output;
335 0           my $tt = $self->template;
336 0 0         $tt->process($template, \%data, \$output) || die 'Failed template: ' . $tt->error;
337 0           return $output;
338             }
339              
340             sub process_template {
341 0     0 0   my $self = shift;
342 0           my $tmpl = shift;
343 0           my $tt = $self->template;
344 0 0         $tt->process($tmpl, undef, \my $output) or die "Failed template: " . $tt->error;
345 0           return $self;
346             }
347              
348             =head2 processHTML
349              
350             Process HTML data.
351              
352             =cut
353              
354             sub processHTML {
355 0     0 1   my ($self, $template, $newData) = @_;
356 0           my $data = { %{$self->{data}}, %$newData };
  0            
357              
358 0           my $tt = $self->template;
359 0           my $output;
360 0 0         $tt->process($template, $data, \$output) || die 'Failed template: ' . $tt->error;
361 0           if(0) {
362             my $origSize = length($output);
363             $output =~ s///g;
364             my $tidy = HTML::Tidy->new({
365             tidy_mark => 0,
366             'preserve-entities' => 1,
367             wrap => 160,
368             'char-encoding' => 'utf8',
369             indent => 0
370             });
371             $output = $tidy->clean($output);
372             my $finalSize = length($output);
373             logDebug("From %d to %d: %3.2f%%", $origSize, $finalSize, (100.0 * $finalSize/$origSize));
374             }
375 0           return $output;
376             }
377              
378             =head2 output
379              
380             Generate output via Apache2 print.
381              
382             =cut
383              
384             sub output {
385 0     0 1   my ($self, $template, $newData, $r) = @_;
386 0           my $data = { %{$self->{data}}, %$newData };
  0            
387              
388 0           logInfo("Output");
389 0           my $output = $self->processHTML($template, $data);
390 0 0         if($r) {
391 0 0         $r->content_type('text/html') if $r;
392 0           $r->no_cache(1);
393 0           $r->setLifetime(0);
394 0           $r->print($output);
395             } else {
396 0           print $output;
397             }
398             }
399              
400             =head2 error
401              
402             Handle any TT2 error messages.
403              
404             =cut
405              
406             sub error {
407 0     0 1   my $self = shift;
408 0           return $self->template->error;
409             }
410              
411             sub addData {
412 0     0 0   my ($self, $data) = @_;
413 0           foreach (keys %$data) {
414 0           $self->{data}->{ $_ } = $data->{$_};
415             }
416 0           return $self;
417             }
418              
419             1;
420              
421             __END__