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