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