File Coverage

blib/lib/Template/Compiled.pm
Criterion Covered Total %
statement 119 134 88.8
branch 31 48 64.5
condition 5 5 100.0
subroutine 19 24 79.1
pod 2 2 100.0
total 176 213 82.6


line stmt bran cond sub pod time code
1 2     2   58348 use 5.008;
  2         12  
2 2     2   11 use strict;
  2         2  
  2         39  
3 2     2   16 use warnings;
  2         4  
  2         149  
4              
5             package Template::Compiled;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.003';
9              
10 2     2   870 use Eval::TypeTiny::CodeAccumulator;
  2         1823  
  2         60  
11 2     2   527 use Types::Standard qw( -types Join );
  2         90607  
  2         23  
12 2     2   14130 use Type::Params 2.000000 qw( signature );
  2         12493  
  2         16  
13 2     2   640 use Carp qw( croak confess );
  2         4  
  2         112  
14 2     2   10 use B qw( perlstring );
  2         4  
  2         93  
15              
16 2     2   12 use constant PERL_NATIVE_ALIASES => ($] >= 5.02200);
  2         3  
  2         146  
17              
18 2     2   1505 use Moo;
  2         18672  
  2         9  
19 2     2   3563 use namespace::autoclean;
  2         22123  
  2         11  
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         23 fallback => 1,
26 2     2   243 ;
  2         4  
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   45 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   73 builder => sub { !!0 },
60             );
61              
62             has outdent => (
63             is => 'lazy',
64             isa => Int,
65 4     4   87 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   111 builder => sub { __PACKAGE__ . '::Utils' },
79             );
80              
81             has 'sub' => (
82             is => 'lazy',
83             isa => CodeRef,
84             builder => !!1,
85             );
86              
87             sub print {
88 0     0 1 0 my $self = shift;
89 0 0       0 my $fh = FileHandle->check( $_[0] ) ? shift() : undef;
90            
91 0 0       0 defined($fh)
92             ? $fh->print( $self->render(@_) )
93             : CORE::print( $self->render(@_) );
94             }
95              
96             sub render {
97 6     6 1 12813 my $self = shift;
98 6         110 $self->sub->(@_);
99             }
100              
101             sub _build_sub {
102 5     5   52 my $self = shift;
103            
104 5         30 my $code = 'Eval::TypeTiny::CodeAccumulator'->new(
105             description => 'template prelude',
106             );
107            
108             my $var_escape = $code->add_variable(
109             '$_ESCAPE',
110 5   100 0   147 \ ( $self->escape or sub { $_[0] } ),
  0         0  
111             );
112            
113 5         81 $code->add_line( 'sub {' );
114 5         79 $code->increase_indent;
115 5         103 $code->add_line( sprintf( 'use %s;', $self->utils_package ) );
116            
117 5         192 $code->add_line( 'sub _ ($);' );
118 5         114 $code->add_line( sprintf( '*_ = sub ($) { goto %s };', $var_escape ) );
119            
120 5         54 $code->add_line( 'local %_;' );
121 5         54 $code->add_line( 'my ($OUT, $INDENT) = (q(), q());' );
122 5         57 $code->add_line( 'our $_OUT_REF = \\$OUT;' );
123            
124 5         47 if ( PERL_NATIVE_ALIASES ) {
125 5         17 $code->add_line( 'use feature qw( refaliasing );' );
126 5         52 $code->add_line( 'no warnings qw( experimental::refaliasing );' );
127             }
128            
129 5         60 my $compiled_signature;
130 5 50       19 if ( $self->has_signature ) {
131 5         27 $compiled_signature = signature(
132             want_object => 1,
133             named => $self->signature,
134             bless => 0,
135             );
136             my $var_signature = $code->add_variable(
137             '$_SIGNATURE',
138 5         85993 \ do { $compiled_signature->coderef->compile },
  5         15  
139             );
140 5         2889 $code->add_line( sprintf( '%%_ = %%{ %s->(@_) };', $var_signature ) );
141             }
142             else {
143 0         0 $code->add_line( '%_ = (@_==1 and ref($_[0]) eq "HASH") ? %{$_[0]} : @_%2 ? Carp::croak("Expected even-sized list of arguments") : @_;' );
144             }
145            
146 5 50       83 if ( $compiled_signature ) {
147             my @sig = (
148 5 50       11 @{ $compiled_signature->parameters },
  5         20  
149             $compiled_signature->has_slurpy ? $compiled_signature->slurpy : (),
150             );
151 5         786 while (@sig) {
152 7         2039 my $param = shift @sig;
153 7         22 my $name = $param->name;
154 7         32 my $type = $param->type;
155            
156 7 50       59 next unless $name =~ /\A[A-Z][A-Z0-9_]*\z/i;
157            
158 7         11 unless ( PERL_NATIVE_ALIASES ) {
159             require Data::Alias;
160             }
161            
162             $code->add_line(
163 7         49 PERL_NATIVE_ALIASES
164             ? "\\my \$$name = \\\$_{$name};"
165             : "Data::Alias::alias( my \$$name = \$_{$name} );"
166             );
167            
168 7 50       142 $code->add_line(
169             PERL_NATIVE_ALIASES
170             ? "\\my \%$name = \$$name;"
171             : "Data::Alias::alias( my \%$name = \%{ \$$name } );"
172             ) if $type->is_a_type_of( HashRef );
173            
174 7 100       8416 $code->add_line(
175             PERL_NATIVE_ALIASES
176             ? "\\my \@$name = \$$name;"
177             : "Data::Alias::alias( my \@$name = \@{ \$$name } );"
178             ) if $type->is_a_type_of( ArrayRef );
179             }
180             }
181            
182             # Break encapsulation!
183 5         4870 push @{ $code->{code} }, "#line 1 \"template\"\n";
  5         19  
184            
185 5         28 my $template = $self->template;
186 5 100       172 if ($self->trim) {
187 2         69 $template =~ s/(?:\A\s*)|(?:\s*\z)//gsm;
188             }
189            
190 5 100       172 if (my $outdent = $self->outdent) {
191 1 50       40 $outdent > 0
192             ? ($template =~ s/^\s{0,$outdent}//gsm)
193             : ($template =~ s/^\s+//gsm);
194             }
195            
196 5         124 my @delims = @{ $self->delimiters };
  5         81  
197 5         99 my $regexp = join('|', map quotemeta($_), @delims);
198 5         177 my @parts = split /($regexp)/, $template;
199            
200 5         14 my $mode = 'text';
201 5         16 while (@parts) {
202            
203 31         47 my $next = shift @parts;
204            
205 31 100       58 if ($next eq $delims[0]) {
206 7 50       20 $mode = ( $mode eq 'text' ) ? 'code' : confess( "Impossible state" );
207 7         15 next;
208             }
209            
210 24 100       39 if ($next eq $delims[1]) {
211 7 50       17 $mode = ( $mode eq 'code' ) ? 'text' : confess( "Impossible state" );
212 7         15 next;
213             }
214              
215 17         49 my $terminator = $delims[ 0 + ( $mode ne 'text' ) ];
216 17   100     58 while ( @parts and $parts[0] ne $terminator ) {
217 3         9 $next .= shift( @parts );
218             }
219              
220 17 100       49 if ($mode eq 'text') {
    100          
221 10         92 $code->{code}[-1] .= sprintf( '$OUT .= %s;', perlstring($next) );
222 10 100       37 if ($next =~ /\n/sm) {
223 6         10 my $count = $next;
224 6         13 $count = ( $count =~ y/\n// );
225 6         29 $code->{code}[-1] .= "\n" x $count;
226             }
227             }
228             elsif ( $next =~ /\A=/ ) {
229 6         51 my ( $indent ) = map /\A(\s*)/, grep /\S/, split /\n/, substr($next, 1);
230 6 100       70 $code->{code}[-1] .= $self->has_escape
231             ? sprintf(
232             '$OUT .= %s->( do { $INDENT = %s; %s } );',
233             $var_escape,
234             perlstring($indent),
235             substr($next, 1),
236             )
237             : sprintf(
238             '$OUT .= do { $INDENT = %s; %s };',
239             perlstring($indent),
240             substr($next, 1),
241             );
242             }
243             else {
244 1         23 my ($indent) = map /\A(\s*)/, grep /\S/, split /\n/, $next;
245 1         9 $code->{code}[-1] .= sprintf(
246             "\$INDENT = %s; %s;",
247             perlstring($indent),
248             $next,
249             );
250             }
251             }
252            
253 5 100       100 if ( $self->trim ) {
254 2         20 $code->add_line( '$OUT =~ s/(?:\A\s*)|(?:\s*\z)//gsm;' );
255             }
256            
257 5 50       67 if ( $self->has_post_process ) {
258             my $var_post = $code->add_variable(
259             '$_POST',
260 0         0 \ do { $self->post_process },
  0         0  
261             );
262 0         0 $code->add_line( sprintf 'do { local *_ = \$OUT; %s->($OUT) };', $var_post );
263             }
264             else {
265 5         18 $code->add_line( '$OUT;' );
266             }
267            
268 5         81 $code->decrease_indent;
269 5         45 $code->add_line( '}' );
270            
271 5         77 return $code->compile;
272             }
273              
274             sub _lookup_escaping {
275 1     1   6344 my $style = shift;
276            
277 1 50       4 return $style if CodeRef->check($style);
278            
279 1         11 $style = lc $style;
280            
281 1 50       4 if ($style eq 'html') {
282 1         466 require HTML::Entities;
283 1         5962 return \&HTML::Entities::encode_entities;
284             };
285            
286 0 0         if ($style eq 'xml') {
287 0           require HTML::Entities;
288 0           return \&HTML::Entities::encode_entities_numeric;
289             };
290            
291 0           croak "Unsupported escaping style '$style'";
292             }
293              
294             1;
295              
296             __END__