X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Plugin-ConfigLoader.git;a=blobdiff_plain;f=lib%2FCatalyst%2FPlugin%2FConfigLoader.pm;h=4fcfdf1eff815355961ff8b5dbdaab61967e18d5;hp=0fcbc704af057709680571fb8ef8389b8d45b9a2;hb=bf799fe18a35e9d6b0d8ad75feb08ac7aa04d60d;hpb=a8288af59a63ff771caddbec3f47250c05c6ce0b diff --git a/lib/Catalyst/Plugin/ConfigLoader.pm b/lib/Catalyst/Plugin/ConfigLoader.pm index 0fcbc70..4fcfdf1 100644 --- a/lib/Catalyst/Plugin/ConfigLoader.pm +++ b/lib/Catalyst/Plugin/ConfigLoader.pm @@ -1,136 +1,378 @@ -package Catalyst::Plugin::ConfigLoader; - -use strict; -use warnings; - -use NEXT; -use Module::Pluggable::Fast - name => '_config_loaders', - search => [ __PACKAGE__ ], - require => 1; -use Data::Visitor::Callback; - -our $VERSION = '0.04'; - -=head1 NAME - -Catalyst::Plugin::ConfigLoader - Load config files of various types - -=head1 SYNOPSIS - - package MyApp; - - # ConfigLoader should be first in your list so - # other plugins can get the config information - use Catalyst qw( ConfigLoader ... ); - - # by default myapp.* will be loaded - # you can specify a file if you'd like - __PACKAGE__->config( file = > 'config.yaml' ); - -=head1 DESCRIPTION - -This mdoule will attempt to load find and load a configuration -file of various types. Currently it supports YAML, JSON, XML, -INI and Perl formats. - -=head1 METHODS - -=head2 setup( ) - -This method is automatically called by Catalyst's setup routine. It will -attempt to use each plugin and, once a file has been successfully -loaded, set the C section. - -=cut - -sub setup { - my $c = shift; - my $path = $c->config->{ file } || $c->path_to( Catalyst::Utils::appprefix( ref $c || $c ) ); - - my( $extension ) = ( $path =~ /\.(.{1,4})$/ ); - - for my $loader ( $c->_config_loaders ) { - my @files; - my @extensions = $loader->extensions; - if( $extension ) { - next unless grep { $_ eq $extension } @extensions; - push @files, $path; - } - else { - push @files, "$path.$_" for @extensions; - } - - for( @files ) { - next unless -f $_; - my $config = $loader->load( $_ ); - if( $config ) { - $c->config( $config ); - last; - } - } - } - - $c->finalize_config; - - $c->NEXT::setup( @_ ); -} - -=head2 finalize_config - -This method is called after the config file is loaded. It can be -used to implement tuning of config values that can only be done -at runtime. If you need to do this to properly configure any -plugins, it's important to load ConfigLoader before them. -ConfigLoader provides a default finalize_config method which -walks through the loaded config hash and replaces any strings -beginning with C<< __HOME__/ >> with the full path to -the file inside the app's home directory. - -=cut - -sub finalize_config { - my $c = shift; - my $v = Data::Visitor::Callback->new( - plain_value => sub { s[^__HOME__/(.+)$][ $c->path_to($1) ]e } - ); - $v->visit( $c->config ); -} - -=head1 AUTHOR - -=over 4 - -=item * Brian Cassidy Ebricas@cpan.orgE - -=back - -=head1 CONTRIBUTORS - -The following people have generously donated their time to the -development of this module: - -=over 4 - -=item * David Kamholz Edkamholz@cpan.orgE - -=back - -=head1 COPYRIGHT AND LICENSE - -Copyright 2006 by Brian Cassidy - -This library is free software; you can redistribute it and/or modify -it under the same terms as Perl itself. - -=head1 SEE ALSO - -=over 4 - -=item * L - -=back - -=cut - -1; \ No newline at end of file +package Catalyst::Plugin::ConfigLoader; + +use strict; +use warnings; + +use Config::Any; +use MRO::Compat; +use Data::Visitor::Callback; +use Catalyst::Utils (); + +our $VERSION = '0.32'; + +=head1 NAME + +Catalyst::Plugin::ConfigLoader - Load config files of various types + +=head1 SYNOPSIS + + package MyApp; + + # ConfigLoader should be first in your list so + # other plugins can get the config information + use Catalyst qw( ConfigLoader ... ); + + # by default myapp.* will be loaded + # you can specify a file if you'd like + __PACKAGE__->config( 'Plugin::ConfigLoader' => { file => 'config.yaml' } ); + + In the file, assuming it's in YAML format: + + foo: bar + + Accessible through the context object, or the class itself + + $c->config->{foo} # bar + MyApp->config->{foo} # bar + +=head1 DESCRIPTION + +This module will attempt to load find and load a configuration +file of various types. Currently it supports YAML, JSON, XML, +INI and Perl formats. Special configuration for a particular driver format can +be stored in Cconfig-E{ 'Plugin::ConfigLoader' }-E{ driver }>. +For example, to pass arguments to L, use the following: + + __PACKAGE__->config( 'Plugin::ConfigLoader' => { + driver => { + 'General' => { -LowerCaseNames => 1 } + } + } ); + +See L's C parameter for more information. + +To support the distinction between development and production environments, +this module will also attemp to load a local config (e.g. myapp_local.yaml) +which will override any duplicate settings. See +L +for details on how this is configured. + +=head1 METHODS + +=head2 setup( ) + +This method is automatically called by Catalyst's setup routine. It will +attempt to use each plugin and, once a file has been successfully +loaded, set the C section. + +=cut + +sub setup { + my $c = shift; + my @files = $c->find_files; + my $cfg = Config::Any->load_files( + { files => \@files, + filter => \&_fix_syntax, + use_ext => 1, + driver_args => $c->config->{ 'Plugin::ConfigLoader' }->{ driver } + || {}, + } + ); + # map the array of hashrefs to a simple hash + my %configs = map { %$_ } @$cfg; + + # split the responses into normal and local cfg + my $local_suffix = $c->get_config_local_suffix; + my ( @main, @locals ); + for ( sort keys %configs ) { + if ( m{$local_suffix\.}ms ) { + push @locals, $_; + } + else { + push @main, $_; + } + } + + # load all the normal cfgs, then the local cfgs last so they can override + # normal cfgs + $c->load_config( { $_ => $configs{ $_ } } ) for @main, @locals; + + $c->finalize_config; + $c->next::method( @_ ); +} + +=head2 load_config + +This method handles loading the configuration data into the Catalyst +context object. It does not return a value. + +=cut + +sub load_config { + my $c = shift; + my $ref = shift; + + my ( $file, $config ) = %$ref; + + $c->config( $config ); + $c->log->debug( qq(Loaded Config "$file") ) + if $c->debug; + + return; +} + +=head2 find_files + +This method determines the potential file paths to be used for config loading. +It returns an array of paths (up to the filename less the extension) to pass to +L for loading. + +=cut + +sub find_files { + my $c = shift; + my ( $path, $extension ) = $c->get_config_path; + my $suffix = $c->get_config_local_suffix; + my @extensions = @{ Config::Any->extensions }; + + my @files; + if ( $extension ) { + die "Unable to handle files with the extension '${extension}'" + unless grep { $_ eq $extension } @extensions; + ( my $local = $path ) =~ s{\.$extension}{_$suffix.$extension}; + push @files, $path, $local; + } + else { + @files = map { ( "$path.$_", "${path}_${suffix}.$_" ) } @extensions; + } + @files; +} + +=head2 get_config_path + +This method determines the path, filename prefix and file extension to be used +for config loading. It returns the path (up to the filename less the +extension) to check and the specific extension to use (if it was specified). + +The order of preference is specified as: + +=over 4 + +=item * C<$ENV{ MYAPP_CONFIG }> + +=item * C<$ENV{ CATALYST_CONFIG }> + +=item * C<$c-Econfig-E{ 'Plugin::ConfigLoader' }-E{ file }> + +=item * C<$c-Epath_to( $application_prefix )> + +=back + +If either of the first two user-specified options are directories, the +application prefix will be added on to the end of the path. + +=cut + +sub get_config_path { + my $c = shift; + + + my $appname = ref $c || $c; + my $prefix = Catalyst::Utils::appprefix( $appname ); + my $path = Catalyst::Utils::env_value( $appname, 'CONFIG' ) + || $c->config->{ 'Plugin::ConfigLoader' }->{ file } + || $c->path_to( $prefix ); + + my ( $extension ) = ( $path =~ m{\.([^\/\\.]{1,4})$} ); + + if ( -d $path ) { + $path =~ s{[\/\\]$}{}; + $path .= "/$prefix"; + } + + return ( $path, $extension ); +} + +=head2 get_config_local_suffix + +Determines the suffix of files used to override the main config. By default +this value is C, which will load C. The suffix can +be specified in the following order of preference: + +=over 4 + +=item * C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> + +=item * C<$ENV{ CATALYST_CONFIG_LOCAL_SUFFIX }> + +=item * C<$c-Econfig-E{ 'Plugin::ConfigLoader' }-E{ config_local_suffix }> + +=back + +The first one of these values found replaces the default of C in the +name of the local config file to be loaded. + +For example, if C< $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> is set to C, +ConfigLoader will try and load C instead of +C. + +=cut + +sub get_config_local_suffix { + my $c = shift; + + my $appname = ref $c || $c; + my $suffix = Catalyst::Utils::env_value( $appname, 'CONFIG_LOCAL_SUFFIX' ) + || $c->config->{ 'Plugin::ConfigLoader' }->{ config_local_suffix } + || 'local'; + + return $suffix; +} + +sub _fix_syntax { + my $config = shift; + my @components = ( + map +{ + prefix => $_ eq 'Component' ? '' : $_ . '::', + values => delete $config->{ lc $_ } || delete $config->{ $_ } + }, + grep { ref $config->{ lc $_ } || ref $config->{ $_ } } + qw( Component Model M View V Controller C Plugin ) + ); + + foreach my $comp ( @components ) { + my $prefix = $comp->{ prefix }; + foreach my $element ( keys %{ $comp->{ values } } ) { + $config->{ "$prefix$element" } = $comp->{ values }->{ $element }; + } + } +} + +=head2 finalize_config + +This method is called after the config file is loaded. It can be +used to implement tuning of config values that can only be done +at runtime. If you need to do this to properly configure any +plugins, it's important to load ConfigLoader before them. +ConfigLoader provides a default finalize_config method which +walks through the loaded config hash and calls the C +sub on any string. + +=cut + +sub finalize_config { + my $c = shift; + my $v = Data::Visitor::Callback->new( + plain_value => sub { + return unless defined $_; + $c->config_substitutions( $_ ); + } + ); + $v->visit( $c->config ); +} + +=head2 config_substitutions( $value ) + +This method substitutes macros found with calls to a function. There are a +number of default macros: + +=over 4 + +=item * C<__HOME__> - replaced with C<$c-Epath_to('')> + +=item * C<__ENV(foo)__> - replaced with the value of C<$ENV{foo}> + +=item * C<__path_to(foo/bar)__> - replaced with C<$c-Epath_to('foo/bar')> + +=item * C<__literal(__FOO__)__> - leaves __FOO__ alone (allows you to use +C<__DATA__> as a config value, for example) + +=back + +The parameter list is split on comma (C<,>). You can override this method to +do your own string munging, or you can define your own macros in +Cconfig-E{ 'Plugin::ConfigLoader' }-E{ substitutions }>. +Example: + + MyApp->config->{ 'Plugin::ConfigLoader' }->{ substitutions } = { + baz => sub { my $c = shift; qux( @_ ); } + } + +The above will respond to C<__baz(x,y)__> in config strings. + +=cut + +sub config_substitutions { + my $c = shift; + my $subs = $c->config->{ 'Plugin::ConfigLoader' }->{ substitutions } + || {}; + $subs->{ HOME } ||= sub { shift->path_to( '' ); }; + $subs->{ ENV } ||= + sub { + my ( $c, $v ) = @_; + if (! defined($ENV{$v})) { + Catalyst::Exception->throw( message => + "Missing environment variable: $v" ); + return ""; + } else { + return $ENV{ $v }; + } + }; + $subs->{ path_to } ||= sub { shift->path_to( @_ ); }; + $subs->{ literal } ||= sub { return $_[ 1 ]; }; + my $subsre = join( '|', keys %$subs ); + + for ( @_ ) { + s{__($subsre)(?:\((.+?)\))?__}{ $subs->{ $1 }->( $c, $2 ? split( /,/, $2 ) : () ) }eg; + } +} + +=head1 AUTHOR + +Brian Cassidy Ebricas@cpan.orgE + +=head1 CONTRIBUTORS + +The following people have generously donated their time to the +development of this module: + +=over 4 + +=item * Joel Bernstein Erataxis@cpan.orgE - Rewrite to use L + +=item * David Kamholz Edkamholz@cpan.orgE - L integration + +=item * Stuart Watt - Addition of ENV macro. + +=back + +Work to this module has been generously sponsored by: + +=over 4 + +=item * Portugal Telecom L - Work done by Joel Bernstein + +=back + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2010 by Brian Cassidy + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 SEE ALSO + +=over 4 + +=item * L + +=item * L + +=item * L + +=back + +=cut + +1;