File Coverage

blib/lib/Hash/Digger.pm
Criterion Covered Total %
statement 43 44 97.7
branch 11 12 91.6
condition 2 3 66.6
subroutine 13 13 100.0
pod 3 3 100.0
total 72 75 96.0


line stmt bran cond sub pod time code
1             package Hash::Digger;
2              
3 2     2   1853 use 5.006;
  2         12  
4 2     2   10 use strict;
  2         4  
  2         40  
5 2     2   16 use warnings;
  2         9  
  2         63  
6 2     2   10 use Carp 'croak';
  2         4  
  2         267  
7              
8             =head1 NAME
9              
10             Hash::Digger - Access nested hash structures without vivification
11              
12             =head1 VERSION
13              
14             Version 0.0.5
15              
16             =cut
17              
18             our $VERSION = '0.0.5';
19              
20             =head1 SYNOPSIS
21              
22             Allows accessing hash structures without triggering autovivification.
23              
24             my %hash;
25              
26             $hash{'foo'}{'bar'} = 'baz';
27              
28             diggable \%hash, 'foo', 'bar';
29             # Truthy
30              
31             diggable \%hash, 'xxx', 'yyy';
32             # Falsey
33              
34             dig \%hash, 'foo', 'bar';
35             # 'baz'
36              
37             dig \%hash, 'foo', 'bar', 'xxx';
38             # undef
39              
40             exhume 'some default', \%hash, 'foo', 'bar';
41             # 'baz'
42              
43             exhume 'some default', \%hash, 'foo', 'xxx';
44             # 'some default'
45              
46             # Hash structure has not changed:
47             use Data::Dumper;
48             Dumper \%hash;
49             # $VAR1 = {
50             # 'foo' => {
51             # 'bar' => 'baz'
52             # }
53             # };
54              
55             =head1 EXPORT
56              
57             dig, diggable, exhume
58              
59             =cut
60              
61 2     2   14 use Exporter 'import';
  2         4  
  2         468  
62             our @EXPORT_OK = qw(dig diggable exhume);
63              
64             =head1 SUBROUTINES/METHODS
65              
66             =head2 diggable
67              
68             Check if given path is diggable on the hash (`exists` equivalent)
69              
70             =cut
71              
72             sub diggable {
73 4     4 1 14 my ( $root, @path ) = @_;
74 4         10 return ( _traverse_hash( $root, @path ) )[1];
75             }
76              
77             =head2 dig
78              
79             Dig the hash and return the value. If the path is not valid, it returns undef.
80              
81             =cut
82              
83             sub dig {
84 8     8 1 215 my ( $root, @path ) = @_;
85 8         22 return exhume( undef, $root, @path );
86             }
87              
88             =head2 exhume
89              
90             Dig the hash and return the value. If the path is not valid, it returns a default value.
91              
92             =cut
93              
94             sub exhume {
95 9     9 1 22 my ( $default, $root, @path ) = @_;
96 9         20 my $value = ( _traverse_hash( $root, @path ) )[0];
97 6 100       35 return defined $value ? $value : $default;
98             }
99              
100             ## no critic (ValuesAndExpressions::ProhibitConstantPragma)
101 2     2   15 use constant E_NO_ROOT => 'Root node is undefined';
  2         3  
  2         216  
102 2     2   14 use constant E_NO_ROOT_HASH => 'Root node is not a hash reference';
  2         3  
  2         121  
103 2     2   14 use constant E_NO_PATH => 'No path to exhume';
  2         2  
  2         505  
104              
105             # Traverse hash for the given path and return the data.
106             # Last item could be `undef` as in `$hash{'foo'}{'bar'} = undef`,
107             # so we also need to return if the element exists or not
108             sub _traverse_hash {
109 13     13   27 my ( $root, @path ) = @_;
110 13         18 my $exists = 0;
111              
112 13 100       52 croak E_NO_ROOT if !defined $root;
113 12 100       18 croak E_NO_ROOT_HASH if !_is_hash_reference($root);
114 11 100       47 croak E_NO_PATH if @path == 0;
115              
116 10         24 while ( my $element = shift @path ) {
117 28 100       51 if ( !exists $root->{$element} ) {
118 5         23 return ( undef, q() );
119             }
120              
121 23         34 $root = $root->{$element};
122              
123 23 50 66     35 if ( !_is_hash_reference($root) && @path > 0 ) {
124 0         0 return ( undef, q() );
125             }
126             }
127              
128 5         22 return ( $root, 1 );
129             }
130              
131             sub _is_hash_reference {
132 35     35   48 my $item = shift;
133 35         147 return ref $item eq ref {};
134             }
135              
136             =head1 REPOSITORY
137              
138             L
139              
140             =head1 AUTHOR
141              
142             Julio de Castro, C<< >>
143              
144             =head1 BUGS
145              
146             Please report any bugs or feature requests to C, or through
147             the web interface at L. I will be notified, and then you'll
148             automatically be notified of progress on your bug as I make changes.
149              
150             =head1 SUPPORT
151              
152             You can find documentation for this module with the perldoc command.
153              
154             perldoc Hash::Digger
155              
156              
157             You can also look for information at:
158              
159             =over 4
160              
161             =item * RT: CPAN's request tracker (report bugs here)
162              
163             L
164              
165             =item * CPAN Ratings
166              
167             L
168              
169             =item * Search CPAN
170              
171             L
172              
173             =back
174              
175             =head1 ACKNOWLEDGEMENTS
176              
177              
178             =head1 LICENSE AND COPYRIGHT
179              
180             This software is Copyright (c) 2020 by Julio de Castro.
181              
182             This is free software, licensed under:
183              
184             The Artistic License 2.0 (GPL Compatible)
185              
186              
187             =cut
188              
189             1; # End of Hash::Digger