File Coverage

blib/lib/Config/Layered.pm
Criterion Covered Total %
statement 67 73 91.7
branch 21 28 75.0
condition n/a
subroutine 12 12 100.0
pod 2 2 100.0
total 102 115 88.7


line stmt bran cond sub pod time code
1             package Config::Layered;
2 5     5   4149 use warnings;
  5         9  
  5         157  
3 5     5   25 use strict;
  5         7  
  5         139  
4 5     5   30829 use Data::Dumper;
  5         55176  
  5         422  
5 5     5   25097 use Storable qw( dclone );
  5         25773  
  5         12260  
6              
7             our $VERSION = '0.000003'; # 0.0.3
8             $VERSION = eval $VERSION;
9              
10             sub new {
11 47     47 1 139 my ( $class, %args ) = @_;
12 47         145 my $self = bless {}, $class;
13              
14 47         101 for ( qw( sources default merge ) ) {
15 141 100       618 $self->can($_) || $self->_build_accessor($_);
16             }
17              
18 47         123 for my $arg ( keys %args ) {
19 75 100       259 $self->_build_accessor( $arg ) unless $self->can($arg);
20              
21 75 100       165 if ( $arg eq 'sources' ) {
22 24         61 $self->$arg( $self->_normalize_sources($args{$arg}) );
23             } else {
24 51         120 $self->$arg( $args{$arg} );
25             }
26             }
27              
28             $self->default( {} )
29 47 50       106 unless $self->default;
30              
31 47 100       108 $self->sources([
32             [ 'ConfigAny' => {} ],
33             [ 'ENV' => {} ],
34             [ 'Getopt' => {} ],
35             ]) unless $self->sources;
36              
37 47         173 return $self;
38              
39             }
40              
41             sub load_config {
42 94     94 1 35549 my ( $self, @args ) = @_;
43              
44             # Allow to skip calling ->new()->load_config
45 94 100       339 return $self->new( @args )->load_config unless ref $self eq __PACKAGE__;
46              
47 47         88 my $config = $self->default;
48              
49 47         59 for my $source ( @{ $self->sources } ) {
  47         87  
50 107         286 my $pkg = $self->_load_source( $source->[0] )
51             ->new( $self, $source->[1] );
52              
53 107         375 $config = $self->_merge( $config, dclone($pkg->get_config) );
54             }
55              
56 47         302 return $config;
57             }
58              
59             sub _normalize_sources {
60 24     24   31 my ( $self, $sources ) = @_;
61              
62 24         25 my @new_sources;
63 24         23 while ( my $source = shift @{$sources} ) {
  62         158  
64 38 50       37 if ( ref @{$self->{sources}}[0] eq 'HASH' ) {
  38         120  
65 0         0 push @new_sources, [ $source, shift @{$self->{sources}} ];
  0         0  
66             } else {
67 38         114 push @new_sources, [ $source, {} ];
68             }
69             }
70 24         112 $self->{sources} = [@new_sources];
71             }
72              
73             sub _build_accessor {
74 13     13   27 my ( $self, $method ) = @_;
75            
76             my $accessor = sub {
77 486     486   530 my $self = shift;
78 486 100       1025 $self->{$method} = shift if @_;
79 486         3070 return $self->{$method};
80 13         82 };
81             {
82 5     5   58 no strict 'refs';
  5         10  
  5         2083  
  13         18  
83 13         52 *$method = $accessor;
84             }
85 13         28 return ();
86             }
87              
88             sub _load_source {
89 107     107   155 my ( $self, $source ) = @_;
90            
91 107         184 my $class = "Config::Layered::Source::$source";
92              
93 107         5846 eval "require $class";
94 107 50       381 if ( $@ ) {
95 0         0 eval "require $source";
96 0 0       0 if ( $@ ) {
97 0         0 die "Couldn't find $source or $class";
98             } else {
99 0         0 $class = $source;
100             }
101             }
102              
103 107         582 return $class;
104             }
105              
106             sub _merge {
107 116     116   182 my ( $self, $content, $data ) = @_;
108              
109             # Allow this method to be replaced by a coderef.
110 116 50       230 return $self->merge->( $content, $data ) if ref $self->merge eq 'CODE';
111              
112 116 50       273 if ( ref $content eq 'HASH' ) {
113 116         272 for my $key ( keys %$content ) {
114 279 100       633 if ( ref $content->{$key} eq 'HASH' ) {
115 9         30 $content->{$key} = $self->_merge($content->{$key}, $data->{$key});
116 9         19 delete $data->{$key};
117             } else {
118 270 100       753 $content->{$key} = delete $data->{$key} if exists $data->{$key};
119             }
120             }
121             # Unhandled keys (simply do injection on uneven rhs structure)
122 116         351 for my $key ( keys %$data ) {
123 10         31 $content->{$key} = delete $data->{$key};
124             }
125             }
126              
127 116         557 return $content;
128             }
129              
130             1;
131              
132             =head1 NAME
133              
134             Config::Layered - Layered config from files, command line, and other sources.
135              
136             =head1 DESCRIPTION
137              
138             Config::Layered aims to make it easy for programmers, operations teams and those
139             who run the programs to have the configuration methods they prefer with one simple
140             interface.
141              
142             By default options will be taken from the program source code itself, then
143             -- if provided -- a configuration file, and finally command-line options.
144              
145             =head1 SYNOPSIS
146              
147             By default options will be taken from the program source code itself, then
148             merged -- if provided -- with a configuration file, then environment variables
149             in the form of C and finally command-line options.
150              
151             my $config = Config::Layered->load_config(
152             file => "/etc/myapp",
153             default => {
154             verbose => 0,
155             run => 1,
156             input => "/tmp/to_process",
157             output => "/tmp/done_processing",
158             plugins => [ qw( process ) ]
159             },
160             );
161              
162             Given the above, the data structure would look like:
163              
164            
165             {
166             verbose => 0,
167             run => 1,
168             input => "/tmp/to_process",
169             output => "/tmp/done_processing",
170             plugins => [ qw( process ) ]
171             }
172              
173             Provided a file, C with the line C
174             the data structure would look like:
175              
176             {
177             verbose => 0,
178             run => 1,
179             input => "/tmp/pending_process",
180             output => "/tmp/done_processing",
181             plugins => [ qw( process ) ]
182             }
183              
184             Provided the command line arguments C<--norun --verbose --output /tmp/completed_process>
185             -- in addition to the configuration file above -- the data structure would look like:
186              
187             {
188             verbose => 1,
189             run => 0,
190             input => "/tmp/pending_process",
191             output => "/tmp/completed_process",
192             plugins => [ qw( process ) ]
193             }
194              
195             Provided the environment variable C
196             -- in addition to the configuration file above -- the data structure would look like:
197              
198             {
199             verbose => 1,
200             run => 0,
201             input => "/tmp/awaiting_process",
202             output => "/tmp/completed_process",
203             plugins => [ qw( process ) ]
204             }
205              
206             =head1 METHODS
207              
208             =head2 load_config
209              
210             =over 4
211              
212             =item * file
213              
214             By default the file given here will be loaded by Config::Any and the data
215             structure provided will be merged ontop of the default data structure.
216              
217             Example:
218              
219             file => "/etc/myapp",
220              
221             This will atempt to load C as a stem in L, meanig
222             files like C, C, C and such
223             will be checked for existence.
224              
225             =item * default
226              
227             This is the default data structure that L will load.
228              
229             Example:
230              
231             default => {
232             verbose => 1,
233             run => 0,
234             },
235              
236             The above data structure will have C<$config-E{verbose}> set to 1, and
237             C<$config-E{run}> set to 0 if there are no configuration files, and no
238             command line options used.
239              
240             =item * sources
241              
242             A source returns an instance of configuration to merge with previously loaded
243             sources. Following a source a specific configuration may be sent the to source.
244              
245             Example
246              
247             sources => [ 'ConfigAny', { file => "/etc/myapp }, 'Getopts' ]
248              
249             In the above example, L will be loaded,
250             and the following hashref will be sent to the source. This allows source-specific
251             configuration to be used. For more information on creating a soure, see
252             L.
253              
254             =item * merge
255              
256             You may provide a method as a coderef that will be used to merge the data
257             structures returned from a source together. By default the method used favors
258             the newer sources that are loaded.
259              
260             Example:
261              
262             merge => sub {
263             my ( $lhs, $rhs ) = @_;
264              
265             ... Do something with the data structures ...
266              
267             return $merged_data_structure;
268             }
269              
270             =back
271              
272             =head1 INCLUDED SOURCES
273              
274             Each source provides its own documentation for source-specific options,
275             please see the POD pages for the source you're interested in learning more
276             about
277              
278             =over4
279              
280             =item * L is used for configuration files
281              
282             =item * L is used for environment variables
283              
284             =item * L is used for command-line options
285              
286             =back
287              
288             =head1 CREATING A SOURCE
289              
290             If you would like to create your own source to provide a configuration method,
291             the following documents the creation of a source. You can also check
292             L for a source that is used by default.
293              
294             =head2 WRITING THE SOURCE CLASS
295              
296             A source requires at least two methods, C and C.
297              
298             =over 4
299              
300             =item * new
301              
302             The C method should take the following arguments and return an instance of itself:
303              
304             C<$layered> is the instance of L which called it. You may look at all
305             arguments given at construction of the instance.
306              
307             C<$arguments> is the source-specific configuration information. You should B parse
308             C<$config-Esources> yourself, instead look at C<$arguments>, and optionally fall-back
309             to using information in C<$layered> to make decisions.
310              
311             sub new {
312             my ( $class, $layered, $args ) = @_;
313             my $self = bless { layered => $layered, args => $args }, $class;
314             return $self;
315             }
316              
317             =item * get_config
318              
319             The C method is given no arguments, and expected to return a hashref that
320             is merged with previous sources, and will be merged over by future sources.
321              
322             Example:
323              
324             sub get_config {
325             my ( $self ) = @_;
326            
327             # Load a specific file with Config::Any
328             if ( exists $self->{args}->{file} ) {
329             return Config::Any->load_file( { file => $self->{args}->{file} );
330             # Otherwise, load the global file with Config::Any
331             } elsif ( exists $config->{layered}->{file} )
332             return Config::Any->load_file( { file => $self->{layered}->{file} );
333             }
334             # No configuration file, our source is being ignored.
335             return {};
336             }
337              
338             =back
339              
340             =head2 GLOBAL OR SOURCE ARGUMENTS?
341              
342             Config::Layered will accept any constructor arguments and a source may
343             look at C<$layered> to check them. However, source instance specific arguments
344             are also available. Both should be supported under the following reasoning:
345              
346             Suppose that I would like to load a global file, but I would also like to merge arguments
347             from a configuration file in my home directory. With only global arguments this isn't
348             possible. With source-specific arguments, this is easily enabled:
349              
350             my $config = Config::Layered->get_config(
351             sources => [
352             'ConfigAny', { file => "/etc/myapp" },
353             'ConfigAny', { file => $ENV{"HOME"} . "/.myapp",
354             ] ,
355             );
356              
357             Global arguments are useful in the context that writing out the data structure for the
358             default use-cases and single-use sources can be tedious.
359              
360             =head1 AUTHOR
361              
362             =over 4
363              
364             =item * Kaitlyn Parkhurst (SymKat) Isymkat@symkat.comE> (L)
365              
366             =back
367              
368             =head1 CONTRIBUTORS
369              
370             =head1 COPYRIGHT
371              
372             Copyright (c) 2012 the Config::Layered L and L as listed
373             above.
374              
375             =head1 LICENSE
376              
377             This library is free software and may be distributed under the same terms as
378             perl itself.
379              
380             =head1 AVAILABILITY
381              
382             The latest version of this software is available at
383             L
384