File Coverage

blib/lib/Data/Visitor/Callback.pm
Criterion Covered Total %
statement 180 194 92.7
branch 52 70 74.2
condition 23 29 79.3
subroutine 35 35 100.0
pod 13 15 86.6
total 303 343 88.3


line stmt bran cond sub pod time code
1             package Data::Visitor::Callback;
2 7     7   422494 use Moose;
  7         2884740  
  7         55  
3             # ABSTRACT: A Data::Visitor with callbacks.
4              
5             our $VERSION = '0.31';
6 7     7   56876 use Data::Visitor ();
  7         33  
  7         426  
7              
8 7     7   71 use Carp qw(carp);
  7         17  
  7         523  
9 7     7   50 use Scalar::Util qw/blessed refaddr reftype/;
  7         18  
  7         427  
10              
11 7     7   52 no warnings 'recursion';
  7         18  
  7         413  
12              
13 7     7   46 use namespace::clean -except => 'meta';
  7         123  
  7         59  
14              
15 7     7   2841 use constant DEBUG => Data::Visitor::DEBUG();
  7         18  
  7         628  
16 7     7   148 use constant FIVE_EIGHT => ( $] >= 5.008 );
  7         25  
  7         2160  
17              
18             extends qw(Data::Visitor);
19              
20             has callbacks => (
21             isa => "HashRef",
22             is => "rw",
23             default => sub { {} },
24             );
25              
26             has class_callbacks => (
27             isa => "ArrayRef",
28             is => "rw",
29             default => sub { [] },
30             );
31              
32             has ignore_return_values => (
33             isa => "Bool",
34             is => "rw",
35             );
36              
37             sub BUILDARGS {
38 26     26 1 93 my ( $class, @args ) = @_;
39              
40 26         194 my $args = $class->SUPER::BUILDARGS(@args);
41              
42 26         366 my %init_args = map { $_->init_arg => undef } $class->meta->get_all_attributes;
  130         2716  
43              
44 26         116 my %callbacks = map { $_ => $args->{$_} } grep { not exists $init_args{$_} } keys %$args;
  158         284  
  177         315  
45              
46 26         64 my @class_callbacks = do {
47 7     7   57 no strict 'refs';
  7         22  
  7         4477  
48             grep {
49             # this check can be half assed because an ->isa check will be
50             # performed later. Anything that cold plausibly be a class name
51             # should be included in the list, even if the class doesn't
52             # actually exist.
53              
54 26         71 m{ :: | ^[A-Z] }x # if it looks kinda lack a class name
55             or
56 158 100       531 scalar keys %{"${_}::"} # or it really is a class
  122         458  
57             } keys %callbacks;
58             };
59              
60             # sort from least derived to most derived
61 26         92 @class_callbacks = sort { !$a->isa($b) <=> !$b->isa($a) } @class_callbacks;
  36         193  
62              
63             return {
64 26         1060 %$args,
65             callbacks => \%callbacks,
66             class_callbacks => \@class_callbacks,
67             };
68             }
69              
70             sub visit {
71 280     280 1 3361 my $self = shift;
72              
73 280   100     931 my $replaced_hash = local $self->{_replaced} = ($self->{_replaced} || {}); # delete it after we're done with the whole visit
74              
75 280         417 my @ret;
76              
77 280         556 for my $data (@_) {
78 280   66     832 my $refaddr = ref($data) && refaddr($data); # we need this early, it may change by the time we write replaced hash
79              
80 280         620 local *_ = \$data; # alias $_
81              
82 280 100 100     738 if ( $refaddr and exists $replaced_hash->{ $refaddr } ) {
83 1         2 if ( FIVE_EIGHT ) {
84 1         1 $self->trace( mapping => replace => $data, with => $replaced_hash->{$refaddr} ) if DEBUG;
85 1         3 push @ret, $data = $replaced_hash->{$refaddr};
86 1         2 next;
87             } else {
88             carp(q{Assignment of replacement value for already seen reference } . overload::StrVal($data) . q{ to container doesn't work on Perls older than 5.8, structure shape may have lost integrity.});
89             }
90             }
91              
92 279         373 my $ret;
93              
94 279 100       514 if ( defined wantarray ) {
95 256         496 $ret = $self->SUPER::visit( $self->callback( visit => $data ) );
96             } else {
97 23         71 $self->SUPER::visit( $self->callback( visit => $data ) );
98             }
99              
100 279 100 100     1080 $replaced_hash->{$refaddr} = $_ if $refaddr and ( not ref $_ or $refaddr ne refaddr($_) );
      100        
101              
102 279 100       794 push @ret, $ret if defined wantarray;
103             }
104              
105 280 50       1376 return ( @_ == 1 ? $ret[0] : @ret );
106             }
107              
108             sub visit_ref {
109 102     102 1 197 my ( $self, $data ) = @_;
110              
111 102         208 my $mapped = $self->callback( ref => $data );
112              
113 102 50       230 if ( ref $mapped ) {
114 102         293 return $self->SUPER::visit_ref($mapped);
115             } else {
116 0         0 return $self->visit($mapped);
117             }
118             }
119              
120             sub visit_seen {
121 9     9 1 22 my ( $self, $data, $result ) = @_;
122              
123 9         32 my $mapped = $self->callback( seen => $data, $result );
124              
125 7     7   59 no warnings 'uninitialized';
  7         25  
  7         3373  
126 9 100       45 if ( refaddr($mapped) == refaddr($data) ) {
127 7         18 return $result;
128             } else {
129 2         6 return $mapped;
130             }
131             }
132              
133             sub visit_value {
134 187     187 1 363 my ( $self, $data ) = @_;
135              
136 187         346 $data = $self->callback_and_reg( value => $data );
137 187 100       483 $self->callback_and_reg( ( ref($data) ? "ref_value" : "plain_value" ) => $data );
138             }
139              
140             sub visit_object {
141 24     24 1 53 my ( $self, $data ) = @_;
142              
143 24         35 $self->trace( flow => visit_object => $data ) if DEBUG;
144              
145 24         60 $data = $self->callback_and_reg( object => $data );
146              
147 24         43 my $class_cb = 0;
148              
149 24         38 foreach my $class ( grep { $data->isa($_) } @{ $self->class_callbacks } ) {
  33         154  
  24         653  
150 5 50       19 last unless blessed($data);
151 5 50       20 die "Unexpected object $data found"
152             unless $data->isa($class);
153 5         6 $self->trace( flow => class_callback => $class, on => $data ) if DEBUG;
154              
155 5         10 $class_cb++;
156 5         10 $data = $self->callback_and_reg( $class => $data );
157             }
158              
159 24 100       71 $data = $self->callback_and_reg( object_no_class => $data ) unless $class_cb;
160              
161 24 50       100 $data = $self->callback_and_reg( object_final => $data )
162             if blessed($data);
163              
164 24         99 $data;
165             }
166              
167             sub visit_scalar {
168 4     4 1 9 my ( $self, $data ) = @_;
169 4         12 my $new_data = $self->callback_and_reg( scalar => $data );
170 4 50 50     28 if ( (reftype($new_data)||"") =~ /^(?: SCALAR | REF | LVALUE | VSTRING ) $/x ) {
171 4         22 my $visited = $self->SUPER::visit_scalar( $new_data );
172              
173 7     7   55 no warnings "uninitialized";
  7         15  
  7         1051  
174 4 50       21 if ( refaddr($visited) != refaddr($data) ) {
175 4         12 return $self->_register_mapping( $data, $visited );
176             } else {
177 0         0 return $visited;
178             }
179             } else {
180 0         0 return $self->_register_mapping( $data, $self->visit( $new_data ) );
181             }
182             }
183              
184             sub subname { $_[1] }
185              
186             BEGIN {
187 7     7   27 eval {
188 7         44 require Sub::Name;
189 7     7   50 no warnings 'redefine';
  7         16  
  7         612  
190 7         54 *subname = \&Sub::Name::subname;
191             };
192              
193 7         23 foreach my $reftype ( qw/array hash glob code/ ) {
194 28         84 my $name = "visit_$reftype";
195 7     7   48 no strict 'refs';
  7         13  
  7         757  
196 28 50 50 7 1 3698 *$name = subname(__PACKAGE__ . "::$name", eval '
  7 100 100 7 1 57  
  7 50 50 7 1 25  
  7 50 50 7 1 707  
  7 50 100 16   57  
  7 50   34   19  
  7 50   3   652  
  7 100   45   53  
  7         25  
  7         694  
  7         52  
  7         16  
  7         639  
  16         52  
  16         49  
  16         90  
  15         69  
  15         64  
  15         49  
  0         0  
  1         7  
  34         91  
  34         85  
  34         138  
  34         97  
  34         121  
  0         0  
  34         166  
  0         0  
  3         11  
  3         12  
  3         25  
  3         20  
  3         18  
  3         12  
  0         0  
  0         0  
  45         125  
  45         142  
  45         221  
  44         156  
  44         185  
  44         127  
  0         0  
  1         5  
197             sub {
198             my ( $self, $data ) = @_;
199             my $new_data = $self->callback_and_reg( '.$reftype.' => $data );
200             if ( "'.uc($reftype).'" eq (reftype($new_data)||"") ) {
201             my $visited = $self->SUPER::visit_'.$reftype.'( $new_data );
202              
203             no warnings "uninitialized";
204             if ( refaddr($visited) != refaddr($data) ) {
205             return $self->_register_mapping( $data, $visited );
206             } else {
207             return $visited;
208             }
209             } else {
210             return $self->_register_mapping( $data, $self->visit( $new_data ) );
211             }
212             }
213             ' || die $@);
214             }
215             }
216              
217             sub visit_hash_entry {
218 106     106 1 257 my ( $self, $key, $value, $hash ) = @_;
219              
220 106         297 my ( $new_key, $new_value ) = $self->callback( hash_entry => $_[1], $_[2], $_[3] );
221              
222 106 100       3091 unless ( $self->ignore_return_values ) {
223 7     7   47 no warnings 'uninitialized';
  7         25  
  7         2946  
224 87 50 66     394 if ( ref($value) and refaddr($value) != refaddr($new_value) ) {
225 0         0 $self->_register_mapping( $value, $new_value );
226 0 0       0 if ( $key ne $new_key ) {
227 0         0 return $self->SUPER::visit_hash_entry($new_key, $new_value, $_[3]);
228             } else {
229 0         0 return $self->SUPER::visit_hash_entry($_[1], $new_value, $_[3]);
230             }
231             } else {
232 87 50       167 if ( $key ne $new_key ) {
233 0         0 return $self->SUPER::visit_hash_entry($new_key, $_[2], $_[3]);
234             } else {
235 87         278 return $self->SUPER::visit_hash_entry($_[1], $_[2], $_[3]);
236             }
237             }
238             } else {
239 19         86 return $self->SUPER::visit_hash_entry($_[1], $_[2], $_[3]);
240             }
241             }
242              
243             sub callback {
244 1054     1054 0 1845 my ( $self, $name, $data, @args ) = @_;
245              
246 1054 100       28624 if ( my $code = $self->callbacks->{$name} ) {
247 104         142 $self->trace( flow => callback => $name, on => $data ) if DEBUG;
248 104 100       185 if ( wantarray ) {
249 32         81 my @ret = $self->$code( $data, @args );
250 32 50       1010 return $self->ignore_return_values ? ( $data, @args ) : @ret;
251             } else {
252 72         182 my $ret = $self->$code( $data, @args );
253 72 100       2388 return $self->ignore_return_values ? $data : $ret ;
254             }
255             } else {
256 950 100       2963 return wantarray ? ( $data, @args ) : $data;
257             }
258             }
259              
260             sub callback_and_reg {
261 558     558 0 1054 my ( $self, $name, $data, @args ) = @_;
262              
263 558         1030 my $new_data = $self->callback( $name, $data, @args );
264              
265 558 100       15806 unless ( $self->ignore_return_values ) {
266 7     7   73 no warnings 'uninitialized';
  7         14  
  7         1338  
267 451 100       917 if ( ref $data ) {
268 221 100       594 if ( refaddr($data) != refaddr($new_data) ) {
269 14         52 return $self->_register_mapping( $data, $new_data );
270             }
271             }
272              
273 437         2938 return $new_data;
274             }
275              
276 107         570 return $data;
277             }
278              
279             sub visit_tied {
280 9     9 1 20 my ( $self, $tied, @args ) = @_;
281 9         21 $self->SUPER::visit_tied( $self->callback_and_reg( tied => $tied, @args ), @args );
282             }
283              
284             __PACKAGE__->meta->make_immutable if __PACKAGE__->meta->can("make_immutable");
285              
286             __PACKAGE__;
287              
288             __END__
289              
290             =pod
291              
292             =encoding UTF-8
293              
294             =head1 NAME
295              
296             Data::Visitor::Callback - A Data::Visitor with callbacks.
297              
298             =head1 VERSION
299              
300             version 0.31
301              
302             =head1 SYNOPSIS
303              
304             use Data::Visitor::Callback;
305              
306             my $v = Data::Visitor::Callback->new(
307             # you can provide callbacks
308             # $_ will contain the visited value
309              
310             value => sub { ... },
311             array => sub { ... },
312              
313              
314             # you can also delegate to method names
315             # this specific example will force traversal on objects, by using the
316             # 'visit_ref' callback which normally traverse unblessed references
317              
318             object => "visit_ref",
319              
320              
321             # you can also use class names as callbacks
322             # the callback will be invoked on all objects which inherit that class
323              
324             'Some::Class' => sub {
325             my ( $v, $obj ) = @_; # $v is the visitor
326              
327             ...
328             },
329             );
330              
331             $v->visit( $some_perl_value );
332              
333             =head1 DESCRIPTION
334              
335             This is a L<Data::Visitor> subclass that lets you invoke callbacks instead of
336             needing to subclass yourself.
337              
338             =head1 METHODS
339              
340             =over 4
341              
342             =item new %opts, %callbacks
343              
344             Construct a new visitor.
345              
346             The options supported are:
347              
348             =over 4
349              
350             =item ignore_return_values
351              
352             When this is true (off by default) the return values from the callbacks are
353             ignored, thus disabling the fmapping behavior as documented in
354             L<Data::Visitor>.
355              
356             This is useful when you want to modify $_ directly
357              
358             =item tied_as_objects
359              
360             Whether or not to visit the L<perlfunc/tied> of a tied structure instead of
361             pretending the structure is just a normal one.
362              
363             See L<Data::Visitor/visit_tied>.
364              
365             =back
366              
367             =back
368              
369             =head1 CALLBACKS
370              
371             Use these keys for the corresponding callbacks.
372              
373             The callback is in the form:
374              
375             sub {
376             my ( $visitor, $data ) = @_;
377              
378             # or you can use $_, it's aliased
379              
380             return $data; # or modified data
381             }
382              
383             Within the callback $_ is aliased to the data, and this is also passed in the
384             parameter list.
385              
386             Any method can also be used as a callback:
387              
388             object => "visit_ref", # visit objects anyway
389              
390             =over 4
391              
392             =item visit
393              
394             Called for all values
395              
396             =item value
397              
398             Called for non objects, non container (hash, array, glob or scalar ref) values.
399              
400             =item ref_value
401              
402             Called after C<value>, for references to regexes, globs and code.
403              
404             =item plain_value
405              
406             Called after C<value> for non references.
407              
408             =item object
409              
410             Called for blessed objects.
411              
412             Since L<Data::Visitor/visit_object> will not recurse downwards unless you
413             delegate to C<visit_ref>, you can specify C<visit_ref> as the callback for
414             C<object> in order to enter objects.
415              
416             It is recommended that you specify the classes (or base classes) you want
417             though, instead of just visiting any object forcefully.
418              
419             =item Some::Class
420              
421             You can use any class name as a callback. This is called only after the
422             C<object> callback.
423              
424             If the object C<isa> the class then the callback will fire.
425              
426             These callbacks are called from least derived to most derived by comparing the
427             classes' C<isa> at construction time.
428              
429             =item object_no_class
430              
431             Called for every object that did not have a class callback.
432              
433             =item object_final
434              
435             The last callback called for objects, useful if you want to post process the
436             output of any class callbacks.
437              
438             =item array
439              
440             Called for array references.
441              
442             =item hash
443              
444             Called for hash references.
445              
446             =item glob
447              
448             Called for glob references.
449              
450             =item scalar
451              
452             Called for scalar references.
453              
454             =item tied
455              
456             Called on the return value of C<tied> for all tied containers. Also passes in
457             the variable as the second argument.
458              
459             =item seen
460              
461             Called for a reference value encountered a second time.
462              
463             Passes in the result mapping as the second argument.
464              
465             =back
466              
467             =for Pod::Coverage DEBUG
468             FIVE_EIGHT
469             callback
470             callback_and_reg
471             subname
472              
473             =head1 SUPPORT
474              
475             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Visitor>
476             (or L<bug-Data-Visitor@rt.cpan.org|mailto:bug-Data-Visitor@rt.cpan.org>).
477              
478             =head1 AUTHORS
479              
480             =over 4
481              
482             =item *
483              
484             Yuval Kogman <nothingmuch@woobling.org>
485              
486             =item *
487              
488             Marcel Grünauer <marcel@cpan.org>
489              
490             =back
491              
492             =head1 COPYRIGHT AND LICENCE
493              
494             This software is copyright (c) 2020 by Yuval Kogman.
495              
496             This is free software; you can redistribute it and/or modify it under
497             the same terms as the Perl 5 programming language system itself.
498              
499             =cut