File Coverage

blib/lib/Data/Decycle.pm
Criterion Covered Total %
statement 63 88 71.5
branch 26 48 54.1
condition 2 12 16.6
subroutine 19 22 86.3
pod 7 7 100.0
total 117 177 66.1


line stmt bran cond sub pod time code
1             #
2             # $Id: Decycle.pm,v 0.2 2010/08/23 09:11:03 dankogai Exp dankogai $
3             #
4             package Data::Decycle;
5 5     5   230223 use 5.008001;
  5         20  
  5         183  
6 5     5   27 use warnings;
  5         10  
  5         140  
7 5     5   34 use strict;
  5         29  
  5         185  
8 5     5   28 use Carp;
  5         9  
  5         481  
9 5     5   32 use Scalar::Util qw/refaddr weaken isweak/;
  5         9  
  5         768  
10              
11             our $VERSION = sprintf "%d.%02d", q$Revision: 0.2 $ =~ /(\d+)/g;
12             our $DEBUG = 0;
13              
14 5     5   35 use base 'Exporter';
  5         9  
  5         1509  
15             our @EXPORT = ();
16             our @EXPORT_OK = qw(recsub $CALLEE
17             may_leak has_cyclic_ref decycle_deeply weaken_deeply
18             );
19             our %EXPORT_TAGS = ( all => [ @EXPORT, @EXPORT_OK ], );
20              
21             BEGIN {
22 5     5   29 require constant;
23             constant->import(
24 5         10 HAS_PADWALKER => eval {
25 5         4686 require PadWalker;
26 0         0 $PadWalker::VERSION >= 1.0;
27             }
28             );
29             }
30              
31             sub new {
32 0     0 1 0 my $class = shift;
33 0         0 my $self = bless [], $class;
34 0         0 $self->add(@_);
35             }
36              
37             sub add {
38 0     0 1 0 my $self = shift;
39 0         0 for (@_){
40 0 0       0 croak "$_ is not a reference" unless ref $_;
41 0         0 push @{$self}, $_;
  0         0  
42             }
43 0         0 $self;
44             }
45              
46             sub DESTROY {
47 0     0   0 my $self = shift;
48 0 0       0 if ($DEBUG > 1){
49 0 0       0 require Data::Dumper and Data::Dumper->import;
50 0         0 print Dumper($self);
51             }
52 0         0 for (@{$self}){
  0         0  
53 0 0       0 next unless ref $_;
54 0 0       0 carp "decyling ($_)" if $DEBUG;
55 0         0 decycle_deeply($_);
56             }
57             }
58              
59             our $CALLEE;
60              
61             sub recsub(&) {
62 20     20 1 25 my $code = shift;
63             sub {
64 34     34   74 local *CALLEE = \$code;
65 34         81 $code->(@_);
66             }
67 20         70 }
68              
69             sub _mkfinder(&) {
70 10     10   19 my $cb = shift;
71             return recsub {
72 38 100   38   132 return unless ref $_[0];
73 5     5   25 no warnings 'uninitialized';
  5         7  
  5         2695  
74 33 100       245 return $cb->( $_[0] ) if $_[1]->{ refaddr $_[0] }++;
75 20 100 33     160 if ( UNIVERSAL::isa( $_[0], 'HASH' ) ) {
    100 0        
    50          
    0          
76 8         11 for ( values %{ $_[0] } ) {
  8         28  
77 9 100       29 next unless ref $_;
78 5 100       16 return 1 if $CALLEE->( $_, $_[1] );
79             }
80             }
81             elsif ( UNIVERSAL::isa( $_[0], 'ARRAY' ) ) {
82 8         13 for ( @{ $_[0] } ) {
  8         177  
83 13 100       622 next unless ref $_;
84 4 100       11 return 1 if $CALLEE->( $_, $_[1] );
85             }
86             }
87             elsif (UNIVERSAL::isa( $_[0], 'SCALAR' )
88             || UNIVERSAL::isa( $_[0], 'REF' ) )
89             {
90 4         10 return $CALLEE->( ${ $_[0] }, $_[1] );
  4         33  
91             }
92             elsif ( HAS_PADWALKER && UNIVERSAL::isa( $_[0], 'CODE' ) ) {
93 0         0 my $r = PadWalker::closed_over( $_[0] );
94 0 0       0 return unless keys %$r;
95 0 0       0 $CALLEE->( $r, $_[1] ) && return 1;
96             }
97 9         118 return;
98             }
99 10         47 }
100              
101             *_has_cyclic_ref = _mkfinder { 1 };
102 17     17 1 1329 sub has_cyclic_ref($){ _has_cyclic_ref($_[0], {}) }
103              
104             *_may_leak = _mkfinder { !isweak($_[0]) };
105 8     8 1 207 sub may_leak($){ _may_leak($_[0], {}) }
106              
107             sub _mkwalker(&){
108 10     10   17 my $cb = shift;
109             return recsub {
110 22 100   22   90 return unless ref $_[0];
111 5     5   43 no warnings 'uninitialized';
  5         9  
  5         2554  
112 18 100       127 return $cb->( $_[0] ) if $_[1]->{ refaddr $_[0] }++;
113 9 100 33     99 if ( UNIVERSAL::isa( $_[0], 'HASH' ) ) {
    100 0        
    50          
    0          
114 3         8 $CALLEE->( $_, $_[1] ) for values %{ $_[0] };
  3         18  
115             }
116             elsif ( UNIVERSAL::isa( $_[0], 'ARRAY' ) ) {
117 3         6 $CALLEE->( $_, $_[1] ) for @{ $_[0] };
  3         14  
118             }
119             elsif (UNIVERSAL::isa( $_[0], 'SCALAR' )
120             || UNIVERSAL::isa( $_[0], 'REF' ) )
121             {
122 3         5 $CALLEE->( ${ $_[0] }, $_[1] );
  3         22  
123             }
124             elsif ( HAS_PADWALKER && UNIVERSAL::isa( $_[0], 'CODE' ) ) {
125 0         0 my $r = PadWalker::closed_over( $_[0] );
126 0 0       0 return unless keys %$r;
127 0         0 $CALLEE->( $r, $_[1] );
128             }
129 9         33 return;
130 10         44 };
131             }
132              
133             *_decycle_deeply = _mkwalker { undef $_[0] };
134 3     3 1 11 sub decycle_deeply($) { _decycle_deeply( $_[0], {} ) }
135              
136             *_weaken_deeply = _mkwalker {
137             weaken $_[0] unless UNIVERSAL::isa( $_[0], 'CODE' )
138             };
139 6     6 1 18876 sub weaken_deeply($) { _weaken_deeply( $_[0], {} ) }
140              
141             1; # End of Data::Decycle
142              
143             __END__