| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package YAML::LoadBundle; | 
| 2 |  |  |  |  |  |  | # ABSTRACT: Load a directory of YAML files as a bundle | 
| 3 | 4 |  |  | 4 |  | 234167 | use version; | 
|  | 4 |  |  |  |  | 6611 |  | 
|  | 4 |  |  |  |  | 36 |  | 
| 4 |  |  |  |  |  |  | our $VERSION = 'v0.4.3'; # VERSION | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 4 |  |  | 4 |  | 400 | use base qw(Exporter); | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 463 |  | 
| 7 | 4 |  |  | 4 |  | 22 | use warnings; | 
|  | 4 |  |  |  |  | 5 |  | 
|  | 4 |  |  |  |  | 98 |  | 
| 8 | 4 |  |  | 4 |  | 21 | use strict; | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 77 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 | 4 |  |  | 4 |  | 16 | use Carp; | 
|  | 4 |  |  |  |  | 16 |  | 
|  | 4 |  |  |  |  | 282 |  | 
| 11 | 4 |  |  | 4 |  | 21 | use Cwd                 qw( abs_path ); | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 163 |  | 
| 12 | 4 |  |  | 4 |  | 1669 | use Digest::SHA1        qw( sha1_hex sha1 ); | 
|  | 4 |  |  |  |  | 2283 |  | 
|  | 4 |  |  |  |  | 230 |  | 
| 13 | 4 |  |  | 4 |  | 27 | use File::Find          qw( find ); | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 267 |  | 
| 14 | 4 |  |  | 4 |  | 1613 | use Hash::Merge::Simple (); | 
|  | 4 |  |  |  |  | 1668 |  | 
|  | 4 |  |  |  |  | 93 |  | 
| 15 | 4 |  |  | 4 |  | 23 | use Scalar::Util        qw( reftype refaddr ); | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 250 |  | 
| 16 | 4 |  |  | 4 |  | 2142 | use Storable            qw( freeze dclone ); | 
|  | 4 |  |  |  |  | 10961 |  | 
|  | 4 |  |  |  |  | 265 |  | 
| 17 | 4 |  |  | 4 |  | 1207 | use YAML::XS            qw(Load); | 
|  | 4 |  |  |  |  | 7068 |  | 
|  | 4 |  |  |  |  | 9824 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | our @EXPORT_OK = qw( | 
| 20 |  |  |  |  |  |  | load_yaml | 
| 21 |  |  |  |  |  |  | load_yaml_bundle | 
| 22 |  |  |  |  |  |  | add_yaml_observer | 
| 23 |  |  |  |  |  |  | remove_yaml_observer | 
| 24 |  |  |  |  |  |  | ); | 
| 25 |  |  |  |  |  |  | our %EXPORT_TAGS = ( all => [@EXPORT_OK] ); | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | our $CacheDir; | 
| 28 |  |  |  |  |  |  | $CacheDir = $ENV{YAML_LOADBUNDLE_CACHEDIR} unless defined $CacheDir; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | my @load_yaml_observers; | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | sub add_yaml_observer { | 
| 33 | 1 |  |  | 1 | 1 | 597 | my $observer = shift; | 
| 34 | 1 | 50 |  |  |  | 4 | die "Observer must be a code ref." unless ref($observer) eq 'CODE'; | 
| 35 | 1 |  |  |  |  | 4 | push @load_yaml_observers, $observer; | 
| 36 |  |  |  |  |  |  | } | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | sub _notify_yaml_observers { | 
| 39 | 5 |  |  | 5 |  | 1465 | my $file = shift; | 
| 40 | 5 |  |  |  |  | 21 | for my $observer (@load_yaml_observers) { | 
| 41 | 1 |  |  |  |  | 4 | $observer->($file); | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | sub remove_yaml_observer { | 
| 46 | 1 |  |  | 1 | 1 | 1236 | my $observer = shift; | 
| 47 | 1 | 50 |  |  |  | 4 | die "Observer must be a code ref." unless ref($observer) eq 'CODE'; | 
| 48 | 1 |  |  |  |  | 5 | my $obref = refaddr $observer; | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | @load_yaml_observers = grep { | 
| 51 | 1 |  |  |  |  | 3 | refaddr($_) != $obref | 
|  | 1 |  |  |  |  | 6 |  | 
| 52 |  |  |  |  |  |  | } @load_yaml_observers; | 
| 53 |  |  |  |  |  |  | } | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | our %seen; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | sub load_yaml { | 
| 58 | 29 |  |  | 29 | 1 | 3154883 | my ($arg, $dont_cache) = @_; | 
| 59 | 29 |  |  |  |  | 78 | my @yaml; | 
| 60 |  |  |  |  |  |  | my $cache_mtime; | 
| 61 | 29 |  |  |  |  | 0 | my %params; | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  | # We clone references that appear more than once in the data | 
| 64 |  |  |  |  |  |  | # structure. (For compatibility with Data::Visitor.) | 
| 65 | 29 |  |  |  |  | 71 | local %seen = (); | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 29 | 100 | 33 |  |  | 289 | if (ref $arg) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 68 | 2 |  |  |  |  | 35 | @yaml = <$arg>; | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  | elsif ($arg =~ /\n/) { | 
| 71 | 22 |  |  |  |  | 20050 | my $digest      = sha1($arg); | 
| 72 | 22 |  |  |  |  | 46 | $cache_mtime = 1; | 
| 73 | 22 |  |  |  |  | 62 | my $perl        = _yaml_cache_peek($digest, $cache_mtime); | 
| 74 | 22 | 100 |  |  |  | 40856 | return $perl if defined $perl; | 
| 75 | 3 |  |  | 3 |  | 27 | open my $fh, '<', \$arg; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 19 |  | 
|  | 18 |  |  |  |  | 275 |  | 
| 76 | 18 |  |  |  |  | 51700 | @yaml = <$fh>; | 
| 77 | 18 |  |  |  |  | 453 | $arg  = $digest; | 
| 78 | 18 |  |  |  |  | 92 | $params{no_disk_cache} = 1; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  | elsif (-f $arg and -s _) { | 
| 81 |  |  |  |  |  |  | # $arg is a file path. | 
| 82 | 5 |  |  |  |  | 26 | _notify_yaml_observers($arg); | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 5 |  |  |  |  | 23 | my $mtime = (stat _)[9]; | 
| 85 | 5 |  |  |  |  | 19 | my $perl = _yaml_cache_peek($arg, $mtime); | 
| 86 | 5 | 100 |  |  |  | 42 | return $perl if defined $perl; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 4 | 50 |  |  |  | 192 | open my $fh, $arg | 
| 89 |  |  |  |  |  |  | or croak "Can't open YAML file $arg: $!"; | 
| 90 | 4 |  |  |  |  | 17789 | @yaml = <$fh>; | 
| 91 | 4 |  |  |  |  | 64 | $cache_mtime = $mtime; | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  | else { | 
| 94 | 0 |  |  |  |  | 0 | croak "Can't load empty/missing YAML file: $arg."; | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 24 |  |  |  |  | 44 | my $perl; | 
| 98 | 24 |  |  |  |  | 36 | eval { $perl = Load(join '', @yaml) }; | 
|  | 24 |  |  |  |  | 229323 |  | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 24 | 100 |  |  |  | 10431 | die "$@\nYAML File: $arg\n" if $@; | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | # Can't cache/flatten empty YAML | 
| 103 | 23 | 50 |  |  |  | 68 | return unless $perl; | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # TODO: this is a temporary fix. previous functionaly skipped caching if a | 
| 106 |  |  |  |  |  |  | # second arg was passed into load_yaml. a recent refactor introduced a bug | 
| 107 |  |  |  |  |  |  | # that caused the code to never cache. as a temporary workaround we will | 
| 108 |  |  |  |  |  |  | # just set $cache_mtime to 0 if there's a second arg to this method. this | 
| 109 |  |  |  |  |  |  | # will tell _unravel_and_cache to skip the caching step. | 
| 110 | 23 | 100 |  |  |  | 54 | $cache_mtime = 0 if $dont_cache; | 
| 111 | 23 |  |  |  |  | 100 | $perl = _unravel_and_cache($arg, $perl, $cache_mtime, %params); | 
| 112 |  |  |  |  |  |  |  | 
| 113 | 23 |  |  |  |  | 10593 | return $perl; | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | my $shallow_merge = sub { | 
| 117 |  |  |  |  |  |  | my ($left, $right) = @_; | 
| 118 |  |  |  |  |  |  | if (reftype($left) eq 'ARRAY') { | 
| 119 |  |  |  |  |  |  | $left = { map %$_, @$left }; | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  | return ( | 
| 122 |  |  |  |  |  |  | (map { %$_ } (reftype($left) eq 'ARRAY' ? @$left : $left)), | 
| 123 |  |  |  |  |  |  | %$right | 
| 124 |  |  |  |  |  |  | ); | 
| 125 |  |  |  |  |  |  | }; | 
| 126 |  |  |  |  |  |  | my $deep_merge = sub { | 
| 127 |  |  |  |  |  |  | my ($left, $right) = @_; | 
| 128 |  |  |  |  |  |  | return %{ Hash::Merge::Simple->merge( | 
| 129 |  |  |  |  |  |  | (reftype($left) eq 'ARRAY' ? @$left : $left), | 
| 130 |  |  |  |  |  |  | $right, | 
| 131 |  |  |  |  |  |  | ) }; | 
| 132 |  |  |  |  |  |  | }; | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | my $clone_merge = sub { | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | my ($left, $right) = @_; | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | if (reftype($left) eq 'HASH' && reftype($right) eq 'HASH') | 
| 139 |  |  |  |  |  |  | { | 
| 140 |  |  |  |  |  |  | my %seen; | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | foreach my $key (keys %$left) | 
| 143 |  |  |  |  |  |  | { | 
| 144 |  |  |  |  |  |  | my $clone = $left->{$key}; | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | die "-clone of $key is not a hash\n" | 
| 147 |  |  |  |  |  |  | unless reftype($clone) eq "HASH"; | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | my $list = delete $clone->{$key}; | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | die "-clone of $key is missing a list of keys under the same key\n" | 
| 152 |  |  |  |  |  |  | unless $list && reftype($list) eq 'ARRAY'; | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | foreach my $cloned_key (@$list) | 
| 155 |  |  |  |  |  |  | { | 
| 156 |  |  |  |  |  |  | die "-clone key $cloned_key must appear only once.\n" | 
| 157 |  |  |  |  |  |  | if $seen{$cloned_key}++; | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | $right->{$cloned_key} = $clone; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | return %$right; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | die "-clone must appear in a hash, and contain a hash.\n"; | 
| 167 |  |  |  |  |  |  | }; | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | # in order of priority: | 
| 170 |  |  |  |  |  |  | my @SPECIAL = qw( | 
| 171 |  |  |  |  |  |  | -merge | 
| 172 |  |  |  |  |  |  | export | 
| 173 |  |  |  |  |  |  | -export | 
| 174 |  |  |  |  |  |  | import | 
| 175 |  |  |  |  |  |  | -import | 
| 176 |  |  |  |  |  |  | -clone | 
| 177 |  |  |  |  |  |  | ); | 
| 178 |  |  |  |  |  |  | my %SPECIAL = ( | 
| 179 |  |  |  |  |  |  | -import => $shallow_merge, | 
| 180 |  |  |  |  |  |  | import  => $shallow_merge, | 
| 181 |  |  |  |  |  |  | -export => $shallow_merge, | 
| 182 |  |  |  |  |  |  | export  => $shallow_merge, | 
| 183 |  |  |  |  |  |  | -merge  => $deep_merge, | 
| 184 |  |  |  |  |  |  | -clone  => $clone_merge, | 
| 185 |  |  |  |  |  |  | ); | 
| 186 |  |  |  |  |  |  |  | 
| 187 |  |  |  |  |  |  | # Note: This used to add a (heavy) dependency on Data::Visitor | 
| 188 |  |  |  |  |  |  | # to do these simple transformations. I *think* this is exactly | 
| 189 |  |  |  |  |  |  | # equivalent to what it used to do. | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | sub _unravel { | 
| 192 | 12565 |  |  | 12565 |  | 15345 | my $data = shift; | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 12565 | 100 |  |  |  | 18278 | if (ref $data) { | 
| 195 | 12495 | 100 |  |  |  | 38752 | $data = dclone($data) if $seen{$data}++; | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 12495 | 100 |  |  |  | 25440 | if (reftype $data eq 'HASH') { | 
|  |  | 50 |  |  |  |  |  | 
| 198 | 12460 |  |  |  |  | 16164 | return _unravel_hash($data); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  | elsif (reftype $data eq 'ARRAY') { | 
| 201 | 35 |  |  |  |  | 46 | for my $elt (@$data) { | 
| 202 | 75 |  |  |  |  | 105 | $elt = _unravel($elt); | 
| 203 |  |  |  |  |  |  | } | 
| 204 | 35 |  |  |  |  | 66 | return $data; | 
| 205 |  |  |  |  |  |  | } | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 70 |  |  |  |  | 109 | return $data; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # Note: this modifies the argument in place. But sometimes it returns | 
| 212 |  |  |  |  |  |  | # a different reference, in order to replace itself in the enclosing | 
| 213 |  |  |  |  |  |  | # data structure. (If it encounters a "-flatten" entry.) | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | sub _unravel_hash { | 
| 216 | 12460 |  |  | 12460 |  | 13613 | my $data = shift; | 
| 217 |  |  |  |  |  |  |  | 
| 218 | 12460 |  |  |  |  | 16491 | while (my @keys = grep { exists $data->{$_} } @SPECIAL) { | 
|  | 74880 |  |  |  |  | 116136 |  | 
| 219 |  |  |  |  |  |  | # Make sure that deeper -merges and such will be handled first | 
| 220 | 20 |  |  |  |  | 49 | for my $key ( grep { ! $SPECIAL{ $_ } } keys %$data ) { | 
|  | 36 |  |  |  |  | 82 |  | 
| 221 |  |  |  |  |  |  | # False values can be skipped for performance | 
| 222 | 14 | 50 |  |  |  | 34 | next unless $data->{$key}; | 
| 223 | 14 |  |  |  |  | 26 | $data->{$key} = _unravel($data->{$key}) | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 | 20 |  |  |  |  | 40 | for my $key (@keys) { | 
| 227 | 22 |  |  |  |  | 34 | my $handler = $SPECIAL{$key}; | 
| 228 | 22 |  |  |  |  | 35 | my $val = delete $data->{$key}; | 
| 229 | 22 | 50 |  |  |  | 43 | next unless $val; | 
| 230 | 22 |  |  |  |  | 37 | %$data = $handler->(_unravel($val), $data); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 12460 | 100 |  |  |  | 27049 | if (keys %$data == 1) { | 
| 235 | 38 | 100 |  |  |  | 103 | if (my $arrs = $data->{-flatten}) { | 
|  |  | 100 |  |  |  |  |  | 
| 236 | 3 |  |  |  |  | 5 | _unravel($arrs); | 
| 237 | 3 |  |  |  |  | 17 | return [ map @$_, @$arrs ]; | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  | elsif (my $hrefs = $data->{-flattenhash}) { | 
| 240 | 1 |  |  |  |  | 4 | _unravel($hrefs); | 
| 241 | 1 |  |  |  |  | 9 | return { map %$_, @$hrefs }; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  | } | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 12456 |  |  |  |  | 23307 | for my $elt (values %$data) { | 
| 246 | 123684 | 100 |  |  |  | 181327 | $elt = _unravel($elt) if ref($elt); | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 12456 | 100 |  |  |  | 442779 | $data = dclone($data) if $seen{$data}++; | 
| 250 | 12456 |  |  |  |  | 98333 | return $data; | 
| 251 |  |  |  |  |  |  | } | 
| 252 |  |  |  |  |  |  |  | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | { | 
| 255 |  |  |  |  |  |  | my %YAML_cache; | 
| 256 |  |  |  |  |  |  | sub _unravel_and_cache { | 
| 257 | 25 |  |  | 25 |  | 85 | my ($path, $perl, $cache_mtime, %params) = @_; | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 25 |  |  |  |  | 76 | _unravel($perl); | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | # TODO: need a better way to explicitly not cache here | 
| 262 | 25 | 100 |  |  |  | 64 | if ($cache_mtime) { | 
| 263 | 22 |  |  |  |  | 101 | my $frozen = Storable::freeze($perl); | 
| 264 | 22 |  |  |  |  | 50225 | $YAML_cache{$path} = [ $cache_mtime, $frozen ]; | 
| 265 | 22 | 100 | 100 |  |  | 99 | if ($CacheDir and not $params{no_disk_cache}) { | 
| 266 | 2 |  |  |  |  | 57 | my $cache_file = join "/", $CacheDir, sha1_hex($path); | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 2 |  |  |  |  | 6 | eval { mkdir $CacheDir }; | 
|  | 2 |  |  |  |  | 131 |  | 
| 269 | 2 | 50 |  |  |  | 11 | if ($@) { | 
| 270 | 0 |  |  |  |  | 0 | warn "Can't write yaml cache: $@"; | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  | else { | 
| 273 | 2 | 50 |  |  |  | 169 | open my $fh, '>', $cache_file or die "Cannot open $cache_file for writing $!"; | 
| 274 | 2 |  |  |  |  | 119 | print $fh $frozen; | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  |  | 
| 279 | 25 |  |  |  |  | 61 | return $perl; | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | sub _yaml_cache_peek { | 
| 283 | 29 |  |  | 29 |  | 91 | my ($path, $mtime) = @_; | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 29 |  |  |  |  | 65 | my $cache = $YAML_cache{$path}; | 
| 286 | 29 | 100 |  |  |  | 100 | if ($cache) { | 
|  |  | 100 |  |  |  |  |  | 
| 287 | 5 |  |  |  |  | 16 | my ($oldtime, $oldyaml) = @$cache; | 
| 288 | 5 | 50 |  |  |  | 35 | return Storable::thaw($oldyaml) if $oldtime == $mtime; | 
| 289 |  |  |  |  |  |  | } | 
| 290 |  |  |  |  |  |  | elsif ($CacheDir) { | 
| 291 | 7 |  |  |  |  | 51 | my $cache_file = join "/", $CacheDir, sha1_hex($path); | 
| 292 | 7 | 50 |  |  |  | 142 | if (-f $cache_file) { | 
| 293 | 0 |  |  |  |  | 0 | my $cache_time = (stat $cache_file)[9]; | 
| 294 | 0 | 0 |  |  |  | 0 | if ($cache_time >= $mtime) { | 
| 295 | 0 |  |  |  |  | 0 | open my $fh, "<$cache_file"; | 
| 296 | 0 |  |  |  |  | 0 | my $file_contents = do { local $/; <$fh> }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 297 | 0 |  |  |  |  | 0 | my $thawed = Storable::thaw($file_contents); | 
| 298 | 0 |  |  |  |  | 0 | $YAML_cache{$path} = [ $mtime, $file_contents ]; | 
| 299 | 0 | 0 |  |  |  | 0 | return $thawed if $cache_time >= $mtime; | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  | else { | 
| 302 | 0 |  |  |  |  | 0 | unlink $cache_file; | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  |  | 
| 307 | 24 |  |  |  |  | 55 | return; | 
| 308 |  |  |  |  |  |  | } | 
| 309 |  |  |  |  |  |  | } | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | { | 
| 312 |  |  |  |  |  |  | my %default_options = ( | 
| 313 |  |  |  |  |  |  | follow_symlinks_when => 'bundled', | 
| 314 |  |  |  |  |  |  | follow_symlinks_fail => 'error', | 
| 315 |  |  |  |  |  |  | conf_suffixes => [ 'conf', 'yml' ], | 
| 316 |  |  |  |  |  |  | max_depth => 20, | 
| 317 |  |  |  |  |  |  | ); | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | my %symlink_skipper = ( | 
| 320 |  |  |  |  |  |  | error  => sub { croak "Symlink $_[1] was skipped.\nYAML Bundle: $_[0]\n" }, | 
| 321 |  |  |  |  |  |  | warn   => sub { carp  "Symlink $_[1] was skipped.\nYAML Bundle: $_[0]\n" }, | 
| 322 |  |  |  |  |  |  | ignore => sub { }, | 
| 323 |  |  |  |  |  |  | ); | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | sub _merge_bundle { | 
| 326 | 16 |  |  | 16 |  | 45 | my ($current, $nested) = @_; | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 16 | 50 |  |  |  | 38 | if (ref($nested) eq 'ARRAY') { | 
| 329 | 0 | 0 |  |  |  | 0 | $current = [] unless defined $current; | 
| 330 | 0 |  |  |  |  | 0 | return +{ $deep_merge->($current, $nested) }; | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  | else { | 
| 333 | 16 | 100 |  |  |  | 37 | $current = {} unless defined $current; | 
| 334 | 16 |  |  |  |  | 32 | return +{ $deep_merge->($current, $nested) }; | 
| 335 |  |  |  |  |  |  | } | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | sub load_yaml_bundle { | 
| 339 | 18 |  |  | 18 | 1 | 127 | my ($path, $given_options) = @_; | 
| 340 | 18 |  |  |  |  | 21 | my $cache_mtime; | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | # Setup the default configuration | 
| 343 |  |  |  |  |  |  | my %options = ( | 
| 344 | 18 | 100 |  |  |  | 22 | %{ $given_options || {} }, | 
|  | 18 |  |  |  |  | 150 |  | 
| 345 |  |  |  |  |  |  | %default_options, | 
| 346 |  |  |  |  |  |  | ); | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | # Add _vars to the options to allow recursive calls to share state. | 
| 349 | 4 |  |  |  |  | 16 | $options{_match_suffix} = join "|", map { quotemeta } @{ $options{conf_suffixes} } | 
|  | 2 |  |  |  |  | 7 |  | 
| 350 | 18 | 100 |  |  |  | 51 | unless defined $options{_match_suffix}; | 
| 351 | 18 |  |  |  |  | 29 | $options{max_depth}--; | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | # Calculate the absolute base path to start from | 
| 354 | 18 | 100 |  |  |  | 32 | unless (defined $options{_original_path}) { | 
| 355 | 2 |  |  |  |  | 143 | $options{_original_path} = abs_path($path); | 
| 356 | 2 |  |  |  |  | 10 | $options{_original_path_length} = length $options{_original_path}; | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | # This is the top call, so check the cache | 
| 359 | 2 |  |  |  |  | 4 | my $this_mtime; | 
| 360 | 2 |  |  |  |  | 4 | $cache_mtime = 0; | 
| 361 |  |  |  |  |  |  | find({ | 
| 362 |  |  |  |  |  |  | follow_fast => 1, | 
| 363 |  |  |  |  |  |  | wanted      => sub { | 
| 364 | 22 | 100 |  | 22 |  | 1226 | if (/^.*\.(?:$options{_match_suffix})\z/s) { | 
| 365 | 10 |  |  |  |  | 25 | $this_mtime = (lstat _)[9]; | 
| 366 | 10 | 100 |  |  |  | 332 | $cache_mtime = $this_mtime if $this_mtime > $cache_mtime; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  | }, | 
| 369 |  |  |  |  |  |  | }, | 
| 370 |  |  |  |  |  |  | $options{_original_path}, | 
| 371 | 4 |  |  |  |  | 415 | grep { -f $_ } | 
| 372 | 4 |  |  |  |  | 14 | map { "$options{_original_path}.$_" } | 
| 373 | 2 |  |  |  |  | 20 | @{ $options{conf_suffixes} } | 
|  | 2 |  |  |  |  | 7 |  | 
| 374 |  |  |  |  |  |  | ); | 
| 375 | 2 |  |  |  |  | 20 | my $perl = _yaml_cache_peek($path, $cache_mtime); | 
| 376 |  |  |  |  |  |  |  | 
| 377 | 2 | 50 |  |  |  | 10 | return $perl if defined $perl; | 
| 378 |  |  |  |  |  |  | } | 
| 379 |  |  |  |  |  |  |  | 
| 380 | 18 |  |  |  |  | 35 | my $symlink_skipper = $symlink_skipper{ $options{follow_symlinks_fail} }; | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | # Stop, we've gone too far. | 
| 383 | 18 | 50 |  |  |  | 35 | if ($options{max_depth} < 0) { | 
| 384 | 0 |  |  |  |  | 0 | carp "Reached maximum path search depth while at $path.\nYAML Bundle: $options{_original_path}\n"; | 
| 385 | 0 |  |  |  |  | 0 | return; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 | 18 |  |  |  |  | 22 | my $perl; | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | # Do we have a top level .conf/.yml/.whatever in the bundle? | 
| 391 | 18 |  |  |  |  | 21 | for my $suffix (@{ $options{conf_suffixes} }) { | 
|  | 18 |  |  |  |  | 35 |  | 
| 392 | 36 |  |  |  |  | 90 | my $file = $path . '.' . $suffix; | 
| 393 | 36 | 100 | 66 |  |  | 503 | if (-f $file and -s _) { | 
| 394 |  |  |  |  |  |  |  | 
| 395 |  |  |  |  |  |  | # If $perl is already defined, we have a case where multiple | 
| 396 |  |  |  |  |  |  | # configuration files are present, which is not a defined case. | 
| 397 | 10 | 50 |  |  |  | 41 | carp "Multiple configuration files match $path. This will lead to unexpected results.\nYAML Bundle: $options{_original_path}\n" | 
| 398 |  |  |  |  |  |  | if defined $perl; | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | # We don't use load_yml because we don't want the intermediate | 
| 401 |  |  |  |  |  |  | # pieces cached and it does a lot of work we'd repeat anyway. | 
| 402 |  |  |  |  |  |  |  | 
| 403 | 10 | 50 |  |  |  | 320 | open my $fh, $file | 
| 404 |  |  |  |  |  |  | or croak "Can't open YAML file $file: $!"; | 
| 405 | 10 |  |  |  |  | 30 | my $yaml = do { local $/; <$fh> }; | 
|  | 10 |  |  |  |  | 38 |  | 
|  | 10 |  |  |  |  | 250 |  | 
| 406 |  |  |  |  |  |  |  | 
| 407 | 10 |  |  |  |  | 22 | $perl = eval { Load($yaml) }; | 
|  | 10 |  |  |  |  | 420 |  | 
| 408 | 10 | 50 |  |  |  | 133 | if ($@) { | 
| 409 | 0 |  |  |  |  | 0 | croak "Eror in file $file: $@\nYAML Bundle: $options{_original_path}\n"; | 
| 410 |  |  |  |  |  |  | } | 
| 411 |  |  |  |  |  |  | } | 
| 412 |  |  |  |  |  |  | } | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | # if no file found, we have to start somewhere | 
| 415 | 18 | 100 |  |  |  | 60 | $perl = {} unless defined $perl; | 
| 416 |  |  |  |  |  |  |  | 
| 417 |  |  |  |  |  |  | # If this is a directory, let's suck in all the nested configs | 
| 418 | 18 | 100 |  |  |  | 223 | if (-d $path) { | 
| 419 | 12 | 50 |  |  |  | 303 | opendir my $dir_fh, $path or croak "Cannot opendir $path: $!"; | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # Saves us from duplicating work while recursing... | 
| 422 | 12 |  |  |  |  | 60 | my %closed_list; | 
| 423 |  |  |  |  |  |  |  | 
| 424 | 12 |  |  |  |  | 156 | ENTRY: while (my $entry = readdir $dir_fh) { | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | # Ignore all dot files | 
| 427 | 44 | 100 |  |  |  | 727 | next if $entry =~ m{^[.]}; | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 20 |  |  |  |  | 1184 | my $nested_path = abs_path("$path/$entry"); | 
| 430 |  |  |  |  |  |  |  | 
| 431 | 20 | 50 |  |  |  | 66 | if (not defined $nested_path) { | 
| 432 | 0 |  |  |  |  | 0 | croak "Broken symlink or other problem while locating $path/$entry.\nYAML Bundle: $options{_original_path}\n"; | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | # If bundled, make sure this abs path is in the root bas path | 
| 436 | 20 | 50 |  |  |  | 71 | if ($options{follow_symlinks_when} eq 'bundled') { | 
|  |  | 0 |  |  |  |  |  | 
| 437 | 20 | 50 |  |  |  | 64 | unless (substr($nested_path, 0, $options{_original_path_length}) eq $options{_original_path}) { | 
| 438 | 0 |  |  |  |  | 0 | $symlink_skipper->($options{_original_path}, "$path/$entry"); | 
| 439 | 0 |  |  |  |  | 0 | next ENTRY; | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | # If never, skip any symlink | 
| 444 |  |  |  |  |  |  | elsif ($options{follow_symlinks_when} eq 'never') { | 
| 445 | 0 | 0 |  |  |  | 0 | if (-l "$path/$entry") { | 
| 446 | 0 |  |  |  |  | 0 | $symlink_skipper->($options{_original_path}, "$path/$entry"); | 
| 447 | 0 |  |  |  |  | 0 | next ENTRY; | 
| 448 |  |  |  |  |  |  | } | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | # Is this a directory? If so, load that as a bundle. | 
| 452 | 20 | 100 | 33 |  |  | 464 | if (-d $nested_path) { | 
|  |  | 50 |  |  |  |  |  | 
| 453 | 10 | 100 |  |  |  | 45 | next ENTRY if $closed_list{$nested_path}; | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | # We don't follow symlinks to directories. This is a naive way | 
| 456 |  |  |  |  |  |  | # to prevent infinite recursion. | 
| 457 | 6 | 50 |  |  |  | 74 | if (-l "$path/$entry") { | 
| 458 | 0 |  |  |  |  | 0 | croak "Symlink to directory $path/$entry is not permitted.\nYAML Bundle: $options{_original_path}\n"; | 
| 459 |  |  |  |  |  |  | } | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | # Load the nested bundle and merge. | 
| 462 | 6 |  |  |  |  | 23 | $closed_list{$nested_path}++; | 
| 463 |  |  |  |  |  |  | $perl->{ $entry } = _merge_bundle( | 
| 464 | 6 |  |  |  |  | 61 | $perl->{ $entry }, | 
| 465 |  |  |  |  |  |  | load_yaml_bundle($nested_path, \%options) | 
| 466 |  |  |  |  |  |  | ); | 
| 467 |  |  |  |  |  |  | } | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | # Is this a file with the right suffix? | 
| 470 |  |  |  |  |  |  | elsif (-f $nested_path and $entry =~ s/[.](?:$options{_match_suffix})$//) { | 
| 471 | 10 |  |  |  |  | 27 | my $nested_path_minus_suffix = $nested_path; | 
| 472 | 10 |  |  |  |  | 79 | $nested_path_minus_suffix =~ s/[.](?:$options{_match_suffix})$//; | 
| 473 | 10 | 50 |  |  |  | 29 | next ENTRY if $closed_list{$nested_path_minus_suffix}; | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | # Load the nested bundle and merge. | 
| 476 | 10 |  |  |  |  | 30 | $closed_list{$nested_path_minus_suffix}++; | 
| 477 |  |  |  |  |  |  | $perl->{ $entry } = _merge_bundle( | 
| 478 | 10 |  |  |  |  | 76 | $perl->{ $entry }, | 
| 479 |  |  |  |  |  |  | load_yaml_bundle($nested_path_minus_suffix, \%options) | 
| 480 |  |  |  |  |  |  | ); | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  |  | 
| 483 |  |  |  |  |  |  | # What the hey? Carp about this... | 
| 484 |  |  |  |  |  |  | else { | 
| 485 | 0 |  |  |  |  | 0 | carp "Ignoring unexpected path $nested_path of unknown type.\nYAML Bundle: $options{_original_path}\n"; | 
| 486 |  |  |  |  |  |  | } | 
| 487 |  |  |  |  |  |  | } | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | # Only unravel our format layer and cache the top | 
| 491 |  |  |  |  |  |  | # $cache_mtime is only set in the call _original_path is set | 
| 492 | 18 | 100 |  |  |  | 307 | if ($cache_mtime) { | 
| 493 | 2 |  |  |  |  | 11 | $perl = _unravel_and_cache($options{_original_path}, $perl, $cache_mtime); | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  |  | 
| 496 | 18 |  |  |  |  | 84 | return $perl; | 
| 497 |  |  |  |  |  |  | } | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | 1; | 
| 501 |  |  |  |  |  |  |  | 
| 502 |  |  |  |  |  |  | __END__ |