File Coverage

blib/lib/Config/Source.pm
Criterion Covered Total %
statement 109 120 90.8
branch 38 56 67.8
condition 9 15 60.0
subroutine 20 22 90.9
pod 11 11 100.0
total 187 224 83.4


line stmt bran cond sub pod time code
1             package Config::Source;
2            
3 5     5   98827 use 5.14.0;
  5         20  
  5         510  
4 5     5   27 use strict;
  5         7  
  5         181  
5            
6 5     5   27 use warnings FATAL => 'all';
  5         35  
  5         243  
7            
8 5     5   26 use List::Util 1.35 qw( any none );
  5         135  
  5         639  
9            
10 5     5   33 use Carp qw( croak );
  5         8  
  5         7748  
11            
12             =head1 NAME
13            
14             Config::Source - manage a configuration from multiple sources
15            
16             =head1 VERSION
17            
18             Version 0.08
19            
20             =cut
21            
22             our $VERSION = '0.08';
23            
24             =head1 SYNOPSIS
25            
26             use Config::Source;
27            
28             my $config = Config::Source->new;
29             $config->add_source( get_default_config() );
30            
31             # override values from the default keys with
32             $config->add_source( File::Spec->catfile( $HOME, '.application', 'config' ) );
33            
34             # and now with
35             $config->add_source( '/etc/application.config' );
36            
37             my $value = $config->get( 'user.key' );
38             $config->set( 'user.key' => $value );
39            
40             $config->save_file( File::Spec->catfile( $HOME, '.application', 'config' ) );
41            
42             sub get_default_config {
43             return {
44             'app.name' => '...',
45             'app.version' => 1,
46             'user.key' => 'test',
47             'user.array' => [ 200, 300 ],
48             'user.deeper.struct' => { a => 'b', c => [ 'd', 'e' ] },
49             };
50             }
51            
52             =head1 DESCRIPTION
53            
54             This module allows defining and loading multiple sources to generate a configuration.
55            
56             Sometimes you want a configuration initially provided by your application, but partially or
57             fully redefined at multiple locations. You can have a default configuration
58             distributed with your program and under your control as developer.
59             On the first startup you want to generate a user configuration file to
60             store individual relevant data. And for the administration, you want to provide a
61             central configuration file indented to specify shared resources. You may also
62             want a file which is only loaded on debug sessions.
63            
64             This module uses Perl data structures for representing your configuration. It can also assure,
65             that you only work with a true copy of the data.
66            
67             =head1 CONFIGURATION FILE
68            
69             Your configuration file must simply return an hash with the last
70             evaluated statement. Additionally, you can use all the perl code you want.
71             But this code is discarded if you save your config back.
72            
73             This module proposes a flat hash for storing your configuration. It treats
74             everything behind the first level of keys as a value.
75            
76             instead of writing:
77            
78             {
79             'log' => {
80             'file' => 'path',
81             'level' => 'DEBUG',
82             },
83             }
84            
85             you should write:
86            
87             {
88             'log.file' => 'path,
89             'log.level' => 'DEBUG',
90             }
91            
92             Of course, you can use any separator in the string you want.
93            
94             If you want get a more hierarchical access, take a look at
95             Config::Source::Hierarchical (not implemented, currently only a throught).
96            
97             =head1 METHODS
98            
99             =head2 new( parameter => value, ... )
100            
101             All the following parameter are optional.
102            
103             =over 4
104            
105             =item C
106            
107             If true, then every time you try to C a ref-data, a clone will performed,
108             before returning it. Default is false.
109            
110             =item C
111            
112             If true, then every time you try to C a ref-data, a clone will performed,
113             before assign it to the key. Default is true.
114            
115             =back
116            
117             =cut
118            
119             sub new {
120 6     6 1 1406 my ( $class, %p ) = @_;
121            
122             # be sure, a clone module is set
123 6 100       69 $class->import if not $class->can( 'clone' );
124            
125 6         19 my $this = bless {}, $class;
126            
127 6   50     62 $this->{clone_get} = $p{clone_get} // 0;
128 6   50     44 $this->{clone_set} = $p{clone_set} // 1;
129            
130 6         24 return $this;
131             }
132            
133             =head2 add_source( source, parameter => value, ... )
134            
135             Loads the given source. This can either be a filepath, a hashref or a scalarref.
136            
137             The following parameter are supportet:
138            
139             =over 4
140            
141             =item C
142            
143             If you want to exclude some keys from loading from the given source, you can pass
144             a arrayref with these keys or regexes.
145            
146             $config->add_source( $source, discard => [ 'key.to.remove', qr/^match/ ] );
147            
148             =item C
149            
150             Discard all keys, which are not currently loaded by the configuration. Default is false
151             for the first source you want to load and true for each subsequent one. Keys matched
152             by C will always be discarded.
153            
154             =item C
155            
156             Takes a reference to a list of keys or regular expressions for merging. Keys matched
157             by C will always be discarded.
158            
159             I
160            
161             =back
162            
163             =cut
164            
165             sub add_source {
166 10     10 1 1887 my ( $this, $source, %p ) = @_;
167            
168 10   100     44 $p{discard_additional_keys} //= 1;
169            
170             # load the source into a hashref
171 10         30 my $hash = $this->_load_source( $source );
172            
173             # delete keys from discard
174 10 100       32 if ( $p{discard} ) {
175 1         4 for my $key ( keys %$hash ) {
176 5 100   9   12 delete $hash->{ $key } if any { $key =~ $_ } @{ $p{discard} };
  9         73  
  5         20  
177             }
178             }
179            
180             # always alias
181             # if currently no config
182 10 100       29 if ( not defined $this->{_} ) {
183 6         11 $this->{_} = $hash;
184 6         22 return $this;
185             }
186            
187             # delete additional key
188             # if they should discarded
189             # and always override the keys
190 4 100       11 if ( $p{discard_additional_keys} ) {
191 2         8 while ( my ( $key, $value ) = each %$hash ) {
192 7 100       28 $this->{_}{ $key } = $value
193             if exists $this->{_}{ $key };
194             }
195             }
196             else {
197 2         7 while ( my ( $key, $value ) = each %$hash ) {
198 7         21 $this->{_}{ $key } = $value;
199             }
200             }
201            
202 4         16 return $this;
203             }
204            
205             =head2 get( key )
206            
207             Returns the value for the given key.
208            
209             Dies if the key is not found.
210            
211             =cut
212            
213             sub get {
214 10     10 1 757 my ( $this, $key ) = @_;
215            
216 10 50 66     46 if ( ref $this->{_}{ $key } and $this->{clone_get} ) {
217 0         0 return clone( $this->{_}{ $key } );
218             }
219            
220             return
221 10 50       78 exists( $this->{_}{ $key } )
222             ? $this->{_}{ $key }
223             : croak "config key: $key does not exist"
224             ;
225             }
226            
227             =head2 set( key => value )
228            
229             Set the key to the given value.
230            
231             Dies if the key not exists.
232            
233             Before setting deep data structures a copy with clone is performed by default.
234            
235             =cut
236            
237             sub set {
238 5     5 1 49 my ( $this, $key, $value ) = @_;
239            
240 5 50       12 if ( $this->exists( $key ) ) {
241 5 100 66     20 if ( ref $value and $this->{clone_set} ) {
242 1         64 $this->{_}{ $key } = clone( $value );
243             } else {
244 4         7 $this->{_}{ $key } = $value;
245             }
246             } else {
247 0         0 croak "key does not exist: $key";
248             }
249            
250 5         13 1;
251             }
252            
253            
254             =head2 exists( key )
255            
256             Return true, if the key exists. False otherwise.
257            
258             =cut
259            
260             sub exists {
261 12     12 1 31 my ( $this, $key ) = @_;
262            
263 12 100       54 return 1 if exists $this->{_}{ $key };
264 4         18 return 0;
265             }
266            
267             =head2 keys( regex )
268            
269             Returns all matching keys in sorted order, so you can
270             easily iterate over it.
271            
272             If Regex is omitted, all keys are returned.
273            
274             =cut
275            
276             sub keys {
277 1     1 1 2 my ( $this, $regex ) = @_;
278            
279 1 50       4 return sort keys %{ $this->{_} } if not defined $regex;
  0         0  
280 1         1 return sort grep { /$regex/ } keys %{ $this->{_} };
  7         28  
  1         4  
281             }
282            
283             =head2 reset( key, source )
284            
285             Resets the given key to the value in the given configs.
286            
287             Dies, if the key is not found either in the current config, or the source.
288            
289             =cut
290            
291             sub reset {
292 3     3 1 69 my ( $this, $key, $source ) = @_;
293            
294             # SMELL: hm... can we optimize this?
295             # there possible a double clone!
296 3         7 my $hash = $this->_load_source( $source );
297            
298 3 50       10 croak "key does not exist in source: $key"
299             if not exists $hash->{ $key };
300            
301 3         8 $this->set( $key, $hash->{ $key } );
302            
303 3         17 1;
304             }
305            
306             =head2 getall( parameter => value )
307            
308             Returns a cloned copy from the configuration hash. This is a hashref.
309            
310             You can restrict the given keys with the following parameters:
311            
312             =over 4
313            
314             =item C
315            
316             Arrayref with keys or regular expressions. Only the matched keys from the configuration will saved.
317            
318             =item C
319            
320             Arrayref with keys or regular expressions. All matched keys will excluded from saving.
321             Keys matched by include and exclude will excluded.
322            
323             =back
324            
325             =cut
326            
327             sub getall {
328 9     9 1 28 my ( $this, %p ) = @_;
329            
330 9         335 my $hash = clone( $this->{_} );
331            
332             # i use alway a tmp hash - because key should not
333             # deleted in a loop around the hash
334 9 100       27 if ( $p{include} ) {
335            
336 2         3 my $tmp_hash;
337            
338 2         8 while ( my ( $key, $value ) = each %$hash ) {
339 12 100   17   24 if ( any { $key =~ $_ } @{ $p{include} } ) {
  17         111  
  12         36  
340 6         27 $tmp_hash->{ $key } = $value;
341             }
342             }
343            
344 2         3 $hash = $tmp_hash;
345             }
346            
347 9 100       26 if ( $p{exclude} ) {
348            
349 2         17 my $tmp_hash;
350            
351 2         9 while( my ( $key, $value ) = each %$hash ) {
352 11 100   21   27 if ( none { $key =~ $_ } @{ $p{exclude} } ) {
  21         163  
  11         24  
353 2         10 $tmp_hash->{ $key } = $value;
354             }
355             }
356            
357 2         4 $hash = $tmp_hash;
358             }
359            
360 9         50 return $hash;
361             }
362            
363             =head2 save_file( file, paramter => value, ... )
364            
365             Saves the configuration to the given file.
366            
367             Dies if no file spezified.
368            
369             You can restrict the saved keys with the same parameters specified in C.
370            
371             =cut
372            
373             sub save_file {
374 1     1 1 20 my ( $this, $file, %p ) = @_;
375            
376 1 50       3 croak 'No user file spezified' if not $file;
377            
378             # a little bit optimised ;) - but fragile base class!
379 1 50 33     7 my $hash = ( $p{include} or $p{exclude} )
380             ? $this->getall( %p )
381             : $this->{_}
382             ;
383            
384 1         2800 require Data::Dumper;
385            
386 1         14828 my $dumper = Data::Dumper->new( [ $hash ] );
387 1         51 $dumper->Useperl( 1 );
388 1         19 $dumper->Terse( 1 );
389 1         10 $dumper->Sortkeys( 1 );
390            
391 1 50       183 open my $fh, '>', $file or croak $!;
392 1         7 print $fh $dumper->Dump;
393 1         1441 close $fh;
394            
395 1         22 1;
396             }
397            
398             =head1 INTERNAL METHODS
399            
400             =head2 _load_source
401            
402             =cut
403            
404             sub _load_source {
405 13     13   20 my ( $this, $source ) = @_;
406            
407 13 100       45 if ( ref $source eq 'HASH' ) {
    50          
408 6         575 return clone( $source );
409             }
410             elsif ( ref $source eq 'SCALAR' ) {
411 0         0 return eval $$source;
412 0 0       0 croak "error parsing scalar source: $@" if $@;
413             }
414             else {
415 7 50       316 open my $fh, '<', $source or croak "error opening $source: $!";
416 7         10 my $hash = eval do { local $/; <$fh> };
  7         21  
  7         594  
417 7 50       26 croak "error parsing $source: $@" if $@;
418            
419 7         99 return $hash;
420             }
421             }
422            
423             =head1 ACCESSORS
424            
425             =over 4
426            
427             =item C
428            
429             =item C
430            
431             =back
432            
433             =cut
434            
435             # Code partly inspired from Object::Tiny and Object Tiny::RW
436 0 0   0 1 0 sub clone_get { if ( @_ > 1 ) { $_[0]->{clone_get} = $_[1] } ; return $_[0]->{clone_get} }
  0         0  
  0         0  
