File Coverage

blib/lib/Data/DPath/Context.pm
Criterion Covered Total %
statement 257 267 96.2
branch 63 74 85.1
condition 33 35 94.2
subroutine 48 51 94.1
pod 7 7 100.0
total 408 434 94.0


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.57';
5 12     12   109288 use strict;
  12         34  
  12         492  
6 12     12   86 use warnings;
  12         31  
  12         367  
7              
8 12     12   626 use Data::Dumper;
  12         8589  
  12         826  
9 12     12   464 use aliased 'Data::DPath::Point';
  12         955  
  12         101  
10 12     12   2221 use aliased 'Data::DPath::Attrs';
  12         34  
  12         63  
11 12     12   6382 use List::MoreUtils 'uniq';
  12         120897  
  12         168  
12 12     12   10010 use Scalar::Util 'reftype';
  12         43  
  12         1278  
13 12     12   5665 use Data::DPath::Filters;
  12         39  
  12         414  
14 12     12   4778 use Iterator::Util;
  12         193055  
  12         1642  
15 12     12   361 use List::Util 'min';
  12         40  
  12         1281  
16             #use Sys::CPU;
17 12     12   5950 use POSIX ();
  12         78320  
  12         438  
18 12     12   5950 use Safe;
  12         497354  
  12         2459  
19              
20             # run filter expressions in own Safe.pm compartment
21             our $COMPARTMENT;
22             our $THREADCOUNT;
23              
24             BEGIN {
25             #$THREADCOUNT = $Data::DPath::PARALLELIZE ? Sys::CPU::cpu_count : 1;
26             #print "THREADCOUNT: $THREADCOUNT\n";
27             package
28             Data::DPath::Filters;
29              
30 12     12   97 $COMPARTMENT = Safe->new;
31 12         16993 $COMPARTMENT->permit(qw":base_core");
32 12         223 $COMPARTMENT->reval( 'no warnings;' ); # just so warnings is loaded
33 12 50       8123 if ($] >= 5.010) {
34 12         80 $COMPARTMENT->deny(qw":load");
35             } else {
36 0         0 $COMPARTMENT->deny(qw"require dofile caller");
37             }
38             # map DPath filter functions into new namespace
39 12         167 $COMPARTMENT->share(qw(affe
40             idx
41             size
42             key
43             value
44             isa
45             reftype
46             is_reftype
47             ));
48             }
49              
50             # print "use $]\n" if $] >= 5.010; # allow new-school Perl inside filter expressions
51             # eval "use $]" if $] >= 5.010; # allow new-school Perl inside filter expressions
52              
53             use Class::XSAccessor::Array
54 12         161 chained => 1,
55             constructor => 'new',
56             accessors => {
57             current_points => 0,
58             give_references => 1,
59 12     12   3813 };
  12         1056  
60              
61 12         4120 use constant { HASH => 'HASH',
62             ARRAY => 'ARRAY',
63             SCALAR => 'SCALAR',
64             ROOT => 'ROOT',
65             ANYWHERE => 'ANYWHERE',
66             KEY => 'KEY',
67             ANYSTEP => 'ANYSTEP',
68             NOSTEP => 'NOSTEP',
69             PARENT => 'PARENT',
70             ANCESTOR => 'ANCESTOR',
71             ANCESTOR_OR_SELF => 'ANCESTOR_OR_SELF',
72 12     12   3869 };
  12         34  
