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