437 0 0   0 1 0 sub clone_set { if ( @_ > 1 ) { $_[0]->{clone_set} = $_[1] } ; return $_[0]->{clone_set} }
  0         0  
  0         0  
438            
439             =head1 CLONING
440            
441             You can change the cloning implementation with a package parameter:
442            
443             use Data::Clone;
444             use Config::Source clone => \&Data::Clone::clone;
445            
446             Or change it at any time with the class method C. The default
447             implementation is Storables dclone.
448            
449             =cut
450            
451             sub import {
452 5     5   33 my ( $class, %p ) = @_;
453            
454             my $sub = ref $p{clone} eq 'CODE'
455             ? $p{clone}
456 5 50       22 : do { require Storable; \&Storable::dclone }
  5         5436  
  5         19103  
457             ;
458            
459 5     5   40 no strict 'refs';
  5         10  
  5         455  
460 5         12 *{__PACKAGE__ . '::clone'} = $sub;
  5         3821  
461             }
462            
463             =head1 OTHER FILE FORMATS
464            
465             Most of the config modules out there can return a simple hash
466             of the configuration. The following example shows how
467             to read a default configuration and a user configuration
468             with Config::General, as well as the saving of the
469             configuration file back.
470            
471             use Config::General;
472             use Config::Source;
473            
474             my %default = Config::General->new( 'default_location' )->getall;
475             my %user = Config::General->new( 'user_location' ) ->getall;
476            
477             my $config = Config::Source->new
478             ->add_source( \%default )
479             ->add_source( \%user );
480            
481             # ...
482            
483             my $hash = $config->getall;
484            
485             Config::General->new->save_file( 'user_location', $hash );
486            
487             Be sure the passed values are unblessed hash references. And know the limitations
488             of the other modules.
489            
490             Maybe i add the option to direct load these file formats in a future release.
491            
492             =head1 AUTHOR
493            
494             Tarek Unger, C<< >>
495            
496             =head1 BUGS
497            
498             Please report any bugs or feature requests to C, or through
499             the web interface at L. I will be notified, and then you'll
500             automatically be notified of progress on your bug as I make changes.
501            
502            
503            
504            
505             =head1 SUPPORT
506            
507             You can find documentation for this module with the perldoc command.
508            
509             perldoc Config::Source
510            
511            
512             You can also look for information at:
513            
514             =over 20
515            
516             =item * RT: CPAN's request tracker (report bugs here)
517            
518             L
519            
520             =item * AnnoCPAN: Annotated CPAN documentation
521            
522             L
523            
524             =item * CPAN Ratings
525            
526             L
527            
528             =item * Search CPAN
529            
530             L
531            
532             =item * Repository
533            
534             L
535            
536             =back
537            
538            
539             =head1 ACKNOWLEDGEMENTS
540            
541            
542             =head1 LICENSE AND COPYRIGHT
543            
544             Copyright 2013-2014 Tarek Unger.
545            
546             This program is free software; you can redistribute it and/or modify it
547             under the terms of either: the GNU General Public License as published
548             by the Free Software Foundation; or the Artistic License.
549            
550             See L for more information.
551            
552            
553             =cut
554            
555             1; # End of Config::Source