File Coverage

blib/lib/YAML/PP/Schema/Perl.pm
Criterion Covered Total %
statement 322 331 97.2
branch 76 88 86.3
condition 12 18 66.6
subroutine 64 64 100.0
pod 13 13 100.0
total 487 514 94.7


line stmt bran cond sub pod time code
1 7     7   159698 use strict;
  7         8  
  7         218  
2 7     7   25 use warnings;
  7         9  
  7         568  
3             package YAML::PP::Schema::Perl;
4              
5             our $VERSION = 'v0.41.0'; # VERSION
6              
7 7     7   29 use Scalar::Util qw/ blessed reftype /;
  7         9  
  7         18040  
8              
9             my $qr_prefix;
10             # workaround to avoid growing regexes when repeatedly loading and dumping
11             # e.g. (?^:(?^:regex))
12             {
13             $qr_prefix = qr{\(\?-xism\:};
14             if ($] >= 5.014) {
15             $qr_prefix = qr{\(\?\^(?:[uadl])?\:};
16             }
17             }
18              
19             sub new {
20 6     6 1 192807 my ($class, %args) = @_;
21 6   50     30 my $tags = $args{tags} || [];
22 6   100     21 my $loadcode = $args{loadcode} || 0;
23 6         8 my $dumpcode = $args{dumpcode};
24 6 50       15 $dumpcode = 1 unless defined $dumpcode;
25 6         8 my $classes = $args{classes};
26              
27 6         26 my $self = bless {
28             tags => $tags,
29             loadcode => $loadcode,
30             dumpcode => $dumpcode,
31             classes => $classes,
32             }, $class;
33             }
34              
35             sub register {
36 24     24 1 63 my ($self, %args) = @_;
37 24         51 my $schema = $args{schema};
38              
39 24         43 my $tags;
40 24         32 my $loadcode = 0;
41 24         27 my $dumpcode = 1;
42 24         33 my $classes;
43 24 100       77 if (blessed($self)) {
44 6         10 $tags = $self->{tags};
45 6 50       15 @$tags = ('!perl') unless @$tags;
46 6         9 $loadcode = $self->{loadcode};
47 6         7 $dumpcode = $self->{dumpcode};
48 6         6 $classes = $self->{classes};
49             }
50             else {
51 18         25 my $options = $args{options};
52 18         24 my $tagtype = '!perl';
53 18         32 for my $option (@$options) {
54 12 100       44 if ($option =~ m/^tags?=(.+)$/) {
    100          
    50          
55 7         18 $tagtype = $1;
56             }
57             elsif ($option eq '+loadcode') {
58 4         7 $loadcode = 1;
59             }
60             elsif ($option eq '-dumpcode') {
61 1         1 $dumpcode = 0;
62             }
63             }
64 18         51 $tags = [split m/\+/, $tagtype];
65             }
66              
67              
68 24         124 my $perl_tag;
69             my %tagtypes;
70 24         0 my @perl_tags;
71 24         45 for my $type (@$tags) {
72 28 100       54 if ($type eq '!perl') {
    50          
73 22   66     82 $perl_tag ||= $type;
74 22         30 push @perl_tags, '!perl';
75             }
76             elsif ($type eq '!!perl') {
77 6   100     35 $perl_tag ||= 'tag:yaml.org,2002:perl';
78 6         7 push @perl_tags, 'tag:yaml.org,2002:perl';
79             }
80             else {
81 0         0 die "Invalid tagtype '$type'";
82             }
83 28         45 $tagtypes{ $type } = 1;
84             }
85              
86 24         34 my $perl_regex = '!perl';
87 24 100 100     93 if ($tagtypes{'!perl'} and $tagtypes{'!!perl'}) {
    100          
    50          
88 4         4 $perl_regex = '(?:tag:yaml\\.org,2002:|!)perl';
89             }
90             elsif ($tagtypes{'!perl'}) {
91 18         28 $perl_regex = '!perl';
92             }
93             elsif ($tagtypes{'!!perl'}) {
94 2         3 $perl_regex = 'tag:yaml\\.org,2002:perl';
95             }
96              
97 24         55 my $class_regex = qr{.+};
98 24         33 my $no_objects = 0;
99 24 100       42 if ($classes) {
100 5 100       9 if (@$classes) {
101 1         6 $class_regex = '(' . join( '|', map "\Q$_\E", @$classes ) . ')';
102             }
103             else {
104 4         6 $no_objects = 1;
105 4         7 $class_regex = '';
106             }
107             }
108              
109             # Code
110 24 100       45 if ($loadcode) {
111             my $load_code = sub {
112 6     6   10 my ($constructor, $event) = @_;
113 6         25 return $self->evaluate_code($event->{value});
114 5         16 };
115             my $load_code_blessed = sub {
116 6     6   12 my ($constructor, $event) = @_;
117 6         12 my $class = $event->{tag};
118 6         90 $class =~ s{^$perl_regex/code:}{};
119 6         42 my $sub = $self->evaluate_code($event->{value});
120 4         15 return $self->object($sub, $class);
121 5         9 };
122             $schema->add_resolver(
123             tag => "$_/code",
124             match => [ all => $load_code],
125             implicit => 0,
126 5         18 ) for @perl_tags;
127 5         140 $schema->add_resolver(
128             tag => qr{^$perl_regex/code:$class_regex$},
129             match => [ all => $load_code_blessed ],
130             implicit => 0,
131             );
132 5 100       20 $schema->add_resolver(
133             tag => qr{^$perl_regex/code:.+},
134             match => [ all => $load_code ],
135             implicit => 0,
136             ) if $no_objects;
137             }
138             else {
139 19     3   142 my $loadcode_dummy = sub { return sub {} };
  3         14  
140             my $loadcode_blessed_dummy = sub {
141 2     2   3 my ($constructor, $event) = @_;
142 2         4 my $class = $event->{tag};
143 2         19 $class =~ s{^$perl_regex/code:}{};
144 2         12 return $self->object(sub {}, $class);
145 19         68 };
146             $schema->add_resolver(
147             tag => "$_/code",
148             match => [ all => $loadcode_dummy ],
149             implicit => 0,
150 19         97 ) for @perl_tags;
151 19         287 $schema->add_resolver(
152             tag => qr{^$perl_regex/code:$class_regex$},
153             match => [ all => $loadcode_blessed_dummy ],
154             implicit => 0,
155             );
156 19         187 $schema->add_resolver(
157             tag => qr{^$perl_regex/code:.+},
158             match => [ all => $loadcode_dummy ],
159             implicit => 0,
160             );
161             }
162              
163             # Glob
164             my $load_glob = sub {
165 8     8   11 my $value = undef;
166 8         17 return \$value;
167 24         71 };
168             my $load_glob_blessed = sub {
169 2     2   4 my ($constructor, $event) = @_;
170 2         4 my $class = $event->{tag};
171 2         20 $class =~ s{^$perl_regex/glob:}{};
172 2         5 my $value = undef;
173 2         6 return $self->object(\$value, $class);
174 24         69 };
175              
176             $schema->add_mapping_resolver(
177             tag => "$_/glob",
178             on_create => $load_glob,
179             on_data => sub {
180 6     6   13 my ($constructor, $ref, $list) = @_;
181 6         31 $$ref = $self->construct_glob($list);
182             },
183 24         164 ) for @perl_tags;
184 24 100       42 if ($no_objects) {
185             $schema->add_mapping_resolver(
186             tag => qr{^$perl_regex/glob:.+$},
187             on_create => $load_glob,
188             on_data => sub {
189 2     2   5 my ($constructor, $ref, $list) = @_;
190 2         11 $$ref = $self->construct_glob($list);
191             },
192 4         46 );
193             }
194             else {
195             $schema->add_mapping_resolver(
196             tag => qr{^$perl_regex/glob:$class_regex$},
197             on_create => $load_glob_blessed,
198             on_data => sub {
199 2     2   3 my ($constructor, $ref, $list) = @_;
200 2         22 $$$ref = $self->construct_glob($list);
201             },
202 20         289 );
203             }
204              
205             # Regex
206             my $load_regex = sub {
207 5     5   10 my ($constructor, $event) = @_;
208 5         24 return $self->construct_regex($event->{value});
209 24         68 };
210             my $load_regex_dummy = sub {
211 2     2   4 my ($constructor, $event) = @_;
212 2         6 return $event->{value};
213 24         95 };
214             my $load_regex_blessed = sub {
215 6     6   38 my ($constructor, $event) = @_;
216 6         13 my $class = $event->{tag};
217 6         95 $class =~ s{^$perl_regex/regexp:}{};
218 6         28 my $qr = $self->construct_regex($event->{value});
219 6         16 return $self->object($qr, $class);
220 24         74 };
221             $schema->add_resolver(
222             tag => "$_/regexp",
223             match => [ all => $load_regex ],
224             implicit => 0,
225 24         128 ) for @perl_tags;
226 24         245 $schema->add_resolver(
227             tag => qr{^$perl_regex/regexp:$class_regex$},
228             match => [ all => $load_regex_blessed ],
229             implicit => 0,
230             );
231 24         186 $schema->add_resolver(
232             tag => qr{^$perl_regex/regexp:.*$},
233             match => [ all => $load_regex_dummy ],
234             implicit => 0,
235             );
236              
237 24     7   61 my $load_sequence = sub { return [] };
  7         13  
238             my $load_sequence_blessed = sub {
239 13     13   22 my ($constructor, $event) = @_;
240 13         23 my $class = $event->{tag};
241 13         122 $class =~ s{^$perl_regex/array:}{};
242 13         47 return $self->object([], $class);
243 24         54 };
244             $schema->add_sequence_resolver(
245             tag => "$_/array",
246             on_create => $load_sequence,
247 24         72 ) for @perl_tags;
248 24         223 $schema->add_sequence_resolver(
249             tag => qr{^$perl_regex/array:$class_regex$},
250             on_create => $load_sequence_blessed,
251             );
252 24         160 $schema->add_sequence_resolver(
253             tag => qr{^$perl_regex/array:.+$},
254             on_create => $load_sequence,
255             );# if $no_objects;
256              
257 24     6   62 my $load_mapping = sub { return {} };
  6         13  
258             my $load_mapping_blessed = sub {
259 7     7   14 my ($constructor, $event) = @_;
260 7         14 my $class = $event->{tag};
261 7         92 $class =~ s{^$perl_regex/hash:}{};
262 7         32 return $self->object({}, $class);
263 24         60 };
264             $schema->add_mapping_resolver(
265             tag => "$_/hash",
266             on_create => $load_mapping,
267 24         73 ) for @perl_tags;
268 24         283 $schema->add_mapping_resolver(
269             tag => qr{^$perl_regex/hash:$class_regex$},
270             on_create => $load_mapping_blessed,
271             );
272 24         151 $schema->add_mapping_resolver(
273             tag => qr{^$perl_regex/hash:.+$},
274             on_create => $load_mapping,
275             ); # if $no_objects;
276              
277             # Ref
278             my $load_ref = sub {
279 8     8   12 my $value = undef;
280 8         17 return \$value;
281 24         70 };
282             my $load_ref_blessed = sub {
283 7     7   14 my ($constructor, $event) = @_;
284 7         14 my $class = $event->{tag};
285 7         81 $class =~ s{^$perl_regex/ref:}{};
286 7         14 my $value = undef;
287 7         23 return $self->object(\$value, $class);
288 24         62 };
289             $schema->add_mapping_resolver(
290             tag => "$_/ref",
291             on_create => $load_ref,
292             on_data => sub {
293 6     6   13 my ($constructor, $ref, $list) = @_;
294 6         20 $$$ref = $self->construct_ref($list);
295             },
296 24         112 ) for @perl_tags;
297             $schema->add_mapping_resolver(
298             tag => qr{^$perl_regex/ref:$class_regex$},
299             on_create => $load_ref_blessed,
300             on_data => sub {
301 7     7   14 my ($constructor, $ref, $list) = @_;
302 7         22 $$$ref = $self->construct_ref($list);
303             },
304 24         274 );
305             $schema->add_mapping_resolver(
306             tag => qr{^$perl_regex/ref:.+$},
307             on_create => $load_ref,
308             on_data => sub {
309 2     2   4 my ($constructor, $ref, $list) = @_;
310 2         5 $$$ref = $self->construct_ref($list);
311             },
312 24         208 ); # if $no_objects;
313              
314             # Scalar ref
315             my $load_scalar_ref = sub {
316 8     8   15 my $value = undef;
317 8         16 return \$value;
318 24         85 };
319             my $load_scalar_ref_blessed = sub {
320 7     7   16 my ($constructor, $event) = @_;
321 7         13 my $class = $event->{tag};
322 7         91 $class =~ s{^$perl_regex/scalar:}{};
323 7         16 my $value = undef;
324 7         26 return $self->object(\$value, $class);
325 24         65 };
326             $schema->add_mapping_resolver(
327             tag => "$_/scalar",
328             on_create => $load_scalar_ref,
329             on_data => sub {
330 6     6   15 my ($constructor, $ref, $list) = @_;
331 6         23 $$$ref = $self->construct_scalar($list);
332             },
333 24         131 ) for @perl_tags;
334             $schema->add_mapping_resolver(
335             tag => qr{^$perl_regex/scalar:$class_regex$},
336             on_create => $load_scalar_ref_blessed,
337             on_data => sub {
338 7     7   16 my ($constructor, $ref, $list) = @_;
339 7         20 $$$ref = $self->construct_scalar($list);
340             },
341 24         245 );
342             $schema->add_mapping_resolver(
343             tag => qr{^$perl_regex/scalar:.+$},
344             on_create => $load_scalar_ref,
345             on_data => sub {
346 2     2   3 my ($constructor, $ref, $list) = @_;
347 2         5 $$$ref = $self->construct_scalar($list);
348             },
349 24         221 ); # if $no_objects;
350              
351             $schema->add_representer(
352             scalarref => 1,
353             code => sub {
354 4     4   9 my ($rep, $node) = @_;
355 4         10 $node->{tag} = $perl_tag . "/scalar";
356 4         21 $node->{data} = $self->represent_scalar($node->{value});
357             },
358 24         117 );
359             $schema->add_representer(
360             refref => 1,
361             code => sub {
362 4     4   7 my ($rep, $node) = @_;
363 4         11 $node->{tag} = $perl_tag . "/ref";
364 4         16 $node->{data} = $self->represent_ref($node->{value});
365             },
366 24         219 );
367             $schema->add_representer(
368             coderef => 1,
369             code => sub {
370 5     5   10 my ($rep, $node) = @_;
371 5         13 $node->{tag} = $perl_tag . "/code";
372 5 100       24 $node->{data} = $dumpcode ? $self->represent_code($node->{value}) : '{ "DUMMY" }';
373             },
374 24         95 );
375             $schema->add_representer(
376             glob => 1,
377             code => sub {
378 6     6   10 my ($rep, $node) = @_;
379 6         15 $node->{tag} = $perl_tag . "/glob";
380 6         28 $node->{data} = $self->represent_glob($node->{value});
381             },
382 24         98 );
383              
384             $schema->add_representer(
385             class_matches => 1,
386             code => sub {
387 40     40   80 my ($rep, $node) = @_;
388 40         69 my $blessed = blessed $node->{value};
389 40         69 my $tag_blessed = ":$blessed";
390 40 50       379 if ($blessed !~ m/^$class_regex$/) {
391 0         0 $tag_blessed = '';
392             }
393             $node->{tag} = sprintf "$perl_tag/%s%s",
394 40         213 lc($node->{reftype}), $tag_blessed;
395 40 100       185 if ($node->{reftype} eq 'HASH') {
    100          
    100          
    100          
    100          
    100          
    50          
396 6         11 $node->{data} = $node->{value};
397             }
398             elsif ($node->{reftype} eq 'ARRAY') {
399 10         16 $node->{data} = $node->{value};
400             }
401              
402             # Fun with regexes in perl versions!
403             elsif ($node->{reftype} eq 'REGEXP') {
404 8 100       18 if ($blessed eq 'Regexp') {
405 4         10 $node->{tag} = $perl_tag . "/regexp";
406             }
407 8         34 $node->{data} = $self->represent_regex($node->{value});
408             }
409             elsif ($node->{reftype} eq 'SCALAR') {
410              
411             # in perl <= 5.10 regex reftype(regex) was SCALAR
412 4 50 33     21 if ($blessed eq 'Regexp') {
    50 33        
413 0         0 $node->{tag} = $perl_tag . '/regexp';
414 0         0 $node->{data} = $self->represent_regex($node->{value});
415             }
416              
417             # In perl <= 5.10 there seemed to be no better pure perl
418             # way to detect a blessed regex?
419             elsif (
420             $] <= 5.010001
421 0         0 and not defined ${ $node->{value} }
422             and $node->{value} =~ m/^\(\?/
423             ) {
424 0         0 $node->{tag} = $perl_tag . '/regexp' . $tag_blessed;
425 0         0 $node->{data} = $self->represent_regex($node->{value});
426             }
427             else {
428             # phew, just a simple scalarref
429 4         18 $node->{data} = $self->represent_scalar($node->{value});
430             }
431             }
432             elsif ($node->{reftype} eq 'REF') {
433 4         13 $node->{data} = $self->represent_ref($node->{value});
434             }
435              
436             elsif ($node->{reftype} eq 'CODE') {
437 5 100       19 $node->{data} = $dumpcode ? $self->represent_code($node->{value}) : '{ "DUMMY" }';
438             }
439             elsif ($node->{reftype} eq 'GLOB') {
440 3         14 $node->{data} = $self->represent_glob($node->{value});
441             }
442             else {
443 0         0 die "Reftype '$node->{reftype}' not implemented";
444             }
445              
446 40         165 return 1;
447             },
448 24         191 );
449 24         150 return;
450             }
451              
452             sub evaluate_code {
453 12     12 1 19 my ($self, $code) = @_;
454 12 100       65 unless ($code =~ m/^ \s* \{ .* \} \s* \z/xs) {
455 2         21 die "Malformed code";
456             }
457 10         22 $code = "sub $code";
458 10     1   682 my $sub = eval $code;
  1     1   13  
  1     1   1  
  1     1   49  
  1     1   4  
  1     1   1  
  1     1   64  
  1     1   6  
  1     1   1  
  1     1   53  
  1     1   4  
  1     1   1  
  1     1   42  
  1     1   6  
  1     1   1  
  1     1   57  
  1         4  
  1         1  
  1         70  
  1         5  
  1         2  
  1         38  
  1         3  
  1         1  
  1         51  
  1         7  
  1         1  
  1         47  
  1         4  
  1         1  
  1         57  
  1         5  
  1         2  
  1         38  
  1         3  
  1         1  
  1         55  
  1         6  
  1         1  
  1         47  
  1         4  
  1         1  
  1         46  
  1         5  
  1         1  
  1         63  
  1         5  
  1         1  
  1         55  
459 10 100       30 if ($@) {
460 2         20 die "Couldn't eval code: $@>>$code<<";
461             }
462 8         26 return $sub;
463             }
464              
465             sub construct_regex {
466 11     11 1 18 my ($self, $regex) = @_;
467 11 100       98 if ($regex =~ m/^$qr_prefix(.*)\)\z/s) {
468 2         5 $regex = $1;
469             }
470 11         99 my $qr = qr{$regex};
471 11         27 return $qr;
472             }
473              
474             sub construct_glob {
475 10     10 1 19 my ($self, $list) = @_;
476 10 50       23 if (@$list % 2) {
477 0         0 die "Unexpected data in perl/glob construction";
478             }
479 10         48 my %globdata = @$list;
480 10 100       41 my $name = delete $globdata{NAME} or die "Missing NAME in perl/glob";
481 9         18 my $pkg = delete $globdata{PACKAGE};
482 9 100       18 $pkg = 'main' unless defined $pkg;
483 9         30 my @allowed = qw(SCALAR ARRAY HASH CODE IO);
484 9         20 delete @globdata{ @allowed };
485 9 100       19 if (my @keys = keys %globdata) {
486 1         15 die "Unexpected keys in perl/glob: @keys";
487             }
488 7     7   49 no strict 'refs';
  7         10  
  7         3266  
489 8         13 return *{"${pkg}::$name"};
  8         75  
490             }
491              
492             sub construct_scalar {
493 30     30 1 45 my ($self, $list) = @_;
494 30 100       54 if (@$list != 2) {
495 4         42 die "Unexpected data in perl/scalar construction";
496             }
497 26         45 my ($key, $value) = @$list;
498 26 100       49 unless ($key eq '=') {
499 4         43 die "Unexpected data in perl/scalar construction";
500             }
501 22         46 return $value;
502             }
503              
504             sub construct_ref {
505 15     15 1 30 &construct_scalar;
506             }
507              
508             sub represent_scalar {
509 16     16 1 26 my ($self, $value) = @_;
510 16         65 return { '=' => $$value };
511             }
512              
513             sub represent_ref {
514 8     8 1 13 &represent_scalar;
515             }
516              
517             sub represent_code {
518 8     8 1 16 my ($self, $code) = @_;
519 8         52 require B::Deparse;
520 8         307 my $deparse = B::Deparse->new("-p", "-sC");
521 8         13094 return $deparse->coderef2text($code);
522             }
523              
524              
525             my @stats = qw/ device inode mode links uid gid rdev size
526             atime mtime ctime blksize blocks /;
527             sub represent_glob {
528 9     9 1 17 my ($self, $glob) = @_;
529 9         18 my %glob;
530 9         19 for my $type (qw/ PACKAGE NAME SCALAR ARRAY HASH CODE IO /) {
531 63         60 my $value = *{ $glob }{ $type };
  63         106  
532 63 100       159 if ($type eq 'SCALAR') {
    100          
533 9         12 $value = $$value;
534             }
535             elsif ($type eq 'IO') {
536 9 100       16 if (defined $value) {
537 5         6 undef $value;
538 5         14 $value->{stat} = {};
539 5 50       6 if ($value->{fileno} = fileno(*{ $glob })) {
  5         20  
540 5         8 @{ $value->{stat} }{ @stats } = stat(*{ $glob });
  5         63  
  5         103  
541 5         8 $value->{tell} = tell *{ $glob };
  5         16  
542             }
543             }
544             }
545 63 100       116 $glob{ $type } = $value if defined $value;
546             }
547 9         52 return \%glob;
548             }
549              
550             sub represent_regex {
551 8     8 1 12 my ($self, $regex) = @_;
552 8         13 $regex = "$regex";
553 8 50       83 if ($regex =~ m/^$qr_prefix(.*)\)\z/s) {
554 8         22 $regex = $1;
555             }
556 8         15 return $regex;
557             }
558              
559             sub object {
560 48     48 1 87 my ($self, $data, $class) = @_;
561 48         168 return bless $data, $class;
562             }
563              
564             1;
565              
566             __END__