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   7051 use strict; use utf8; use warnings FATAL => 'all';
  10     10   20  
  10     10   266  
  10         55  
  10         22  
  10         435  
  10         266  
  10         19  
  10         407  
3 10     10   47 use parent 'DTL::Fast::Tag';
  10         17  
  10         64  
4              
5             $DTL::Fast::TAG_HANDLERS{'for'} = __PACKAGE__;
6              
7 10     10   693 use DTL::Fast::Variable;
  10         19  
  10         303  
8 10     10   55 use Scalar::Util qw(blessed reftype);
  10         19  
  10         17662  
9              
10             #@Override
11 59     59 0 192 sub get_close_tag{ return 'endfor';}
12              
13             #@Override
14             sub parse_parameters
15             {
16 40     40 0 132 my( $self ) = @_;
17              
18 40         55 my(@target_names, $source_name, $reversed);
19 40 50       433 if( $self->{'parameter'} =~ /^\s*(.+)\s+in\s+(.+?)\s*(reversed)?\s*$/si )
20             {
21 40         97 $source_name = $2;
22 40         133 $reversed = $3;
23             @target_names = map{
24 40 50       151 die $self->get_parse_error("iterator variable can't be traversable: $_") if /\./;
  51         129  
25 51         166 $_;
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         103 $self->{'renderers'} = [];
34 40         172 $self->add_renderer();
35              
36 40         112 $self->{'targets'} = [@target_names];
37              
38 40         164 $self->{'source'} = DTL::Fast::Variable->new($source_name);
39              
40 40 100       198 if( $reversed )
41             {
42 1         6 $self->{'source'}->add_filter('reverse');
43             }
44              
45 40 50       48 if( not scalar @{$self->{'targets'}} )
  40         129  
46             {
47 0         0 die $self->get_parse_error("there is no target variables defined for iteration");
48             }
49              
50 40         112 return $self;
51             }
52              
53             #@Override
54             sub add_chunk
55             {
56 287     287 0 520 my( $self, $chunk ) = @_;
57              
58 287         884 $self->{'renderers'}->[-1]->add_chunk($chunk);
59 287         629 return $self;
60             }
61              
62             #@Override
63             sub parse_tag_chunk
64             {
65 63     63 0 202 my( $self, $tag_name, $tag_param, $chunk_lines ) = @_;
66              
67 63         89 my $result = undef;
68              
69 63 100       136 if( $tag_name eq 'empty' )
70             {
71 4 100       4 if( scalar @{$self->{'renderers'}} == 2 )
  4         14  
72             {
73 1         7 die $self->get_block_parse_error("there can be only one {% empty %} block, ingoring");
74             }
75             else
76             {
77 3         9 $self->add_renderer;
78             }
79 3         6 $DTL::Fast::Template::CURRENT_TEMPLATE_LINE += $chunk_lines;
80             }
81             else
82             {
83 59         195 $result = $self->SUPER::parse_tag_chunk($tag_name, $tag_param, $chunk_lines);
84             }
85              
86 62         159 return $result;
87             }
88              
89             #@Override
90             sub render
91             {
92 45     45 0 69 my( $self, $context ) = @_;
93              
94 45         82 my $result = '';
95              
96 45         166 my $source_data = $self->{'source'}->render($context);
97 45         89 my $source_ref = ref $source_data;
98 45         191 my $source_type = reftype $source_data;
99              
100 45 100 33     167 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         122 $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         6 $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         199 return $result;
136             }
137              
138             sub render_array
139             {
140 43     43 0 67 my( $self, $context, $source_data ) = @_;
141              
142 43         65 my $result = '';
143              
144 43 100       103 if( scalar @$source_data )
    50          
145             {
146 42         125 $context->push_scope();
147              
148 42         160 my $source_size = scalar @$source_data;
149 42         101 my $forloop = $self->get_forloop($context, $source_size);
150              
151 42         104 $context->{'ns'}->[-1]->{'forloop'} = $forloop;
152              
153 42         53 my $variables_number = scalar @{$self->{'targets'}};
  42         79  
154              
155 42         88 foreach my $value (@$source_data)
156             {
157 191         284 my $value_type = ref $value;
158 191 100       377 if( $variables_number == 1 )
159             {
160 172         405 $context->{'ns'}->[-1]->{$self->{'targets'}->[0]} = $value;
161             }
162             else
163             {
164 19 50 0     59 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       36 if( scalar @$value >= $variables_number )
173             {
174 19         53 for( my $i = 0; $i < $variables_number; $i++ )
175             {
176 47         172 $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     622 $result .= $self->{'renderers'}->[0]->render($context) // '';
194              
195 191         451 $self->step_forloop($forloop);
196             }
197              
198 42         137 $context->pop_scope();
199             }
200 1         5 elsif( scalar @{$self->{'renderers'}} == 2 ) # there is an empty block
201             {
202 1         6 $result = $self->{'renderers'}->[1]->render($context);
203             }
204              
205 43         104 return $result;
206             }
207              
208             sub render_hash
209             {
210 2     2 0 4 my( $self, $context, $source_data ) = @_;
211              
212 2         4 my $result = '';
213              
214 2         6 my @keys = keys %$source_data;
215 2         4 my $source_size = scalar @keys;
216 2 100       6 if( $source_size )
    50          
217             {
218 1 50       3 if( scalar @{$self->{'targets'}} == 2 )
  1         4  
219             {
220 1         4 $context->push_scope();
221 1         3 my $forloop = $self->get_forloop($context, $source_size);
222 1         3 $context->{'ns'}->[-1]->{'forloop'} = $forloop;
223              
224 1         4 foreach my $key (@keys)
225             {
226 4         6 my $val = $source_data->{$key};
227 4         9 $context->{'ns'}->[-1]->{$self->{'targets'}->[0]} = $key;
228 4         9 $context->{'ns'}->[-1]->{$self->{'targets'}->[1]} = $val;
229 4   50     14 $result .= $self->{'renderers'}->[0]->render($context) // '';
230              
231 4         10 $self->step_forloop($forloop);
232             }
233              
234 1         5 $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         5 elsif( scalar @{$self->{'renderers'}} == 2 ) # there is an empty block
242             {
243 1         4 $result = $self->{'renderers'}->[1]->render($context);
244             }
245              
246 2         6 return $result;
247             }
248              
249             sub add_renderer
250             {
251 43     43 0 67 my( $self ) = @_;
252 43         66 push @{$self->{'renderers'}}, DTL::Fast::Renderer->new();
  43         188  
253 43         76 return $self;
254             }
255              
256             sub get_forloop
257             {
258 43     43 0 74 my( $self, $context, $source_size ) = @_;
259              
260             return {
261 43 100       382 '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 289 my( $self, $forloop ) = @_;
279              
280 195         263 $forloop->{'counter'}++;
281 195         261 $forloop->{'counter0'}++;
282 195         313 $forloop->{'revcounter'}--;
283 195         239 $forloop->{'revcounter0'}--;
284 195 100       409 $forloop->{'odd'} = $forloop->{'odd'} ? 0: 1;
285 195 100       385 $forloop->{'odd0'} = $forloop->{'odd0'} ? 0: 1;
286 195 100       359 $forloop->{'even'} = $forloop->{'even'} ? 0: 1;
287 195 100       371 $forloop->{'even0'} = $forloop->{'even0'} ? 0: 1;
288 195         324 $forloop->{'first'} = 0;
289 195 100       472 if( $forloop->{'counter'} == $forloop->{'length'} )
290             {
291 40         72 $forloop->{'last'} = 1;
292             }
293 195         381 return $self;
294             }
295              
296             1;