File Coverage

blib/lib/Data/DPath/Context.pm
Criterion Covered Total %
statement 253 263 96.2
branch 63 74 85.1
condition 33 35 94.2
subroutine 47 50 94.0
pod 7 7 100.0
total 403 429 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.59';
5 13     13   78635 use strict;
  13         41  
  13         420  
6 13     13   65 use warnings;
  13         24  
  13         318  
7              
8 13     13   723 use Data::Dumper;
  13         7480  
  13         590  
9 13     13   626 use aliased 'Data::DPath::Point';
  13         820  
  13         110  
10 13     13   2292 use aliased 'Data::DPath::Attrs';
  13         205  
  13         70  
11 13     13   1086 use Scalar::Util 'reftype';
  13         28  
  13         603  
12 13     13   6055 use Data::DPath::Filters;
  13         29  
  13         355  
13 13     13   6813 use Iterator::Util;
  13         206658  
  13         1333  
14 13     13   99 use List::Util 1.45 'min', 'uniq';
  13         380  
  13         1438  
15             #use Sys::CPU;
16 13     13   7439 use POSIX ();
  13         86701  
  13         363  
17 13     13   6937 use Safe;
  13         502409  
  13         2116  
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   84 $COMPARTMENT = Safe->new;
30 13         14054 $COMPARTMENT->permit(qw":base_core");
31 13 50       152 if ($] >= 5.010) {
32 13         43 $COMPARTMENT->deny(qw":load");
33             } else {
34 0         0 $COMPARTMENT->deny(qw"require dofile caller");
35             }
36             # map DPath filter functions into new namespace
37 13         117 $COMPARTMENT->share(qw(affe
38             idx
39             size
40             key
41             value
42             isa
43             reftype
44             is_reftype
45             ));
46             }
47              
48             # print "use $]\n" if $] >= 5.010; # allow new-school Perl inside filter expressions
49             # eval "use $]" if $] >= 5.010; # allow new-school Perl inside filter expressions
50              
51             use Class::XSAccessor::Array
52 13         142 chained => 1,
53             constructor => 'new',
54             accessors => {
55             current_points => 0,
56             give_references => 1,
57 13     13   3241 };
  13         898  
58              
59 13         3905 use constant { HASH => 'HASH',
60             ARRAY => 'ARRAY',
61             SCALAR => 'SCALAR',
62             ROOT => 'ROOT',
63             ANYWHERE => 'ANYWHERE',
64             KEY => 'KEY',
65             ANYSTEP => 'ANYSTEP',
66             NOSTEP => 'NOSTEP',
67             PARENT => 'PARENT',
68             ANCESTOR => 'ANCESTOR',
69             ANCESTOR_OR_SELF => 'ANCESTOR_OR_SELF',
70 13     13   3617 };
  13         36  
