File Coverage

blib/lib/Config/JFDI.pm
Criterion Covered Total %
statement 29 31 93.5
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 40 42 95.2


line stmt bran cond sub pod time code
1             package Config::JFDI;
2             BEGIN {
3 14     14   1086677 $Config::JFDI::VERSION = '0.065';
4             }
5             # ABSTRACT: Just * Do it: A Catalyst::Plugin::ConfigLoader-style layer over Config::Any
6              
7 14     14   138 use warnings;
  14         252  
  14         827  
8 14     14   71 use strict;
  14         32  
  14         476  
9              
10              
11 14     14   13748 use Any::Moose;
  14         569906  
  14         109  
12              
13 14     14   19323 use Config::JFDI::Source::Loader;
  14         43  
  14         498  
14 14     14   8881 use Config::JFDI::Carp;
  14         44  
  14         114  
15              
16 14     14   11098 use Path::Class;
  14         649083  
  14         1008  
17 14     14   143 use Config::Any;
  14         27  
  14         386  
18 14     14   23010 use Hash::Merge::Simple;
  14         7332  
  14         683  
19 14     14   12531 use Sub::Install;
  14         26780  
  14         89  
20 14     14   41785 use Data::Visitor::Callback;
  0            
  0            
21             use Clone qw//;
22              
23             has package => qw/ is ro isa Str /;
24              
25             has source => qw/ is ro /, handles => [qw/ driver local_suffix no_env env_lookup path found /];
26              
27             has load_once => qw/ is ro required 1 /, default => 1;
28              
29             has loaded => qw/ is ro required 1 /, default => 0;
30              
31             has substitution => qw/ reader _substitution lazy_build 1 isa HashRef /;
32             sub _build_substitution {
33             return {};
34             }
35              
36             has default => qw/ is ro lazy_build 1 isa HashRef /;
37             sub _build_default {
38             return {};
39             }
40              
41             has path_to => qw/ reader _path_to lazy_build 1 isa Str /;
42             sub _build_path_to {
43             my $self = shift;
44             return $self->config->{home} if $self->config->{home};
45             return $self->source->path unless $self->source->path_is_file;
46             return '.';
47             }
48              
49             has _config => qw/ is rw isa HashRef /;
50              
51              
52             sub BUILD {
53             my $self = shift;
54             my $given = shift;
55              
56             $self->{package} = $given->{name} if defined $given->{name} && ! defined $self->{package} && ! ref $given->{name};
57              
58             my ($source, %source);
59             if ($given->{file}) {
60              
61             if ( 0 ) { # Deprecate the deprecation warning
62             carp "The behavior of the 'file' option has changed, pass in 'quiet_deprecation' or 'no_06_warning' to disable this warning"
63             unless $given->{quiet_deprecation} || $given->{no_06_warning};
64             carp "Warning, overriding path setting with file (\"$given->{file}\" instead of \"$given->{path}\")" if $given->{path};
65             }
66             $given->{path} = $given->{file};
67             $source{path_is_file} = 1;
68             }
69              
70             {
71             for (qw/
72             name
73             path
74             driver
75              
76             no_local
77             local_suffix
78              
79             no_env
80             env_lookup
81              
82             /) {
83             $source{$_} = $given->{$_} if exists $given->{$_};
84             }
85              
86             carp "Warning, 'local_suffix' will be ignored if 'file' is given, use 'path' instead" if
87             exists $source{local_suffix} && exists $given->{file};
88              
89             $source{local_suffix} = $given->{config_local_suffix} if $given->{config_local_suffix};
90              
91             $source = Config::JFDI::Source::Loader->new( %source );
92             }
93              
94             $self->{source} = $source;
95              
96             for (qw/substitute substitutes substitutions substitution/) {
97             if ($given->{$_}) {
98             $self->{substitution} = $given->{$_};
99             last;
100             }
101             }
102              
103             if (my $package = $given->{install_accessor}) {
104             $package = $self->package if $package eq 1;
105             Sub::Install::install_sub({
106             code => sub {
107             return $self->config;
108             },
109             into => $package,
110             as => "config"
111             });
112              
113             }
114             }
115              
116              
117             sub open {
118             if ( ! ref $_[0] ) {
119             my $class = shift;
120             return $class->new( no_06_warning => 1, 1 == @_ ? (file => $_[0]) : @_ )->open;
121             }
122             my $self = shift;
123             carp "You called ->open on an instantiated object with arguments" if @_;
124             return unless $self->found;
125             return wantarray ? ($self->get, $self) : $self->get;
126             }
127              
128             sub get {
129             my $self = shift;
130              
131             my $config = $self->config;
132             return $config;
133             # TODO Expand to allow dotted key access (?)
134             }
135              
136             sub config {
137             my $self = shift;
138              
139             return $self->_config if $self->loaded;
140             return $self->load;
141             }
142              
143             sub load {
144             my $self = shift;
145              
146             if ($self->loaded && $self->load_once) {
147             return $self->get;
148             }
149              
150             $self->_config($self->default);
151              
152             {
153             my @read = $self->source->read;
154              
155             $self->_load($_) for @read;
156             }
157              
158             $self->{loaded} = 1;
159              
160             {
161             my $visitor = Data::Visitor::Callback->new(
162             plain_value => sub {
163             return unless defined $_;
164             $self->substitute($_);
165             }
166             );
167             $visitor->visit( $self->config );
168              
169             }
170              
171             return $self->config;
172             }
173              
174              
175             sub clone {
176             my $self = shift;
177             return Clone::clone($self->config);
178             }
179              
180              
181             sub reload {
182             my $self = shift;
183             $self->{loaded} = 0;
184             return $self->load;
185             }
186              
187              
188             sub substitute {
189             my $self = shift;
190              
191             my $substitution = $self->_substitution;
192             $substitution->{ HOME } ||= sub { shift->path_to( '' ); };
193             $substitution->{ path_to } ||= sub { shift->path_to( @_ ); };
194             $substitution->{ literal } ||= sub { return $_[ 1 ]; };
195             my $matcher = join( '|', keys %$substitution );
196              
197             for ( @_ ) {
198             s{__($matcher)(?:\((.+?)\))?__}{ $substitution->{ $1 }->( $self, $2 ? split( /,/, $2 ) : () ) }eg;
199             }
200             }
201              
202             sub path_to {
203             my $self = shift;
204             my @path = @_;
205              
206             my $path_to = $self->_path_to;
207              
208             my $path = Path::Class::Dir->new( $path_to, @path );
209             if ( -d $path ) {
210             return $path;
211             }
212             else {
213             return Path::Class::File->new( $path_to, @path );
214             }
215             }
216              
217             sub _load {
218             my $self = shift;
219             my $cfg = shift;
220              
221             my ($file, $hash) = %$cfg;
222              
223             $self->{_config} = Hash::Merge::Simple->merge($self->_config, $hash);
224             }
225              
226              
227             1;
228              
229             __END__
230             =pod
231              
232             =head1 NAME
233              
234             Config::JFDI - Just * Do it: A Catalyst::Plugin::ConfigLoader-style layer over Config::Any
235              
236             =head1 VERSION
237              
238             version 0.065
239              
240             =head1 DESCRIPTION
241              
242             Config::JFDI is an implementation of L<Catalyst::Plugin::ConfigLoader> that exists outside of L<Catalyst>.
243              
244             Essentially, Config::JFDI will scan a directory for files matching a certain name. If such a file is found which also matches an extension
245             that Config::Any can read, then the configuration from that file will be loaded.
246              
247             Config::JFDI will also look for special files that end with a "_local" suffix. Files with this special suffix will take
248             precedence over any other existing configuration file, if any. The precedence takes place by merging the local configuration with the
249             "standard" configuration via L<Hash::Merge::Simple>.
250              
251             Finally, you can override/modify the path search from outside your application, by setting the <NAME>_CONFIG variable outside your application (where <NAME>
252             is the uppercase version of what you passed to Config::JFDI->new).
253              
254             =head1 SYNPOSIS
255              
256             use Config::JFDI;
257              
258             my $config = Config::JFDI->new(name => "my_application", path => "path/to/my/application");
259             my $config_hash = $config->get;
260              
261             This will look for something like (depending on what Config::Any will find):
262              
263             path/to/my/application/my_application_local.{yml,yaml,cnf,conf,jsn,json,...} AND
264              
265             path/to/my/application/my_application.{yml,yaml,cnf,conf,jsn,json,...}
266              
267             ... and load the found configuration information appropiately, with _local taking precedence.
268              
269             You can also specify a file directly:
270              
271             my $config = Config::JFDI->new(file => "/path/to/my/application/my_application.cnf");
272              
273             To later reload your configuration, fresh from disk:
274              
275             $config->reload;
276              
277             =head1 Config::Loader
278              
279             We are currently kicking around ideas for a next-generation configuration loader. The goals are:
280              
281             * A universal platform for configuration slurping and post-processing
282             * Use Config::Any to do configuration loading
283             * A sane API so that developers can roll their own loader according to the needs of their application
284             * A friendly interface so that users can have it just DWIM
285             * Host/application/instance specific configuration via _local and %ENV
286              
287             Find more information and contribute at:
288              
289             Roadmap: L<http://sites.google.com/site/configloader/>
290              
291             Mailing list: L<http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/config-loader>
292              
293             =head1 Behavior change of the 'file' parameter in 0.06
294              
295             In previous versions, Config::JFDI would treat the file parameter as a path parameter, stripping off the extension (ignoring it) and globbing what remained against all the extensions that Config::Any could provide. That is, it would do this:
296              
297             Config::JFDI->new( file => 'xyzzy.cnf' );
298             # Transform 'xyzzy.cnf' into 'xyzzy.pl', 'xyzzy.yaml', 'xyzzy_local.pl', ... (depending on what Config::Any could parse)
299              
300             This is probably not what people intended. Config::JFDI will now squeak a warning if you pass 'file' through, but you can suppress the warning with 'no_06_warning' or 'quiet_deprecation'
301              
302             Config::JFDI->new( file => 'xyzzy.cnf', no_06_warning => 1 );
303             Config::JFDI->new( file => 'xyzzy.cnf', quiet_deprecation => 1 ); # More general
304              
305             If you *do* want the original behavior, simply pass in the file parameter as the path parameter instead:
306              
307             Config::JFDI->new( path => 'xyzzy.cnf' ); # Will work as before
308              
309             =head1 METHODS
310              
311             =head2 $config = Config::JFDI->new(...)
312              
313             You can configure the $config object by passing the following to new:
314              
315             name The name specifying the prefix of the configuration file to look for and
316             the ENV variable to read. This can be a package name. In any case,
317             :: will be substituted with _ in <name> and the result will be lowercased.
318              
319             To prevent modification of <name>, pass it in as a scalar reference.
320              
321             path The directory to search in
322              
323             file Directly read the configuration from this file. Config::Any must recognize
324             the extension. Setting this will override path
325              
326             no_local Disable lookup of a local configuration. The 'local_suffix' option will be ignored. Off by default
327              
328             local_suffix The suffix to match when looking for a local configuration. "local" By default
329             ("config_local_suffix" will also work so as to be drop-in compatible with C::P::CL)
330              
331             no_env Set this to 1 to disregard anything in the ENV. The 'env_lookup' option will be ignored. Off by default
332              
333             env_lookup Additional ENV to check if $ENV{<NAME>...} is not found
334              
335             driver A hash consisting of Config:: driver information. This is passed directly through
336             to Config::Any
337              
338             install_accessor Set this to 1 to install a Catalyst-style accessor as <name>::config
339             You can also specify the package name directly by setting install_accessor to it
340             (e.g. install_accessor => "My::Application")
341              
342             substitute A hash consisting of subroutines called during the substitution phase of configuration
343             preparation. ("substitutions" will also work so as to be drop-in compatible with C::P::CL)
344             A substitution subroutine has the following signature: ($config, [ $argument1, $argument2, ... ])
345              
346             path_to The path to dir to use for the __path_to(...)__ substitution. If nothing is given, then the 'home'
347             config value will be used ($config->get->{home}). Failing that, the current directory will be used.
348              
349             default A hash filled with default keys/values
350              
351             Returns a new Config::JFDI object
352              
353             =head2 $config_hash = Config::JFDI->open( ... )
354              
355             As an alternative way to load a config, ->open will pass given arguments to ->new( ... ), then attempt to do ->load
356              
357             Unlike ->get or ->load, if no configuration files are found, ->open will return undef (or the empty list)
358              
359             This is so you can do something like:
360              
361             my $config_hash = Config::JFDI->open( "/path/to/application.cnf" ) or croak "Couldn't find config file!"
362              
363             In scalar context, ->open will return the config hash, NOT the config object. If you want the config object, call ->open in list context:
364              
365             my ($config_hash, $config) = Config::JFDI->open( ... )
366              
367             You can pass any arguments to ->open that you would to ->new
368              
369             =head2 $config->get
370              
371             =head2 $config->config
372              
373             =head2 $config->load
374              
375             Load a config as specified by ->new( ... ) and ENV and return a hash
376              
377             These will only load the configuration once, so it's safe to call them multiple times without incurring any loading-time penalty
378              
379             =head2 $config->found
380              
381             Returns a list of files found
382              
383             If the list is empty, then no files were loaded/read
384              
385             =head2 $config->clone
386              
387             Return a clone of the configuration hash using L<Clone>
388              
389             This will load the configuration first, if it hasn't already
390              
391             =head2 $config->reload
392              
393             Reload the configuration, examining ENV and scanning the path anew
394              
395             Returns a hash of the configuration
396              
397             =head2 $config->substitute( <value>, <value>, ... )
398              
399             For each given <value>, if <value> looks like a substitution specification, then run
400             the substitution macro on <value> and store the result.
401              
402             There are three default substitutions (the same as L<Catalyst::Plugin::ConfigLoader>)
403              
404             =over 4
405              
406             =item * C<__HOME__> - replaced with C<$c-E<gt>path_to('')>
407              
408             =item * C<__path_to(foo/bar)__> - replaced with C<$c-E<gt>path_to('foo/bar')>
409              
410             =item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use
411             C<__DATA__> as a config value, for example)
412              
413             =back
414              
415             The parameter list is split on comma (C<,>).
416              
417             You can define your own substitutions by supplying the substitute option to ->new
418              
419             =head1 SEE ALSO
420              
421             L<Catalyst::Plugin::ConfigLoader>
422              
423             L<Config::Any>
424              
425             L<Catalyst>
426              
427             L<Config::Merge>
428              
429             L<Config::General>
430              
431             =head1 AUTHOR
432              
433             Robert Krimen <robertkrimen@gmail.com>
434              
435             =head1 COPYRIGHT AND LICENSE
436              
437             This software is copyright (c) 2011 by Robert Krimen.
438              
439             This is free software; you can redistribute it and/or modify it under
440             the same terms as the Perl 5 programming language system itself.
441              
442             =cut
443