File Coverage

blib/lib/RapidApp/Data/Dmap.pm
Criterion Covered Total %
statement 18 75 24.0
branch 0 28 0.0
condition 0 3 0.0
subroutine 6 14 42.8
pod 2 2 100.0
total 26 122 21.3


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