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