File Coverage

blib/lib/YAML/PP/Schema/Include.pm
Criterion Covered Total %
statement 80 86 93.0
branch 25 30 83.3
condition 2 4 50.0
subroutine 14 15 93.3
pod 0 9 0.0
total 121 144 84.0


line stmt bran cond sub pod time code
1 1     1   461 use strict;
  1         2  
  1         51  
2 1     1   6 use warnings;
  1         2  
  1         60  
3             package YAML::PP::Schema::Include;
4              
5             our $VERSION = '0.036'; # VERSION
6              
7 1     1   5 use Carp qw/ croak /;
  1         2  
  1         42  
8 1     1   7 use Scalar::Util qw/ weaken /;
  1         1  
  1         42  
9 1     1   6 use File::Basename qw/ dirname /;
  1         2  
  1         967  
10              
11             sub new {
12 4     4 0 10584 my ($class, %args) = @_;
13              
14 4         10 my $paths = delete $args{paths};
15 4 100       12 if (defined $paths) {
16 2 50       7 unless (ref $paths eq 'ARRAY') {
17 2         5 $paths = [$paths];
18             }
19             }
20             else {
21 2         4 $paths = [];
22             }
23 4   50     20 my $allow_absolute = $args{allow_absolute} || 0;
24 4   50     15 my $loader = $args{loader} || \&default_loader;
25              
26 4         15 my $self = bless {
27             paths => $paths,
28             allow_absolute => $allow_absolute,
29             last_includes => [],
30             cached => {},
31             loader => $loader,
32             }, $class;
33 4         13 return $self;
34             }
35              
36             sub init {
37 0     0 0 0 my ($self) = @_;
38 0         0 $self->{last_includes} = [];
39 0         0 $self->{cached} = [];
40             }
41              
42 11     11 0 23 sub paths { $_[0]->{paths} }
43 11     11 0 18 sub allow_absolute { $_[0]->{allow_absolute} }
44             sub yp {
45 15     15 0 51 my ($self, $yp) = @_;
46 15 100       33 if (@_ == 2) {
47 4         8 $self->{yp} = $yp;
48 4         14 weaken $self->{yp};
49 4         15 return $yp;
50             }
51 11         21 return $self->{yp};
52             }
53              
54             sub register {
55 4     4 0 13 my ($self, %args) = @_;
56 4         7 my $schema = $args{schema};
57              
58             $schema->add_resolver(
59             tag => '!include',
60 4     11   20 match => [ all => sub { $self->include(@_) } ],
  11         38  
61             implicit => 0,
62             );
63             }
64              
65             sub include {
66 11     11 0 23 my ($self, $constructor, $event) = @_;
67 11         20 my $yp = $self->yp;
68 11         26 my $search_paths = $self->paths;
69 11         17 my $allow_absolute = $self->allow_absolute;
70              
71 11         17 my $relative = not @$search_paths;
72 11 100       22 if ($relative) {
73 6         9 my $last_includes = $self->{last_includes};
74 6 100       17 if (@$last_includes) {
75 3         7 $search_paths = [ $last_includes->[-1] ];
76             }
77             else {
78             # we are in the top-level file and need to look into
79             # the original YAML::PP instance
80 3         12 my $filename = $yp->loader->filename;
81 3         147 $search_paths = [dirname $filename];
82             }
83             }
84 11         26 my $filename = $event->{value};
85              
86 11         12 my $fullpath;
87 11 100       95 if (File::Spec->file_name_is_absolute($filename)) {
88 1 50       4 unless ($allow_absolute) {
89 1         88 croak "Absolute filenames not allowed";
90             }
91 0         0 $fullpath = $filename;
92             }
93             else {
94 10         67 my @paths = File::Spec->splitdir($filename);
95 10 50       26 unless ($allow_absolute) {
96             # if absolute paths are not allowed, we also may not use upwards ..
97 10         62 @paths = File::Spec->no_upwards(@paths);
98             }
99 10         23 for my $candidate (@$search_paths) {
100 10         98 my $test = File::Spec->catfile( $candidate, @paths );
101 10 100       275 if (-e $test) {
102 9         28 $fullpath = $test;
103 9         25 last;
104             }
105             }
106 10 100       258 croak "File '$filename' not found" unless defined $fullpath;
107             }
108              
109 9 100       42 if ($self->{cached}->{ $fullpath }++) {
110 1         96 croak "Circular include '$fullpath'";
111             }
112 8 100       16 if ($relative) {
113 5         8 push @{ $self->{last_includes} }, dirname $fullpath;
  5         197  
114             }
115              
116             # We need a new object because we are still in the parsing and
117             # constructing process
118 8         34 my $clone = $yp->clone;
119 8         19 my ($data) = $self->loader->($clone, $fullpath);
120              
121 6 100       15 if ($relative) {
122 3         9 pop @{ $self->{last_includes} };
  3         12  
123             }
124 6 50       20 unless (--$self->{cached}->{ $fullpath }) {
125 6         15 delete $self->{cached}->{ $fullpath };
126             }
127 6         26 return $data;
128             }
129              
130             sub loader {
131 8     8 0 17 my ($self, $code) = @_;
132 8 50       18 if (@_ == 2) {
133 0         0 $self->{loader} = $code;
134 0         0 return $code;
135             }
136 8         18 return $self->{loader};
137             }
138             sub default_loader {
139 8     8 0 17 my ($yp, $filename) = @_;
140 8         23 $yp->load_file($filename);
141             }
142              
143             1;
144              
145             __END__