File Coverage

blib/lib/Template/Liquid/Context.pm
Criterion Covered Total %
statement 116 143 81.1
branch 85 106 80.1
condition 7 20 35.0
subroutine 14 21 66.6
pod 0 7 0.0
total 222 297 74.7


line stmt bran cond sub pod time code
1             package Template::Liquid::Context;
2             our $VERSION = '1.0.23';
3             require Template::Liquid::Utility;
4             require Template::Liquid::Error;
5 26     26   110505 use strict;
  26         103  
  26         1004  
6 26     26   161 use warnings;
  26         52  
  26         656  
7 26     26   138 use Scalar::Util;
  26         53  
  26         53541  
8              
9             sub new {
10 370     370 0 1329 my ($class, %args) = @_;
11             return bless {scopes => [$args{assigns}],
12             template => $args{template}, # Required
13 370         2359 errors => []
14             }, $class;
15             }
16              
17             sub push {
18 127     127 0 275 my ($s, $context) = @_;
19             return
20             raise Template::Liquid::Error {type => 'Stack',
21             template => $s->{template},
22             message => 'Cannot push new scope!'
23             }
24 127 50       176 if scalar @{$s->{'scopes'}} == 100;
  127         360  
25 127 100       200 return push @{$s->{'scopes'}}, (defined $context ? $context : {});
  127         491  
26             }
27              
28             sub pop {
29 127     127 0 265 my ($s) = @_;
30             return
31             raise Template::Liquid::Error {type => 'Stack',
32             template => $s->{template},
33             message => 'Cannot pop scope!'
34             }
35 127 50       194 if scalar @{$s->{'scopes'}} == 1;
  127         380  
36 127         194 return pop @{$s->{'scopes'}};
  127         280  
37             }
38              
39             sub stack {
40 107     107 0 248 my ($s, $block) = @_;
41 107         202 my $old_scope = $s->{scopes}[-1];
42 107         271 $s->push();
43 107         307 $s->merge($old_scope);
44 107         296 my $result = $block->($s);
45 107         327 $s->pop;
46 107         365 return $result;
47             }
48              
49             sub merge {
50 107     107 0 209 my ($s, $new) = @_;
51 107         246 return $s->{'scopes'}->[0] = __merge(reverse $s->{scopes}[-1], $new);
52             }
53              
54             sub _merge { # Deeply merges data structures
55 0     0   0 my ($source, $target) = @_;
56 0         0 my $return = $target;
57 0         0 for (keys %$source) {
58 0 0 0     0 if ('ARRAY' eq ref $target->{$_} &&
    0 0        
      0        
      0        
59             ('ARRAY' eq ref $source->{$_} || !ref $source->{$_})) {
60 0         0 @{$return->{$_}} = [@{$target->{$_}}, @{$source->{$_}}];
  0         0  
  0         0  
  0         0  
61             }
62             elsif ('HASH' eq ref $target->{$_} &&
63             ('HASH' eq ref $source->{$_} || !ref $source->{$_})) {
64 0         0 $return->{$_} = _merge($source->{$_}, $target->{$_});
65             }
66 0         0 else { $return->{$_} = $source->{$_}; }
67             }
68 0         0 return $return;
69             }
70             my $merge_precedent;
71              
72             sub __merge { # unless right is more interesting, this is a left-
73 107     107   174 my $return = $_[1]; # precedent merge function
74             $merge_precedent ||= {
75 18 100   18   73 SCALAR => {SCALAR => sub { defined $_[0] ? $_[0] : $_[1] },
76 0     0   0 ARRAY => sub { $_[1] },
77 0     0   0 HASH => sub { $_[1] },
78             },
79             ARRAY => {
80             SCALAR => sub {
81 65 50   65   107 [@{$_[0]}, defined $_[1] ? $_[1] : ()];
  65         370  
82             },
83 0     0   0 ARRAY => sub { [@{$_[0]}] },
  0         0  
84 0     0   0 HASH => sub { [@{$_[0]}, values %{$_[1]}] },
  0         0  
  0         0  
85             },
86 41     41   158 HASH => {SCALAR => sub { $_[0] },
87 0     0   0 ARRAY => sub { $_[0] },
88 0     0   0 HASH => sub { _merge($_[0], $_[1], $_[2]) },
89             }
90 107   100     482 };
91 107         164 for my $key (keys %{$_[0]}) {
  107         362  
92             my ($left_ref, $right_ref)
93 124 100       280 = map { ref($_->{$key}) =~ m[^(HASH|ARRAY)$]o ? $1 : 'SCALAR' }
  248         1160  
94             ($_[0], $_[1]);
95              
96             #warn sprintf '%-12s [%6s|%-6s]', $key, $left_ref, $right_ref;
97             $return->{$key} = $merge_precedent->{$left_ref}{$right_ref}
98 124         510 ->($_[0]->{$key}, $_[1]->{$key});
99             }
100 107         274 return $return;
101             }
102              
103             sub get {
104 2531     2531 0 6269 my ($s, $var) = @_;
105 2531 100       4789 return if !defined $var;
106 2525 100       7339 return $2 if $var =~ m[^(["'])(.+)\1$]o;
107 2156         7302 my @path = split $Template::Liquid::Utility::VariableAttributeSeparator,
108             $var;
109 2156         4284 my $cursor = \$s->{scopes}[-1];
110             return $var
111 2156 100 100     11708 if $var =~ m[^[-\+]?(\d*\.)?\d+$]o && !exists $$cursor->{$path[0]};
112 1249 100       2656 return if $var eq '';
113 1237 100       2202 return '' if $var eq '""';
114 1232 100       2130 return "" if $var eq "''";
115 1229 100       2105 return if $var eq 'null';
116 1228 100       2137 return if $var eq 'nil';
117 1227 100       2184 return if $var eq 'blank';
118 1226 100       2162 return if $var eq 'empty';
119 1225 100       2083 return !1 if $var eq 'false';
120 1222 100       2112 return !!1 if $var eq 'true';
121              
122 1218 100       2517 if ($var =~ m[^\((\S+)\s*\.\.\s*(\S+)\)$]o) {
123 30         105 return [$s->get($1) .. $s->get($2)]; # range
124             }
125              
126             # print STDERR "DEBUG:var=$var. about to get 1 and 2 from regex";
127             # return $s->get($1)->[$2] if $var =~ m'^(.+)\[(\d+)\]$'o; # array index myvar[2]
128 1188 100       2279 if ($var =~ m'^(.+)\[(\d+)\]$'o) {
129              
130             # print STDERR "DEBUG:array index. var=$var. 1=$1,2=$2";
131 4         25 my $arr = $s->get($1);
132 4 50       36 return $arr->[$2] if $arr;
133 0         0 return; # return if nothing
134             }
135              
136             # return $s->get($1)->{$2} if $var =~ m'^(.+)\[(.+)\]$'o;
137 1184 100       2212 if ($var =~ m'^(.+)\[(.+)\]$'o) {
138              
139             # print STDERR "DEBUG:obj property. var=$var. 1=$1,2=$2";
140 1         8 my $obj = $s->get($1);
141 1 50       5 return $obj->{$2} if $obj;
142 0         0 return; # return if nothing
143             }
144 1183         2893 STEP: while (@path) {
145 1380         2360 my $crumb = shift @path;
146 1380         2400 my $reftype = ref $$cursor;
147 1380 100 66     4584 if (Scalar::Util::blessed($$cursor) && $$cursor->can($crumb)) {
    100          
    100          
148 2         6 my $can = $$cursor->can($crumb);
149 2         8 my $val = $can->($$cursor);
150 2 100       18 return $val if !scalar @path;
151 1         3 $cursor = \$val;
152 1         4 next STEP;
153             }
154             elsif ($reftype eq 'HASH') {
155 1346 100       2888 if (exists $$cursor->{$crumb}) {
156 1019 100       3832 return $$cursor->{$crumb} if !@path;
157 191         450 $cursor = \$$cursor->{$crumb};
158 191         517 next STEP;
159             }
160 327         963 return ();
161             }
162             elsif ($reftype eq 'ARRAY') {
163 20 100       44 return scalar @{$$cursor} if $crumb eq 'size';
  2         10  
164 18 100       38 $crumb = 0 if $crumb eq 'first';
165 18 100       33 $crumb = $#$$cursor if $crumb eq 'last';
166 18 100       53 return () if $crumb =~ m[\D]o;
167 17 100       73 return () if scalar @$$cursor < $crumb;
168 13 100       67 return $$cursor->[$crumb] if !scalar @path;
169 5         10 $cursor = \$$cursor->[$crumb];
170 5         15 next STEP;
171             }
172 12         43 return ();
173             }
174             }
175              
176             sub set {
177 862     862 0 1751 my ($s, $var, $val) = @_;
178 862         1397 my $var_reftype = ref $val;
179 862         2876 my @path = split $Template::Liquid::Utility::VariableAttributeSeparator,
180             $var;
181 862         1707 my $cursor = \$s->{scopes}[-1];
182 862 100       2217 $cursor = \$$cursor->{shift @path} if (exists $$cursor->{$path[0]});
183 862         1909 STEP: while (@path) {
184 286         549 my $crumb = shift @path;
185 286         510 my $reftype = ref $$cursor;
186 286 100       613 if ($reftype eq 'HASH') {
    100          
187 280 100       521 if (!@path) {
188 272 100       607 if (exists $$cursor->{$crumb}) {
189              
190             # TODO: If the reftype is different, mention it
191             }
192 272         914 return $$cursor->{$crumb} = $val;
193             }
194             else {
195             $$cursor->{$crumb} = $path[0] =~ m[\D] ? {} : []
196 8 50       39 if !exists $$cursor->{$crumb};
    100          
197 8         17 $cursor = \$$cursor->{$crumb};
198 8         22 next STEP;
199             }
200             }
201             elsif ($reftype eq 'ARRAY') {
202 4 50       43 if ($crumb =~ m[\D]) {
203              
204             # TODO: Let the user know
205             }
206 4 100       15 if (!@path) {
207 1 50       6 if (exists $$cursor->[$crumb]) {
208              
209             # TODO: If the reftype is different, mention it
210             }
211 1         9 return $$cursor->[$crumb] = $val;
212             }
213             else {
214 3 0       24 $$cursor->[$crumb] = $path[0] =~ m[\D] ? {} : []
    50          
215             if !exists $$cursor->[$crumb];
216 3         10 $cursor = \$$cursor->[$crumb];
217 3         7 next STEP;
218             }
219             }
220             else {
221 2 50       6 if (!@path) {
222 2 50       9 if ($crumb =~ m[\D]) {
223 2         7 $$cursor = {};
224 2         21 return $$cursor->{$crumb} = $val;
225             }
226 0         0 $$cursor = [];
227 0         0 return $$cursor->[$crumb] = $val;
228             }
229             else {
230 0 0       0 $$cursor->{$crumb} = $path[0] =~ m[\D] ? {} : []
    0          
231             if !exists $$cursor->[$crumb];
232 0         0 $cursor = \$$cursor->{$crumb};
233 0         0 next STEP;
234             }
235             }
236             }
237 587         1332 return $$cursor = $val;
238             }
239             1;
240              
241             =pod
242              
243             =encoding UTF-8
244              
245             =head1 NAME
246              
247             Template::Liquid::Context - Complex Variable Keeper
248              
249             =head1 Description
250              
251             This is really only to be used internally.
252              
253             =head1 Author
254              
255             Sanko Robinson <sanko@cpan.org> - http://sankorobinson.com/
256              
257             CPAN ID: SANKO
258              
259             =head1 License and Legal
260              
261             Copyright (C) 2009-2022 by Sanko Robinson E<lt>sanko@cpan.orgE<gt>
262              
263             This program is free software; you can redistribute it and/or modify it under
264             the terms of L<The Artistic License
265             2.0|http://www.perlfoundation.org/artistic_license_2_0>. See the F<LICENSE>
266             file included with this distribution or L<notes on the Artistic License
267             2.0|http://www.perlfoundation.org/artistic_2_0_notes> for clarification.
268              
269             When separated from the distribution, all original POD documentation is covered
270             by the L<Creative Commons Attribution-Share Alike 3.0
271             License|http://creativecommons.org/licenses/by-sa/3.0/us/legalcode>. See the
272             L<clarification of the
273             CCA-SA3.0|http://creativecommons.org/licenses/by-sa/3.0/us/>.
274              
275             =cut