File Coverage

blib/lib/Data/DPath/Context.pm
Criterion Covered Total %
statement 254 264 96.2
branch 63 74 85.1
condition 33 35 94.2
subroutine 47 50 94.0
pod 7 7 100.0
total 404 430 93.9


line stmt bran cond sub pod time code
1             package Data::DPath::Context;
2             our $AUTHORITY = 'cpan:SCHWIGON';
3             # ABSTRACT: Abstraction for a current context that enables incremental searches
4             $Data::DPath::Context::VERSION = '0.58';
5 13     13   61056 use strict;
  13         38  
  13         367  
6 13     13   60 use warnings;
  13         24  
  13         350  
7              
8 13     13   603 use Data::Dumper;
  13         5559  
  13         634  
9 13     13   466 use aliased 'Data::DPath::Point';
  13         601  
  13         99  
10 13     13   2099 use aliased 'Data::DPath::Attrs';
  13         185  
  13         60  
11 13     13   1066 use Scalar::Util 'reftype';
  13         21  
  13         565  
12 13     13   5448 use Data::DPath::Filters;
  13         27  
  13         321  
13 13     13   5945 use Iterator::Util;
  13         180153  
  13         1296  
14 13     13   102 use List::Util 1.45 'min', 'uniq';
  13         309  
  13         1324  
15             #use Sys::CPU;
16 13     13   6851 use POSIX ();
  13         77216  
  13         410  
17 13     13   5945 use Safe;
  13         449162  
  13         2129  
18              
19             # run filter expressions in own Safe.pm compartment
20             our $COMPARTMENT;
21             our $THREADCOUNT;
22              
23             BEGIN {
24             #$THREADCOUNT = $Data::DPath::PARALLELIZE ? Sys::CPU::cpu_count : 1;
25             #print "THREADCOUNT: $THREADCOUNT\n";
26             package
27             Data::DPath::Filters;
28              
29 13     13   89 $COMPARTMENT = Safe->new;
30 13         13146 $COMPARTMENT->permit(qw":base_core");
31 13         160 $COMPARTMENT->reval( 'no warnings;' ); # just so warnings is loaded
32 13 50       8245 if ($] >= 5.010) {
33 13         62 $COMPARTMENT->deny(qw":load");
34             } else {
35 0         0 $COMPARTMENT->deny(qw"require dofile caller");
36             }
37             # map DPath filter functions into new namespace
38 13         146 $COMPARTMENT->share(qw(affe
39             idx
40             size
41             key
42             value
43             isa
44             reftype
45             is_reftype
46             ));
47             }
48              
49             # print "use $]\n" if $] >= 5.010; # allow new-school Perl inside filter expressions
50             # eval "use $]" if $] >= 5.010; # allow new-school Perl inside filter expressions
51              
52             use Class::XSAccessor::Array
53 13         144 chained => 1,
54             constructor => 'new',
55             accessors => {
56             current_points => 0,
57             give_references => 1,
58 13     13   3063 };
  13         788  
59              
60 13         3738 use constant { HASH => 'HASH',
61             ARRAY => 'ARRAY',
62             SCALAR => 'SCALAR',
63             ROOT => 'ROOT',
64             ANYWHERE => 'ANYWHERE',
65             KEY => 'KEY',
66             ANYSTEP => 'ANYSTEP',
67             NOSTEP => 'NOSTEP',
68             PARENT => 'PARENT',
69             ANCESTOR => 'ANCESTOR',
70             ANCESTOR_OR_SELF => 'ANCESTOR_OR_SELF',
71 13     13   3452 };
  13         29  
