File Coverage

blib/lib/Config/Entities.pm
Criterion Covered Total %
statement 132 139 94.9
branch 57 74 77.0
condition 14 21 66.6
subroutine 21 21 100.0
pod 4 4 100.0
total 228 259 88.0


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