73              
74             sub _splice_threads {
75 18     18   9291 my ($cargo) = @_;
76              
77 18         35 my $nr_cargo = @$cargo;
78              
79 18 100       47 return [[]] unless $nr_cargo;
80              
81 15   100     34 my $threadcount = $THREADCOUNT || 1;
82 15         47 my $blocksize = POSIX::ceil ($nr_cargo / $threadcount);
83              
84             my @result = map {
85 15         38 my $first = $_ * $blocksize;
  101         124  
86 101         156 my $last = min(($_+1) * $blocksize - 1, $nr_cargo-1);
87 101 100       211 ($first <= $last) ? [ @$cargo[$first .. $last]] : ();
88             } 0 .. $threadcount-1;
89              
90 15         57 return \@result;
91             }
92              
93             # only finds "inner" values; if you need the outer start value
94             # then just wrap it into one more level of array brackets.
95             sub _any
96             {
97 3231     3231   7700 my ($out, $in, $lookahead_key) = @_;
98              
99 12     12   105 no warnings 'uninitialized';
  12         30  
  12         5769  
100              
101 3231 50       6749 $in = defined $in ? $in : [];
102 3231 100       9942 return $out unless @$in;
103              
104 2474         7485 my @newin;
105             my $tmp_ref;
106 2474         0 my $tmp_deref;
107 2474         0 my $tmp_reftype;
108              
109 2474         4626 foreach my $point (@$in) {
110 7118         11662 my @values;
111 7118 50       14997 next unless defined $point;
112 7118         14517 my $ref = $point->ref;
113              
114             # speed optimization: first try faster ref, then reftype
115 7118 100 100     31850 if (ref($$ref) eq HASH or reftype($$ref) eq HASH) {
    100 100        
116             push @$out,
117             map {
118             my $newpoint = Point->new
119 3750         22470 ->ref(\($$ref->{$_}))
120             ->parent($point)
121             ->attrs(Attrs->new(key => $_));
122 3750         8817 push @newin, $newpoint; # remember added points
123 3750         12562 $newpoint;
124             }
125             grep {
126             # speed optimization: only consider a key if lookahead looks promising
127             not defined $lookahead_key
128             or $_ eq $lookahead_key
129 5210 100 100     43338 or ($tmp_ref = ref($tmp_deref =$$ref->{$_})) eq HASH
      100        
      100        
      100        
130             or $tmp_ref eq ARRAY
131             or ($tmp_reftype = reftype($tmp_deref)) eq HASH
132             or $tmp_reftype eq ARRAY
133             # or HASH_or_ARRAY(\($$ref->{$_}))
134             }
135 3547         6356 keys %{$$ref};
  3547         10102  
136             }
137             elsif (ref($$ref) eq ARRAY or reftype($$ref) eq ARRAY) {
138 819         1602 my $idx = 0;
139             push @$out,
140             map {
141             my $newpoint = Point->new
142             ->ref($_->{val_ref})
143 2611         17814 ->parent($point)
144             ->attrs(Attrs->new(idx => $idx++));
145 2611         6030 push @newin, $newpoint; # remember added points
146 2611         7531 $newpoint;
147             }
148 819         1507 map { { val_ref => \$_ } } @{$$ref}
  2611         8087  
  819         2143  
149             }
150             else {
151             next
152 2752         6143 }
153             }
154 2474         7063 return _any ($out, \@newin, $lookahead_key);
155             }
156              
157             sub _all {
158 0     0   0 my ($self) = @_;
159              
160 0         0 return @{$self->_all_ref};
  0         0  
161             }
162              
163             sub _all_ref {
164 433     433   1161 my ($self) = @_;
165              
166 12     12   107 no strict 'refs';
  12         35  
  12         552  
167 12     12   88 no warnings 'uninitialized';
  12         47  
  12         3129  
168              
169             return [
170 505 100       9083 map { $self->give_references ? $_ : $$_ }
171             uniq
172 639 100       2798 map { defined $_ ? $_->ref : () }
173 433         974 @{$self->current_points}
  433         1988  
174             ];
175             }
176              
177             # filter current results by array index
178             sub _filter_points_index {
179 169     169   471 my ($self, $index, $points) = @_;
180              
181 169 50       1284 return $points ? [$points->[$index]] : [];
182             }
183              
184             # filter current results by condition
185             sub _filter_points_eval
186             {
187 778     778   2074 my ($self, $filter, $points) = @_;
188              
189 778 50       2216 return [] unless @$points;
190 778 50       1889 return $points unless defined $filter;
191              
192 778         1683 my $new_points;
193             my $res;
194             {
195 778         1363 package Data::DPath::Filters;
196              
197 778         1589 local our $idx = 0;
198             $new_points = [
199             grep {
200 778         1792 local our $p = $_;
  984         1836  
201 984         1709 local $_;
202 984         2767 my $pref = $p->ref;
203 984 50       2304 if ( defined $pref ) {
204 984         2008 $_ = $$pref;
205 984 100       2212 if ($Data::DPath::USE_SAFE) {
206             # 'uninitialized' values are the norm
207             # but "no warnings 'uninitialized'" does
208             # not work in this restrictive Safe.pm config, so
209             # we deactivate warnings completely by localizing $^W.
210             # on later Perls, ^W doesn't do the whole trick, so explicitly turn
211             # all warnings off. need to do this in a BEGIN, as some warnings
212             # are compile time only.
213 873         4602 $res = $COMPARTMENT->reval('BEGIN{ warnings->unimport}; local $^W;'.$filter);
214             } else {
215             # 'uninitialized' values are the norm
216 12     12   105 no warnings 'uninitialized';
  12         33  
  12         639  
217 12     12   4713 no if $] >= 5.018, warnings => 'experimental::smartmatch';
  12         129  
  12         106  
218 111         5370 $res = eval($filter);
219             }
220 984 50       509779 print STDERR ($@, "\n") if $@;
221             } else {
222 0         0 $res = 0;
223             }
224 984         2164 $idx++;
225 984         3312 $res;
226             } @$points
227             ];
228             }
229 778         6147 return $new_points;
230             }
231              
232             sub _filter_points {
233 7345     7345   14654 my ($self, $step, $points) = @_;
234              
235 12     12   2522 no strict 'refs';
  12         29  
  12         415  
236 12     12   76 no warnings 'uninitialized';
  12         28  
  12         4470  
237              
238 7345 100       22034 return [] unless @$points;
239              
240 4485         9732 my $filter = $step->filter;
241 4485 100       16475 return $points unless defined $filter;
242              
243 947         9018 $filter =~ s/^\[\s*(.*?)\s*\]$/$1/; # strip brackets and whitespace
244              
245 947 100       6184 if ($filter =~ /^-?\d+$/)
    50          
246             {
247 169         578 return $self->_filter_points_index($filter, $points); # simple array index
248             }
249             elsif ($filter =~ /\S/)
250             {
251 778         2891 return $self->_filter_points_eval($filter, $points); # full condition
252             }
253             else
254             {
255 0         0 return $points;
256             }
257             }
258              
259             # the root node
260             # (only makes sense at first step, but currently not asserted)
261             sub _select_root {
262 447     447   1316 my ($self, $step, $current_points, $new_points) = @_;
263              
264 447         1597 my $step_points = $self->_filter_points($step, $current_points);
265 447         1595 push @$new_points, @$step_points;
266             }
267              
268              
269             # //
270             # anywhere in the tree
271             sub _select_anywhere {
272 271     271   797 my ($self, $step, $current_points, $lookahead, $new_points) = @_;
273              
274             # speed optimization: only useful points added
275 271         572 my $lookahead_key;
276 271 100 100     1774 if (defined $lookahead and $lookahead->kind eq KEY) {
277 206         671 $lookahead_key = $lookahead->part;
278             }
279              
280             # '//'
281             # all hash/array nodes of a data structure
282 271         773 foreach my $point (@$current_points) {
283 757         1508 my @step_points = (@{_any([], [ $point ], $lookahead_key)}, $point);
  757         2350  
284 757         2040 push @$new_points, @{$self->_filter_points($step, \@step_points)};
  757         2178  
285             }
286             }
287              
288             # /key
289             # the value of a key
290             sub _select_key {
291 893     893   2153 my ($self, $step, $current_points, $new_points) = @_;
292              
293 893         2010 foreach my $point (@$current_points) {
294 12     12   117 no warnings 'uninitialized';
  12         29  
  12         2296  
295 7088 100       15572 next unless defined $point;
296 7067         14755 my $pref = $point->ref;
297             next unless (
298             # speed optimization:
299             # first try faster ref, then reftype
300 7067 100 100     23653 ref($$pref) eq HASH or
301             reftype($$pref) eq HASH
302             );
303             # take point as hash, skip undefs
304 4215         15163 my $attrs = Attrs->new(key => $step->part);
305 4215         8735 my $step_points = [];
306 4215 100       12040 if (exists $$pref->{$step->part}) {
307 1355         6969 $step_points = [ Point->new->ref(\($$pref->{$step->part}))->parent($point)->attrs($attrs) ];
308             }
309 4215         7693 push @$new_points, @{$self->_filter_points($step, $step_points)};
  4215         9536  
310             }
311             }
312              
313             # '*'
314             # all leaves of a data tree
315             sub _select_anystep {
316 291     291   917 my ($self, $step, $current_points, $new_points) = @_;
317              
318 12     12   96 no warnings 'uninitialized';
  12         32  
  12         10094  
319 291         849 foreach my $point (@$current_points) {
320             # take point as array
321 698         2080 my $pref = $point->ref;
322 698         1468 my $ref = $$pref;
323 698         1282 my $step_points = [];
324             # speed optimization: first try faster ref, then reftype
325 698 100 100     4710 if (ref($ref) eq HASH or reftype($ref) eq HASH) {
    100 100        
326             $step_points = [ map {
327 357         1234 my $v_ref = \($ref->{$_});
  661         1704  
328 661         2654 my $attrs = Attrs->new(key => $_);
329 661         3353 Point->new->ref($v_ref)->parent($point)->attrs($attrs)
330             } keys %$ref ];
331             } elsif (ref($ref) eq ARRAY or reftype($ref) eq ARRAY) {
332 162         457 my $idx = 0;
333             $step_points = [ map {
334 162         484 my $attrs = Attrs->new(idx => $idx++);
  612         2187  
335 612         2925 Point->new->ref(\$_)->parent($point)->attrs($attrs)
336             } @$ref ];
337             } else {
338 179 50 33     659 if (ref($pref) eq SCALAR or reftype($pref) eq SCALAR) {
339             # TODO: without map, it's just one value
340 179         1206 $step_points = [ #map {
341             Point->new->ref($pref)->parent($point) # XXX? why $_? What happens to $pref?
342             ]; # } $ref ];
343             }
344             }
345 698         1739 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  698         1890  
346             }
347             }
348              
349             # '.'
350             # no step (neither up nor down), just allow filtering
351             sub _select_nostep {
352 118     118   303 my ($self, $step, $current_points, $new_points) = @_;
353              
354 118         202 foreach my $point (@{$current_points}) {
  118         264  
355 696         1532 my $step_points = [$point];
356 696         1212 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  696         1610  
357             }
358             }
359              
360             # '..'
361             # the parent
362             sub _select_parent {
363 112     112   282 my ($self, $step, $current_points, $new_points) = @_;
364              
365 112         184 foreach my $point (@{$current_points}) {
  112         246  
366 498 100       947 next unless defined $point;
367 495         939 my $step_points = [$point->parent];
368 495         739 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  495         857  
369             }
370             }
371              
372             # '::ancestor'
373             # all ancestors (parent, grandparent, etc.) of the current node
374             sub _select_ancestor {
375 19     19   53 my ($self, $step, $current_points, $new_points) = @_;
376              
377 19         34 foreach my $point (@{$current_points}) {
  19         44  
378 19         34 my $step_points = [];
379 19         37 my $parent = $point;
380 19         80 while ($parent = $parent->parent) {
381 64         230 push @$step_points, $parent; # order matters
382             }
383 19         34 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  19         46  
384             }
385             }
386              
387             # '::ancestor-or-self'
388             # all ancestors (parent, grandparent, etc.) of the current node and the current node itself
389             sub _select_ancestor_or_self {
390 18     18   71 my ($self, $step, $current_points, $new_points) = @_;
391              
392 18         34 foreach my $point (@{$current_points}) {
  18         42  
393 18         36 my $step_points = [$point];
394 18         33 my $parent = $point;
395 18         60 while ($parent = $parent->parent) {
396 54         152 push @$step_points, $parent; # order matters
397             }
398 18         37 push @$new_points, @{ $self->_filter_points($step, $step_points) };
  18         54  
399             }
400             }
401              
402             sub ref {
403 2     2 1 751 my ($self) = @_;
404 2         11 $self->first_point->{ref};
405             }
406              
407             sub deref {
408 1     1 1 5 my ($self) = @_;
409 1         3 ${$self->ref};
  1         5  
410             }
411              
412             sub first_point {
413 60     60 1 14508 my ($self) = @_;
414 60         502 $self->current_points->[0];
415             }
416              
417             sub all_points {
418 0     0 1 0 my ($self) = @_;
419 0         0 iarray $self->current_points;
420             }
421              
422             sub _iter {
423 14     14   44 my ($self) = @_;
424              
425 14         101 my $iter = iarray $self->current_points;
426 14     48   4970 return imap { __PACKAGE__->new->current_points([ $_ ]) } $iter;
  48         51851  
427             }
428              
429             sub isearch
430             {
431 13     13 1 28401 my ($self, $path_str) = @_;
432 13         120 $self->_search(Data::DPath::Path->new(path => $path_str))->_iter;
433             }
434              
435             sub _search
436             {
437 447     447   1216 my ($self, $dpath) = @_;
438              
439 12     12   98 no strict 'refs';
  12         32  
  12         413  
440 12     12   75 no warnings 'uninitialized';
  12         35  
  12         3886  
441              
442 447         1286 my $current_points = $self->current_points;
443 447         1177 my $steps = $dpath->_steps;
444 447         1761 for (my $i = 0; $i < @$steps; $i++) {
445 2169         4413 my $step = $steps->[$i];
446 2169         4230 my $lookahead = $steps->[$i+1];
447 2169         3922 my $new_points = [];
448              
449 2169 100       9770 if ($step->kind eq ROOT)
    100          
    100          
    100          
    100          
    100          
    100          
    50          
450             {
451 447         1614 $self->_select_root($step, $current_points, $new_points);
452             }
453             elsif ($step->kind eq ANYWHERE)
454             {
455 271         1055 $self->_select_anywhere($step, $current_points, $lookahead, $new_points);
456             }
457             elsif ($step->kind eq KEY)
458             {
459 893         2560 $self->_select_key($step, $current_points, $new_points);
460             }
461             elsif ($step->kind eq ANYSTEP)
462             {
463 291         1124 $self->_select_anystep($step, $current_points, $new_points);
464             }
465             elsif ($step->kind eq NOSTEP)
466             {
467 118         378 $self->_select_nostep($step, $current_points, $new_points);
468             }
469             elsif ($step->kind eq PARENT)
470             {
471 112         362 $self->_select_parent($step, $current_points, $new_points);
472             }
473             elsif ($step->kind eq ANCESTOR)
474             {
475 19         62 $self->_select_ancestor($step, $current_points, $new_points);
476             }
477             elsif ($step->kind eq ANCESTOR_OR_SELF)
478             {
479 18         56 $self->_select_ancestor_or_self($step, $current_points, $new_points);
480             }
481 2169         12339 $current_points = $new_points;
482             }
483 447         2025 $self->current_points( $current_points );
484 447         1865 return $self;
485             }
486              
487             sub match {
488 0     0 1 0 my ($self, $dpath) = @_;
489              
490 0         0 $self->_search($dpath)->_all;
491             }
492              
493             sub matchr {
494 433     433 1 1206 my ($self, $dpath) = @_;
495              
496 433         1437 $self->_search($dpath)->_all_ref;
497             }
498              
499             1;
500              
501             __END__