File Coverage

blib/lib/Venus/Template.pm
Criterion Covered Total %
statement 82 108 75.9
branch 21 32 65.6
condition 11 20 55.0
subroutine 14 21 66.6
pod 2 11 18.1
total 130 192 67.7


line stmt bran cond sub pod time code
1             package Venus::Template;
2              
3 2     2   57 use 5.018;
  2         18  
4              
5 2     2   15 use strict;
  2         4  
  2         59  
6 2     2   14 use warnings;
  2         5  
  2         69  
7              
8 2     2   18 use Venus::Class 'attr', 'base', 'with';
  2         6  
  2         29  
9              
10             base 'Venus::Kind::Utility';
11              
12             with 'Venus::Role::Valuable';
13             with 'Venus::Role::Buildable';
14             with 'Venus::Role::Accessible';
15             with 'Venus::Role::Explainable';
16              
17             use overload (
18             '""' => 'explain',
19 0     0   0 'eq' => sub{$_[0]->render eq "$_[1]"},
20 0     0   0 'ne' => sub{$_[0]->render ne "$_[1]"},
21 0     0   0 'qr' => sub{qr/@{[quotemeta($_[0]->render)]}/},
  0         0  
22 2         27 '~~' => 'explain',
23             fallback => 1,
24 2     2   16 );
  2         11  
