File Coverage

blib/lib/Data/Find.pm
Criterion Covered Total %
statement 64 68 94.1
branch 20 24 83.3
condition 2 3 66.6
subroutine 17 18 94.4
pod 3 3 100.0
total 106 116 91.3


line stmt bran cond sub pod time code
1             package Data::Find;
2              
3 2     2   25356 use warnings;
  2         8  
  2         62  
4 2     2   11 use strict;
  2         5  
  2         59  
5              
6 2     2   11 use Carp;
  2         7  
  2         171  
7 2     2   2783 use Data::Dumper;
  2         26060  
  2         166  
8 2     2   21 use Scalar::Util qw( refaddr );
  2         4  
  2         227  
9              
10 2     2   11 use base qw( Exporter );
  2         4  
  2         2471  
11              
12             our @EXPORT_OK = qw( diter dfind dwith );
13              
14             =head1 NAME
15              
16             Data::Find - Find data in arbitrary data structures
17              
18             =head1 VERSION
19              
20             This document describes Data::Find version 0.03
21              
22             =cut
23              
24             our $VERSION = '0.03';
25              
26             =head1 SYNOPSIS
27              
28             use Data::Find qw( diter );
29              
30             my $data = {
31             ar => [1, 2, 3],
32             ha => {one => 1, two => 2, three => 3}
33             };
34            
35             my $iter = diter $data, 3;
36             while ( defined ( my $path = $iter->() ) ) {
37             print "$path\n";
38             }
39            
40             =head1 DESCRIPTION
41              
42             =head1 INTERFACE
43              
44             Nothing is exported by default. Use, eg,
45              
46             use Data::Find qw( dwith );
47              
48             to get the subroutines you need or call them with their fully
49             qualified name:
50              
51             my $iter = Data::Find::diter $data;
52              
53             =head2 C<< diter >>
54              
55             Given an arbitrary data structure and (optionally) an expression to
56             match against elements in that structure returns an iterator which will
57             yield the path through the data structure to each matching element:
58              
59             my $data = {
60             ar => [1, 2, 3],
61             ha => {one => 1, two => 2, three => 3}
62             };
63            
64             my $iter = diter $data, 3;
65             while ( defined ( my $path = $iter->() ) ) {
66             print "$path\n";
67             }
68              
69             would print:
70              
71             {ar}[2]
72             {ha}{one}
73              
74             In other words it returns paths to each element that contains the scalar
75             3. The returned paths can be used in conjunction with C to access
76             the matching elements.
77              
78             The match expression can be
79              
80             =over
81              
82             =item * a scalar
83              
84             =item * a regular expression
85              
86             =item * a code reference
87              
88             =item * C
89              
90             =back
91              
92             When the match expression is a code ref it will be passed each element
93             in the data structure in turn and should return true or false.
94              
95             my $iter = diter $data, sub {
96             my $v = shift;
97             defined $v && !ref $v && $v % 2 == 1;
98             };
99              
100             while ( defined ( my $path = $iter->() ) ) {
101             print "$path\n";
102             }
103              
104             Note that the match code will see I of the elements in the data
105             structure - not just the scalars.
106              
107             If the match expression is C it will match those elements whose
108             value is also C.
109              
110             =head3 Iterator
111              
112             In a scalar context the returned iterator yields successive paths
113             within the data structure. In an array context it returns the path and
114             the associated element.
115              
116             my $iter = diter $data;
117             while ( my ( $path, $obj ) = $iter->() ) {
118             print "$path, $obj\n";
119             }
120              
121             =cut
122              
123             sub diter {
124 8     8 1 13 my ( $obj, @match ) = @_;
125              
126 8 100   18   38 my $matcher = @match ? _mk_matcher( @match ) : sub { !ref shift };
  18         51  
127 8         19 my @queue = ( [$obj] );
128 8         11 my %seen = ();
129              
130             my %WALK = (
131             HASH => sub {
132 14     14   17 my ( $obj, @path ) = @_;
133 14         50 for my $key ( sort keys %$obj ) {
134 34         62 push @queue,
135             [ $obj->{$key}, @path, '{' . _fmt_key( $key ) . '}' ];
136             }
137             },
138             ARRAY => sub {
139 8     8   15 my ( $obj, @path ) = @_;
140 8         16 for my $idx ( 0 .. $#$obj ) {
141 24         77 push @queue, [ $obj->[$idx], @path, "[$idx]" ];
142             }
143             }
144 8         59 );
145              
146             return sub {
147 34     34   105 while ( my $spec = shift @queue ) {
148 66         169 my ( $obj, @path ) = @$spec;
149 66 100       109 if ( my $ref = ref $obj ) {
150 24 100       129 unless ( $seen{ refaddr $obj}++ ) {
151 22 50       46 my $handler = $WALK{$ref} or croak "Can't walk a $ref";
152 22         34 $handler->( $obj, @path );
153             }
154             }
155 66 100       110 if ( $matcher->( $obj ) ) {
156 26         66 my $path = join '', @path;
157 26 100       647 return wantarray ? ( $path, $obj ) : $path;
158             }
159             }
160 8         29 return;
161 8         35 };
162             }
163              
164             =head2 C
165              
166             Similar to C but returns an array of matching paths rather than
167             an iterator.
168              
169             =cut
170              
171             sub dfind {
172 4     4 1 4559 my $iter = diter @_;
173 4         5 my @got = ();
174 4         9 while ( defined( my $path = $iter->() ) ) {
175 13         33 push @got, $path;
176             }
177 4         56 return @got;
178             }
179              
180             =head2 C
181              
182             Similar to C but call a supplied callback with each
183             matching path.
184              
185             dwith $data, qr/nice/, sub {
186             my ( $path, $obj ) = @_;
187             print "$path, $obj\n";
188             };
189              
190             =cut
191              
192             sub dwith {
193 4     4 1 25 my $cb = pop @_;
194 4         9 my $iter = diter @_;
195 4         9 while ( my ( $path, $obj ) = $iter->() ) {
196 13         24 $cb->( $path, $obj );
197             }
198 4         46 return;
199             }
200              
201             sub _mk_matcher {
202 6     6   9 my $match = shift;
203 6 100       13 if ( ref $match ) {
204 4 100       29 if ( 'CODE' eq ref $match ) {
    50          
205 2         5 return $match;
206             }
207             elsif ( 'Regexp' eq ref $match ) {
208             return sub {
209 12     12   11 my $v = shift;
210 12 100 66     81 return unless defined $v && !ref $v;
211 6         39 return $v =~ $match;
212 2         8 };
213             }
214             }
215              
216 2 50       6 if ( defined $match ) {
217 2     18   7 return sub { shift eq $match };
  18         66  
218             }
219              
220 0     0   0 return sub { !defined shift }
221 0         0 }
222              
223             sub _fmt_key {
224 34     34   45 my $key = shift;
225 34 50       200 return $key if $key =~ /^(?:\d+|[a-z]\w*)$/i;
226 0           chomp( my $rep
227             = Data::Dumper->new( [$key] )->Purity( 1 )->Useqq( 1 )->Terse( 1 )
228             ->Dump );
229 0           return $rep;
230             }
231              
232             1;
233             __END__