72              
73             sub _splice_threads {
74 18     18   7937 my ($cargo) = @_;
75              
76 18         31 my $nr_cargo = @$cargo;
77              
78 18 100       41 return [[]] unless $nr_cargo;
79              
80 15   100     105 my $threadcount = $THREADCOUNT || 1;
81 15         58 my $blocksize = POSIX::ceil ($nr_cargo / $threadcount);
82              
83             my @result = map {
84 15         37 my $first = $_ * $blocksize;
  101         114  
85 101         152 my $last = min(($_+1) * $blocksize - 1, $nr_cargo-1);
86 101 100       210 ($first <= $last) ? [ @$cargo[$first .. $last]] : ();
87             } 0 .. $threadcount-1;
88              
89 15         55 return \@result;
90             }
91              
92             # only finds "inner" values; if you need the outer start value
93             # then just wrap it into one more level of array brackets.
94             sub _any
95             {
96 3231     3231   5406 my ($out, $in, $lookahead_key) = @_;
97              
98 13     13   94 no warnings 'uninitialized';
  13         27  
  13         4974  
99              
100 3231 50       5021 $in = defined $in ? $in : [];
101 3231 100       7504 return $out unless @$in;
102              
103 2474         5863 my @newin;
104             my $tmp_ref;
105 2474         0 my $tmp_deref;
106 2474         0 my $tmp_reftype;
107              
108 2474         3687 foreach my $point (@$in) {
109 7118         9065 my @values;
110 7118 50       11405 next unless defined $point;
111 7118         10202 my $ref = $point->ref;
112              
113             # speed optimization: first try faster ref, then reftype
114 7118 100 100     23880 if (ref($$ref) eq HASH or reftype($$ref) eq HASH) {
    100 100        
115             push @$out,
116             map {
117             my $newpoint = Point->new
118 3750         15663 ->ref(\($$ref->{$_}))
119             ->parent($point)
120             ->attrs(Attrs->new(key => $_));
121 3750         6273 push @newin, $newpoint; # remember added points
122 3750         8359 $newpoint;
123             }
124             grep {
125             # speed optimization: only consider a key if lookahead looks promising
126             not defined $lookahead_key
127             or $_ eq $lookahead_key
128 5210 100 100     30777 or ($tmp_ref = ref($tmp_deref =$$ref->{$_})) eq HASH
      100        
      100        
      100        
129             or $tmp_ref eq ARRAY
130             or ($tmp_reftype = reftype($tmp_deref)) eq HASH
131             or $tmp_reftype eq ARRAY
132             # or HASH_or_ARRAY(\($$ref->{$_}))
133             }
134 3547         5172 keys %{$$ref};
  3547         7168  
135             }
136             elsif (ref($$ref) eq ARRAY or reftype($$ref) eq ARRAY) {
137 819         1167 my $idx = 0;
138             push @$out,
139             map {
140             my $newpoint = Point->new
141             ->ref($_->{val_ref})
142 2611         9481 ->parent($point)
143             ->attrs(Attrs->new(idx => $idx++));
144 2611         4026 push @newin, $newpoint; # remember added points
145 2611         5533 $newpoint;
146             }
147 819         1110 map { { val_ref => \$_ } } @{$$ref}
  2611         5188  
  819         1507  
148             }
149             else {
150             next
151 2752         4398 }
152             }
153 2474         5112 return _any ($out, \@newin, $lookahead_key);
154             }
155              
156             sub _all {
157 0     0   0 my ($self) = @_;
158              
159 0         0 return @{$self->_all_ref};
  0         0  
160             }
161              
162             sub _all_ref {
163 436     436   728 my ($self) = @_;
164              
165 13     13   91 no strict 'refs';
  13         28  
  13         492  
166 13     13   70 no warnings 'uninitialized';
  13         25  
  13         2651  
167              
168             return [
169 734 100       5514 map { $self->give_references ? $_ : $$_ }
170             uniq
171 896 100       3101 map { defined $_ ? $_->ref : () }
172 436         670 @{$self->current_points}
  436         1072  
173             ];
174             }
175              
176             # filter current results by array index
177             sub _filter_points_index {
178 209     209   404 my ($self, $index, $points) = @_;
179              
180 209 50       1222 return $points ? [$points->[$index]] : [];
181             }
182              
183             # filter current results by condition
184             sub _filter_points_eval
185             {
186 787     787   1532 my ($self, $filter, $points) = @_;
187              
188 787 50       1724 return [] unless @$points;
189 787 50       1477 return $points unless defined $filter;
190              
191 787         1239 my $new_points;
192             my $res;
193             {
194 787         1116 package Data::DPath::Filters;
195              
196 787         1292 local our $idx = 0;
197             $new_points = [
198             grep {
199 787         1262 local our $p = $_;
  993         1559  
200 993         1366 local $_;
201 993         2034 my $pref = $p->ref;
202 993 50       1743 if ( defined $pref ) {
203 993         1570 $_ = $$pref;
204 993 100       1756 if ($Data::DPath::USE_SAFE) {
205             # 'uninitialized' values are the norm
206             # but "no warnings 'uninitialized'" does
207             # not work in this restrictive Safe.pm config, so
208             # we deactivate warnings completely by localizing $^W.
209             # on later Perls, ^W doesn't do the whole trick, so explicitly turn
210             # all warnings off. need to do this in a BEGIN, as some warnings
211             # are compile time only.
212 879         3595 $res = $COMPARTMENT->reval('BEGIN{ warnings->unimport}; local $^W;'.$filter);
213             } else {
214             # 'uninitialized' values are the norm
215 13     13   81 no warnings 'uninitialized';
  13         26  
  13         529  
216 13     13   5720 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  13         124  
  13         115  
217 114         4891 $res = eval($filter);
218             }
219 993 50       288754 print STDERR ($@, "\n") if $@;
220             } else {
221 0         0 $res = 0;
222             }
223 993         1631 $idx++;
224 993         2790 $res;
225             } @$points
226             ];
227             }
228 787         4335 return $new_points;
229             }
230              
231             sub _filter_points {
232 7536     7536   11387 my ($self, $step, $points) = @_;
233              
234 13     13   2544 no strict 'refs';
  13         29  
  13         377  
235 13     13   62 no warnings 'uninitialized';
  13         38  
  13         3724  
236              
237 7536 100       16262 return [] unless @$points;
238              
239 4676         7710 my $filter = $step->filter;
240 4676 100       12868 return $points unless defined $filter;
241              
242 996         7700 $filter =~ s/^\[\s*(.*?)\s*\]$/$1/; # strip brackets and whitespace
243              
244 996 100       4882 if ($filter =~ /^-?\d+$/)
    50          
245             {
246 209         502 return $self->_filter_points_index($filter, $points); # simple array index
247             }
248             elsif ($filter =~ /\S/)
249             {
250 787         1828 return $self->_filter_points_eval($filter, $points); # full condition
251             }
252             else
253             {
254 0         0 return $points;
255             }
256             }
257              
258             # the root node
259             # (only makes sense at first step, but currently not asserted)
260             sub _select_root {
261 450     450   937 my ($self, $step, $current_points, $new_points) = @_;
262              
263 450         952 my $step_points = $self->_filter_points($step, $current_points);
264 450         1031 push @$new_points, @$step_points;
265             }
266              
267              
268             # //
269             # anywhere in the tree
270             sub _select_anywhere {
271 271     271   566 my ($self, $step, $current_points, $lookahead, $new_points) = @_;
272              
273             # speed optimization: only useful points added
274 271         383 my $lookahead_key;
275 271 100 100     1161 if (defined $lookahead and $lookahead->kind eq KEY) {
276 206         451 $lookahead_key = $lookahead->part;
277             }
278              
279             # '//'
280             # all hash/array nodes of a data structure
281 271         531 foreach my $point (@$current_points) {
282 757         971 my @step_points = (@{_any([], [ $point ], $lookahead_key)}, $point);
  757         1572  
283 757         1446 push @$new_points, @{$self->_filter_points($step, \@step_points)};
  757         1505  
284             }
285             }
286              
287             # /key
288             # the value of a key
289             sub _select_key {
290 899     899   1467 my ($self, $step, $current_points, $new_points) = @_;
291              
292 899         1452 foreach my $point (@$current_points) {
293 13     13   132 no warnings 'uninitialized';
  13         27  
  13         2035  
294 7201 100       12015 next unless defined $point;
295 7180         10379 my $pref = $point->ref;
296             next unless (
297             # speed optimization:
298             # first try faster ref, then reftype
299 7180 100 100     18445 ref($$pref) eq HASH or
300             reftype($$pref) eq HASH
301             );
302             # take point as hash, skip undefs
303 4328         11100 my $attrs = Attrs->new(key => $step->part);
304 4328         6394 my $step_points = [];
305 4328 100       9003 if (exists $$pref->{$step->part}) {
306 1468         5098 $step_points = [ Point->new->ref(\($$pref->{$step->part}))->parent($point)->attrs($attrs) ];
307             }
308 4328         5977 push @$new_points, @{$self->_filter_points($step, $step_points)};
  4328         7194  
309             }
310             }
311              
312             # '*'
313             # all leaves of a data tree
314             sub _select_anystep {
315 291     291   600 my ($self, $step, $current_points, $new_points) = @_;
316              
317 13     13   80 no warnings 'uninitialized';
  13         29  
  13         8346  
318 291         569 foreach my $point (@$current_points) {
319             # take point as array
320 738         1497 my $pref = $point->ref;
321 738         1189 my $ref = $$pref;
322 738         1154 my $step_points = [];
323             # speed optimization: first try faster ref, then reftype
324 738 100 100     3563 if (ref($ref) eq HASH or reftype($ref) eq HASH) {
    100 100        
325             $step_points = [ map {
326 357         818 my $v_ref = \($ref->{$_});
  661         1031  
327 661         1730 my $attrs = Attrs->new(key => $_);
328 661         2562 Point->new->ref($v_ref)->parent($point)->attrs($attrs)
329             } keys %$ref ];
330             } elsif (ref($ref) eq ARRAY or reftype($ref) eq ARRAY) {
331 202         353 my $idx = 0;
332             $step_points = [ map {
333 202         376 my $attrs = Attrs->new(idx => $idx++);
  766         1970  
334 766         2411 Point->new->ref(\$_)->parent($point)->attrs($attrs)
335             } @$ref ];
336             } else {
337 179 50 33     549 if (ref($pref) eq SCALAR or reftype($pref) eq SCALAR) {
338             # TODO: without map, it's just one value
339 179         887 $step_points = [ #map {
340             Point->new->ref($pref)->parent($point) # XXX? why $_? What happens to $pref?
341             ]; # } $ref ];
342             }
343             }
344 738         1350 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  738         1537  
345             }
346             }
347              
348             # '.'
349             # no step (neither up nor down), just allow filtering
350             sub _select_nostep {
351 118     118   213 my ($self, $step, $current_points, $new_points) = @_;
352              
353 118         150 foreach my $point (@{$current_points}) {
  118         197  
354 702         1193 my $step_points = [$point];
355 702         1006 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  702         1332  
356             }
357             }
358              
359             # '..'
360             # the parent
361             sub _select_parent {
362 112     112   217 my ($self, $step, $current_points, $new_points) = @_;
363              
364 112         161 foreach my $point (@{$current_points}) {
  112         199  
365 527 100       821 next unless defined $point;
366 524         817 my $step_points = [$point->parent];
367 524         653 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  524         778  
368             }
369             }
370              
371             # '::ancestor'
372             # all ancestors (parent, grandparent, etc.) of the current node
373             sub _select_ancestor {
374 19     19   72 my ($self, $step, $current_points, $new_points) = @_;
375              
376 19         35 foreach my $point (@{$current_points}) {
  19         35  
377 19         40 my $step_points = [];
378 19         48 my $parent = $point;
379 19         66 while ($parent = $parent->parent) {
380 64         131 push @$step_points, $parent; # order matters
381             }
382 19         31 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  19         39  
383             }
384             }
385              
386             # '::ancestor-or-self'
387             # all ancestors (parent, grandparent, etc.) of the current node and the current node itself
388             sub _select_ancestor_or_self {
389 18     18   33 my ($self, $step, $current_points, $new_points) = @_;
390              
391 18         26 foreach my $point (@{$current_points}) {
  18         30  
392 18         29 my $step_points = [$point];
393 18         23 my $parent = $point;
394 18         49 while ($parent = $parent->parent) {
395 54         106 push @$step_points, $parent; # order matters
396             }
397 18         27 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  18         30  
398             }
399             }
400              
401             sub ref {
402 2     2 1 585 my ($self) = @_;
403 2         5 $self->first_point->{ref};
404             }
405              
406             sub deref {
407 1     1 1 4 my ($self) = @_;
408 1         2 ${$self->ref};
  1         3  
409             }
410              
411             sub first_point {
412 84     84 1 15204 my ($self) = @_;
413 84         355 $self->current_points->[0];
414             }
415              
416             sub all_points {
417 0     0 1 0 my ($self) = @_;
418 0         0 iarray $self->current_points;
419             }
420              
421             sub _iter {
422 14     14   26 my ($self) = @_;
423              
424 14         50 my $iter = iarray $self->current_points;
425 14     72   809 return imap { __PACKAGE__->new->current_points([ $_ ]) } $iter;
  72         43194  
426             }
427              
428             sub isearch
429             {
430 13     13 1 16635 my ($self, $path_str) = @_;
431 13         78 $self->_search(Data::DPath::Path->new(path => $path_str))->_iter;
432             }
433              
434             sub _search
435             {
436 450     450   766 my ($self, $dpath) = @_;
437              
438 13     13   91 no strict 'refs';
  13         29  
  13         366  
439 13     13   65 no warnings 'uninitialized';
  13         72  
  13         3700  
440              
441 450         765 my $current_points = $self->current_points;
442 450         784 my $steps = $dpath->_steps;
443 450         1194 for (my $i = 0; $i < @$steps; $i++) {
444 2178         3163 my $step = $steps->[$i];
445 2178         3194 my $lookahead = $steps->[$i+1];
446 2178         2963 my $new_points = [];
447              
448 2178 100       6759 if ($step->kind eq ROOT)
    100          
    100          
    100          
    100          
    100          
    100          
    50          
449             {
450 450         1018 $self->_select_root($step, $current_points, $new_points);
451             }
452             elsif ($step->kind eq ANYWHERE)
453             {
454 271         608 $self->_select_anywhere($step, $current_points, $lookahead, $new_points);
455             }
456             elsif ($step->kind eq KEY)
457             {
458 899         1704 $self->_select_key($step, $current_points, $new_points);
459             }
460             elsif ($step->kind eq ANYSTEP)
461             {
462 291         709 $self->_select_anystep($step, $current_points, $new_points);
463             }
464             elsif ($step->kind eq NOSTEP)
465             {
466 118         246 $self->_select_nostep($step, $current_points, $new_points);
467             }
468             elsif ($step->kind eq PARENT)
469             {
470 112         281 $self->_select_parent($step, $current_points, $new_points);
471             }
472             elsif ($step->kind eq ANCESTOR)
473             {
474 19         45 $self->_select_ancestor($step, $current_points, $new_points);
475             }
476             elsif ($step->kind eq ANCESTOR_OR_SELF)
477             {
478 18         34 $self->_select_ancestor_or_self($step, $current_points, $new_points);
479             }
480 2178         8525 $current_points = $new_points;
481             }
482 450         1085 $self->current_points( $current_points );
483 450         1204 return $self;
484             }
485              
486             sub match {
487 0     0 1 0 my ($self, $dpath) = @_;
488              
489 0         0 $self->_search($dpath)->_all;
490             }
491              
492             sub matchr {
493 436     436 1 784 my ($self, $dpath) = @_;
494              
495 436         1434 $self->_search($dpath)->_all_ref;
496             }
497              
498             1;
499              
500             __END__