line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Data::Reach;
|
2
|
9
|
|
|
9
|
|
602449
|
use strict;
|
|
9
|
|
|
|
|
82
|
|
|
9
|
|
|
|
|
283
|
|
3
|
9
|
|
|
9
|
|
80
|
use warnings;
|
|
9
|
|
|
|
|
20
|
|
|
9
|
|
|
|
|
292
|
|
4
|
9
|
|
|
9
|
|
49
|
use Carp qw/carp croak/;
|
|
9
|
|
|
|
|
15
|
|
|
9
|
|
|
|
|
449
|
|
5
|
9
|
|
|
9
|
|
53
|
use Scalar::Util qw/blessed reftype/;
|
|
9
|
|
|
|
|
16
|
|
|
9
|
|
|
|
|
425
|
|
6
|
9
|
|
|
9
|
|
10908
|
use overload;
|
|
9
|
|
|
|
|
9301
|
|
|
9
|
|
|
|
|
58
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '2.00';
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#======================================================================
|
12
|
|
|
|
|
|
|
# reach() and utility functions
|
13
|
|
|
|
|
|
|
#======================================================================
|
14
|
|
|
|
|
|
|
# main entry point
|
15
|
|
|
|
|
|
|
sub reach ($@) {
|
16
|
172
|
|
|
172
|
1
|
10896
|
my ($root, @path) = @_;
|
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# loop until either @path or the datastructure under $root is exhausted
|
19
|
172
|
|
|
|
|
306
|
while (1) {
|
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# exit conditions
|
22
|
413
|
100
|
|
|
|
3741
|
return undef if !defined $root;
|
23
|
405
|
100
|
|
|
|
914
|
return $root if !@path;
|
24
|
252
|
|
|
|
|
375
|
my $path0 = shift @path;
|
25
|
252
|
100
|
|
|
|
421
|
return undef if !defined $path0;
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# otherwise, walk down one step into the datastructure and loop again
|
28
|
251
|
100
|
|
|
|
693
|
$root = blessed $root ? _step_down_obj($root, $path0)
|
29
|
|
|
|
|
|
|
: _step_down_raw($root, $path0);
|
30
|
|
|
|
|
|
|
}
|
31
|
|
|
|
|
|
|
}
|
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# get inner data within a raw datastructure
|
34
|
|
|
|
|
|
|
sub _step_down_raw {
|
35
|
206
|
|
|
206
|
|
333
|
my ($data, $key) = @_;
|
36
|
|
|
|
|
|
|
|
37
|
206
|
|
100
|
|
|
487
|
my $reftype = reftype $data || '';
|
38
|
|
|
|
|
|
|
|
39
|
206
|
100
|
|
|
|
419
|
if ($reftype eq 'HASH') {
|
|
|
100
|
|
|
|
|
|
40
|
127
|
|
|
|
|
335
|
return $data->{$key};
|
41
|
|
|
|
|
|
|
}
|
42
|
|
|
|
|
|
|
elsif ($reftype eq 'ARRAY') {
|
43
|
72
|
100
|
|
|
|
311
|
if ($key =~ /^-?\d+$/) {
|
44
|
71
|
|
|
|
|
186
|
return $data->[$key];
|
45
|
|
|
|
|
|
|
}
|
46
|
|
|
|
|
|
|
else {
|
47
|
1
|
|
|
|
|
189
|
croak "cannot reach index '$key' within an array";
|
48
|
|
|
|
|
|
|
}
|
49
|
|
|
|
|
|
|
}
|
50
|
|
|
|
|
|
|
else {
|
51
|
7
|
50
|
|
|
|
25
|
my $kind = $reftype ? "${reftype}REF"
|
|
|
100
|
|
|
|
|
|
52
|
|
|
|
|
|
|
: defined ref $data ? "SCALAR"
|
53
|
|
|
|
|
|
|
: "undef";
|
54
|
7
|
50
|
|
|
|
28
|
my $article = $kind =~ /^[aeiou]/i ? "an" : "a";
|
55
|
7
|
|
|
|
|
687
|
croak "cannot reach '$key' within $article $kind";
|
56
|
|
|
|
|
|
|
}
|
57
|
|
|
|
|
|
|
}
|
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# get inner data within an object
|
61
|
|
|
|
|
|
|
sub _step_down_obj {
|
62
|
52
|
|
|
52
|
|
105
|
my ($obj, $key) = @_;
|
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# pragmata that may modify our algorithm -- see L
|
65
|
52
|
|
|
|
|
291
|
my $hint_hash = (caller(1))[10];
|
66
|
52
|
|
100
|
|
|
219
|
my $use_overloads = $hint_hash->{'Data::Reach/use_overloads'} // 1; # default
|
67
|
52
|
|
100
|
|
|
138
|
my $peek_blessed = $hint_hash->{'Data::Reach/peek_blessed'} // 1; # default
|
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# choice 1 : call named method in object
|
70
|
52
|
|
100
|
|
|
150
|
my $meth_name = $hint_hash->{'Data::Reach/reach_method'} || '';
|
71
|
52
|
100
|
|
|
|
208
|
return $obj->$meth_name($key) if $obj->can($meth_name);
|
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
# choice 2 : use overloaded methods -- active by default
|
74
|
43
|
100
|
|
|
|
89
|
if ($use_overloads) {
|
75
|
|
|
|
|
|
|
# overloaded array dereferencing is tried first but only if the key is numeric.
|
76
|
|
|
|
|
|
|
# Otherwise, the hash dereferencing is tried.
|
77
|
41
|
100
|
100
|
|
|
101
|
return $obj->[$key] if overload::Method($obj, '@{}')
|
78
|
|
|
|
|
|
|
&& $key =~ /^-?\d+$/;
|
79
|
9
|
100
|
|
|
|
400
|
return $obj->{$key} if overload::Method($obj, '%{}');
|
80
|
|
|
|
|
|
|
}
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# choice 3 : use the object's internal representation -- active by default
|
83
|
9
|
100
|
|
|
|
221
|
if ($peek_blessed) {
|
84
|
7
|
|
|
|
|
14
|
return _step_down_raw($obj, $key);
|
85
|
|
|
|
|
|
|
}
|
86
|
|
|
|
|
|
|
else {
|
87
|
2
|
|
|
|
|
279
|
croak "cannot reach '$key' within an object of class " . ref $obj;
|
88
|
|
|
|
|
|
|
}
|
89
|
|
|
|
|
|
|
}
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
#======================================================================
|
93
|
|
|
|
|
|
|
# map_paths()
|
94
|
|
|
|
|
|
|
#======================================================================
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
sub map_paths (&+;$$$); # the prototype must be declared beforehand, because the sub is recursive
|
97
|
|
|
|
|
|
|
sub map_paths (&+;$$$) {
|
98
|
124
|
|
|
124
|
1
|
2814
|
my ($coderef, $tree, $max_depth, $path, $recurse)= @_;
|
99
|
124
|
|
100
|
|
|
239
|
$max_depth //= -1;
|
100
|
124
|
|
100
|
|
|
213
|
$path //= []; # only used for recursive calls
|
101
|
124
|
|
100
|
|
|
569
|
$recurse //= reftype $tree // ''; # only used for recursive calls
|
|
|
|
100
|
|
|
|
|
102
|
|
|
|
|
|
|
|
103
|
124
|
|
|
|
|
536
|
my $hint_hash = (caller(1))[10];
|
104
|
124
|
|
|
|
|
288
|
my $ignore_empty_subtrees = ! $hint_hash->{'Data::Reach/keep_empty_subtrees'};
|
105
|
|
|
|
|
|
|
|
106
|
124
|
50
|
|
|
|
209
|
if ($max_depth) {
|
107
|
124
|
100
|
100
|
|
|
608
|
if ($recurse eq 'ARRAY' and (@$tree or $ignore_empty_subtrees)) {
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
|
66
|
|
|
|
|
108
|
9
|
|
|
|
|
51
|
return map {map_paths(\&$coderef, $tree->[$_], $max_depth-1, [@$path, $_])} 0 .. $#$tree;
|
|
62
|
|
|
|
|
408
|
|
109
|
|
|
|
|
|
|
}
|
110
|
|
|
|
|
|
|
elsif ($recurse eq 'HASH' and (my @k = sort keys %$tree or $ignore_empty_subtrees)) {
|
111
|
19
|
|
|
|
|
41
|
return map {map_paths(\&$coderef, $tree->{$_}, $max_depth-1, [@$path, $_])} @k;
|
|
50
|
|
|
|
|
342
|
|
112
|
|
|
|
|
|
|
}
|
113
|
|
|
|
|
|
|
elsif (blessed $tree) {
|
114
|
|
|
|
|
|
|
# try to call named method in object
|
115
|
4
|
100
|
|
|
|
15
|
if (my $meth_name = $hint_hash->{'Data::Reach/paths_method'}) {
|
116
|
1
|
50
|
|
|
|
6
|
if ($tree->can($meth_name)) {
|
117
|
1
|
|
|
|
|
5
|
my @paths = $tree->$meth_name();
|
118
|
1
|
|
|
|
|
7
|
return map {map_paths(\&$coderef, reach($tree, $_), $max_depth-1, [@$path, $_])} @paths;
|
|
4
|
|
|
|
|
27
|
|
119
|
|
|
|
|
|
|
}
|
120
|
|
|
|
|
|
|
}
|
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# otherwise, try to use overloaded methods, or else use the object's internal representation (if allowed)
|
123
|
3
|
|
100
|
|
|
33
|
my $use_overloads = $hint_hash->{'Data::Reach/use_overloads'} // 1; # default
|
124
|
3
|
|
100
|
|
|
19
|
my $peek_blessed = $hint_hash->{'Data::Reach/peek_blessed'} // 1; # default
|
125
|
3
|
50
|
66
|
|
|
21
|
$recurse = $use_overloads && overload::Method($tree, '@{}') ? 'ARRAY'
|
|
|
50
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
126
|
|
|
|
|
|
|
: $use_overloads && overload::Method($tree, '%{}') ? 'HASH'
|
127
|
|
|
|
|
|
|
: $peek_blessed ? reftype $tree
|
128
|
|
|
|
|
|
|
: undef;
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# recursive call if appropriate
|
131
|
3
|
100
|
|
|
|
122
|
return map_paths(\&$coderef, $tree, $max_depth, $path, $recurse) if $recurse;
|
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# if all else failed, treat this object as an opaque leaf (see base case below)
|
134
|
|
|
|
|
|
|
}
|
135
|
|
|
|
|
|
|
}
|
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# base case
|
138
|
93
|
|
|
|
|
140
|
for ($tree) {return $coderef->(@$path)}; # @_ contains the path, $_ contains the leaf
|
|
93
|
|
|
|
|
200
|
|
139
|
|
|
|
|
|
|
}
|
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
#======================================================================
|
144
|
|
|
|
|
|
|
# each_path()
|
145
|
|
|
|
|
|
|
#======================================================================
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub each_path (+;$) {
|
148
|
122
|
|
|
122
|
1
|
5239
|
my ($tree, $max_depth) = @_;
|
149
|
122
|
|
100
|
|
|
230
|
$max_depth //= -1;
|
150
|
122
|
|
|
|
|
508
|
my $hint_hash = (caller(1))[10];
|
151
|
122
|
|
100
|
|
|
463
|
my $use_overloads = $hint_hash->{'Data::Reach/use_overloads'} // 1; # default
|
152
|
122
|
|
100
|
|
|
286
|
my $peek_blessed = $hint_hash->{'Data::Reach/peek_blessed'} // 1; # default
|
153
|
122
|
|
|
|
|
162
|
my $keep_empty_subtrees = $hint_hash->{'Data::Reach/keep_empty_subtrees'};
|
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
# local boolean variable to avoid returning the same result multiple times
|
156
|
122
|
|
|
|
|
147
|
my $is_consumed = 0;
|
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# closure to be used at tree leaves
|
159
|
122
|
100
|
|
186
|
|
319
|
my $leaf = sub {return $is_consumed++ ? () : ([], $tree)};
|
|
186
|
|
|
|
|
473
|
|
160
|
|
|
|
|
|
|
|
161
|
122
|
|
|
|
|
185
|
my $paths_method = $hint_hash->{'Data::Reach/paths_method'};
|
162
|
122
|
50
|
66
|
|
|
349
|
my $recurse = !blessed $tree ? reftype $tree
|
|
|
50
|
66
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
163
|
|
|
|
|
|
|
: $paths_method && $tree->can($paths_method) ? 'OBJECT'
|
164
|
|
|
|
|
|
|
: $use_overloads && overload::Method($tree, '@{}') ? 'ARRAY'
|
165
|
|
|
|
|
|
|
: $use_overloads && overload::Method($tree, '%{}') ? 'HASH'
|
166
|
|
|
|
|
|
|
: $peek_blessed ? reftype $tree
|
167
|
|
|
|
|
|
|
: undef;
|
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# either this tree is a leaf, or we must recurse into subtrees
|
170
|
122
|
100
|
66
|
|
|
543
|
if (!$recurse || $recurse !~ /^(OBJECT|HASH|ARRAY)$/ || !$max_depth) {
|
171
|
91
|
|
|
|
|
197
|
return $leaf;
|
172
|
|
|
|
|
|
|
}
|
173
|
|
|
|
|
|
|
else {
|
174
|
31
|
50
|
|
|
|
243
|
my @paths = $recurse eq 'OBJECT' ? $tree->$paths_method()
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
175
|
|
|
|
|
|
|
: $recurse eq 'HASH' ? sort keys %$tree
|
176
|
|
|
|
|
|
|
: $recurse eq 'ARRAY' ? (0 .. $#$tree)
|
177
|
|
|
|
|
|
|
: ();
|
178
|
31
|
100
|
100
|
|
|
133
|
if (!@paths && $keep_empty_subtrees) {
|
179
|
2
|
|
|
|
|
7
|
return $leaf;
|
180
|
|
|
|
|
|
|
}
|
181
|
|
|
|
|
|
|
else {
|
182
|
29
|
|
|
|
|
38
|
my $next_subpath; # iterator into next subtree
|
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
return sub {
|
185
|
260
|
|
|
260
|
|
742
|
while (1) {
|
186
|
376
|
100
|
|
|
|
603
|
if (!$next_subpath) { # if there is no current iterator
|
187
|
145
|
100
|
66
|
|
|
419
|
if (!$is_consumed && @paths) { # if there is a chance to get a new iterator
|
188
|
116
|
|
|
|
|
202
|
my $subtree = reach $tree, $paths[0];
|
189
|
116
|
|
|
|
|
220
|
$next_subpath = each_path($subtree, $max_depth-1); # build an iterator on next subtree
|
190
|
|
|
|
|
|
|
}
|
191
|
|
|
|
|
|
|
else { # end of data
|
192
|
29
|
|
|
|
|
36
|
$is_consumed++;
|
193
|
29
|
|
|
|
|
57
|
return ();
|
194
|
|
|
|
|
|
|
}
|
195
|
|
|
|
|
|
|
}
|
196
|
347
|
100
|
|
|
|
493
|
if (my ($subpath, $subval) = $next_subpath->()) { # try to get content from the current iterator
|
197
|
231
|
|
|
|
|
664
|
return ([$paths[0], @$subpath], $subval); # found a path, return it
|
198
|
|
|
|
|
|
|
}
|
199
|
|
|
|
|
|
|
else { # mark that the iterator on this subtree ..
|
200
|
116
|
|
|
|
|
349
|
$next_subpath = undef; # .. is finished and move to the next data item
|
201
|
116
|
|
|
|
|
182
|
shift @paths;
|
202
|
|
|
|
|
|
|
}
|
203
|
|
|
|
|
|
|
}
|
204
|
|
|
|
|
|
|
}
|
205
|
29
|
|
|
|
|
216
|
}
|
206
|
|
|
|
|
|
|
}
|
207
|
|
|
|
|
|
|
}
|
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
#======================================================================
|
213
|
|
|
|
|
|
|
# class methods: import and unimport
|
214
|
|
|
|
|
|
|
#======================================================================
|
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
# the 'import' method does 2 things : a) export the required functions,
|
217
|
|
|
|
|
|
|
# like the regular Exporter, but possibly with a change of name;
|
218
|
|
|
|
|
|
|
# b) implement optional changes to the algorithm, lexically scoped
|
219
|
|
|
|
|
|
|
# through the %^H hint hash (see L).
|
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
my $exported_functions = qr/^(?: reach | each_path | map_paths )$/x;
|
222
|
|
|
|
|
|
|
my $hint_options = qr/^(?: peek_blessed | use_overloads | keep_empty_subtrees )$/x;
|
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub import {
|
225
|
15
|
|
|
15
|
|
138
|
my $class = shift;
|
226
|
15
|
|
|
|
|
36
|
my $pkg = caller;
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
# defaults
|
229
|
15
|
100
|
|
|
|
59
|
my %export_as = map {($_ => $_)} qw/reach each_path map_paths/ if !@_;
|
|
9
|
|
|
|
|
30
|
|
230
|
15
|
|
|
|
|
34
|
my $last_func = 'reach';
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# loop over args passed to import()
|
233
|
15
|
|
|
|
|
82
|
while (my $option = shift) {
|
234
|
14
|
100
|
|
|
|
165
|
if ($option =~ $exported_functions) {
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
235
|
3
|
|
|
|
|
8
|
$export_as{$option} = $option;
|
236
|
3
|
|
|
|
|
10
|
$last_func = $option;
|
237
|
|
|
|
|
|
|
}
|
238
|
|
|
|
|
|
|
elsif ($option eq 'as') {
|
239
|
3
|
100
|
|
|
|
192
|
my $alias = shift
|
240
|
|
|
|
|
|
|
or croak "use Data::Reach : no export name after 'as'";
|
241
|
2
|
|
|
|
|
7
|
$export_as{$last_func} = $alias;
|
242
|
|
|
|
|
|
|
}
|
243
|
|
|
|
|
|
|
elsif ($option =~ /^(reach|call)_method$/) {
|
244
|
5
|
50
|
|
|
|
42
|
warn q{"use Data::Reach call_method => .." is obsolete; use "reach_method => .."} if $1 eq 'call';
|
245
|
5
|
50
|
|
|
|
19
|
my $method = shift
|
246
|
|
|
|
|
|
|
or croak "use Data::Reach : no method name after 'reach_method'";
|
247
|
5
|
|
|
|
|
35
|
$^H{"Data::Reach/reach_method"} = $method;
|
248
|
|
|
|
|
|
|
}
|
249
|
|
|
|
|
|
|
elsif ($option eq 'paths_method') {
|
250
|
1
|
50
|
|
|
|
4
|
my $method = shift
|
251
|
|
|
|
|
|
|
or croak "use Data::Reach : no method name after 'paths_method'";
|
252
|
1
|
|
|
|
|
6
|
$^H{"Data::Reach/paths_method"} = $method;
|
253
|
|
|
|
|
|
|
}
|
254
|
|
|
|
|
|
|
elsif ($option =~ $hint_options) {
|
255
|
2
|
|
|
|
|
27
|
$^H{"Data::Reach/$option"} = 1;
|
256
|
|
|
|
|
|
|
}
|
257
|
|
|
|
|
|
|
else {
|
258
|
0
|
|
|
|
|
0
|
croak "use Data::Reach : unknown option : $option";
|
259
|
|
|
|
|
|
|
}
|
260
|
|
|
|
|
|
|
}
|
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# export into caller's package, under the required alias names
|
263
|
14
|
|
|
|
|
432
|
while (my ($func, $alias) = each %export_as) {
|
264
|
9
|
|
|
9
|
|
17311
|
no strict 'refs';
|
|
9
|
|
|
|
|
20
|
|
|
9
|
|
|
|
|
1817
|
|
265
|
17
|
50
|
|
|
|
51
|
*{$pkg . "::" . $alias} = \&$func if $alias;
|
|
17
|
|
|
|
|
6164
|
|
266
|
|
|
|
|
|
|
}
|
267
|
|
|
|
|
|
|
}
|
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub unimport {
|
271
|
5
|
|
|
5
|
|
3666
|
my $class = shift;
|
272
|
5
|
|
|
|
|
21
|
while (my $option = shift) {
|
273
|
7
|
50
|
|
|
|
1150
|
$^H{"Data::Reach/$option"} = '' if $option =~ $hint_options;
|
274
|
|
|
|
|
|
|
# NOTE : mark with a false value, instead of deleting from the
|
275
|
|
|
|
|
|
|
# hint hash, in order to distinguish options explicitly turned off
|
276
|
|
|
|
|
|
|
# from default options
|
277
|
|
|
|
|
|
|
}
|
278
|
|
|
|
|
|
|
}
|
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
1;
|
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
__END__
|