File Coverage

blib/lib/JSYNC.pm
Criterion Covered Total %
statement 104 134 77.6
branch 40 58 68.9
condition 14 23 60.8
subroutine 16 16 100.0
pod 0 3 0.0
total 174 234 74.3


line stmt bran cond sub pod time code
1 4     4   2111 use strict; use warnings;
  4     4   6  
  4         179  
  4         19  
  4         6  
  4         212  
2             package JSYNC;
3             our $VERSION = '0.25';
4              
5 4     4   1848 use JSON;
  4         34922  
  4         18  
6              
7             {
8             package JSYNC;
9              
10             sub dump {
11 7     7 0 10 my ($object, $config) = @_;
12 7   50     22 $config ||= {};
13 7         25 return JSYNC::Dumper->new(%$config)->dump($object);
14             }
15              
16             sub load {
17 7     7 0 13 my ($jsync) = @_;
18 7         18 return JSYNC::Loader->new->load($jsync);
19             }
20              
21             sub info {
22 55     55 0 38 my ($kind, $id, $class);
23 55 50       111 if (ref(\$_[0]) eq 'GLOB') {
    100          
24 0 0       0 (\$_[0] . "") =~ /^(?:(.+)=)?(GLOB)\((0x.*)\)$/
25             or die "Can't get info for '$_[0]'";
26 0   0     0 ($kind, $id, $class) = ('glob', $3, $1 || '');
27             }
28             elsif (not ref($_[0])) {
29 34         32 $kind = 'scalar';
30             }
31             else {
32 21 50       130 "$_[0]" =~ /^(?:(.+)=)?(HASH|ARRAY)\((0x.*)\)$/
33             or die "Can't get info for '$_[0]'";
34 21 100 100     124 ($kind, $id, $class) =
35             (($2 eq 'HASH' ? 'map' : 'seq'), $3, $1 || '');
36             }
37 55         90 return ($kind, $id, $class);
38             }
39             };
40              
41             {
42             package JSYNC::Dumper;
43              
44 7     7   30 sub new { bless { @_[1..$#_] }, $_[0] }
45              
46             sub dump {
47 7     7   8 my ($self, $object) = @_;
48 7         16 $self->{anchor} = 1;
49 7         10 $self->{seen} = {};
50 7         18 my $graph = $self->represent($object);
51 7         44 my $json = 'JSON'->new()->canonical();
52 7 50       15 $json->pretty() if $self->{pretty};
53 7         78 return $json->encode($graph);
54             }
55              
56             sub represent {
57 32     32   26 my ($self, $node) = @_;
58 32         28 my $seen = $self->{seen};
59 32         26 my $graph;
60 32         42 my ($kind, $id, $class) = JSYNC::info($node);
61 32 100       57 if ($kind eq 'scalar') {
62 20 100       26 if (not defined $node) {
63 2         5 return undef;
64             }
65 18         26 return $self->escape($node);
66             }
67 12 100       24 if (my $info = $seen->{$id}) {
68 3 100       8 if (not $info->{anchor}) {
69 2         12 $info->{anchor} = $self->{anchor}++ . "";
70 2 50       9 if ($info->{kind} eq 'map') {
71 2         6 $info->{graph}{'&'} = $info->{anchor};
72             }
73             else {
74 0         0 unshift @{$info->{graph}}, '&' . $info->{anchor};
  0         0  
75             }
76             }
77 3         8 return "*" . $info->{anchor};
78             }
79 9         18 my $tag = $self->resolve_to_tag($kind, $class);
80 9 100       21 if ($kind eq 'seq') {
    50          
    0          
81 3         6 $graph = [];
82 3         13 $seen->{$id} = { graph => $graph, kind => $kind };
83 3         7 @$graph = map { $self->represent($_) } @$node;
  7         13  
84 3 100       10 if ($tag) {
85 1         5 unshift @$graph, "!$tag";
86             }
87             }
88             elsif ($kind eq 'map') {
89 6         9 $graph = {};
90 6         18 $seen->{$id} = { graph => $graph, kind => $kind };
91 6         15 for my $k (keys %$node) {
92 9         21 $graph->{$self->represent($k)} = $self->represent($node->{$k});
93             }
94 6 100       14 if ($tag) {
95 1         3 $graph->{'!'} = $tag;
96             }
97             }
98             # XXX glob should not be a kind.
99             elsif ($kind eq 'glob') {
100 0   0     0 $class ||= 'main';
101 0         0 $graph = {};
102 0         0 $graph->{PACKAGE} = $class;
103 0         0 $graph->{'!'} = '!perl/glob:';
104 0         0 for my $type (qw(PACKAGE NAME SCALAR ARRAY HASH CODE IO)) {
105 0         0 my $value = *{$node}{$type};
  0         0  
106 0 0       0 $value = $$value if $type eq 'SCALAR';
107 0 0       0 if (defined $value) {
108 0 0       0 if ($type eq 'IO') {
109 0         0 my @stats = qw(device inode mode links uid gid rdev size
110             atime mtime ctime blksize blocks);
111 0         0 undef $value;
112 0         0 $value->{stat} = {};
113 0         0 map {$value->{stat}{shift @stats} = $_} stat(*{$node});
  0         0  
  0         0  
114 0         0 $value->{fileno} = fileno(*{$node});
  0         0  
115             {
116 0         0 local $^W;
  0         0  
117 0         0 $value->{tell} = tell(*{$node});
  0         0  
118             }
119             }
120 0         0 $graph->{$type} = $value;
121             }
122             }
123              
124             }
125             else {
126             # XXX [$id, $kind, $class];
127 0         0 die "Can't represent kind '$kind'";
128             }
129 9         14 return $graph;
130             }
131              
132             sub escape {
133 18     18   18 my ($self, $string) = @_;
134 18         32 $string =~ s/^(\.*[\!\&\*\%])/.$1/;
135 18         45 return $string;
136             }
137              
138             my $perl_type = {
139             map => 'hash',
140             seq => 'array',
141             scalar => 'scalar',
142             };
143             sub resolve_to_tag {
144 9     9   11 my ($self, $kind, $class) = @_;
145 9   66     27 return $class && "!perl/$perl_type->{$kind}\:$class";
146             }
147             };
148              
149             {
150             package JSYNC::Loader;
151              
152 7     7   36 sub new { bless { @_[1..$#_] }, $_[0] }
153              
154             sub load {
155 7     7   11 my ($self, $jsync) = @_;
156 7         18 $self->{seen} = {};
157 7         75 my $graph = 'JSON'->new()->decode($jsync);
158 7         32 return $self->construct($graph);
159             }
160              
161              
162             sub construct {
163 23     23   21 my ($self, $graph) = @_;
164 23         27 my $seen = $self->{seen};
165 23         17 my $node;
166 23         35 my ($kind, $id, $class) = JSYNC::info($graph);
167 23 100       40 if ($kind eq 'scalar') {
168 14 100       20 if (not defined $graph) {
169 2         6 return undef;
170             }
171 12 100       23 if ($graph =~ /^\*(\S+)$/) {
172 3         9 return $seen->{$1};
173             }
174 9         17 return $self->unescape($graph);
175             }
176 9 100       20 if ($kind eq 'map') {
    50          
177 6         8 $node = {};
178 6 100       16 if ($graph->{'&'}) {
179 2         3 my $anchor = $graph->{'&'};
180 2         4 delete $graph->{'&'};
181 2         5 $seen->{$anchor} = $node;
182             }
183 6 100       12 if ($graph->{'!'}) {
184 1         5 my $class = $self->resolve_from_tag($graph->{'!'});
185 1         3 delete $graph->{'!'};
186 1         5 bless $node, $class;
187             }
188 6         18 for my $k (keys %$graph) {
189 9         23 $node->{$self->unescape($k)} = $self->construct($graph->{$k});
190             }
191             }
192             elsif ($kind eq 'seq') {
193 3         5 $node = [];
194 3 100 66     25 if (@$graph and defined $graph->[0] and $graph->[0] =~ /^!(.*)$/) {
      100        
195 1         3 my $class = $self->resolve_from_tag($1);
196 1         2 shift @$graph;
197 1         2 bless $node, $class;
198             }
199 3 50 66     22 if (@$graph and $graph->[0] and $graph->[0] =~ /^\&(\S+)$/) {
      66        
200 0         0 $seen->{$1} = $node;
201 0         0 shift @$graph;
202             }
203 3         6 @$node = map {$self->construct($_)} @$graph;
  7         10  
204             }
205 9         38 return $node;
206             }
207              
208             sub unescape {
209 18     18   19 my ($self, $string) = @_;
210 18         23 $string =~ s/^\.(\.*[\!\&\*\%])/$1/;
211 18         41 return $string;
212             }
213              
214             sub resolve_from_tag {
215 2     2   4 my ($self, $tag) = @_;
216 2 50       11 $tag =~ m{^!perl/(?:hash|array|object):(\S+)$}
217             or die "Can't resolve tag '$tag'";
218 2         4 return $1;
219             }
220             };
221              
222             1;