File Coverage

blib/lib/Config/Yak.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package Config::Yak;
2             {
3             $Config::Yak::VERSION = '0.23';
4             }
5             BEGIN {
6 1     1   1457 $Config::Yak::AUTHORITY = 'cpan:TEX';
7             }
8             # ABSTRACT: a tree-based versatile config handler
9              
10 1     1   19 use 5.010_000;
  1         3  
  1         41  
11 1     1   7 use mro 'c3';
  1         2  
  1         5  
12 1     1   26 use feature ':5.10';
  1         2  
  1         98  
13              
14 1     1   487 use Moose;
  0            
  0            
15             use Moose::Util::TypeConstraints;
16             use namespace::autoclean;
17              
18             use IO::Handle;
19             use autodie;
20              
21             use Config::Any;
22             use Config::Tiny;
23             use Hash::Merge;
24             use Data::Dumper;
25             use Try::Tiny;
26             use Data::Structure::Util qw();
27              
28             subtype 'ArrayRefOfStr',
29             as 'ArrayRef[Str]';
30              
31             coerce 'ArrayRefOfStr',
32             from 'Str',
33             via { [ $_ ] };
34              
35             extends 'Data::Tree' => { -version => 0.16 };
36              
37             has 'locations' => (
38             'is' => 'rw',
39             'isa' => 'ArrayRefOfStr',
40             'coerce' => 1,
41             'required' => 1,
42             );
43              
44             has 'last_ts' => (
45             'is' => 'rw',
46             'isa' => 'Num',
47             'default' => 0,
48             );
49              
50             has 'files_read' => (
51             'is' => 'rw',
52             'isa' => 'ArrayRef[Str]',
53             'default' => sub { [] },
54             );
55              
56             sub config {
57             my $self = shift;
58             my $arg = shift;
59              
60             if ( defined($arg) ) {
61             return $self->data($arg);
62             }
63             else {
64             return $self->data();
65             }
66             } ## end sub config
67              
68             sub _init_debug {
69             my $self = shift;
70              
71             if($ENV{'CONFIG_YAK_DEBUG'}) {
72             return 1;
73             }
74              
75             return 0;
76             }
77              
78             ############################################
79             # THIS METHOD IS NOT PART OF OUR PUBLIC API!
80             # Usage :
81             # Purpose :
82             # Returns :
83             # Parameters :
84             # Throws : no exceptions
85             # Comments : none
86             # See Also : n/a
87             # THIS METHOD IS NOT PART OF OUR PUBLIC API!
88             sub _init_data {
89             my $self = shift;
90              
91             # glob locations and conf.d dirs!
92             my @files = ();
93             my @legacy_files = ();
94             foreach my $loc ( @{ $self->locations() } ) {
95             if ( -d $loc ) {
96             foreach my $file ( glob( $loc . '/*.conf' ) ) {
97             if ( $self->_is_legacy_config($file) ) {
98             push( @legacy_files, $file );
99             }
100             else {
101             push( @files, $file );
102             }
103             } ## end foreach my $file ( glob( $loc...))
104             ## no critic (ProhibitMismatchedOperators)
105             if ( -d $loc . '/conf.d' ) {
106             ## use critic
107             foreach my $file ( glob( $loc . '/conf.d/*.conf' ) ) {
108             if ( $self->_is_legacy_config($file) ) {
109             push( @legacy_files, $file );
110             }
111             else {
112             push( @files, $file );
113             }
114             } ## end foreach my $file ( glob( $loc...))
115             } ## end if ( -d $loc . '/conf.d')
116             } ## end elsif ( -d $loc )
117             elsif ( -e $loc ) {
118             if ( $self->_is_legacy_config($loc) ) {
119             push( @legacy_files, $loc );
120             }
121             else {
122             push( @files, $loc );
123             }
124             } ## end if ( -e $loc )
125             } ## end foreach my $loc ( @{ $self->locations...})
126             ## no critic (RequireCheckedSyscalls)
127             print '_init_config - glob()ed these files: ' . join( q{:}, @files ) . "\n" if $self->debug();
128             print '_init_config - glob()ed these legacy files: ' . join( q{:}, @legacy_files ) . "\n" if $self->debug();
129             ## use critic
130             my $cfg = {};
131             $cfg = $self->_load_legacy_config( [@legacy_files], $cfg );
132             foreach my $file (@files) {
133             $cfg = $self->_load_config( [$file], $cfg );
134             }
135             return $cfg;
136             } ## end sub _init_data
137              
138             sub _is_legacy_config {
139             my $self = shift;
140             my $file = shift;
141              
142             my $is_legacy = 0;
143             if ( -e $file && open( my $FH, '<', $file ) ) {
144             my @lines = <$FH>;
145             close($FH);
146             foreach my $line (@lines) {
147             if ( $line =~ m/^\[/ ) { # ini-style config, old
148             $is_legacy = 1;
149             last;
150             }
151             elsif ( $line =~ m/^\s*</ ) { # pseudo-XML config, new
152             $is_legacy = 0;
153             last;
154             }
155             } ## end foreach my $line (@lines)
156              
157             } ## end if ( -e $file && open(...))
158             return $is_legacy;
159             } ## end sub _is_legacy_config
160              
161             sub _load_legacy_config {
162             my $self = shift;
163             my $files_ref = shift;
164             my $cfg = shift || {};
165              
166             Hash::Merge::set_behavior('RETAINMENT_PRECEDENT');
167             foreach my $file ( @{$files_ref} ) {
168             if ( -e $file ) {
169             try {
170             my $Config = Config::Tiny::->read($file);
171             print '_load_legacy_config - Loaded ' . $file . "\n" if $self->debug();
172             Data::Structure::Util::unbless($Config);
173             $cfg = Hash::Merge::merge( $cfg, $Config );
174             ## no critic (ProhibitMagicNumbers)
175             my $last_ts = ( stat($file) )[9];
176             ## use critic
177             $self->last_ts($last_ts) if $last_ts > $self->last_ts();
178             1;
179             } ## end try
180             catch {
181             warn "Loading $file failed: $_\n" if $self->debug();
182             };
183             } ## end if ( -e $file )
184             } ## end foreach my $file ( @{$files_ref...})
185             return $cfg;
186             } ## end sub _load_legacy_config
187              
188             sub _load_config {
189             my $self = shift;
190             my $files_ref = shift;
191             my $ccfg = shift || {};
192              
193             ## no critic (ProhibitNoWarnings)
194             no warnings 'once';
195             ## no critic (ProhibitTwoArgOpen ProhibitBarewordFileHandles RequireBriefOpen ProhibitUnixDevNull)
196             if(!$self->debug()) {
197             open( OLD_STDERR, '>&STDERR' )
198             or die('Failed to save STDERR');
199             open( STDERR, '>', '/dev/null' )
200             or die('Failed to redirect STDERR');
201             }
202             ## use critic
203             my $cfg = {};
204             my $success = try {
205             $cfg = Config::Any->load_files(
206             {
207             files => $files_ref,
208             use_ext => 1,
209             driver_args => {
210              
211             # see http://search.cpan.org/~tlinden/Config-General-2.50/General.pm
212             General => {
213             -UseApacheInclude => 0,
214             -IncludeRelative => 0,
215             -IncludeDirectories => 0,
216             -IncludeGlob => 0,
217             -SplitPolicy => 'equalsign',
218             -CComments => 0,
219             -AutoTrue => 1,
220             -MergeDuplicateBlocks => 1,
221             -MergeDuplicateOptions => 0,
222             -LowerCaseNames => 1,
223             -UTF8 => 1,
224             },
225             },
226             flatten_to_hash => 1,
227             },
228             );
229             1;
230             } ## end try
231             catch {
232             print 'Loading ' . join( q{:}, @{$files_ref} ) . " failed: $_\n" if $self->debug();
233             };
234             return $ccfg unless $success;
235              
236             ## no critic (ProhibitTwoArgOpen)
237             if(!$self->debug()) {
238             open( STDERR, '>&OLD_STDERR' );
239             }
240             use warnings 'once';
241             ## use critic
242             Hash::Merge::set_behavior('RETAINMENT_PRECEDENT');
243              
244             # older versions of Config::Any don't know flatten_to_hash,
245             # they'll always return an array of hashes, so we'll
246             # transform them here
247             if ( ref($cfg) eq 'ARRAY' ) {
248             my $ncfg = {};
249             foreach my $c ( @{$cfg} ) {
250             foreach my $file ( keys %{$c} ) {
251             $ncfg->{$file} = $c->{$file};
252             }
253             }
254             $cfg = $ncfg;
255             } ## end if ( ref($cfg) eq 'ARRAY')
256             if ( ref($cfg) eq 'HASH' ) {
257             foreach my $file ( keys %{$cfg} ) {
258             print "_load_config - Loaded $file\n" if $self->debug();
259             push(@{$self->files_read()},$file);
260             $ccfg = Hash::Merge::merge( $ccfg, $cfg->{$file} );
261             ## no critic (ProhibitMagicNumbers)
262             my $last_ts = ( stat($file) )[9];
263             ## use critic
264             $self->last_ts($last_ts) if $last_ts > $self->last_ts();
265             } ## end foreach my $file ( keys %{$cfg...})
266             } ## end if ( ref($cfg) eq 'HASH')
267             return $ccfg;
268             } ## end sub _load_config
269              
270             ############################################
271             # Usage :
272             # Purpose :
273             # Returns :
274             # Parameters :
275             # Throws : no exceptions
276             # Comments : none
277             # See Also : n/a
278             sub add_config {
279             my $self = shift;
280             my $file = shift;
281              
282             $self->config( Hash::Merge::merge( $self->config(), $self->_load_config( [$file] ) ) );
283             return 1;
284             } ## end sub add_config
285              
286             ############################################
287             # Usage :
288             # Purpose :
289             # Returns :
290             # Parameters :
291             # Throws : no exceptions
292             # Comments : none
293             # See Also : n/a
294             sub reset_config {
295             my $self = shift;
296              
297             $self->config( {} );
298              
299             return 1;
300             } ## end sub reset_config
301             ## no critic (ProhibitBuiltinHomonyms)
302             sub dump {
303             ## use critic
304             my $self = shift;
305              
306             $Data::Dumper::Sortkeys = 1;
307             return Dumper( $self->config() );
308             } ## end sub dump
309              
310             no Moose;
311             __PACKAGE__->meta->make_immutable;
312              
313             1;
314              
315             __END__
316              
317             =pod
318              
319             =encoding utf-8
320              
321             =head1 NAME
322              
323             Config::Yak - a tree-based versatile config handler
324              
325             =head1 SYNOPSIS
326              
327             use Config::Yak;
328              
329             my $cfg = Config::Yak::->new({ locations => [qw(/etc/foo)]});
330             ...
331              
332             =head1 METHODS
333              
334             =head2 add_config
335              
336             Parse another config file.
337              
338             =head2 config
339              
340             Get the whole config as an HashRef
341              
342             =head2 dump
343              
344             Stringify the whole config w/ Data::Dumper;
345              
346             =head2 reset_config
347              
348             Delete all configuration items.
349              
350             =head1 NAME
351              
352             Config::Yak - Data::Tree based config handling
353              
354             =head1 AUTHOR
355              
356             Dominik Schulz <dominik.schulz@gauner.org>
357              
358             =head1 COPYRIGHT AND LICENSE
359              
360             This software is copyright (c) 2012 by Dominik Schulz.
361              
362             This is free software; you can redistribute it and/or modify it under
363             the same terms as the Perl 5 programming language system itself.
364              
365             =cut