File Coverage

blib/lib/Data/Circular/Util.pm
Criterion Covered Total %
statement 39 44 88.6
branch 21 30 70.0
condition n/a
subroutine 7 7 100.0
pod 2 2 100.0
total 69 83 83.1


line stmt bran cond sub pod time code
1             package Data::Circular::Util;
2              
3             our $DATE = '2015-09-03'; # DATE
4             our $VERSION = '0.59'; # VERSION
5              
6 1     1   22820 use 5.010001;
  1         4  
7 1     1   5 use strict;
  1         1  
  1         20  
8 1     1   4 use warnings;
  1         1  
  1         627  
9             #use experimental 'smartmatch';
10              
11             require Exporter;
12             our @ISA = qw(Exporter);
13             our @EXPORT_OK = qw(clone_circular_refs has_circular_ref);
14              
15             our %SPEC;
16              
17             $SPEC{clone_circular_refs} = {
18             v => 1.1,
19             summary => 'Remove circular references by deep-copying them',
20             description => <<'_',
21              
22             For example, this data:
23              
24             $x = [1];
25             $data = [$x, 2, $x];
26              
27             contains circular references by referring to `$x` twice. After
28             `clone_circular_refs`, data will become:
29              
30             $data = [$x, 2, [1]];
31              
32             that is, the subsequent circular references will be deep-copied. This makes it
33             safe to transport to JSON, for example.
34              
35             Sometimes it doesn't work, for example:
36              
37             $data = [1];
38             push @$data, $data;
39              
40             Cloning will still create circular references.
41              
42             This function modifies the data structure in-place, and return true for success
43             and false upon failure.
44              
45             _
46             args_as => 'array',
47             args => {
48             data => {
49             schema => "any",
50             pos => 0,
51             req => 1,
52             },
53             },
54             result_naked => 1,
55             };
56             sub clone_circular_refs {
57 6     6 1 2626 require Data::Clone;
58              
59 6         1861 my ($data) = @_;
60 6         7 my %refs;
61             my $doit;
62             $doit = sub {
63 10     10   12 my $x = shift;
64 10         19 my $r = ref($x);
65 10 100       21 return if !$r;
66 8 50       15 if ($r eq 'ARRAY') {
    0          
67 8         40 for (@$x) {
68 9 100       17 next unless ref($_);
69 7 100       23 if ($refs{"$_"}++) {
70 3         16 $_ = Data::Clone::clone($_);
71             } else {
72 4         13 $doit->($_);
73             }
74             }
75             } elsif ($r eq 'HASH') {
76 0         0 for (keys %$x) {
77 0 0       0 next unless ref($x->{$_});
78 0 0       0 if ($refs{"$x->{$_}"}++) {
79 0         0 $x->{$_} = Data::Clone::clone($x->{$_});
80             } else {
81 0         0 $doit->($_);
82             }
83             }
84             }
85 6         29 };
86 6         17 $doit->($data);
87 6         15 !has_circular_ref($data);
88             }
89              
90             $SPEC{has_circular_ref} = {
91             v => 1.1,
92             summary => 'Check whether data item contains circular references',
93             description => <<'_',
94              
95             Does not deal with weak references.
96              
97             _
98             args_as => 'array',
99             args => {
100             data => {
101             schema => "any",
102             pos => 0,
103             req => 1,
104             },
105             },
106             result_naked => 1,
107             };
108             sub has_circular_ref {
109 13     13 1 1189 my ($data) = @_;
110 13         16 my %refs;
111             my $check;
112             $check = sub {
113 27     27   31 my $x = shift;
114 27         43 my $r = ref($x);
115 27 100       68 return 0 if !$r;
116 23 100       95 return 1 if $refs{"$x"}++;
117 19 100       36 if ($r eq 'ARRAY') {
    50          
118 18         35 for (@$x) {
119 16 100       35 next unless ref($_);
120 12 100       32 return 1 if $check->($_);
121             }
122             } elsif ($r eq 'HASH') {
123 1         4 for (values %$x) {
124 2 50       4 next unless ref($_);
125 2 100       7 return 1 if $check->($_);
126             }
127             }
128 14         51 0;
129 13         54 };
130 13         32 $check->($data);
131             }
132              
133             1;
134             # ABSTRACT: Remove circular references by deep-copying them
135              
136             __END__