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