File Coverage

blib/lib/Data/DeepAccess.pm
Criterion Covered Total %
statement 117 128 91.4
branch 107 140 76.4
condition 11 30 36.6
subroutine 12 12 100.0
pod 4 4 100.0
total 251 314 79.9


line stmt bran cond sub pod time code
1             package Data::DeepAccess;
2              
3 1     1   585 use strict;
  1         7  
  1         29  
4 1     1   6 use warnings;
  1         2  
  1         26  
5 1     1   5 use Carp 'croak';
  1         2  
  1         80  
6 1     1   7 use Exporter 'import';
  1         2  
  1         52  
7 1     1   6 use Scalar::Util 'blessed';
  1         2  
  1         133  
8 1     1   545 use Sentinel 'sentinel';
  1         1038  
  1         1694  
9              
10             our $VERSION = '0.002';
11              
12             our @EXPORT_OK = qw(deep_exists deep_get deep_set deep_val);
13              
14             sub deep_exists {
15 19 50   19 1 1423 croak 'deep_exists called with no arguments' unless @_;
16 19         47 my ($parent, @keys) = @_;
17 19 100       51 return !!1 unless @keys;
18 18         47 foreach my $key_i (0..$#keys) {
19 38 100       108 return !!0 unless defined $parent;
20 36         61 my $reftype = ref $parent;
21 36         80 my $blessed = blessed $parent;
22 36 100       76 my $type = defined $blessed ? 'method' : lc $reftype;
23 36 100       112 my $type_str = length $reftype ? "$reftype ref" : 'scalar value';
24 36         57 my $key = $keys[$key_i];
25 36         45 my $lvalue;
26 36 50       73 if (ref $key eq 'HASH') {
27 0 0       0 if (exists $key->{key}) {
    0          
    0          
    0          
28 0         0 ($type, $key) = ('hash', $key->{key});
29             } elsif (exists $key->{index}) {
30 0         0 ($type, $key) = ('array', $key->{index});
31             } elsif (exists $key->{method}) {
32 0         0 ($type, $key) = ('method', $key->{method});
33             } elsif (exists $key->{lvalue}) {
34 0         0 ($type, $key, $lvalue) = ('method', $key->{lvalue}, 1);
35             } else {
36 0         0 croak q{Traversal key hashref must contain "key", "index", "method", or "lvalue"};
37             }
38             }
39 36 100       77 if ($type eq 'hash') {
    100          
    100          
40 22 50 33     82 croak qq{Can't dereference $type_str as HASH to access key "$key"} if !defined $blessed and $reftype ne 'HASH';
41 22 100       65 return !!0 unless exists $parent->{$key};
42 19 100       60 return !!1 if $key_i == $#keys;
43 14         33 $parent = $parent->{$key};
44             } elsif ($type eq 'array') {
45 8 50 33     28 croak qq{Can't dereference $type_str as ARRAY to access index $key} if !defined $blessed and $reftype ne 'ARRAY';
46 8 100       23 return !!0 unless exists $parent->[$key];
47 7 100       23 return !!1 if $key_i == $#keys;
48 5         13 $parent = $parent->[$key];
49             } elsif ($type eq 'method') {
50 3 50 33     15 croak qq{Can't call method "$key" on unblessed reference} if length $reftype and !defined $blessed;
51 3 100       28 return !!0 unless my $sub = $parent->can($key);
52 2 100       11 return !!1 if $key_i == $#keys;
53 1         5 $parent = $parent->$sub;
54             } else {
55 3         360 croak qq{Can't traverse $type_str with key "$key"};
56             }
57             }
58             }
59              
60             sub deep_get {
61 48     48 1 1707 my ($parent, @keys) = @_;
62 48 100       119 return $parent unless @keys;
63 45         112 foreach my $key_i (0..$#keys) {
64 94 100       199 return undef unless defined $parent;
65 90         154 my $reftype = ref $parent;
66 90         182 my $blessed = blessed $parent;
67 90 100       179 my $type = defined $blessed ? 'method' : lc $reftype;
68 90 100       188 my $type_str = length $reftype ? "$reftype ref" : 'scalar value';
69 90         131 my $key = $keys[$key_i];
70 90         111 my $lvalue;
71 90 100       173 if (ref $key eq 'HASH') {
72 4 100       13 if (exists $key->{key}) {
    50          
    0          
    0          
73 1         3 ($type, $key) = ('hash', $key->{key});
74             } elsif (exists $key->{index}) {
75 3         7 ($type, $key) = ('array', $key->{index});
76             } elsif (exists $key->{method}) {
77 0         0 ($type, $key) = ('method', $key->{method});
78             } elsif (exists $key->{lvalue}) {
79 0         0 ($type, $key, $lvalue) = ('method', $key->{lvalue}, 1);
80             } else {
81 0         0 croak q{Traversal key hashref must contain "key", "index", "method", or "lvalue"};
82             }
83             }
84 90 100       177 if ($type eq 'hash') {
    100          
    100          
85 55 50 33     183 croak qq{Can't dereference $type_str as HASH to access key "$key"} if !defined $blessed and $reftype ne 'HASH';
86 55 100       142 return undef unless exists $parent->{$key};
87 49 100       162 return $parent->{$key} if $key_i == $#keys;
88 33         98 $parent = $parent->{$key};
89             } elsif ($type eq 'array') {
90 21 50 33     68 croak qq{Can't dereference $type_str as ARRAY to access index $key} if !defined $blessed and $reftype ne 'ARRAY';
91 21 100       63 return undef unless exists $parent->[$key];
92 19 100       56 return $parent->[$key] if $key_i == $#keys;
93 13         25 $parent = $parent->[$key];
94             } elsif ($type eq 'method') {
95 8 50 33     34 croak qq{Can't call method "$key" on unblessed reference} if length $reftype and !defined $blessed;
96 8 100       39 return undef unless my $sub = $parent->can($key);
97 6 100       18 return $parent->$sub if $key_i == $#keys;
98 3         7 $parent = $parent->$sub;
99             } else {
100 6         511 croak qq{Can't traverse $type_str with key "$key"};
101             }
102             }
103             }
104              
105             sub deep_set {
106 27 50   27 1 15875 croak 'deep_set called with no arguments' unless @_;
107 27 50       60 croak 'deep_set requires a value to set' unless @_ > 1;
108 27         52 my $parent_ref = \$_[0];
109 27         75 my @keys = @_[1..$#_-1];
110 27         52 my $value = $_[-1];
111 27 100       64 return $$parent_ref = $value unless @keys;
112 25         70 foreach my $key_i (0..$#keys) {
113 46         100 my $reftype = ref $$parent_ref;
114 46         102 my $blessed = blessed $$parent_ref;
115 46 100       99 my $type = defined $blessed ? 'method' : lc $reftype;
116 46 100       98 my $type_str = length $reftype ? "$reftype ref" : 'scalar value';
117 46 50 66     167 $type = 'hash' if !length $type and !defined $$parent_ref;
118 46         73 my $key = $keys[$key_i];
119 46         63 my $lvalue;
120 46 100       92 if (ref $key eq 'HASH') {
121 13 100       44 if (exists $key->{key}) {
    100          
    100          
    50          
122 2         7 ($type, $key) = ('hash', $key->{key});
123             } elsif (exists $key->{index}) {
124 7         18 ($type, $key) = ('array', $key->{index});
125             } elsif (exists $key->{method}) {
126 2         6 ($type, $key) = ('method', $key->{method});
127             } elsif (exists $key->{lvalue}) {
128 2         7 ($type, $key, $lvalue) = ('method', $key->{lvalue}, 1);
129             } else {
130 0         0 croak q{Traversal key hashref must contain "key", "index", "method", or "lvalue"};
131             }
132             }
133 46 100       92 if ($type eq 'hash') {
    100          
    50          
134 24 100       36 if (defined $$parent_ref) {
135 14 50 33     85 croak qq{Can't dereference $type_str as HASH to access key "$key"} if !defined $blessed and $reftype ne 'HASH';
136             } else {
137 10         17 $$parent_ref = {};
138             }
139 24 100       130 return $$parent_ref->{$key} = $value if $key_i == $#keys;
140 12         32 $parent_ref = \$$parent_ref->{$key};
141             } elsif ($type eq 'array') {
142 11 100       22 if (defined $$parent_ref) {
143 6 50 33     28 croak qq{Can't dereference $type_str as ARRAY to access index $key} if !defined $blessed and $reftype ne 'ARRAY';
144             } else {
145 5         10 $$parent_ref = [];
146             }
147 11 100       51 return $$parent_ref->[$key] = $value if $key_i == $#keys;
148 6         18 $parent_ref = \$$parent_ref->[$key];
149             } elsif ($type eq 'method') {
150 11 100       390 croak qq{Can't call method "$key" on an undefined value} unless defined $$parent_ref;
151 7 50 33     18 croak qq{Can't call method "$key" on unblessed reference} if !defined $blessed and length $reftype;
152 7 50       14 my $package = length $reftype ? $reftype : $$parent_ref;
153 7 50       30 croak qq{Can't locate object method "$key" via package "$package"} unless my $sub = $$parent_ref->can($key);
154 7 100       17 if ($key_i == $#keys) {
155 4 50       15 $lvalue ? $$parent_ref->$sub = $value : $$parent_ref->$sub($value);
156 4         45 return $value;
157             }
158 3         10 $parent_ref = \$$parent_ref->$sub;
159             } else {
160 0         0 croak qq{Can't traverse $type_str with key "$key"};
161             }
162             }
163             }
164              
165             sub deep_val :lvalue {
166 31 50   31 1 26986 croak 'deep_val called with no arguments' unless @_;
167 31         60 my $parent_ref = \$_[0];
168 31         114 my @keys = @_[1..$#_];
169 29     29   543 sentinel get => sub { deep_get $$parent_ref, @keys },
170 31     12   289 set => sub { deep_set $$parent_ref, @keys, $_[0] };
  12         37  
171             }
172              
173             1;
174              
175             =head1 NAME
176              
177             Data::DeepAccess - Access or set data in deep structures
178              
179             =head1 SYNOPSIS
180              
181             use Data::DeepAccess qw(deep_exists deep_get deep_set);
182              
183             my %things;
184             deep_set(\%things, qw(foo bar), 42);
185             say $things{foo}{bar}; # 42
186              
187             $things{foo}{baz} = ['a'..'z'];
188             say deep_get(\%things, qw(foo baz 5)); # f
189              
190             deep_set(\%things, qw(foo foo), undef);
191             say deep_exists(\%things, qw(foo foo)); # 1
192              
193             deep_val(\%things, qw(bar bar)) = 'lvalue';
194             say deep_val(\%things, qw(bar bar)); # lvalue
195              
196             =head1 DESCRIPTION
197              
198             Provides the functions L, L, L, and
199             L that traverse nested data structures to retrieve or set the
200             value located by a list of keys.
201              
202             When traversing, keys are applied according to the type of referenced data
203             structure. A hash will be traversed by hash key, an array by array index, and
204             an object by method call (scalar context with no arguments). If the data
205             structure is not defined, it will be traversed as a hash by default (but not
206             vivified unless in a set operation).
207              
208             You can override how a key is applied, and thus what type of structure is
209             vivified if necessary, by passing the key in a hashref as the value of C
210             (hash) or C (array).
211              
212             deep_set(my $structure, 'foo', 42); # {foo => 42}
213             deep_set(my $structure, {index => 1}, 42); # [undef, 42]
214             deep_set($object, {key => 'foo'}, 42); # sets $object->{foo} directly
215              
216             For the rare case it's needed, you can also use one of the keys C or
217             C.
218              
219             deep_set($object, {method => 'foo'}, 42); # $object->foo(42)
220             deep_set($object, {lvalue => 'foo'}, 42); # $object->foo = 42
221              
222             Attempting to traverse intermediate structures that are defined and not a
223             reference to a hash, array, or object will result in an exception.
224              
225             If an object method call is the last key in a set operation or the next
226             structure must be vivified, the method will be called passing the new value as
227             an argument or assigned if it is treated as an lvalue. Attempting to call a
228             method on an undefined value or unblessed ref in a set operation will result in
229             an exception.
230              
231             Currently, undefined results from non-lvalue method calls are not vivified back
232             to the object to support setting a further nested value. This may be supported
233             in the future.
234              
235             =head1 FUNCTIONS
236              
237             All functions are exported individually.
238              
239             =head2 deep_exists
240              
241             my $bool = deep_exists($structure, @keys);
242              
243             Returns a true value if the value exists in the referenced structure located by
244             the given keys. No intermediate structures will be altered or vivified; a
245             missing structure will result in a false return value.
246              
247             Array indexes are tested for existence with L, like hash
248             keys, which may have surprising results in sparse arrays. Avoid this situation.
249              
250             Object methods are tested for existence with C<< $object->can($method) >>.
251              
252             If no keys are passed, returns true.
253              
254             =head2 deep_get
255              
256             my $value = deep_get($structure, @keys);
257              
258             Retrieves the value from the referenced structure located by the given keys. No
259             intermediate structures will be altered or vivified; a missing structure will
260             result in C.
261              
262             If no keys are passed, returns the original structure.
263              
264             =head2 deep_set
265              
266             $new_value = deep_set($structure, @keys, $new_value);
267              
268             Sets the value in the referenced structure located by the given keys. Missing
269             intermediate structures will be vivified to hashrefs by default. If the
270             structure is undefined, it must be an assignable lvalue to be vivified.
271              
272             If no keys are passed, the structure must be an assignable lvalue and will be
273             assigned the value directly.
274              
275             =head2 deep_val
276              
277             my $value = deep_val($structure, @keys);
278             deep_val($structure, @keys) = $new_value;
279              
280             L accessor that is equivalent to
281             L when a value is retrieved from it, or L when a
282             value is assigned to it (passing the assigned value as the final argument).
283              
284             The passed keys are used to traverse the structure at the time of retrieval or
285             assignment, not when the function is called. This subtle difference may matter
286             if the lvalue is preserved for later access, such as via reference.
287              
288             =head1 BUGS
289              
290             Report any issues on the public bugtracker.
291              
292             =head1 AUTHOR
293              
294             Dan Book
295              
296             =head1 CONTRIBUTORS
297              
298             =over
299              
300             =item Matt S Trout (mst)
301              
302             =back
303              
304             =head1 COPYRIGHT AND LICENSE
305              
306             This software is Copyright (c) 2019 by Dan Book.
307              
308             This is free software, licensed under:
309              
310             The Artistic License 2.0 (GPL Compatible)
311              
312             =head1 SEE ALSO
313              
314             Many prior modules for accessing specific items within a deep structure, which
315             the author determined were either insufficient or overcomplex.
316              
317             =over
318              
319             =item * L
320              
321             =item * L
322              
323             =item * L
324              
325             =item * L
326              
327             =item * L
328              
329             =back