File Coverage

blib/lib/Config/Entities.pm
Criterion Covered Total %
statement 132 139 94.9
branch 57 74 77.0
condition 16 21 76.1
subroutine 21 21 100.0
pod 4 4 100.0
total 230 259 88.8


line stmt bran cond sub pod time code
1 1     1   16196 use strict;
  1         2  
  1         31  
2 1     1   5 use warnings;
  1         2  
  1         51  
3              
4             package Config::Entities;
5             $Config::Entities::VERSION = '1.07';
6             # ABSTRACT: An multi-level overridable perl based configuration module
7             # PODNAME: Config::Entities
8              
9 1     1   5 use Cwd qw(abs_path);
  1         2  
  1         71  
10 1     1   587 use Data::Dumper;
  1         7096  
  1         74  
11 1     1   11 use File::Find;
  1         3  
  1         69  
12 1     1   8 use File::Spec;
  1         2  
  1         20  
13 1     1   452 use Log::Any;
  1         7569  
  1         5  
14              
15             my $logger = Log::Any->get_logger();
16              
17             sub new {
18 13     13 1 8507 my ( $class, @args ) = @_;
19 13         58 return bless( {}, $class )->_init(@args);
20             }
21              
22             sub as_hashref {
23 2     2 1 23 return _copy(@_);
24             }
25              
26             sub _copy {
27 38     38   95 my ($value) = @_;
28              
29 38         93 my $ref = ref($value);
30 38 100       95 if ($ref) {
31 14 50 66     74 if ( $ref eq 'ARRAY' ) {
    50          
    0          
32 0         0 return [ map { _copy($_) } @$value ];
  0         0  
33             }
34             elsif ( $ref eq 'HASH' || $value->isa('Config::Entities') ) {
35 14         51 return { map { $_ => _copy( $value->{$_} ) } keys(%$value) };
  35         112  
36             }
37             elsif ( $ref eq 'SCALAR' ) {
38 0         0 return $value;
39             }
40             else {
41 0         0 croak("unsupported type '$ref'");
42             }
43             }
44             else {
45 24         155 return $value;
46             }
47             }
48              
49             sub _add_properties {
50 13     13   92 my ( $self, $properties, $more_properties ) = @_;
51              
52 13         22 foreach my $key ( keys( %{$more_properties} ) ) {
  13         38  
53 20         69 $properties->{$key} = $more_properties->{$key};
54             }
55             }
56              
57             sub fill {
58 5     5 1 48 my ( $self, $coordinate, $hashref, %options ) = @_;
59              
60 5         22 my @entity = $self->get_entity( $coordinate, %options );
61 5         24 foreach my $key ( keys(%$hashref) ) {
62 11 100 100     79 if ( ref( $entity[0] ) eq 'HASH' && exists( $entity[0]->{$key} ) ) {
    100 66        
    50          
63 5         18 $hashref->{$key} = $entity[0]->{$key};
64             }
65             elsif ( $hashref->{$key} && $hashref->{$key} eq 'Config::Entities::entity' ) {
66 1         6 $hashref->{$key} = $entity[0];
67             }
68             elsif ( $options{ancestry} ) {
69 5         21 for ( my $index = 1; $index < scalar(@entity); $index++ ) {
70 14 100       71 if ( defined( $entity[$index]->{$key} ) ) {
71 5         13 $hashref->{$key} = $entity[$index]->{$key};
72 5         13 last;
73             }
74             }
75             }
76             }
77              
78 5         39 return $hashref;
79             }
80              
81             sub get_entity {
82 9     9 1 46 my ( $self, $coordinate, %options ) = @_;
83              
84 9         26 my @result = ($self);
85 9 50       28 if ($coordinate) {
86 9         43 foreach my $coordinate_part ( split( /\./, $coordinate ) ) {
87 25         53 my $child = $result[0]->{$coordinate_part};
88 25 50       58 return if ( !defined($child) );
89 25         100 unshift( @result, $child );
90             }
91             }
92 9 100       65 return $options{ancestry} ? @result : shift(@result);
93             }
94              
95             sub _init {
96 13     13   33 my ( $self, @args ) = @_;
97              
98             # if last arg is a hash, it is an options hash
99 13 100       55 my $options =
100             ref( $args[$#args] ) eq 'HASH'
101             ? pop(@args)
102             : {};
103              
104             # all other args are entities roots
105 13         32 my @entities_roots = @args;
106              
107 13         24 my $properties = {};
108 13 100       47 if ( $options->{properties_file} ) {
109              
110             # merge in properties from files
111             my @properties_files =
112             ref( $options->{properties_file} ) eq 'ARRAY'
113 1         3 ? @{ $options->{properties_file} }
114 6 100       22 : ( $options->{properties_file} );
115              
116 6         13 foreach my $properties_file (@properties_files) {
117 7         751 $self->_add_properties( $properties, do($properties_file) );
118             }
119             }
120 13 100       41 if ( $options->{properties} ) {
121              
122             # merge in direct properties
123 6         19 $self->_add_properties( $properties, $options->{properties} );
124             }
125              
126 13 100       36 if ( $options->{entity} ) {
127 4         10 foreach my $key ( keys( %{ $options->{entity} } ) ) {
  4         24  
128 7         23 _merge( $self, $key, $options->{entity}{$key} );
129             }
130             }
131              
132 13 100       39 if ( scalar(@entities_roots) ) {
133             find(
134             sub {
135 52 100 66 52   1475 if ( $_ =~ /^(.*)\.pmc?$/ && -f $File::Find::name ) {
136 32         106 my $key = $1;
137              
138 32         52 my $hashref = $self;
139 32         266 my @directories = File::Spec->splitdir(
140             substr( $File::Find::dir, length($File::Find::topdir) ) );
141 32 100       109 if ( scalar(@directories) ) {
142 16         59 shift(@directories) while ( !$directories[0] );
143 16         31 foreach my $dir (@directories) {
144 24 100       61 if ( !defined( $hashref->{$dir} ) ) {
145 4         12 $hashref->{$dir} = {};
146             }
147 24         50 $hashref = $hashref->{$dir};
148             }
149             }
150              
151 32         59 my $entity;
152             {
153             # export %properties to the entity file
154 32         49 local $Config::Entities::properties = $properties;
  32         56  
155             ## no critic (ProhibitNoStrict)
156 1     1   750 no strict 'vars';
  1         1  
  1         555  
157 32 50       154 local %properties = $properties ? %$properties : ();
158 32         3450 $entity = do($File::Find::name);
159             ## use critic
160             }
161 32 50       468 $logger->warn( 'unable to compile ', $File::Find::name, ': ', $@, "\n" )
162             if ($@);
163 32         83 _merge( $hashref, $key, $entity );
164             }
165             },
166 8         44 map { Cwd::abs_path($_) } @entities_roots
  12         960  
167             );
168             }
169              
170 13         101 &$_($self) foreach $self->_inherit( undef, $self );
171              
172 13         76 return $self;
173             }
174              
175             sub _inherit {
176 134     134   273 my ( $self, $parent, $child ) = @_;
177              
178 134         219 my @after_inherit = ();
179 134 100       303 if ($child) {
180 123         245 my $ref = ref($child);
181 123 100 100     467 if ( $ref eq 'HASH' || $ref eq 'Config::Entities' ) {
182 54 100 100     203 if ( $parent && $child->{'Config::Entities::inherit'} ) {
183             push(
184             @after_inherit,
185             $self->_inherit_each(
186 6         23 delete( $child->{'Config::Entities::inherit'} ),
187             $parent, $child
188             )
189             );
190             }
191 54         354 push( @after_inherit, $self->_inherit( $child, $child->{$_} ) ) foreach keys(%$child);
192             }
193             }
194 134         362 return @after_inherit;
195             }
196              
197             sub _inherit_each {
198 6     6   34 my ( $self, $inherit, $parent, $child ) = @_;
199              
200 6         14 my @after_inherit = ();
201 6 50       25 if ( ref($inherit) eq 'ARRAY' ) {
202 6         16 foreach my $spec (@$inherit) {
203 8         20 my $spec_ref = ref($spec);
204 8 100       34 if ($spec_ref) {
    50          
205 2 50       14 if ( $spec_ref eq 'HASH' ) {
206 2         11 push( @after_inherit, $self->_inherit_spec( $spec, $parent, $child ) );
207             }
208             else {
209 0         0 croak('invalid inherit');
210             }
211             }
212             elsif ( defined( $parent->{$spec} ) ) {
213             $child->{$spec} = $parent->{$spec}
214 6 50       30 unless ( defined( $child->{$spec} ) );
215             }
216             }
217             }
218 6         16 return @after_inherit;
219             }
220              
221             sub _inherit_spec {
222 2     2   9 my ( $self, $spec, $parent, $child ) = @_;
223              
224 2 100       36 if ( $spec->{name} ) {
    50          
225 1   33     8 my $as = $spec->{as} || $spec->{name};
226             $child->{$as} = $parent->{ $spec->{name} }
227 1 50       8 unless ( defined( $child->{$as} ) );
228             }
229             elsif ( $spec->{coordinate} ) {
230 1         6 my $as = $spec->{as};
231 1 50       6 unless ($as) {
232 0         0 $as = $spec->{coordinate};
233 0         0 $as =~ s/^.*\.//;
234             }
235              
236             return sub {
237 1     1   5 my ($entities) = @_;
238 1         7 $child->{$as} = _copy( $entities->get_entity( $spec->{coordinate} ) );
239 1 50       19 _merge( $child, $as, $spec->{using} ) if ( $spec->{using} );
240 1         12 };
241             }
242 1         6 return;
243             }
244              
245             sub _merge {
246 128     128   268 my ( $hashref, $key, $value ) = @_;
247              
248 128 100       300 if ( ref($value) eq 'HASH' ) {
249              
250             # transfer key/value pairs from hashref
251             # will merge rather than replace...
252 43 100       121 if ( !defined( $hashref->{$key} ) ) {
253 37         87 $hashref->{$key} = {};
254             }
255 43         89 $hashref = $hashref->{$key};
256              
257 43         181 while ( my ( $sub_key, $sub_value ) = each(%$value) ) {
258 88         187 _merge( $hashref, $sub_key, $sub_value );
259             }
260             }
261             else {
262             # anything not a hashref will replace
263 85         980 $hashref->{$key} = $value;
264             }
265             }
266              
267             1;
268              
269             __END__