File Coverage

blib/lib/Config/Onion.pm
Criterion Covered Total %
statement 100 103 97.0
branch 30 42 71.4
condition 5 6 83.3
subroutine 14 14 100.0
pod 5 5 100.0
total 154 170 90.5


line stmt bran cond sub pod time code
1             package Config::Onion;
2              
3 5     5   56252 use strict;
  5         8  
  5         131  
4 5     5   17 use warnings;
  5         4  
  5         164  
5              
6             our $VERSION = 1.006;
7              
8 5     5   2203 use Config::Any;
  5         46648  
  5         166  
9 5     5   2405 use Hash::Merge::Simple 'merge';
  5         2101  
  5         310  
10 5     5   2647 use Moo;
  5         46109  
  5         23  
11              
12             has cfg => ( is => 'lazy', clearer => '_reset_cfg' );
13             has prefix_key => ( is => 'rw' );
14 31     31 1 5175 sub get { goto &cfg }
15              
16             has [qw( default main local override )]
17             => ( is => 'rwp', default => sub { {} } );
18              
19             sub set_default {
20 8     8 1 1758 my $self = shift;
21 8 100       80 $self = $self->new unless ref $self;
22              
23 8         28 my $default = $self->default;
24 8         25 $default = merge $default, shift while ref $_[0] eq 'HASH';
25 8 100       77 $default = merge $default, { @_ } if @_;
26              
27 8         128 $self->_set_default($default);
28 8         94 $self->_reset_cfg;
29 8         560 return $self;
30             }
31              
32             sub set_override {
33 1     1 1 402 my $self = shift;
34 1 50       4 $self = $self->new unless ref $self;
35              
36 1         3 my $override = $self->override;
37 1         4 $override = merge $override, shift while ref $_[0] eq 'HASH';
38 1 50       6 $override = merge $override, { @_ } if @_;
39              
40 1         30 $self->_set_override($override);
41 1         20 $self->_reset_cfg;
42 1         4 return $self;
43             }
44              
45             sub load {
46 8     8 1 3415 my $self = shift;
47 8 100       159 $self = $self->new unless ref $self;
48              
49 8         49 my $ca_opts = $self->_ca_opts;
50             # user passed in a hash ref as the last argument.
51 8 50       36 if (ref $_[$#_] eq "HASH") {
52             # allow user to override the default use_ext => 1
53 0 0       0 delete $ca_opts->{use_ext} unless exists $_[$#_]{use_ext};
54             # merge in any other options passed in
55 0         0 $ca_opts = merge $ca_opts, pop @_;
56             # ensure that flatten_to_hash feature that Config::Any supports is turned off
57 0 0       0 delete $ca_opts->{flatten_to_hash} if exists $ca_opts->{flatten_to_hash};
58             }
59              
60 8         81 my $main = Config::Any->load_stems({ stems => \@_ , %$ca_opts });
61 8         88015 my $local = Config::Any->load_stems({ stems => [ map { "$_.local" } @_ ],
  9         96  
62             %$ca_opts });
63              
64 8         63349 $self->_add_loaded($main, $local);
65 8         664 return $self;
66             }
67              
68             sub load_glob {
69 3     3 1 1309 my $self = shift;
70 3 50       79 $self = $self->new unless ref $self;
71              
72 3         20 my $ca_opts = $self->_ca_opts;
73             # user passed in a hash ref as the last argument.
74 3 100       14 if (ref $_[$#_] eq "HASH") {
75             # allow user to override the default use_ext => 1
76 1 50       4 delete $ca_opts->{use_ext} unless exists $_[$#_]{use_ext};
77             # merge in any other options passed in
78 1         4 $ca_opts = merge $ca_opts, pop @_;
79             # ensure that flatten_to_hash feature that Config::Any supports is turned off
80 1 50       17 delete $ca_opts->{flatten_to_hash} if exists $ca_opts->{flatten_to_hash};
81             }
82              
83             # if use_ext is on, we need to query Config::Any to see what extensions are allowed
84 3         5 my $ext_re = '';
85 3 100       8 if ($ca_opts->{use_ext}) {
86 2         12 my @exts = Config::Any->extensions();
87 2         6477 $ext_re = '\.' . (shift @exts) . '$';
88 2         16 $ext_re .= "|\\.$_\$" foreach @exts;
89             }
90              
91 3         5 my (@main_files, @local_files);
92 3         6 for my $globspec (@_) {
93 3         254 for (glob $globspec) {
94 8 100 100     120 next if $ca_opts->{use_ext} && !/$ext_re/;
95 6 100       41 if (/\.local\./) { push @local_files, $_ }
  1         3  
96 5         13 else { push @main_files, $_ }
97             }
98             }
99              
100 3         27 my $main = Config::Any->load_files({ files => \@main_files, %$ca_opts });
101 3         8221 my $local = Config::Any->load_files({ files => \@local_files, %$ca_opts });
102              
103 3         8743 $self->_add_loaded($main, $local);
104              
105 3         32 return $self;
106             }
107              
108             sub _add_loaded {
109 11     11   22 my $self = shift;
110 11         20 my ($main, $local) = @_;
111              
112 11 100       12 my @main; @main = map { values %$_ } @$main if @$main;
  11         58  
  14         50  
113 11 100       15 my @local; @local = map { values %$_ } @$local if @$local;
  11         35  
  4         14  
114              
115 11 100       64 if ($self->prefix_key) {
116 1         2 for my $cfg (@main, @local) {
117 1 50       6 $self->_replace_prefix_key($cfg) if exists $cfg->{$self->prefix_key};
118             }
119             }
120              
121 11         73 $self->_set_main( merge $self->main, @main);
122 11         542 $self->_set_local(merge $self->local, @local);
123              
124 11         383 $self->_reset_cfg;
125             }
126              
127             sub _build_cfg {
128 20     20   1608 my $self = shift;
129 20         127 merge $self->default, $self->main, $self->local, $self->override;
130             }
131              
132 11     11   37 sub _ca_opts { { use_ext => 1 } }
133              
134             sub _replace_prefix_key {
135 1     1   2 my $self = shift;
136 1         2 my $cfg = shift;
137              
138 1         1 my $top_key;
139 1         2 my $root = $cfg->{$self->prefix_key};
140 1         2 while (1) {
141 3 50       7 die "Config::Onion prefix key structure may not branch" if keys %$root > 1;
142 3   66     9 $top_key ||= (keys %$root)[0];
143 3         2 my $child = (values %$root)[0];
144 3 100       6 unless ($child) {
145 1         2 my $key = (keys %$root)[0];
146 1         1 $root = $root->{$key} = {};
147 1         2 last;
148             }
149 2         2 $root = $child;
150             }
151              
152 1         3 my $new = $cfg->{$self->prefix_key}{$top_key};
153 1         4 delete $cfg->{$self->prefix_key};
154              
155 1         2 for (keys %$cfg) {
156 2         3 $root->{$_} = $cfg->{$_};
157 2         3 delete $cfg->{$_};
158             }
159 1         2 $cfg->{$top_key} = $new;
160             }
161              
162             1;
163              
164             =pod
165              
166             =encoding UTF-8
167              
168             =head1 NAME
169              
170             Config::Onion - Layered configuration, because configs are like ogres
171              
172             =head1 VERSION
173              
174             version 1.006
175              
176             =head1 SYNOPSIS
177              
178             my $cfg = Config::Onion->new;
179             my $cfg = Config::Onion->set_default(db => {name => 'foo', password => 'bar'});
180             my $cfg = Config::Onion->load('/etc/myapp', './myapp');
181             my $cfg = Config::Onion->load('/etc/myapp', './myapp', {use_ext => 1, filter => \&filter});
182             my $cfg = Config::Onion->load_glob('./plugins/*');
183             my $cfg = Config::Onion->load_glob('./plugins/*', {force_plugins => ['Config::Any::YAML']});
184              
185             $cfg->set_default(font => 'Comic Sans');
186             $cfg->load('config');
187             $cfg->load_glob('conf.d/myapp*');
188             $cfg->set_override(font => 'Arial');
189              
190             my $dbname = $cfg->get->{db}{name};
191             my $plain_hashref_conf = $cfg->get;
192             my $dbpassword = $plain_hashref_conf->{db}{password};
193              
194             =head1 DESCRIPTION
195              
196             All too often, configuration is not a universal or one-time thing, yet most
197             configuration-handling treats it as such. Perhaps you can only load one config
198             file. If you can load more than one, you often have to load all of them at the
199             same time or each is stored completely independently, preventing one from being
200             able to override another. Config::Onion changes that.
201              
202             Config::Onion stores all configuration settings in four layers: Defaults,
203             Main, Local, and Override. Each layer can be added to as many times as you
204             like. Within each layer, settings which are given multiple times will take the
205             last specified value, while those which are not repeated will remain untouched.
206              
207             $cfg->set_default(name => 'Arthur Dent', location => 'Earth');
208             $cfg->set_default(location => 'Magrathea');
209             # In the Default layer, 'name' is still 'Arthur Dent', but 'location' has
210             # been changed to 'Magrathea'.
211              
212             Regardless of the order in which they are set, values in Main will always
213             override values in the Default layer, the Local layer always overrides both
214             Default and Main, and the Override layer overrides all the others.
215              
216             The design intent for each layer is:
217              
218             =over 4
219              
220             =item * Default
221              
222             Hardcoded default values to be used when no further configuration is present
223              
224             =item * Main
225              
226             Values loaded from standard configuration files shipped with the application
227              
228             =item * Local
229              
230             Values loaded from local configuration files which are kept separate to prevent
231             them from being overwritten by application upgrades, etc.
232              
233             =item * Override
234              
235             Settings provided at run-time which take precendence over all configuration
236             files, such as settings provided via command line switches
237              
238             =back
239              
240             =head1 METHODS
241              
242             =head2 new
243              
244             Returns a new, empty configuration object.
245              
246             =head2 load(@file_stems)
247             =head2 load(@file\_stems, {...})
248              
249             Loads files matching the given stems using C<< Config::Any->load_stems >> into
250             the Main layer. Also concatenates ".local" to each stem and loads matching
251             files into the Local layer. e.g., C<< $cfg->load('myapp') >> would load
252             C<myapp.yml> into Main and C<myapp.local.js> into Local. All filename
253             extensions supported by C<Config::Any> are recognized along with their
254             corresponding formats.
255              
256             An optional hash ref final argument can be provided to override the default
257             option C<< use_ext => 1 >> passed to C<Config::Any>. All options supported by C<Config::Any>
258             are supported except flatten_to_hash. See C<< Config::Any->load_files >>
259             documentation for available options.
260              
261             =head2 load_glob(@globs)
262             =head2 load_glob(@globs, {...})
263              
264             Uses the Perl C<glob> function to expand each parameter into a list of
265             filenames and loads each file using C<Config::Any>. Files whose names contain
266             the string ".local." are loaded into the Local layer. All other files are
267             loaded into the Main layer.
268              
269             An optional hash ref final argument can be provided to override the default
270             option C<< use_ext => 1 >> passed to C<Config::Any>. All options supported by C<Config::Any>
271             are supported except flatten_to_hash. See C<< Config::Any->load_files >>
272             documentation for available options.
273              
274             =head2 set_default([\%settings,...,] %settings)
275              
276             =head2 set_override([\%settings,...,] %settings)
277              
278             Imports C<%settings> into the Default or Override layer. Accepts settings both
279             as a plain hash and as hash references, but, if the two are mixed, all hash
280             references must appear at the beginning of the parameter list, before any
281             non-hashref settings.
282              
283             =head1 PROPERTIES
284              
285             =head2 cfg
286              
287             =head2 get
288              
289             Returns the complete configuration as a hash reference.
290              
291             =head2 default
292              
293             =head2 main
294              
295             =head2 local
296              
297             =head2 override
298              
299             These properties each return a single layer of the configuration. This is
300             not likely to be useful other than for debugging. For most other purposes,
301             you probably want to use C<get> instead.
302              
303             =head2 prefix_key
304              
305             If set, enables the Prefix Structures functionality described below when using
306             the C<load> or C<load_glob> methods. The value of C<prefix_key> specifies the
307             name of the key under which the prefix structure may be found.
308              
309             Default value is C<undef>.
310              
311             =head1 Prefix Structures
312              
313             If you find that your configuration structure is becoming unwieldy due to
314             deeply-nested structures, you can define a file-specific "prefix structure"
315             and all other settings within that file will be loaded as children of the
316             prefix structure. For example, if your main program uses
317              
318             $cfg = Config::Onion->new(prefix_key => '_prefix');
319             $cfg->load("myapp/config");
320              
321             and C<myapp/config.yml> contains
322              
323             _prefix:
324             foo:
325             bar:
326              
327             baz: 1
328              
329             then C<$cfg> will contain the configuration
330              
331             foo:
332             bar:
333             baz: 1
334              
335             Note that the top-level C<prefix_key> is removed.
336              
337             There are some limitations on the prefix structure, in order to keep it sane
338             and deterministic. First, the prefix structure may only contain hashes.
339             Second, each hash must contain exactly one key. Finally, the value associated
340             with the final key must be left undefined.
341              
342             =head1 BUGS AND LIMITATIONS
343              
344             No bugs have been reported.
345              
346             Please report any bugs or feature requests at
347             L<https://github.com/dsheroh/Config-Onion/issues>
348              
349             =head1 AUTHOR
350              
351             Dave Sherohman <dsheroh@cpan.org>
352              
353             =head1 COPYRIGHT AND LICENSE
354              
355             This software is copyright (c) 2012 by Lund University Library.
356              
357             This is free software; you can redistribute it and/or modify it under
358             the same terms as the Perl 5 programming language system itself.
359              
360             =cut
361              
362             __END__
363              
364             # ABSTRACT: Layered configuration, because configs are like ogres
365