File Coverage

blib/lib/Data/Leaf/Walker.pm
Criterion Covered Total %
statement 131 136 96.3
branch 42 50 84.0
condition 13 14 92.8
subroutine 14 14 100.0
pod 10 10 100.0
total 210 224 93.7


line stmt bran cond sub pod time code
1             package Data::Leaf::Walker;
2              
3 6     6   244866 use warnings;
  6         16  
  6         199  
4 6     6   41 use strict;
  6         12  
  6         10434  
5              
6             =head1 NAME
7              
8             Data::Leaf::Walker - Walk the leaves of arbitrarily deep nested data structures.
9              
10             =head1 VERSION
11              
12             Version 0.21
13              
14             =cut
15              
16             our $VERSION = '0.21';
17              
18             =head1 SYNOPSIS
19              
20             $data = {
21             a => 'hash',
22             or => [ 'array', 'ref' ],
23             with => { arbitrary => 'nesting' },
24             };
25              
26             $walker = Data::Leaf::Walker->new( $data );
27            
28             while ( my ( $k, $v ) = $walker->each )
29             {
30             print "@{ $k } : $v\n";
31             }
32            
33             ## output might be
34             ## a : hash
35             ## or 0 : array
36             ## or 1 : ref
37             ## with arbitrary : nesting
38              
39             =head1 DESCRIPTION
40              
41             C provides simplified access to nested data structures. It
42             operates on key paths in place of keys. A key path is a list of HASH and ARRAY
43             indexes which define a path through your data structure. For example, in the
44             following data structure, the value corresponding to key path C<[ 0, 'foo' ]> is
45             C:
46              
47             $aoh = [ { foo => 'bar' } ];
48              
49             You can get and set that value like so:
50              
51             $walker = Data::Leaf::Walker->new( $aoh ); ## create the walker
52             $bar = $walker->fetch( [ 0, 'foo' ] ); ## get the value 'bar'
53             $walker->store( [ 0, 'foo'], 'baz' ); ## change value to 'baz'
54              
55             =head1 FUNCTIONS
56              
57             =head2 new( $data )
58              
59             Construct a new C instance.
60              
61             $data = {
62             a => 'hash',
63             or => [ 'array', 'ref' ],
64             with => { arbitrary => 'nesting' },
65             };
66              
67             $walker = Data::Leaf::Walker->new( $data );
68            
69             =head3 Options
70              
71             =over 3
72              
73             =item * max_depth: the C, C and C methods iterate no deeper
74             than C keys deep.
75              
76             =item * min_depth: the C, C and C methods iterate no shallower
77             than C keys deep.
78              
79             =back
80              
81             =cut
82              
83             sub new
84             {
85 31     31 1 18013 my ( $class, $data, %opts ) = @_;
86 31         228 my $self = bless
87             {
88             _data => $data,
89             _data_stack => [],
90             _key_path => [],
91             _array_tracker => {},
92             _opts => {},
93             }, $class;
94 31         115 $self->opts( %opts );
95 31         75 return $self;
96             }
97              
98             =head2 each()
99              
100             Iterates over the leaf values of the nested HASH or ARRAY structures. Much like
101             the built-in C function, the iterators for individual structures are
102             global and the caller should be careful about what state they are in. Invoking
103             the C or C methods will reset the iterators. In scalar
104             context it returns the key path only.
105              
106             while ( my ( $key_path, $value ) = $walker->each )
107             {
108             ## do something
109             }
110              
111             =cut
112              
113             sub each
114             {
115 282     282 1 2664 my ( $self ) = @_;
116            
117 282 100       305 if ( ! @{ $self->{_data_stack} } )
  282         704  
118             {
119 49         58 push @{ $self->{_data_stack} }, $self->{_data};
  49         107  
120             }
121            
122 282         563 return $self->_iterate;
123             }
124              
125             =head2 keys()
126              
127             Returns the list of all key paths.
128              
129             @key_paths = $walker->keys;
130              
131             =cut
132              
133             sub keys
134             {
135 28     28 1 10429 my ( $self ) = @_;
136              
137 28         35 my @keys;
138              
139 28         1275 while ( defined( my $key = $self->each ) )
140             {
141 61         161 push @keys, $key;
142             }
143            
144 28         71 return @keys;
145             }
146            
147             =head2 values()
148              
149             Returns the list of all leaf values.
150              
151             @leaf_values = $walker->values;
152              
153             =cut
154              
155             sub values
156             {
157 3     3 1 19187 my ( $self ) = @_;
158              
159 3         8 my @values;
160              
161 3         16 while ( my ($key, $value) = $self->each )
162             {
163 57         193 push @values, $value;
164             }
165              
166 3         40 return @values;
167             }
168              
169             =head2 fetch( $key_path )
170              
171             Lookup the value corresponding to the given key path. If an individual key
172             attempts to fetch from an invalid the fetch method dies.
173              
174             $key_path = [ $key1, $index1, $index2, $key2 ];
175             $leaf = $walker->fetch( $key_path );
176              
177             =cut
178              
179             sub fetch
180             {
181 178     178 1 74879 my ( $self, $key_path ) = @_;
182              
183 178         269 my $data = $self->{_data};
184            
185 178         222 for my $key ( @{ $key_path } )
  178         318  
186             {
187              
188 431         585 my $type = ref $data;
189            
190 431 100       823 if ( $type eq 'ARRAY' )
    100          
191             {
192 282         602 $data = $data->[$key];
193             }
194             elsif ( $type eq 'HASH' )
195             {
196 146         428 $data = $data->{$key};
197             }
198             else
199             {
200 3         34 die "Error: cannot lookup key ($key) in invalid ref type ($type)";
201             }
202            
203             }
204            
205 175         438 return $data;
206             }
207              
208             =head2 store( $key_path, $value )
209              
210             Set the value for the corresponding key path.
211              
212             $key_path = [ $key1, $index1, $index2, $key2 ];
213             $walker->store( $key_path, $value );
214              
215             =cut
216              
217             sub store
218             {
219 54     54 1 2335 my ( $self, $key_path, $value ) = @_;
220            
221 54         56 my @store_path = @{ $key_path };
  54         123  
222            
223 54         67 my $twig_key = pop @store_path;
224            
225 54         111 my $twig = $self->fetch( \@store_path );
226            
227 54 50       115 if ( ! defined $twig )
228             {
229 0         0 die "Error: cannot autovivify arbitrarily";
230             }
231            
232 54         71 my $type = ref $twig;
233            
234 54 100       115 if ( $type eq 'HASH' )
    50          
235             {
236 21         121 return $twig->{ $twig_key } = $value;
237             }
238             elsif ( $type eq 'ARRAY' )
239             {
240 33         99 return $twig->[ $twig_key ] = $value;
241             }
242            
243             }
244              
245             =head2 delete( $key_path )
246              
247             Delete the leaf key in the corresponding key path. Only works for a HASH leaf,
248             dies otherwise. Returns the deleted value.
249              
250             $key_path = [ $key1, $index1, $index2, $key2 ];
251             $old_value = $walker->delete( $key_path );
252              
253             =cut
254              
255             sub delete
256             {
257 3     3 1 1412 my ( $self, $key_path ) = @_;
258              
259 3         7 my @delete_path = @{ $key_path };
  3         10  
260            
261 3         9 my $twig_key = pop @delete_path;
262            
263 3         10 my $twig = $self->fetch( \@delete_path );
264            
265 3 50       12 defined $twig || return;
266            
267 3         7 my $type = ref $twig;
268            
269 3 50       11 if ( $type eq 'HASH' )
    0          
270             {
271 3         15 return delete $twig->{ $twig_key };
272             }
273             elsif ( $type eq 'ARRAY' )
274             {
275 0         0 die "Error: cannot delete() from an ARRAY leaf";
276             }
277            
278             }
279              
280             =head2 exists( $key_path )
281              
282             Returns true if the corresponding key path exists.
283              
284             $key_path = [ $key1, $index1, $index2, $key2 ];
285             if ( $walker->exists( $key_path ) )
286             {
287             ## do something
288             }
289              
290             =cut
291              
292             sub exists
293             {
294 15     15 1 5854 my ( $self, $key_path ) = @_;
295              
296 15         26 my $data = $self->{_data};
297            
298 15         16 for my $key ( @{ $key_path } )
  15         33  
299             {
300              
301 51         73 my $type = ref $data;
302            
303 51 100       95 if ( $type eq 'ARRAY' )
    100          
304             {
305 39 100       82 if ( exists $data->[$key] )
306             {
307 36         69 $data = $data->[$key];
308             }
309             else
310             {
311 3         9 return;
312             }
313             }
314             elsif ( $type eq 'HASH' )
315             {
316 9 50       18 if ( exists $data->{$key} )
317             {
318 0         0 $data = $data->{$key};
319             }
320             else
321             {
322 9         25 return;
323             }
324             }
325             else
326             {
327 3         11 return;
328             }
329            
330             }
331            
332 0         0 return 1;
333             }
334              
335             =head2 reset()
336              
337             Resets the current iterators. This is faster than using the C or
338             C methods to do an iterator reset.
339              
340             ## set the max depth one above the bottom, to get the twig structures
341             $key_path = $walker->each;
342             $walker->opts( max_depth => @{ $key_path } - 1 );
343             $walker->reset;
344             @twigs = $walker->values;
345              
346             =cut
347              
348             sub reset
349             {
350 7     7 1 13378 my ( $self ) = @_;
351            
352 7         13 for my $data ( @{ $self->{_data_stack} } )
  7         19  
353             {
354 21 100       71 if ( ref $data eq 'HASH' )
355             {
356 9         13 CORE::keys %{ $data };
  9         577  
357             }
358             }
359              
360 7         13 %{ $self->{_array_tracker} } = ();
  7         29  
361 7         12 @{ $self->{_data_stack} } = ();
  7         18  
362 7         11 @{ $self->{_key_path} } = ();
  7         17  
363            
364 7         41 return;
365             }
366              
367             =head2 opts()
368              
369             Change the values of the constructor options. Only given options are affected.
370             See C for a description of the options. Returns the current option hash
371             after changes are applied.
372              
373             ## change the max_depth
374             $walker->opts( max_depth => 3 );
375            
376             ## get the current options
377             %opts = $walker->opts;
378              
379             =cut
380              
381             sub opts
382             {
383 38     38 1 6453 my ( $self, %opts ) = @_;
384              
385 38 100       104 if ( CORE::keys %opts )
386             {
387              
388 5         15 for my $key ( CORE::keys %opts )
389             {
390 6         50 $self->{_opts}{$key} = $opts{$key};
391             }
392              
393             }
394              
395 38         47 return %{ $self->{_opts} };
  38         125  
396             }
397              
398             sub _iterate
399             {
400 618     618   705 my ( $self ) = @_;
401              
402             ## find the top of the stack
403 618         586 my $data = ${ $self->{_data_stack} }[-1];
  618         1003  
404            
405             ## iterate on the stack top
406 618         1244 my ( $key, $val ) = $self->_each($data);
407              
408             ## if we're at the end of the stack top
409 618 100       1537 if ( ! defined $key )
410             {
411             ## remove the stack top
412 193         182 pop @{ $self->{_data_stack} };
  193         367  
413 193         219 pop @{ $self->{_key_path} };
  193         291  
414              
415             ## iterate on the new stack top if available
416 193 100       228 if ( @{ $self->{_data_stack} } )
  193         470  
417             {
418 151         359 return $self->_iterate;
419             }
420             ## mark the stack as empty
421             ## return empty/undef
422             else
423             {
424 42         139 return;
425             }
426              
427             }
428            
429             ## _each() succeeded
430              
431             ## return right away if we're at max_depth
432 425         714 my $max_depth = $self->{_opts}{max_depth};
433 425 100 100     901 if ( defined $max_depth && @{ $self->{_key_path} } + 1 >= $max_depth )
  203         693  
434             {
435 84         87 my $key_path = [ @{ $self->{_key_path} }, $key ];
  84         196  
436 84 100       775 return wantarray ? ( $key_path, $val ) : $key_path;
437             }
438              
439             ## if the value is a HASH/ARRAY, add it to the stack and iterate
440 341 100 100     1825 if ( defined $val && ( ref $val eq 'HASH' || ref $val eq 'ARRAY' ) )
      66        
441             {
442 165         234 push @{ $self->{_data_stack} }, $val;
  165         456  
443 165         201 push @{ $self->{_key_path} }, $key;
  165         285  
444 165         483 return $self->_iterate;
445             }
446            
447             ## continue iterating if we are less than min_depth
448 176         284 my $min_depth = $self->{_opts}{min_depth};
449 176 100 100     678 if ( defined $min_depth && @{ $self->{_key_path} } + 1 < $min_depth )
  51         176  
450             {
451 20         70 return $self->_iterate;
452             }
453              
454 156         172 my $key_path = [ @{ $self->{_key_path} }, $key ];
  156         434  
455              
456 156 100       798 return wantarray ? ( $key_path, $val ) : $key_path;
457             }
458              
459             sub _each
460             {
461 618     618   841 my ( $self, $data ) = @_;
462            
463 618 100       1475 if ( ref $data eq 'HASH' )
    50          
464             {
465 247         538 return CORE::each %{ $data };
  247         911  
466             }
467             elsif ( ref $data eq 'ARRAY' )
468             {
469 371         500 my $array_tracker = $self->{_array_tracker};
470 371   100     1192 $array_tracker->{ $data } ||= 0;
471 371 100       646 if ( $array_tracker->{ $data } <= $#{ $data } )
  371         3334  
472             {
473 259         7615 my $index = $array_tracker->{ $data };
474 259         1528 ++ $array_tracker->{ $data };
475 259         794 return( $index, $data->[ $index ] );
476             }
477             else
478             {
479 112         183 $array_tracker->{ $data } = 0;
480 112         394 return;
481             }
482            
483             }
484             else
485             {
486 0           die "Error: cannot call _each() on non-HASH/non-ARRAY data record";
487             }
488            
489             }
490              
491             =head1 AUTHOR
492              
493             Dan Boorstein, C<< >>
494              
495             =head1 CAVEATS
496              
497             =head2 Global Iterators
498              
499             Because the iterators are global, data structures which contain cyclical
500             references or repeated sub structures are not handled correctly.
501              
502             =head2 Hash Iterators
503              
504             If you iterate directly over a hash which is also contained in your leaf walker
505             instance, be sure to leave it in a proper state. If that hash is a sub reference
506             within the leaf walker, calling the C or C methods, for the
507             purpose of resetting the iterator, may not be able to reach the hash. A second
508             reset attempt should work as expected. If you consistently use the leaf walker
509             instance to access the data structure, you should be fine.
510              
511             =head1 PLANS
512              
513             =over 3
514              
515             =item * add type and twig limiters for C, C, C
516              
517             =item * optional autovivification (Data::Peek, Scalar::Util, String::Numeric)
518              
519             =back
520              
521             =head1 BUGS
522              
523             Please report any bugs or feature requests to C, or through
524             the web interface at L. I will be notified, and then you'll
525             automatically be notified of progress on your bug as I make changes.
526              
527             =head1 SUPPORT
528              
529             You can find documentation for this module with the perldoc command.
530              
531             perldoc Data::Leaf::Walker
532              
533              
534             You can also look for information at:
535              
536             =over 4
537              
538             =item * RT: CPAN's request tracker
539              
540             L
541              
542             =item * AnnoCPAN: Annotated CPAN documentation
543              
544             L
545              
546             =item * CPAN Ratings
547              
548             L
549              
550             =item * Search CPAN
551              
552             L
553              
554             =back
555              
556              
557             =head1 ACKNOWLEDGEMENTS
558              
559              
560             =head1 COPYRIGHT & LICENSE
561              
562             Copyright 2009 Dan Boorstein.
563              
564             This program is free software; you can redistribute it and/or modify it
565             under the terms of either: the GNU General Public License as published
566             by the Free Software Foundation; or the Artistic License.
567              
568             See http://dev.perl.org/licenses/ for more information.
569              
570              
571             =cut
572              
573             1; # End of Data::Leaf::Walker