File Coverage

blib/lib/DTL/Fast/Context.pm
Criterion Covered Total %
statement 84 86 97.6
branch 30 32 93.7
condition 18 34 52.9
subroutine 15 15 100.0
pod 0 11 0.0
total 147 178 82.5


line stmt bran cond sub pod time code
1             package DTL::Fast::Context;
2 99     99   13659 use strict;
  99         223  
  99         2622  
3 99     99   965 use utf8;
  99         205  
  99         552  
4 99     99   2187 use warnings FATAL => 'all';
  99         199  
  99         3823  
5              
6 99     99   520 use Scalar::Util qw(reftype blessed);
  99         189  
  99         91735  
7              
8             sub new
9             {
10 619     619 0 235618 my ( $proto, $context, %kwargs ) = @_;
11 619   100     1840 $context //= { };
12              
13 619 50       1945 die "Context should be a HASH reference"
14             if (ref $context ne 'HASH');
15              
16             return bless {
17             ns => [ $context ],
18 619 100       3812 die_on_missing_path => exists $kwargs{die_on_missing_path} ? $kwargs{die_on_missing_path} : 1
19             }, $proto;
20             }
21              
22             #@returns DTL::Fast::Context
23             sub set_die_on_missing_path
24             {
25 1     1 0 389 my ($self, $value) = @_;
26 1         3 $self->{die_on_missing_path} = $value;
27 1         3 return $self;
28             }
29              
30             sub should_die_on_missing_path
31             {
32 8     8 0 23 return shift->{die_on_missing_path};
33             }
34              
35             sub get
36             {
37 1209     1209 0 6102 my ( $self, $variable_path, $source_object ) = @_;
38              
39 1209 100       2375 if (ref $variable_path ne 'ARRAY') # suppose that raw variable invoked, period separated
40             {
41 956         2275 $variable_path = [ split /\.+/x, $variable_path ];
42             }
43             else
44             {
45 253         581 $variable_path = [ @$variable_path ]; # cloning for re-use
46             }
47              
48 1209         2092 my $variable_name = shift @$variable_path;
49              
50             # faster version
51 1209         1843 my $namespace = $self->{ns}->[- 1];
52 1209 100       2443 my $variable = exists $namespace->{$variable_name} ? $namespace->{$variable_name}
53             : undef;
54              
55 1209         2666 while( ref $variable eq 'CODE' )
56             {
57 0         0 $variable = $variable->();
58             }
59              
60 1209 100 100     3675 $variable = $self->traverse($variable, $variable_path, $source_object)
61             if
62             (defined $variable
63             and scalar @$variable_path);
64              
65 1203         2747 return $variable;
66             }
67              
68             # tracing variable path
69             sub traverse
70             {
71 288     288 0 535 my ( $self, $variable, $path, $source_object ) = @_;
72              
73 288         491 my $variable_original = $variable;
74              
75 288         497 foreach my $step (@$path)
76             {
77 343         741 my $current_type = reftype $variable;
78 343 100 66     1897 if (
    100 66        
    100          
    100          
79             blessed($variable)
80             and $variable->can($step))
81             {
82 3         11 $variable = $variable->$step();
83             }
84             elsif (not defined $current_type)
85             {
86 6   50     47 return $self->handle_error($self->get_error(
87             $source_object
88             , sprintf( "non-reference value encountered on step `%s` while traversing context path",
89             $step // 'undef' )
90             , 'Traversing path: '.join( '.', @$path )
91             , 'Traversed variable: '.$self->dump_with_indent($variable_original)
92             ));
93             }
94             elsif ($current_type eq 'HASH')
95             {
96 214         443 $variable = $variable->{$step};
97             }
98             elsif (
99             $current_type eq 'ARRAY'
100             and $step =~ /^\-?\d+$/x
101             )
102             {
103 118         354 $variable = $variable->[$step];
104             }
105             else
106             {
107 2   50     21 return $self->handle_error($self->get_error(
      50        
      50        
108             $source_object
109             , sprintf(
110             "don't know how continue traversing %s (%s) with step `%s`"
111             , ref $variable || 'SCALAR'
112             , reftype $variable // 'not blessed'
113             , $step // 'undef'
114             )
115             , 'Traversing path: '.join( '.', @$path )
116             , 'Traversable variable: '.$self->dump_with_indent($variable_original)
117             ));
118              
119             }
120             }
121              
122 280         655 while( ref $variable eq 'CODE' )
123             {
124 4         10 $variable = $variable->();
125             }
126              
127 280         549 return $variable;
128             }
129              
130             sub handle_error
131             {
132 8     8 0 18 my ($self, $error_message) = @_;
133 8 100       19 if ($self->should_die_on_missing_path) {
134 6         59 die $error_message;
135             }
136             else {
137 2         126 warn $error_message;
138 2         13 return undef;
139             }
140             }
141              
142             sub dump_with_indent
143             {
144 8     8 0 19 my ($self, $object ) = @_;
145 8         42 require Data::Dumper;
146 8         37 my $result = Data::Dumper->Dump([ $object ]);
147             # $result =~ s/^/ /mg; # now done in Entity::compile_error_messages
148 8         476 return $result;
149             }
150              
151             sub set
152             {
153 1502     1502 0 660955 my ( $self, @sets ) = @_;
154              
155 1502         4545 while( scalar @sets > 1 )
156             {
157 2487         4166 my $key = shift @sets;
158 2487         3761 my $val = shift @sets;
159 2487 100       6102 if ($key =~ /\./x) # traversed set
160             {
161 4         20 my @key = split /\.+/x, $key;
162 4         9 my $variable_name = pop @key;
163 4         13 my $variable = $self->get([ @key ]);
164              
165 4 100 50     23 die sprintf('Unable to set variable %s because parent %s is not defined.'
      50        
166             , $key // 'undef'
167             , join('.', @key) // 'undef'
168             ) if (not defined $variable);
169              
170 3         6 my $variable_type = ref $variable;
171 3 100 33     18 if ($variable_type eq 'HASH')
    50          
172             {
173 1         5 $variable->{$variable_name} = $val;
174             }
175             elsif (
176             $variable_type eq 'ARRAY'
177             and $variable_name =~ /^\-?\d+$/x
178             )
179             {
180 2         10 $variable->[$variable_name] = $val;
181             }
182             else
183             {
184 0   0     0 die sprintf("Don't know how to set variable %s for parent node of type %s"
      0        
185             , $variable_name // 'undef'
186             , $variable_type // 'undef'
187             );
188             }
189             }
190             else
191             {
192 2483         7364 $self->{ns}->[- 1]->{$key} = $val;
193             }
194             }
195 1501         3221 return $self;
196             }
197              
198             sub get_error
199             {
200 8     8 0 23 my ($self, $source_object, $message, @messages) = @_;
201              
202 8         15 my $result;
203 8 100 66     54 if (
204             blessed $source_object
205             and $source_object->can('get_render_error')
206             ) {
207 4         14 $result = $source_object->get_render_error(
208             $self
209             , $message
210             , @messages
211             );
212             } else {
213 4         18 $result = sprintf <<'_EOT_'
214             Rendering error: %s
215             %s
216             _EOT_
217             , $message
218             , join "\n", @messages
219             ;
220             }
221 8         29 return $result;
222             }
223              
224             sub push_scope
225             {
226 2432     2432 0 4134 my ( $self ) = @_;
227 2432   50     3415 push @{$self->{ns}}, { %{$self->{ns}->[- 1] // { }} };
  2432         4340  
  2432         11144  
228 2432         5854 return $self;
229             }
230              
231             sub pop_scope
232             {
233 2399     2399 0 4135 my ( $self ) = @_;
234             die "It's a last context layer available."
235 2399 100       3391 if (scalar @{$self->{ns}} == 1);
  2399         5920  
236 2398         3422 pop @{$self->{ns}};
  2398         3710  
237 2398         6864 return $self;
238             }
239              
240             1;