71              
72             sub _splice_threads {
73 18     18   9637 my ($cargo) = @_;
74              
75 18         34 my $nr_cargo = @$cargo;
76              
77 18 100       44 return [[]] unless $nr_cargo;
78              
79 15   100     36 my $threadcount = $THREADCOUNT || 1;
80 15         53 my $blocksize = POSIX::ceil ($nr_cargo / $threadcount);
81              
82             my @result = map {
83 15         42 my $first = $_ * $blocksize;
  101         145  
84 101         172 my $last = min(($_+1) * $blocksize - 1, $nr_cargo-1);
85 101 100       247 ($first <= $last) ? [ @$cargo[$first .. $last]] : ();
86             } 0 .. $threadcount-1;
87              
88 15         55 return \@result;
89             }
90              
91             # only finds "inner" values; if you need the outer start value
92             # then just wrap it into one more level of array brackets.
93             sub _any
94             {
95 3231     3231   5700 my ($out, $in, $lookahead_key) = @_;
96              
97 13     13   96 no warnings 'uninitialized';
  13         27  
  13         5245  
98              
99 3231 50       5244 $in = defined $in ? $in : [];
100 3231 100       8022 return $out unless @$in;
101              
102 2474         6262 my @newin;
103             my $tmp_ref;
104 2474         0 my $tmp_deref;
105 2474         0 my $tmp_reftype;
106              
107 2474         3838 foreach my $point (@$in) {
108 7118         9257 my @values;
109 7118 50       11985 next unless defined $point;
110 7118         10668 my $ref = $point->ref;
111              
112             # speed optimization: first try faster ref, then reftype
113 7118 100 100     24044 if (ref($$ref) eq HASH or reftype($$ref) eq HASH) {
    100 100        
114             push @$out,
115             map {
116             my $newpoint = Point->new
117 3750         15605 ->ref(\($$ref->{$_}))
118             ->parent($point)
119             ->attrs(Attrs->new(key => $_));
120 3750         6408 push @newin, $newpoint; # remember added points
121 3750         8494 $newpoint;
122             }
123             grep {
124             # speed optimization: only consider a key if lookahead looks promising
125             not defined $lookahead_key
126             or $_ eq $lookahead_key
127 5210 100 100     30366 or ($tmp_ref = ref($tmp_deref =$$ref->{$_})) eq HASH
      100        
      100        
      100        
128             or $tmp_ref eq ARRAY
129             or ($tmp_reftype = reftype($tmp_deref)) eq HASH
130             or $tmp_reftype eq ARRAY
131             # or HASH_or_ARRAY(\($$ref->{$_}))
132             }
133 3547         4797 keys %{$$ref};
  3547         7470  
134             }
135             elsif (ref($$ref) eq ARRAY or reftype($$ref) eq ARRAY) {
136 819         1231 my $idx = 0;
137             push @$out,
138             map {
139             my $newpoint = Point->new
140             ->ref($_->{val_ref})
141 2611         9382 ->parent($point)
142             ->attrs(Attrs->new(idx => $idx++));
143 2611         4220 push @newin, $newpoint; # remember added points
144 2611         5532 $newpoint;
145             }
146 819         1206 map { { val_ref => \$_ } } @{$$ref}
  2611         5238  
  819         1458  
147             }
148             else {
149             next
150 2752         4408 }
151             }
152 2474         5264 return _any ($out, \@newin, $lookahead_key);
153             }
154              
155             sub _all {
156 0     0   0 my ($self) = @_;
157              
158 0         0 return @{$self->_all_ref};
  0         0  
159             }
160              
161             sub _all_ref {
162 436     436   792 my ($self) = @_;
163              
164 13     13   109 no strict 'refs';
  13         27  
  13         470  
165 13     13   69 no warnings 'uninitialized';
  13         37  
  13         2950  
166              
167             return [
168 734 100       5860 map { $self->give_references ? $_ : $$_ }
169             uniq
170 896 100       3394 map { defined $_ ? $_->ref : () }
171 436         616 @{$self->current_points}
  436         1102  
172             ];
173             }
174              
175             # filter current results by array index
176             sub _filter_points_index {
177 209     209   418 my ($self, $index, $points) = @_;
178              
179 209 50       1278 return $points ? [$points->[$index]] : [];
180             }
181              
182             # filter current results by condition
183             sub _filter_points_eval
184             {
185 787     787   1531 my ($self, $filter, $points) = @_;
186              
187 787 50       1653 return [] unless @$points;
188 787 50       1520 return $points unless defined $filter;
189              
190 787         1309 my $new_points;
191             my $res;
192             {
193 787         1114 package Data::DPath::Filters;
194              
195 787         1231 local our $idx = 0;
196             $new_points = [
197             grep {
198 787         1380 local our $p = $_;
  993         1526  
199 993         1290 local $_;
200 993         2168 my $pref = $p->ref;
201 993 50       1912 if ( defined $pref ) {
202 993         1593 $_ = $$pref;
203 993 100       1880 if ($Data::DPath::USE_SAFE) {
204             # 'uninitialized' values are the norm
205             # but "no warnings 'uninitialized'" does
206             # not work in this restrictive Safe.pm config, so
207             # we deactivate warnings completely by localizing $^W.
208             # on later Perls, ^W doesn't do the whole trick, so explicitly turn
209             # all warnings off. need to do this in a BEGIN, as some warnings
210             # are compile time only.
211 879         3428 $res = $COMPARTMENT->reval('BEGIN{ ${^WARNING_BITS} = "" }; local $^W;'.$filter);
212             } else {
213             # 'uninitialized' values are the norm
214 13     13   86 no warnings 'uninitialized';
  13         28  
  13         529  
215 13     13   5603 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  13         137  
  13         93  
216 114         5653 $res = eval($filter);
217             }
218 993 50       289965 print STDERR ($@, "\n") if $@;
219             } else {
220 0         0 $res = 0;
221             }
222 993         1517 $idx++;
223 993         2969 $res;
224             } @$points
225             ];
226             }
227 787         4368 return $new_points;
228             }
229              
230             sub _filter_points {
231 7536     7536   11514 my ($self, $step, $points) = @_;
232              
233 13     13   2955 no strict 'refs';
  13         33  
  13         387  
234 13     13   76 no warnings 'uninitialized';
  13         34  
  13         4067  
235              
236 7536 100       16655 return [] unless @$points;
237              
238 4676         7654 my $filter = $step->filter;
239 4676 100       13253 return $points unless defined $filter;
240              
241 996         7546 $filter =~ s/^\[\s*(.*?)\s*\]$/$1/; # strip brackets and whitespace
242              
243 996 100       4938 if ($filter =~ /^-?\d+$/)
    50          
244             {
245 209         474 return $self->_filter_points_index($filter, $points); # simple array index
246             }
247             elsif ($filter =~ /\S/)
248             {
249 787         1942 return $self->_filter_points_eval($filter, $points); # full condition
250             }
251             else
252             {
253 0         0 return $points;
254             }
255             }
256              
257             # the root node
258             # (only makes sense at first step, but currently not asserted)
259             sub _select_root {
260 450     450   870 my ($self, $step, $current_points, $new_points) = @_;
261              
262 450         1021 my $step_points = $self->_filter_points($step, $current_points);
263 450         1011 push @$new_points, @$step_points;
264             }
265              
266              
267             # //
268             # anywhere in the tree
269             sub _select_anywhere {
270 271     271   581 my ($self, $step, $current_points, $lookahead, $new_points) = @_;
271              
272             # speed optimization: only useful points added
273 271         395 my $lookahead_key;
274 271 100 100     1120 if (defined $lookahead and $lookahead->kind eq KEY) {
275 206         421 $lookahead_key = $lookahead->part;
276             }
277              
278             # '//'
279             # all hash/array nodes of a data structure
280 271         563 foreach my $point (@$current_points) {
281 757         1039 my @step_points = (@{_any([], [ $point ], $lookahead_key)}, $point);
  757         1601  
282 757         1532 push @$new_points, @{$self->_filter_points($step, \@step_points)};
  757         1530  
283             }
284             }
285              
286             # /key
287             # the value of a key
288             sub _select_key {
289 899     899   1632 my ($self, $step, $current_points, $new_points) = @_;
290              
291 899         1552 foreach my $point (@$current_points) {
292 13     13   94 no warnings 'uninitialized';
  13         46  
  13         2197  
293 7201 100       12133 next unless defined $point;
294 7180         10454 my $pref = $point->ref;
295             next unless (
296             # speed optimization:
297             # first try faster ref, then reftype
298 7180 100 100     17829 ref($$pref) eq HASH or
299             reftype($$pref) eq HASH
300             );
301             # take point as hash, skip undefs
302 4328         11175 my $attrs = Attrs->new(key => $step->part);
303 4328         6563 my $step_points = [];
304 4328 100       9268 if (exists $$pref->{$step->part}) {
305 1468         5335 $step_points = [ Point->new->ref(\($$pref->{$step->part}))->parent($point)->attrs($attrs) ];
306             }
307 4328         6093 push @$new_points, @{$self->_filter_points($step, $step_points)};
  4328         7235  
308             }
309             }
310              
311             # '*'
312             # all leaves of a data tree
313             sub _select_anystep {
314 291     291   551 my ($self, $step, $current_points, $new_points) = @_;
315              
316 13     13   85 no warnings 'uninitialized';
  13         24  
  13         8833  
317 291         540 foreach my $point (@$current_points) {
318             # take point as array
319 738         1506 my $pref = $point->ref;
320 738         1108 my $ref = $$pref;
321 738         1098 my $step_points = [];
322             # speed optimization: first try faster ref, then reftype
323 738 100 100     3544 if (ref($ref) eq HASH or reftype($ref) eq HASH) {
    100 100        
324             $step_points = [ map {
325 357         845 my $v_ref = \($ref->{$_});
  661         1040  
326 661         1837 my $attrs = Attrs->new(key => $_);
327 661         2355 Point->new->ref($v_ref)->parent($point)->attrs($attrs)
328             } keys %$ref ];
329             } elsif (ref($ref) eq ARRAY or reftype($ref) eq ARRAY) {
330 202         377 my $idx = 0;
331             $step_points = [ map {
332 202         451 my $attrs = Attrs->new(idx => $idx++);
  766         1883  
333 766         2575 Point->new->ref(\$_)->parent($point)->attrs($attrs)
334             } @$ref ];
335             } else {
336 179 50 33     569 if (ref($pref) eq SCALAR or reftype($pref) eq SCALAR) {
337             # TODO: without map, it's just one value
338 179         891 $step_points = [ #map {
339             Point->new->ref($pref)->parent($point) # XXX? why $_? What happens to $pref?
340             ]; # } $ref ];
341             }
342             }
343 738         1336 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  738         1435  
344             }
345             }
346              
347             # '.'
348             # no step (neither up nor down), just allow filtering
349             sub _select_nostep {
350 118     118   235 my ($self, $step, $current_points, $new_points) = @_;
351              
352 118         168 foreach my $point (@{$current_points}) {
  118         205  
353 702         1207 my $step_points = [$point];
354 702         966 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  702         1356  
355             }
356             }
357              
358             # '..'
359             # the parent
360             sub _select_parent {
361 112     112   239 my ($self, $step, $current_points, $new_points) = @_;
362              
363 112         148 foreach my $point (@{$current_points}) {
  112         206  
364 527 100       877 next unless defined $point;
365 524         933 my $step_points = [$point->parent];
366 524         665 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  524         827  
367             }
368             }
369              
370             # '::ancestor'
371             # all ancestors (parent, grandparent, etc.) of the current node
372             sub _select_ancestor {
373 19     19   37 my ($self, $step, $current_points, $new_points) = @_;
374              
375 19         30 foreach my $point (@{$current_points}) {
  19         41  
376 19         34 my $step_points = [];
377 19         30 my $parent = $point;
378 19         93 while ($parent = $parent->parent) {
379 64         150 push @$step_points, $parent; # order matters
380             }
381 19         36 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  19         39  
382             }
383             }
384              
385             # '::ancestor-or-self'
386             # all ancestors (parent, grandparent, etc.) of the current node and the current node itself
387             sub _select_ancestor_or_self {
388 18     18   39 my ($self, $step, $current_points, $new_points) = @_;
389              
390 18         27 foreach my $point (@{$current_points}) {
  18         31  
391 18         34 my $step_points = [$point];
392 18         28 my $parent = $point;
393 18         50 while ($parent = $parent->parent) {
394 54         122 push @$step_points, $parent; # order matters
395             }
396 18         34 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  18         35  
397             }
398             }
399              
400             sub ref {
401 2     2 1 575 my ($self) = @_;
402 2         7 $self->first_point->{ref};
403             }
404              
405             sub deref {
406 1     1 1 3 my ($self) = @_;
407 1         4 ${$self->ref};
  1         4  
408             }
409              
410             sub first_point {
411 84     84 1 17005 my ($self) = @_;
412 84         360 $self->current_points->[0];
413             }
414              
415             sub all_points {
416 0     0 1 0 my ($self) = @_;
417 0         0 iarray $self->current_points;
418             }
419              
420             sub _iter {
421 14     14   25 my ($self) = @_;
422              
423 14         47 my $iter = iarray $self->current_points;
424 14     72   770 return imap { __PACKAGE__->new->current_points([ $_ ]) } $iter;
  72         48892  
425             }
426              
427             sub isearch
428             {
429 13     13 1 20427 my ($self, $path_str) = @_;
430 13         59 $self->_search(Data::DPath::Path->new(path => $path_str))->_iter;
431             }
432              
433             sub _search
434             {
435 450     450   750 my ($self, $dpath) = @_;
436              
437 13     13   95 no strict 'refs';
  13         28  
  13         367  
438 13     13   60 no warnings 'uninitialized';
  13         30  
  13         4197  
439              
440 450         772 my $current_points = $self->current_points;
441 450         770 my $steps = $dpath->_steps;
442 450         1165 for (my $i = 0; $i < @$steps; $i++) {
443 2178         3605 my $step = $steps->[$i];
444 2178         3392 my $lookahead = $steps->[$i+1];
445 2178         3090 my $new_points = [];
446              
447 2178 100       6681 if ($step->kind eq ROOT)
    100          
    100          
    100          
    100          
    100          
    100          
    50          
448             {
449 450         1013 $self->_select_root($step, $current_points, $new_points);
450             }
451             elsif ($step->kind eq ANYWHERE)
452             {
453 271         630 $self->_select_anywhere($step, $current_points, $lookahead, $new_points);
454             }
455             elsif ($step->kind eq KEY)
456             {
457 899         2108 $self->_select_key($step, $current_points, $new_points);
458             }
459             elsif ($step->kind eq ANYSTEP)
460             {
461 291         701 $self->_select_anystep($step, $current_points, $new_points);
462             }
463             elsif ($step->kind eq NOSTEP)
464             {
465 118         290 $self->_select_nostep($step, $current_points, $new_points);
466             }
467             elsif ($step->kind eq PARENT)
468             {
469 112         255 $self->_select_parent($step, $current_points, $new_points);
470             }
471             elsif ($step->kind eq ANCESTOR)
472             {
473 19         53 $self->_select_ancestor($step, $current_points, $new_points);
474             }
475             elsif ($step->kind eq ANCESTOR_OR_SELF)
476             {
477 18         45 $self->_select_ancestor_or_self($step, $current_points, $new_points);
478             }
479 2178         8667 $current_points = $new_points;
480             }
481 450         1116 $self->current_points( $current_points );
482 450         1145 return $self;
483             }
484              
485             sub match {
486 0     0 1 0 my ($self, $dpath) = @_;
487              
488 0         0 $self->_search($dpath)->_all;
489             }
490              
491             sub matchr {
492 436     436 1 825 my ($self, $dpath) = @_;
493              
494 436         948 $self->_search($dpath)->_all_ref;
495             }
496              
497             1;
498              
499             __END__