File Coverage

blib/lib/Template/Compiled.pm
Criterion Covered Total %
statement 120 137 87.5
branch 35 54 64.8
condition 4 6 66.6
subroutine 20 25 80.0
pod 2 2 100.0
total 181 224 80.8


line stmt bran cond sub pod time code
1 2     2   19260 use 5.008;
  2         6  
2 2     2   10 use strict;
  2         5  
  2         34  
3 2     2   8 use warnings;
  2         3  
  2         104  
4              
5             package Template::Compiled;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.002';
9              
10 2     2   432 use Eval::TypeTiny qw( eval_closure );
  2         951  
  2         12  
11 2     2   3590 use Types::Standard qw( -types slurpy Join );
  2         84920  
  2         19  
12 2     2   9885 use Type::Params 1.002000 qw( compile_named );
  2         23062  
  2         17  
13 2     2   536 use Carp qw( croak confess );
  2         5  
  2         137  
14 2     2   15 use B qw( perlstring );
  2         5  
  2         112  
15              
16 2     2   15 use constant PERL_NATIVE_ALIASES => ($] >= 5.02200);
  2         6  
  2         152  
17              
18 2     2   1122 use Moo;
  2         21127  
  2         12  
19 2     2   3726 use namespace::autoclean;
  2         22785  
  2         8  
20              
21             use overload
22 0     0   0 '""' => sub { $_[0]->template },
23 0     0   0 'bool' => sub { !!1 },
24 0     0   0 '&{}' => sub { $_[0]->sub },
25 2         27 fallback => 1,
26 2     2   211 ;
  2         6  
