File Coverage

blib/lib/Data/Dmap.pm
Criterion Covered Total %
statement 21 81 25.9
branch 0 22 0.0
condition 0 3 0.0
subroutine 7 15 46.6
pod 2 2 100.0
total 30 123 24.3


line stmt bran cond sub pod time code
1             package Data::Dmap;
2              
3 1     1   24169 use warnings;
  1         2  
  1         30  
4 1     1   6 use strict;
  1         2  
  1         41  
5             require v5.10;
6 1     1   5 use feature 'switch';
  1         7  
  1         101  
7 1     1   6 use Exporter 'import';
  1         1  
  1         74  
8             our @EXPORT = qw{ dmap };
9             our @EXPORT_OK = qw{ cut };
10 1     1   5 use Carp 'croak';
  1         2  
  1         70  
11 1     1   7 use Scalar::Util qw{ reftype refaddr };
  1         1  
  1         90  
12 1     1   1011 use Try::Tiny;
  1         1896  
  1         1250  
13              
14             =head1 NAME
15              
16             Data::Dmap - just like map, but on deep data structures
17              
18             =head1 VERSION
19              
20             Version 0.08.
21              
22             =cut
23              
24             our $VERSION = '0.08';
25              
26             =head1 SYNOPSIS
27              
28             This module provides the single function C which carries out a
29             C-like operation on deep data structures.
30              
31             use Data::Dmap;
32              
33             my $foo = {
34             cars => [ 'ford', 'opel', 'BMW' ],
35             birds => [ 'cuckatoo', 'ostrich', 'frigate' ],
36             handler => sub { print "barf\n" }
37             };
38              
39             # This removes all keys named 'cars'
40             my($bar) = dmap { delete $_->{cars} if ref eq 'HASH'; $_ } $foo;
41              
42             # This replaces arrays with the number of elements they contains
43             my($other) = dmap { $_ = scalar @$_ if ref eq 'ARRAY'; $_ } $foo;
44              
45             use Data::Dumper;
46             print Dumper $other;
47             #
48             # Prints
49             # {
50             # birds => 3,
51             # handler => sub { "DUMMY" }
52             # }
53             # (Data::Dumper doesn't dump subs)
54              
55             $other->{handler}->();
56             # Prints
57             # barf
58              
59             =head1 EXPORTS
60              
61             =over
62              
63             =item C (always exported) - the dmap function that does deep in-place mapping
64              
65             =item C (optional) - a function for stopping recursion.
66              
67             =back
68              
69             =head1 SUBROUTINES
70              
71             =head2 C
72              
73             This function works like C - it takes an expression followed by a list,
74             evaluates the expression on each member of the list and returns the result.
75              
76             The only difference is that any references returned by the expression will
77             also be traversed and passed to the expression once again, thus making it
78             possible to make deep traversal of any data structure.
79              
80             Objects (references blessed to something) are just traversed as if they
81             weren't blessed.
82              
83             =head3 Examples
84              
85             Delete all hash references
86              
87             use Data::Dmap;
88             use Data::Dump 'pp';
89            
90             pp dmap { return $_ unless ref eq 'HASH'; return; } 1, 'foo', [ { a => 1 }, 2];
91            
92             # Prints:
93             # (1, "foo", [2])
94            
95             Delete every odd number
96              
97             use Data::Dmap;
98             use Data::Dump 'pp';
99            
100             pp dmap { return if $_ % 2; $_ } [ 1 .. 10 ];
101              
102             # Prints:
103             # [2, 4, 6, 8, 10]
104              
105             Replace all hash refs with some C<$object> of class C.
106              
107             use Data::Dmap;
108             use Data::Dump 'pp';
109            
110             pp dmap { return bless $_, 'thingy' if ref eq 'HASH'; $_ } [ 1, "hello", { a => 1 } ];
111            
112             # Prints:
113             # [1, "hello", bless({ a => 1 }, "thingy")]
114              
115             C understands what you want, if you return nothing (as opposed to C) when
116             evaluating the expression for a hash key:
117              
118             use Data::Dmap;
119             use Data::Dump 'pp;
120              
121             my $characters = { main => 'pooh', secondary => 'piglet' };
122             pp dmap { return if $_ eq "piglet"; $_ } $characters;
123            
124             # Prints:
125             # { main => "pooh" }
126            
127             Because the output from the expression is being traversed, you can use C to generate
128             data structures:
129              
130             use Data::Dmap;
131             use Data::Dump 'pp';
132            
133             my $height = 3;
134             pp dmap { if(ref eq 'HASH' and $height--) { $_->{a} = {height => $height} } $_ } {};
135            
136             # Prints:
137             # {
138             # a => {
139             # a => {
140             # a => {
141             # height => 0
142             # },
143             # height => 1
144             # },
145             # height => 2
146             # }
147             # }
148             # (My own formatting above.)
149              
150             =head2 C
151              
152             The C routine stops recursion at any point and returns any data as it is
153             in place of the current node.
154              
155             =head3 Examples
156              
157             use Data::Dmap 'cut';
158             use Data::Dump 'pp';
159            
160             my $deep = {
161             level => 1,
162             data => {
163             level => 2,
164             data => {
165             level => 3
166             }
167             }
168             };
169            
170             pp dmap { cut('stop') if ref eq 'HASH' and $_->{level} == 2} $deep;
171              
172             # Prints:
173             #
174             # { data => { data => "stop", level => 2 }, level => 1 }
175            
176            
177             =cut
178              
179             sub _store_cache {
180 0     0     my $cache = shift;
181 0           my $ref = shift;
182 0           $cache->{refaddr($ref)} = [@_];
183             }
184              
185             sub _get_cache {
186 0     0     my $cache = shift;
187 0           my $ref = shift;
188 0           @{$cache->{refaddr($ref)}};
  0            
189             }
190              
191             sub _has_cache {
192 0     0     my $cache = shift;
193 0           my $ref = shift;
194 0           exists $cache->{refaddr($ref)};
195             }
196              
197             sub _dmap {
198 0     0     my $cache = shift;
199 0           my $callback = shift;
200 0           map {
201 0           my @result;
202 0 0         if(ref) {
203 0           my $orig_ref = $_;
204 0 0         if(not _has_cache($cache, $orig_ref)) {
205 0           my $recurse = 1;
206 0           my @mapped;
207             try {
208 0     0     @mapped = $callback->($orig_ref);
209             } catch {
210 0 0   0     if(ref eq 'Data::Dmap::Cut') {
211 0           $recurse = 0;
212 0           @result = @$_;
213             } else {
214 0           die $_
215             }
216 0           };
217 0 0         if($recurse) {
218 0           foreach my $val (@mapped) {
219 0           given(reftype $val) {
220 0           when('HASH') {
221 0           for(keys %$val) {
222 0           my @res = _dmap($cache, $callback, $val->{$_});
223 0 0         croak 'Multi value return in hash value assignment'
224             if @res > 1;
225 0 0         if(@res) {
226 0           $val->{$_} = $res[0];
227             } else {
228 0           delete $val->{$_};
229             }
230             }
231 0           push @result, $val;
232             }
233 0           when('ARRAY') {
234 0           my $i = 0;
235 0           while($i <= $#$val) {
236 0 0         if(exists $val->[$i]) {
237             # TODO Use splice to allow multi-value returns
238 0           my @res = _dmap($cache, $callback, $val->[$i]);
239 0 0         croak 'Multi value return in array single value assignment'
240             if @res > 1;
241 0 0         if(@res) {
242 0           $val->[$i] = $res[0];
243             } else {
244 0           splice @$val, $i, 1;
245             }
246             }
247 0           $i++;
248             }
249 0           push @result, $val;
250             }
251 0           when('SCALAR') {
252 0           my @res = _dmap($cache, $callback, $$val);
253 0 0         croak 'Multi value return in single value assignment'
254             if @res > 1;
255 0 0 0       $$val = $res[0] if @res and $$val ne $res[0];
256 0           push @result, $val;
257             }
258 0           default {
259 0           push @result, $val;
260             }
261             }
262             }
263             }
264 0           _store_cache($cache, $orig_ref, @result);
265             } else {
266 0           push @result, _get_cache($cache, $_);
267             }
268             } else {
269 0           @result = $callback->($_);
270             }
271 0           @result;
272             } @_
273             }
274              
275 0     0 1   sub cut { die bless [@_], 'Data::Dmap::Cut' }
276              
277             # Stub that inserts empty map cache
278 0     0 1   sub dmap(&@) { _dmap({}, @_) }
279              
280             =head1 AUTHOR
281              
282             Michael Zedeler, C<< >>
283              
284             =head1 BUGS
285              
286             If you find a bug, please consider helping to fix the bug by doing this:
287              
288             =over
289              
290             =item * Fork C from L
291              
292             =item * Write a test case in the C directory, commit and push it.
293              
294             =item * Fix the bug or (if you don't know how to fix it), report the bug
295              
296             =back
297              
298             Bugs and feature requests can be reported through the web interface at
299             L. I may not be notified, so send
300             me a mail too.
301              
302             =head1 SUPPORT
303              
304             You can find documentation for this module with the perldoc command.
305              
306             perldoc Data::Dmap
307              
308             You can also look for information at:
309              
310             =over 4
311              
312             =item * The github issue tracker
313              
314             L
315              
316             =item * AnnoCPAN: Annotated CPAN documentation
317              
318             L
319              
320             =item * CPAN Ratings
321              
322             L
323              
324             =item * Search CPAN
325              
326             L
327              
328             =back
329              
330              
331             =head1 SEE ALSO
332              
333             L, L, L, L, L.
334              
335             =head1 TODO
336              
337             =over
338              
339             =item Some kind of option making it possible to traverse objects with L
340             metaclasses, so we can avoid breaking encapsulation.
341              
342             =item Options to provide more information about the current node to the
343             callback handler, such as path, depth and data types. Should not affect
344             performance if not used.
345              
346             =back
347              
348             =head1 LICENSE AND COPYRIGHT
349              
350             Copyright 2010 Michael Zedeler.
351              
352             This program is free software; you can redistribute it and/or modify it
353             under the terms of either: the GNU General Public License as published
354             by the Free Software Foundation; or the Artistic License.
355              
356             See http://dev.perl.org/licenses/ for more information.
357              
358              
359             =cut
360              
361             1; # End of Data::Dmap