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