25              
26             # ATTRIBUTES
27              
28             attr 'markers';
29             attr 'variables';
30              
31             # BUILDERS
32              
33             sub build_self {
34 13     13 0 41 my ($self, $data) = @_;
35              
36 13 50       41 $self->markers([qr/\{\{/, qr/\}\}/]) if !defined $self->markers;
37 13 50       52 $self->variables({}) if !defined $self->variables;
38              
39 13         33 return $self;
40             }
41              
42             # METHODS
43              
44             sub assertion {
45 0     0 1 0 my ($self) = @_;
46              
47 0         0 my $assertion = $self->SUPER::assertion;
48              
49             $assertion->match('string')->format(sub{
50 0   0 0   0 (ref $self || $self)->new($_)
51 0         0 });
52              
53 0         0 return $assertion;
54             }
55              
56             sub default {
57 0     0 0 0 return '';
58             }
59              
60             sub explain {
61 8     8 0 152 my ($self) = @_;
62              
63 8         27 return $self->render;
64             }
65              
66             sub mappable {
67 162     162 0 316 my ($self, $data) = @_;
68              
69 162         639 require Scalar::Util;
70 162         1209 require Venus::Array;
71 162         1067 require Venus::Hash;
72              
73 162 50       440 if (!$data) {
74 0         0 return Venus::Hash->new;
75             }
76 162 100 100     783 if (!Scalar::Util::blessed($data) && ref($data) eq 'ARRAY') {
77 5         45 return Venus::Array->new($data);
78             }
79 157 100 66     532 if (!Scalar::Util::blessed($data) && ref($data) eq 'HASH') {
80 118         371 return Venus::Hash->new($data);
81             }
82 39 50 66     405 if (!Scalar::Util::blessed($data) || (Scalar::Util::blessed($data)
      33        
      33        
83             && !($data->isa('Venus::Array') || $data->isa('Venus::Hash'))))
84             {
85 0         0 return Venus::Hash->new;
86             }
87             else {
88 39         147 return $data;
89             }
90             }
91              
92             sub render {
93 40     40 1 94 my ($self, $content, $variables) = @_;
94              
95 40 100       95 if (!defined $content) {
96 19         79 $content = $self->get;
97             }
98              
99 40 100       93 if (!defined $variables) {
100 18         59 $variables = $self->variables;
101             }
102             else {
103 22         82 $variables = $self->mappable($self->variables)->merge(
104             $self->mappable($variables)->get
105             );
106             }
107              
108 40         159 $content =~ s/^\r?\n//;
109 40         168 $content =~ s/\r?\n\ *$//;
110              
111 40         120 $content = $self->render_blocks($content, $variables);
112              
113 40         138 $content = $self->render_tokens($content, $variables);
114              
115 40         261 return $content;
116             }
117              
118             sub render_blocks {
119 40     40 0 96 my ($self, $content, $variables) = @_;
120              
121 40         53 my ($stag, $etag) = @{$self->markers};
  40         93  
122              
123 40         162 my $path = qr/[a-z_][\w.]*/;
124              
125 40         475 my $regexp = qr{
126             $stag
127             \s*
128             (FOR|IF|IF\sNOT)
129             \s+
130             ($path)
131             \s*
132             $etag
133             (.+)
134             $stag
135             \s*
136             (END)
137             \s+
138             \2
139             \s*
140             $etag
141             }xis;
142              
143 40         143 $variables = $self->mappable($variables);
144              
145 40         597 $content =~ s{
146             $regexp
147             }{
148 13         68 my ($type, $path, $body) = ($1, $2, $3);
149 13 100       71 if (lc($type) eq 'if') {
    50          
    50          
150 8         37 $self->render_if(
151             $body, $variables, !!scalar($variables->path($path)), $path
152             );
153             }
154             elsif (lc($type) eq 'if not') {
155 0         0 $self->render_if_not(
156             $body, $variables, !!scalar($variables->path($path)), $path
157             );
158             }
159             elsif (lc($type) eq 'for') {
160 5         26 $self->render_foreach(
161             $body, $self->mappable($variables->path($path))
162             );
163             }
164             }gsex;
165              
166 40         244 return $content;
167             }
168              
169             sub render_if {
170 8     8 0 26 my ($self, $context, $variables, $boolean, $path) = @_;
171              
172 8         21 my $mappable = $self->mappable($variables);
173              
174 8         19 my ($stag, $etag) = @{$self->markers};
  8         20  
175              
176 8         24 $path = quotemeta $path;
177              
178 8         127 my $regexp = qr{
179             $stag
180             \s*
181             ELSE
182             \s+
183             $path
184             \s*
185             $etag
186             }xis;
187              
188 8         59 my ($a, $b) = split /$regexp/, $context;
189              
190 8 100       24 if ($boolean) {
191 5         26 return $self->render($a, $mappable);
192             }
193             else {
194 3 100       8 if ($b) {
195 1         6 return $self->render($b, $mappable);
196             }
197             else {
198 2         15 return '';
199             }
200             }
201             }
202              
203             sub render_if_not {
204 0     0 0 0 my ($self, $context, $variables, $boolean, $path) = @_;
205              
206 0         0 my $mappable = $self->mappable($variables);
207              
208 0         0 my ($stag, $etag) = @{$self->markers};
  0         0  
209              
210 0         0 $path = quotemeta $path;
211              
212 0         0 my $regexp = qr{
213             $stag
214             \s*
215             ELSE
216             \s+
217             $path
218             \s*
219             $etag
220             }xis;
221              
222 0         0 my ($a, $b) = split /$regexp/, $context;
223              
224 0 0       0 if (!$boolean) {
225 0         0 return $self->render($a, $mappable);
226             }
227             else {
228 0 0       0 if ($b) {
229 0         0 return $self->render($b, $mappable);
230             }
231             else {
232 0         0 return '';
233             }
234             }
235             }
236              
237             sub render_foreach {
238 5     5 0 23 my ($self, $context, $mappable) = @_;
239              
240 5         17 $mappable = $self->mappable($mappable);
241              
242 5 50       21 if (!$mappable->isa('Venus::Array')) {
243 0         0 return '';
244             }
245              
246             my @results = $self->mappable($mappable)->each(sub {
247 15     15   48 my (@args) = @_;
248 15         37 $self->render($context, $self->mappable($args[1])->do(
249             'set', 'loop', {index => $args[0], place => $args[0]+1},
250             ));
251 5         21 });
252              
253 5         65 return join "\n", grep !!$_, @results;
254             }
255              
256             sub render_tokens {
257 40     40 0 96 my ($self, $content, $variables) = @_;
258              
259 40         56 my ($stag, $etag) = @{$self->markers};
  40         96  
260              
261 40         149 my $path = qr/[a-z_][\w.]*/;
262              
263 40         354 my $regexp = qr{
264             $stag
265             \s*
266             ($path)
267             \s*
268             $etag
269             }xi;
270              
271 40         118 $variables = $self->mappable($variables);
272              
273 40         495 $content =~ s{
274             $regexp
275             }{
276 51   100     191 scalar($variables->path($1)) // ''
277             }gsex;
278              
279 40         202 return $content;
280             }
281              
282             1;
283              
284              
285              
286             =head1 NAME
287              
288             Venus::Template - Template Class
289              
290             =cut
291              
292             =head1 ABSTRACT
293              
294             Template Class for Perl 5
295              
296             =cut
297              
298             =head1 SYNOPSIS
299              
300             package main;
301              
302             use Venus::Template;
303              
304             my $template = Venus::Template->new(
305             'From: <{{ email }}>',
306             );
307              
308             # $template->render;
309              
310             # "From: <>"
311              
312             =cut
313              
314             =head1 DESCRIPTION
315              
316             This package provides a templating system, and methods for rendering templates
317             using simple markup and minimal control structures. The default opening and
318             closing markers, denoting a template token, block, or control structure, are
319             C<{{> and C<}}>. A token takes the form of C<{{ foo }}> or C<{{ foo.bar }}>. A
320             block takes the form of C<{{ for foo.bar }}> where C represents any
321             valid path, resolvable by L or L, which
322             returns an arrayref or L object, and must be followed by
323             C<{{ end foo }}>. Control structures take the form of C<{{ if foo }}> or
324             C<{{ if not foo }}>, may contain a nested C<{{ else foo }}> control structure,
325             and must be followed by C<{{ end foo }}>. Leading and trailing whitespace is
326             automatically removed from all replacements.
327              
328             =cut
329              
330             =head1 ATTRIBUTES
331              
332             This package has the following attributes:
333              
334             =cut
335              
336             =head2 variables
337              
338             variables(HashRef)
339              
340             This attribute is read-write, accepts C<(HashRef)> values, is optional, and defaults to C<{}>.
341              
342             =cut
343              
344             =head1 INHERITS
345              
346             This package inherits behaviors from:
347              
348             L
349              
350             =cut
351              
352             =head1 INTEGRATES
353              
354             This package integrates behaviors from:
355              
356             L
357              
358             L
359              
360             L
361              
362             L
363              
364             =cut
365              
366             =head1 METHODS
367              
368             This package provides the following methods:
369              
370             =cut
371              
372             =head2 render
373              
374             render(string $template, hashref $variables) (string)
375              
376             The render method processes the template by replacing the tokens and control
377             structurs with the appropriate replacements and returns the result. B
378             The rendering process expects variables to be hashrefs and sets (arrayrefs) of
379             hashrefs.
380              
381             I>
382              
383             =over 4
384              
385             =item render example 1
386              
387             # given: synopsis;
388              
389             my $result = $template->render;
390              
391             # "From: <>"
392              
393             =back
394              
395             =over 4
396              
397             =item render example 2
398              
399             # given: synopsis;
400              
401             $template->value(
402             'From: {{ if name }}{{ name }}{{ end name }} <{{ email }}>',
403             );
404              
405             $template->variables({
406             email => 'noreply@example.com',
407             });
408              
409             my $result = $template->render;
410              
411             # "From: "
412              
413             =back
414              
415             =over 4
416              
417             =item render example 3
418              
419             # given: synopsis;
420              
421             $template->value(
422             'From: {{ if name }}{{ name }}{{ end name }} <{{ email }}>',
423             );
424              
425             $template->variables({
426             name => 'No-Reply',
427             email => 'noreply@example.com',
428             });
429              
430             my $result = $template->render;
431              
432             # "From: No-Reply "
433              
434             =back
435              
436             =over 4
437              
438             =item render example 4
439              
440             package main;
441              
442             use Venus::Template;
443              
444             my $template = Venus::Template->new(q(
445             {{ for chat.messages }}
446             {{ user.name }}: {{ message }}
447             {{ end chat.messages }}
448             ));
449              
450             $template->variables({
451             chat => { messages => [
452             { user => { name => 'user1' }, message => 'ready?' },
453             { user => { name => 'user2' }, message => 'ready!' },
454             { user => { name => 'user1' }, message => 'lets begin!' },
455             ]}
456             });
457              
458             my $result = $template->render;
459              
460             # user1: ready?
461             # user2: ready!
462             # user1: lets begin!
463              
464             =back
465              
466             =over 4
467              
468             =item render example 5
469              
470             package main;
471              
472             use Venus::Template;
473              
474             my $template = Venus::Template->new(q(
475             {{ for chat.messages }}
476             {{ if user.legal }}
477             {{ user.name }} [18+]: {{ message }}
478             {{ else user.legal }}
479             {{ user.name }} [-18]: {{ message }}
480             {{ end user.legal }}
481             {{ end chat.messages }}
482             ));
483              
484             $template->variables({
485             chat => { messages => [
486             { user => { name => 'user1', legal => 1 }, message => 'ready?' },
487             { user => { name => 'user2', legal => 0 }, message => 'ready!' },
488             { user => { name => 'user1', legal => 1 }, message => 'lets begin!' },
489             ]}
490             });
491              
492             my $result = $template->render;
493              
494             # user1 [18+]: ready?
495             # user2 [-18]: ready!
496             # user1 [18+]: lets begin!
497              
498             =back
499              
500             =over 4
501              
502             =item render example 6
503              
504             package main;
505              
506             use Venus::Template;
507              
508             my $template = Venus::Template->new(q(
509             {{ for chat.messages }}
510             {{ if user.admin }}@{{ end user.admin }}{{ user.name }}: {{ message }}
511             {{ end chat.messages }}
512             ));
513              
514             $template->variables({
515             chat => { messages => [
516             { user => { name => 'user1', admin => 1 }, message => 'ready?' },
517             { user => { name => 'user2', admin => 0 }, message => 'ready!' },
518             { user => { name => 'user1', admin => 1 }, message => 'lets begin!' },
519             ]}
520             });
521              
522             my $result = $template->render;
523              
524             # @user1: ready?
525             # user2: ready!
526             # @user1: lets begin!
527              
528             =back
529              
530             =over 4
531              
532             =item render example 7
533              
534             package main;
535              
536             use Venus::Template;
537              
538             my $template = Venus::Template->new(q(
539             {{ for chat.messages }}
540             [{{ loop.place }}] {{ user.name }}: {{ message }}
541             {{ end chat.messages }}
542             ));
543              
544             $template->variables({
545             chat => { messages => [
546             { user => { name => 'user1' }, message => 'ready?' },
547             { user => { name => 'user2' }, message => 'ready!' },
548             { user => { name => 'user1' }, message => 'lets begin!' },
549             ]}
550             });
551              
552             my $result = $template->render;
553              
554             # [1] user1: ready?
555             # [2] user2: ready!
556             # [3] user1: lets begin!
557              
558             =back
559              
560             =over 4
561              
562             =item render example 8
563              
564             package main;
565              
566             use Venus::Template;
567              
568             my $template = Venus::Template->new(q(
569             {{ for chat.messages }}
570             [{{ loop.index }}] {{ user.name }}: {{ message }}
571             {{ end chat.messages }}
572             ));
573              
574             $template->variables({
575             chat => { messages => [
576             { user => { name => 'user1' }, message => 'ready?' },
577             { user => { name => 'user2' }, message => 'ready!' },
578             { user => { name => 'user1' }, message => 'lets begin!' },
579             ]}
580             });
581              
582             my $result = $template->render;
583              
584             # [0] user1: ready?
585             # [1] user2: ready!
586             # [2] user1: lets begin!
587              
588             =back
589              
590             =cut
591              
592             =head1 OPERATORS
593              
594             This package overloads the following operators:
595              
596             =cut
597              
598             =over 4
599              
600             =item operation: C<("")>
601              
602             This package overloads the C<""> operator.
603              
604             B
605              
606             # given: synopsis;
607              
608             my $result = "$template";
609              
610             # "From: <>"
611              
612             B
613              
614             # given: synopsis;
615              
616             my $result = "$template, $template";
617              
618             # "From: <>, From: <>"
619              
620             =back
621              
622             =over 4
623              
624             =item operation: C<(~~)>
625              
626             This package overloads the C<~~> operator.
627              
628             B
629              
630             # given: synopsis;
631              
632             my $result = $template ~~ 'From: <>';
633              
634             # 1
635              
636             =back
637              
638             =head1 AUTHORS
639              
640             Awncorp, C
641              
642             =cut
643              
644             =head1 LICENSE
645              
646             Copyright (C) 2000, Awncorp, C.
647              
648             This program is free software, you can redistribute it and/or modify it under
649             the terms of the Apache license version 2.0.
650              
651             =cut