File Coverage

blib/lib/Venus/Template.pm
Criterion Covered Total %
statement 82 107 76.6
branch 21 32 65.6
condition 11 17 64.7
subroutine 14 20 70.0
pod 2 11 18.1
total 130 187 69.5


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