File Coverage

blib/lib/DTL/Fast/Tag/For.pm
Criterion Covered Total %
statement 120 127 94.4
branch 35 44 79.5
condition 7 28 25.0
subroutine 16 16 100.0
pod 0 10 0.0
total 178 225 79.1


line stmt bran cond sub pod time code
1             package DTL::Fast::Tag::For;
2 10     10   4467 use strict; use utf8; use warnings FATAL => 'all';
  10     10   14  
  10     10   232  
  10         32  
  10         10  
  10         318  
  10         202  
  10         10  
  10         334  
3 10     10   29 use parent 'DTL::Fast::Tag';
  10         13  
  10         43  
4              
5             $DTL::Fast::TAG_HANDLERS{'for'} = __PACKAGE__;
6              
7 10     10   550 use DTL::Fast::Variable;
  10         12  
  10         222  
8 10     10   34 use Scalar::Util qw(blessed reftype);
  10         14  
  10         11074  
9              
10             #@Override
11 59     59 0 117 sub get_close_tag{ return 'endfor';}
12              
13             #@Override
14             sub parse_parameters
15             {
16 40     40 0 34 my( $self ) = @_;
17              
18 40         30 my(@target_names, $source_name, $reversed);
19 40 50       317 if( $self->{'parameter'} =~ /^\s*(.+)\s+in\s+(.+?)\s*(reversed)?\s*$/si )
20             {
21 40         66 $source_name = $2;
22 40         41 $reversed = $3;
23             @target_names = map{
24 40 50       102 die $self->get_parse_error("iterator variable can't be traversable: $_") if /\./;
  51         91  
25 51         106 $_;
26             } split( /\s*,\s*/, $1 );
27             }
28             else
29             {
30 0         0 die $self->get_parse_error("do not understand condition: $self->{'parameter'}");
31             }
32              
33 40         91 $self->{'renderers'} = [];
34 40         71 $self->add_renderer();
35              
36 40         61 $self->{'targets'} = [@target_names];
37              
38 40         112 $self->{'source'} = DTL::Fast::Variable->new($source_name);
39              
40 40 100       67 if( $reversed )
41             {
42 1         3 $self->{'source'}->add_filter('reverse');
43             }
44              
45 40 50       39 if( not scalar @{$self->{'targets'}} )
  40         73  
46             {
47 0         0 die $self->get_parse_error("there is no target variables defined for iteration");
48             }
49              
50 40         97 return $self;
51             }
52              
53             #@Override
54             sub add_chunk
55             {
56 287     287 0 215 my( $self, $chunk ) = @_;
57              
58 287         489 $self->{'renderers'}->[-1]->add_chunk($chunk);
59 287         310 return $self;
60             }
61              
62             #@Override
63             sub parse_tag_chunk
64             {
65 63     63 0 103 my( $self, $tag_name, $tag_param, $chunk_lines ) = @_;
66              
67 63         52 my $result = undef;
68              
69 63 100       94 if( $tag_name eq 'empty' )
70             {
71 4 100       3 if( scalar @{$self->{'renderers'}} == 2 )
  4         14  
72             {
73 1         6 die $self->get_block_parse_error("there can be only one {% empty %} block, ingoring");
74             }
75             else
76             {
77 3         7 $self->add_renderer;
78             }
79 3         4 $DTL::Fast::Template::CURRENT_TEMPLATE_LINE += $chunk_lines;
80             }
81             else
82             {
83 59         138 $result = $self->SUPER::parse_tag_chunk($tag_name, $tag_param, $chunk_lines);
84             }
85              
86 62         91 return $result;
87             }
88              
89             #@Override
90             sub render
91             {
92 45     45 0 42 my( $self, $context ) = @_;
93              
94 45         42 my $result = '';
95              
96 45         91 my $source_data = $self->{'source'}->render($context);
97 45         60 my $source_ref = ref $source_data;
98 45         97 my $source_type = reftype $source_data;
99              
100 45 100 33     116 if( # iterating array
    50 66        
      0        
      33        
101             $source_ref eq 'ARRAY'
102             or (
103             UNIVERSAL::can($source_data, 'as_array')
104             and ($source_data = $source_data->as_array($context))
105             )
106             )
107             {
108 43         83 $result = $self->render_array(
109             $context
110             , $source_data
111             );
112             }
113             elsif( # iterating hash
114             $source_ref eq 'HASH'
115             or (
116             UNIVERSAL::can($source_data, 'as_hash')
117             and ($source_data = $source_data->as_hash($context))
118             )
119             )
120             {
121 2         5 $result = $self->render_hash(
122             $context
123             , $source_data
124             );
125             }
126             else
127             {
128             die sprintf('Do not know how to iterate %s (%s, %s)'
129 0   0     0 , $self->{'source'}->{'original'} // 'undef'
      0        
      0        
130             , $source_data // 'undef'
131             , $source_ref // 'SCALAR'
132             );
133             }
134              
135 45         121 return $result;
136             }
137              
138             sub render_array
139             {
140 43     43 0 41 my( $self, $context, $source_data ) = @_;
141              
142 43         50 my $result = '';
143              
144 43 100       58 if( scalar @$source_data )
    50          
145             {
146 42         76 $context->push_scope();
147              
148 42         40 my $source_size = scalar @$source_data;
149 42         67 my $forloop = $self->get_forloop($context, $source_size);
150              
151 42         49 $context->{'ns'}->[-1]->{'forloop'} = $forloop;
152              
153 42         31 my $variables_number = scalar @{$self->{'targets'}};
  42         44  
154              
155 42         52 foreach my $value (@$source_data)
156             {
157 191         165 my $value_type = ref $value;
158 191 100       205 if( $variables_number == 1 )
159             {
160 172         229 $context->{'ns'}->[-1]->{$self->{'targets'}->[0]} = $value;
161             }
162             else
163             {
164 19 50 0     39 if(
      33        
165             $value_type eq 'ARRAY'
166             or (
167             UNIVERSAL::can($value, 'as_array')
168             and ($value = $value->as_array($context))
169             )
170             )
171             {
172 19 50       26 if( scalar @$value >= $variables_number )
173             {
174 19         30 for( my $i = 0; $i < $variables_number; $i++ )
175             {
176 47         89 $context->{'ns'}->[-1]->{$self->{'targets'}->[$i]} = $value->[$i];
177             }
178             }
179             else
180             {
181             die sprintf(
182             'Sub-array (%s) contains less items than variables number (%s)'
183             , join(', ', @$value)
184 0         0 , join(', ', @{$self->{'targets'}})
  0         0  
185             );
186             }
187             }
188             else
189             {
190 0         0 die "Multi-var iteration argument $value ($value_type) is not an ARRAY and has no as_array method";
191             }
192             }
193 191   50     364 $result .= $self->{'renderers'}->[0]->render($context) // '';
194              
195 191         254 $self->step_forloop($forloop);
196             }
197              
198 42         94 $context->pop_scope();
199             }
200 1         4 elsif( scalar @{$self->{'renderers'}} == 2 ) # there is an empty block
201             {
202 1         3 $result = $self->{'renderers'}->[1]->render($context);
203             }
204              
205 43         59 return $result;
206             }
207              
208             sub render_hash
209             {
210 2     2 0 3 my( $self, $context, $source_data ) = @_;
211              
212 2         1 my $result = '';
213              
214 2         4 my @keys = keys %$source_data;
215 2         2 my $source_size = scalar @keys;
216 2 100       4 if( $source_size )
    50          
217             {
218 1 50       1 if( scalar @{$self->{'targets'}} == 2 )
  1         3  
219             {
220 1         3 $context->push_scope();
221 1         2 my $forloop = $self->get_forloop($context, $source_size);
222 1         2 $context->{'ns'}->[-1]->{'forloop'} = $forloop;
223              
224 1         1 foreach my $key (@keys)
225             {
226 4         5 my $val = $source_data->{$key};
227 4         4 $context->{'ns'}->[-1]->{$self->{'targets'}->[0]} = $key;
228 4         4 $context->{'ns'}->[-1]->{$self->{'targets'}->[1]} = $val;
229 4   50     8 $result .= $self->{'renderers'}->[0]->render($context) // '';
230              
231 4         7 $self->step_forloop($forloop);
232             }
233              
234 1         2 $context->pop_scope();
235             }
236             else
237             {
238 0         0 die $self->get_render_error("hash can be only iterated with 2 target variables");
239             }
240             }
241 1         3 elsif( scalar @{$self->{'renderers'}} == 2 ) # there is an empty block
242             {
243 1         3 $result = $self->{'renderers'}->[1]->render($context);
244             }
245              
246 2         4 return $result;
247             }
248              
249             sub add_renderer
250             {
251 43     43 0 36 my( $self ) = @_;
252 43         36 push @{$self->{'renderers'}}, DTL::Fast::Renderer->new();
  43         111  
253 43         41 return $self;
254             }
255              
256             sub get_forloop
257             {
258 43     43 0 39 my( $self, $context, $source_size ) = @_;
259              
260             return {
261 43 100       251 'parentloop' => $context->{'ns'}->[-1]->{'forloop'}
262             , 'counter' => 1
263             , 'counter0' => 0
264             , 'revcounter' => $source_size
265             , 'revcounter0' => $source_size-1
266             , 'first' => 1
267             , 'last' => $source_size == 1 ? 1: 0
268             , 'length' => $source_size
269             , 'odd' => 1
270             , 'odd0' => 0
271             , 'even' => 0
272             , 'even0' => 1
273             };
274             }
275              
276             sub step_forloop
277             {
278 195     195 0 166 my( $self, $forloop ) = @_;
279              
280 195         144 $forloop->{'counter'}++;
281 195         134 $forloop->{'counter0'}++;
282 195         133 $forloop->{'revcounter'}--;
283 195         122 $forloop->{'revcounter0'}--;
284 195 100       224 $forloop->{'odd'} = $forloop->{'odd'} ? 0: 1;
285 195 100       213 $forloop->{'odd0'} = $forloop->{'odd0'} ? 0: 1;
286 195 100       209 $forloop->{'even'} = $forloop->{'even'} ? 0: 1;
287 195 100       192 $forloop->{'even0'} = $forloop->{'even0'} ? 0: 1;
288 195         150 $forloop->{'first'} = 0;
289 195 100       269 if( $forloop->{'counter'} == $forloop->{'length'} )
290             {
291 40         40 $forloop->{'last'} = 1;
292             }
293 195         209 return $self;
294             }
295              
296             1;