File Coverage

blib/lib/Data/DeepAccess.pm
Criterion Covered Total %
statement 105 119 88.2
branch 102 138 73.9
condition 11 30 36.6
subroutine 8 8 100.0
pod 3 3 100.0
total 229 298 76.8


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