File Coverage

blib/lib/Data/Visitor/Callback.pm
Criterion Covered Total %
statement 2 4 50.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 4 6 66.6


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