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