File Coverage

blib/lib/Template/Extract/Compile.pm
Criterion Covered Total %
statement 82 94 87.2
branch 18 26 69.2
condition n/a
subroutine 16 17 94.1
pod 2 11 18.1
total 118 148 79.7


line stmt bran cond sub pod time code
1             package Template::Extract::Compile;
2             $Template::Extract::Compile::VERSION = '0.41';
3              
4 1     1   14 use 5.006;
  1         3  
  1         33  
5 1     1   5 use strict;
  1         1  
  1         25  
6 1     1   4 use warnings;
  1         8  
  1         1899  
7              
8             our ( $DEBUG, $EXACT );
9             my ( $paren_id, $block_id );
10              
11             sub new {
12 13     13 1 30 my $class = shift;
13 13         25 my $self = {};
14 13         92 return bless( $self, $class );
15             }
16              
17             sub compile {
18 12     12 1 26 my ( $self, $template, $parser ) = @_;
19              
20 12         42 $self->_init();
21              
22 12 50       39 if ( defined $template ) {
23 12         28 $parser->{FACTORY} = ref($self);
24 12 50       116 $template = $$template if UNIVERSAL::isa( $template, 'SCALAR' );
25 12         63 $template =~ s/\n+$//;
26 12         84 $template =~ s/\[%\s*(?:\.\.\.|_|__)\s*%\]/[% \/.*?\/ %]/g;
27 12         64 $template =~ s/\[%\s*(\/.*?\/)\s*%\]/'[% "' . quotemeta($1) . '" %]'/eg;
  8         58  
28 12         32 $template =~ s{
29             \[%\s*([a-zA-z0-9]+)\s*\=\~\s*(/.*?/)\s*%\]
30             }{
31 1         7 '[% SET ' . $1 . ' = "' . quotemeta($2) . '" %]'
32             }mxegi;
33              
34 12         59 return $parser->parse($template)->{BLOCK};
35             }
36 0         0 return undef;
37             }
38              
39             # initialize temporary variables
40             sub _init {
41 12     12   29 $paren_id = 0;
42 12         17 $block_id = 0;
43             }
44              
45             # utility function to add regex eval brackets
46 68     68   366 sub _re { "(?{\n @_\n})" }
47              
48             # --- Factory API implementation begins here ---
49              
50             sub template {
51 12     12 0 408 my $regex = $_[1];
52              
53 12         75 $regex =~ s/\*\*//g;
54 12         53 $regex =~ s/\+\+/+/g;
55 12 100       37 $regex = "^$regex\$" if $EXACT;
56              
57             # Deal with backtracking here -- substitute repeated occurences of
58             # the variable into backtracking sequences like (\1)
59 12         24 my %seen;
60 12         95 $regex =~ s{( # entire sequence [1]
61             \(\.\*\?\) # matching regex
62             \(\?\{ # post-matching regex...
63             \s* # whitespaces
64             _ext\( # capturing handler...
65             \( # inner cluster of...
66             \[ (.+?) \],\s* # var name [2]
67             \$.*?,\s* # dollar with ^N/counter
68             (\d+) # counter [3]
69             \) # ...end inner cluster
70             (.*?) # outer loop stack [4]
71             \) # ...end capturing handler
72             \s* # whitespaces
73             \}\) # ...end post-maching regex
74             )}{
75 41 100       152 if ($seen{$2, $4}) { # if var reoccured in the same loop
76 8         41 "(\\$seen{$2, $4})"; # replace it with backtracker
77             }
78             else {
79 33         117 $seen{$2, $4} = $3;
80 33 100       111 if ($+[0] == length $regex) { # otherwise, if it is the end
81 2         12 '(.*)' . substr( $1, 5 ); # make it greedy
82             }
83             else {
84 31         169 $1; # otherwise, preserve the sequence
85             }
86             }
87             }gex;
88              
89 12         67 return $regex;
90             }
91              
92             sub foreach {
93 12     12 0 805 my $regex = $_[4];
94              
95             # find out immediate children
96 12         164 my %vars =
97             reverse( $regex =~ /_ext\(\(\[(\[?)('\w+').*?\], [^,]+, \d+\)\*\*/g );
98 12 100       68 my $vars = join( ',', map { $vars{$_} ? "\\$_" : $_ } sort keys %vars );
  27         82  
99              
100             # append this block's id into the _get calling chain
101 12         25 ++$block_id;
102 12         19 ++$paren_id;
103 12         173 $regex =~ s/\*\*/, $block_id**/g;
104 12         35 $regex =~ s/\+\+/*/g;
105              
106             return (
107              
108             # sets $cur_loop
109 12         44 _re("_enter_loop($_[2], $block_id)") .
110              
111             # match loop content
112             "(?:\\n*?$regex)++()" .
113              
114             # weed out partial matches
115             _re("_ext(([[$_[2],[$vars]]], \\'leave_loop', $paren_id)**)") .
116              
117             # optional, implicit newline
118             "\\n*?"
119             );
120             }
121              
122             sub get {
123 49 100   49 0 5059 return "(?:$1)" if $_[1] =~ m{^/(.*)/$};
124              
125 41         46 ++$paren_id;
126              
127             # ** is the placeholder for parent loop ids
128 41         147 return "(.*?)" . _re("_ext(([$_[1]], \$$paren_id, $paren_id)\*\*)");
129             }
130              
131             sub set {
132 3     3 0 1074 my $regex = undef;
133              
134 3         6 ++$paren_id;
135              
136 3 100       15 if ( $_[1][1] =~ m|^/(.*)/$| ) {
137 1         3 $regex = $1;
138             }
139              
140 3         8 my $val = $_[1][1];
141 3         11 $val =~ s/^'(.*)'\z/$1/;
142 3         10 $val = quotemeta($val);
143              
144 5         17 my $parents =
145 3         8 join( ',', map { $_[1][0][ $_ * 2 ] } ( 0 .. $#{ $_[1][0] } / 2 ) );
  3         10  
146              
147 3 100       11 if ( defined($regex) ) {
148 1         5 return $1 . _re("_ext(([$parents], \$$paren_id, $paren_id)\*\*)");
149             }
150             else {
151 2         25 return '()' . _re("_ext(([$parents], \\\\'$val', $paren_id)\*\*)");
152             }
153             }
154              
155             sub textblock {
156 57     57 0 10638 return quotemeta( $_[1] );
157             }
158              
159             sub block {
160 24     24 0 2393 my $rv = '';
161 24 50       39 foreach my $chunk ( map "$_", @{ $_[1] || [] } ) {
  24         168  
162 121         291 $chunk =~ s/^#line .*\n//;
163 121         198 $rv .= $chunk;
164             }
165 24         98 return $rv;
166             }
167              
168             sub quoted {
169 9     9 0 669 my $rv = '';
170              
171 9         16 foreach my $token ( @{ $_[1] } ) {
  9         28  
172 9 50       26 if ( $token =~ m/^'(.+)'$/ ) { # nested hash traversal
173 0         0 $rv .= '$';
174 0         0 $rv .= "{$_}" foreach split( /','/, $1 );
175             }
176             else {
177 9         23 $rv .= $token;
178             }
179             }
180              
181 9         34 return $rv;
182             }
183              
184             sub ident {
185 53     53 0 13952 return join( ',', map { $_[1][ $_ * 2 ] } ( 0 .. $#{ $_[1] } / 2 ) );
  56         238  
  53         147  
186             }
187              
188             sub text {
189 9     9 0 1441 return $_[1];
190             }
191              
192             # debug routine to catch unsupported directives
193             sub AUTOLOAD {
194 0 0   0     $DEBUG or return;
195              
196 0           require Data::Dumper;
197 0           $Data::Dumper::Indent = $Data::Dumper::Indent = 1;
198              
199 0           our $AUTOLOAD;
200 0           print "\n$AUTOLOAD -";
201              
202 0           for my $arg ( 1 .. $#_ ) {
203 0           print "\n [$arg]: ";
204 0 0         print ref( $_[$arg] )
205             ? Data::Dumper->Dump( [ $_[$arg] ], ['__'] )
206             : $_[$arg];
207             }
208              
209 0           return '';
210             }
211              
212             1;
213              
214             __END__