File Coverage

blib/lib/Template/Extract/Compile.pm
Criterion Covered Total %
statement 83 94 88.3
branch 19 26 73.0
condition n/a
subroutine 17 17 100.0
pod 2 11 18.1
total 121 148 81.7


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