27              
28             has template => (
29             is => 'ro',
30             isa => Str->plus_coercions( Join["\n"] ),
31             required => !!1,
32             coerce => !!1,
33             );
34              
35             has signature => (
36             is => 'ro',
37             isa => Maybe[ArrayRef],
38             predicate => !!1,
39             required => !!0,
40             );
41              
42             has delimiters => (
43             is => 'lazy',
44             isa => Tuple[Str, Str],
45 2     2   53 builder => sub { [qw/ /] },
46             );
47              
48             has escape => (
49             is => 'ro',
50             isa => CodeRef->plus_coercions(Str, \&_lookup_escaping),
51             required => !!0,
52             predicate => !!1,
53             coerce => !!1,
54             );
55              
56             has trim => (
57             is => 'lazy',
58             isa => Bool,
59 3     3   104 builder => sub { !!0 },
60             );
61              
62             has outdent => (
63             is => 'lazy',
64             isa => Int,
65 4     4   119 builder => sub { 0 },
66             );
67              
68             has post_process => (
69             is => 'ro',
70             isa => CodeRef,
71             required => !!0,
72             predicate => !!1,
73             );
74              
75             has utils_package => (
76             is => 'lazy',
77             isa => Str,
78 5     5   130 builder => sub { __PACKAGE__ . '::Utils' },
79             );
80              
81             has 'sub' => (
82             is => 'lazy',
83             isa => CodeRef,
84             builder => sub {
85 5     5   72 my ($code, $env) = $_[0]->_build_code_and_env;
86 5         25 eval_closure(
87             source => $code,
88             environment => $env,
89             description => 'template prelude',
90             );
91             },
92             );
93              
94             sub print {
95 0     0 1 0 my $self = shift;
96 0 0       0 my $fh = FileHandle->check($_[0]) ? shift() : undef;
97            
98 0 0       0 defined($fh)
99             ? $fh->print( $self->render(@_) )
100             : CORE::print( $self->render(@_) );
101             }
102              
103             sub render {
104 6     6 1 12691 my $self = shift;
105 6         124 $self->sub->(@_);
106             }
107              
108             sub _build_code_and_env {
109 5     5   15 my $self = shift;
110            
111 5         13 my @code;
112             my %env;
113            
114 5         17 push @code, 'sub {';
115 5         114 push @code, sprintf('use %s;', $self->utils_package);
116            
117 5         161 push @code, 'sub _ ($);';
118 5         12 push @code, '*_ = sub ($) { goto $_ESCAPE };';
119 5 100   0   13 $env{'$_ESCAPE'} = \ do { $self->escape or sub { $_[0] } };
  5         59  
  0         0  
120            
121 5         16 push @code, 'local %_;';
122 5         14 push @code, 'my ($OUT, $INDENT) = (q(), q());';
123 5         13 push @code, 'our $_OUT_REF = \\$OUT;';
124            
125 5         11 if (PERL_NATIVE_ALIASES) {
126 5         10 push @code, 'use feature qw( refaliasing );';
127 5         13 push @code, 'no warnings qw( experimental::refaliasing );';
128             }
129            
130 5 50       24 if ($self->has_signature) {
131 5         15 push @code, '%_ = %{ $_SIGNATURE->(@_) };';
132 5         12 $env{'$_SIGNATURE'} = \ do { compile_named(@{ $self->signature }) };
  5         10  
  5         35  
133             }
134             else {
135 0         0 push @code, '%_ = (@_==1 and ref($_[0]) eq "HASH") ? %{$_[0]} : @_%2 ? Carp::croak("Expected even-sized list of arguments") : @_;';
136             }
137            
138 5 50       5455 if ($self->has_signature) {
139 5         15 my @sig = @{ $self->signature };
  5         27  
140 5         36 while (@sig) {
141             shift @sig
142 7   33     2199 while HashRef->check($sig[0]) && !$sig[0]{slurpy};
143            
144 7         154 my $name = shift @sig;
145 7         20 my $type = shift @sig;
146            
147 7 50       54 next unless $name =~ /\A[A-Z][A-Z0-9_]*\z/i;
148            
149 7 50       29 if (Bool->check($type)) {
150 0 0       0 $type = $type ? Any : Optional[Any];
151             }
152            
153 7         173 unless (PERL_NATIVE_ALIASES) {
154             require Data::Alias;
155             }
156            
157 7         38 push @code, PERL_NATIVE_ALIASES
158             ? "\\my \$$name = \\\$_{$name};"
159             : "Data::Alias::alias( my \$$name = \$_{$name} );";
160            
161 7 50       25 if ($type->is_a_type_of(HashRef)) {
162 0         0 push @code, PERL_NATIVE_ALIASES
163             ? "\\my \%$name = \$$name;"
164             : "Data::Alias::alias( my \%$name = \%{ \$$name } );";
165             }
166            
167 7 100       8553 if ($type->is_a_type_of(ArrayRef)) {
168 1         126 push @code, PERL_NATIVE_ALIASES
169             ? "\\my \@$name = \$$name;"
170             : "Data::Alias::alias( my \@$name = \@{ \$$name } );";
171             }
172             }
173             }
174            
175 5         4637 push @code, "#line 1 \"template\"\n";
176            
177 5         24 my $template = $self->template;
178 5 100       218 if ($self->trim) {
179 2         99 $template =~ s/(?:\A\s*)|(?:\s*\z)//gsm;
180             }
181            
182 5 100       248 if (my $outdent = $self->outdent) {
183 1 50       56 $outdent > 0
184             ? ($template =~ s/^\s{0,$outdent}//gsm)
185             : ($template =~ s/^\s+//gsm);
186             }
187            
188 5         130 my @delims = @{ $self->delimiters };
  5         137  
189 5         182 my $regexp = join('|', map quotemeta($_), @delims);
190 5         159 my @parts = split /($regexp)/, $template;
191            
192 5         19 my $mode = 'text';
193 5         18 while (@parts) {
194            
195 31         98 my $next = shift @parts;
196            
197 31 100       122 if ($next eq $delims[0]) {
198 7 50       42 $mode = ($mode eq 'text') ? 'code' : confess("Impossible state");
199 7         25 next;
200             }
201            
202 24 100       68 if ($next eq $delims[1]) {
203 7 50       52 $mode = ($mode eq 'code') ? 'text' : confess("Impossible state");
204 7         24 next;
205             }
206              
207 17 100       60 my $terminator = $delims[ ($mode eq 'text') ? 0 : 1 ];
208 17   100     94 while (@parts and $parts[0] ne $terminator) {
209 3         12 $next .= shift(@parts);
210             }
211              
212 17 100       66 if ($mode eq 'text') {
    100          
213 10         72 $code[-1] .= sprintf('$OUT .= %s;', perlstring($next));
214 10 100       71 if ($next =~ /\n/sm) {
215 6         14 my $count = $next;
216 6         19 $count = ($count =~ y/\n//);
217 6         28 $code[-1] .= "\n" x $count;
218             }
219             }
220             elsif ($next =~ /\A=/) {
221 6         65 my ($indent) = map /\A(\s*)/, grep /\S/, split /\n/, substr($next, 1);
222 6 100       109 $code[-1] .= sprintf(
223             $self->has_escape ? '$OUT .= $_ESCAPE->(do { %s %s });' : '$OUT .= do { %s %s };',
224             sprintf("\$INDENT = %s;", perlstring($indent)),
225             substr($next, 1),
226             );
227             }
228             else {
229 1         20 my ($indent) = map /\A(\s*)/, grep /\S/, split /\n/, $next;
230 1         15 $code[-1] .= sprintf("\$INDENT = %s; %s;", perlstring($indent), $next);
231             }
232             }
233            
234 5 100       109 if ($self->trim) {
235 2         30 push @code, '$OUT =~ s/(?:\A\s*)|(?:\s*\z)//gsm;';
236             }
237            
238 5 50       56 if ($self->has_post_process) {
239 0         0 push @code, 'do { local *_ = \$OUT; $_POST->($OUT) };';
240 0         0 $env{'$_POST'} = \ do { $self->post_process };
  0         0  
241             }
242             else {
243 5         16 push @code, '$OUT;';
244             }
245            
246 5         13 push @code, '}';
247            
248             #warn join "\n", @code, "";
249 5         26 return (\@code, \%env);
250             }
251              
252             sub _lookup_escaping {
253 1     1   11064 my $style = shift;
254            
255 1 50       8 return $style if CodeRef->check($style);
256            
257 1         22 $style = lc $style;
258            
259 1 50       6 if ($style eq 'html') {
260 1         703 require HTML::Entities;
261 1         8142 return \&HTML::Entities::encode_entities;
262             };
263            
264 0 0         if ($style eq 'xml') {
265 0           require HTML::Entities;
266 0           return \&HTML::Entities::encode_entities_numeric;
267             };
268            
269 0           croak "Unsupported escaping style '$style'";
270             }
271              
272             1;
273              
274             __END__