Activator

 view release on metacpan or  search on metacpan

lib/Activator/Config.pm  view on Meta::CPAN

use base 'Class::StrongSingleton';

=head1 NAME

C<Activator::Config> - provides a merged configuration to a script
combining command line options, environment variables, and
configuration files.

=head1 SYNOPSIS

  use Activator::Config;

  my $config = Activator::Config->get_config( \@ARGV);  # default realm
  my $config = Activator::Config->get_config( \@ARGV, $otherrealm);

  #### Get a hashref of command line arguments, and an arrayref of bareword arguments
  my ( $config, $args ) = Activator::Config->get_args( \@ARGV );

=head1 DESCRIPTION

This module allows a script or application to have a complex
configuration combining options from command line, environment
variables, and YAML configuration files.

For a script or application, one creates any number of YAML
configuration files. These files will be deterministically merged into
one hash. You can then pass this to an application or write it to file.

This module is not an options validator. It uses command line options
as overrides to existing keys in configuration files and DOES NOT
validate them. Unrecognized command line options are ignored and
C<@ARGV> is modified to remove recognized options, leaving barewords
and unrecognized options in place and the same order for a real
options validator (like L<Getopt::Long>). If you do use another
options module, make sure you call C<get_config()> BEFORE you call
their processor, so that C<@ARGV> will be in an appropriate state.

Environment variables can be used to act as a default to command line
options, and/or override any top level configuration file key which is
a scalar.

This module is cool because:

=over

=item *

You can generate merged, complex configuration heirarchies that are
context sensitive very easily.

=item *

You can pass as complex a config as you like to any script or
application, and override any scalar configuration option with your
environment variables or from the command line.

=item *

It supports realms, allowing you to have default configurations for
development, QA, production, or any number of arbitrary realms you
desire. That is, with a simple command line flag, you can switch
your configuration context.

=back

=head2 Configuration Source Precedence

The precedence heirarchy for configuration from highest to lowest is:

=over

=item *

command line options

=item *

environment variables

=item *

forced overrides from config files

=item *

merged settings from YAML configuration files

=back

=head1 COMMAND LINE ARGUMENTS

This module allows you to override configuration file settings from
the command line. You can use long or short options using C<'-'> or
C<'--'> notation, allows barewords in any order, and recognizes the
arguments terminator C<'--'>. Also supported are multiple flag
arguments:

  #### turn on super verbosity. sets $config->{v} = 2
  myscript.pl -v -v

You can specify configured options at the command line for
override:

  #### override the configuration file setting for 'foo'
  myscript.pl --foo=bar

Note that while YAML configuration (and this module) support deep
structures for configuration, you can only override top level keys
that are scalars using command line arguments and/or environment
variables.

=head2 Reserved Arguments

There are a few reserved command line arguments:

 --skip_env        : ignore environment variables (EXCEPT $USER)
 --project=<>      : used to search for the C<E<lt>projectE<gt>.yml> file
 --realm=<>        : use C<E<lt>realmE<gt>.yml> in config file processing and
                     consider all command line arguments to be in this realm
 --conf_path       : colon separated list of directories to search for config files

=head2 Project as a Bareword Argument

There are times where a script takes the project name as a required
bareword argument. For these cases, require that project be the last
argument, and pass a flag to L</get_config()>. 

That is, when your script is called like this:

  myscript.pl --options <project>

get the config like this:

  Activator::Config->get_config( \@ARGV, undef, 1 );

The second argument to L</get_config()> is the realm, so you pass
C<undef> (unless you know the realm you are looking for) to allow the
command line options and environment variables to take affect.

=head1 ENVIRONMENT VARIABLES

Environment variables can be used to act as a default to command line
options, and/or override any top level configuration file key which is
a scalar. The expected format is C<ACT_CONFIG_[key]>. Note that YAML is
case sensitive, so the environment variables must match. Be especially
wary of command shell senstive characters in your YAML keys (like
C<:~E<gt>E<lt>|>).

If you wish to override a key for only a particular realm, you
can insert the realm into the env variable wrapped by double
underscores:

 ACT_CONFIG_foo       - set 'foo' for default realm
 ACT_CONFIG__bar__foo - set 'foo' only for 'bar' realm

The L</Reserved Arguments> listed in the L</COMMAND LINE ARGUMENTS>
section also have corresponding environment variables with only
C<skip_env> being slightly different:

 ACT_CONFIG_skip_env     : set to 1 to skip, or 0 (or don't set it at all) to
                        not skip
 ACT_CONFIG_project      : same as command line argument
 ACT_CONFIG_realm        : same as command line argument
 ACT_CONFIG_conf_path    : same as command line argument

=head2 Automatically Imported Environment Variables

Since they tend to be generally useful, the following environment
variables are automatically imported into your configuration:

=over

=item *

HOME

=item *

USER

=back

it is L</FUTURE WORK> to make these cross-platform compatible.

=head1 CONFIGURATION FILES

lib/Activator/Config.pm  view on Meta::CPAN

    my $barewords = [];
    my $found_terminator = 0;

    foreach my $arg ( @$argv_raw ) {
	my ( $key, $value ) = $self->_get_arg( $arg );

	if ( $found_terminator || !defined( $key ) ) {
	    DEBUG("'$arg' is a bareword or after the args terminator '--'");
	    push @$barewords, $arg;
            next;
	}

	if( $key eq '--' ) {
	    DEBUG("'$arg' is the terminator");
	    $found_terminator = 1;
	    next;
	}

        if ( defined $value ) {
	    DEBUG("got key '$key' = '$value'");

	    # if we see an argument again, coerce this value into an
	    # array
            if ( exists $argv->{ $key } ) {
		if ( reftype ( $argv->{ $key } ) eq 'ARRAY' ) {
		    DEBUG("added '$value' to key list '$key'" );
		    push @{ $argv->{ $key } }, $value;
		}
		else {
		    DEBUG("created key list '$key' and added '$value'" );
		  $argv->{ $key } = [ $argv->{ $key }, $value ];
	      }
	    }
	    # just set it
	    else {
		$argv->{ $key } = $value;
	    }
	}
        else {

	    # if we see a value again, increment the occurence count
	    if ( exists $argv->{ $key } ) {
		DEBUG("incremented key '$key'" );
		$argv->{ $key }++;
	    }
	    else {
		DEBUG("set $key" );
		$argv->{ $key } = 1;
	    }
        }
    }

    # save these so we don't have to do it again
    $self->{ARGV}      = $argv;
    $self->{BAREWORDS} = $barewords;

    return ( $argv, $barewords );
}

# Helper to split an arg into key/value. Returns ($key, $value), where
# $value is undef if the argument is flag format (--debug), undef if
# it is a bareword ( foo ) and '--' if it is the arguments terminator
# symbol.
#
sub _get_arg {
    my ( $self, $arg ) = @_;

    if ( $arg !~ /^-(-)?/ ) {
	return;
    }

    if ( $arg eq '--' ) {
	return $arg;
    }

    my ( $key, $value ) = split /=/xms, $arg, 2;

    if ( !defined $key ) {
	Activator::Exception::Config->throw( 'argument',
					      'invalid',
					      $arg );
    }

    # clean up key
    $key =~ s/^--?//;

    # clean up value, if quoted
    if ( defined $value ) {
	$value =~ s/^"//;
	$value =~ s/"$//;
    }

    return ( $key, $value );
}

# Merge config files into this objects Activator::Registry object
sub _process_config_files {
    my ( $pkg, $realm, $skip_env, $project_is_arg ) = @_;
    my $self = &new( @_ );

    # figure out what project we are working on
    my $project =
      $self->{ARGV}->{project} ||
	( $project_is_arg ? $self->{BAREWORDS}->[-1] : undef ) ||
	  ( $skip_env ? undef : $ENV{ACT_CONFIG_project} ) ||
	    Activator::Exception::Config->throw( 'project', 'missing' );

    # process these files:
    #     $ENV{USER}.yml
    #     <realm>.yml    - realm specific settings and defaults
    #     <project>.yml  - project specific settings and defaults
    #     org.yml        - top level organization settings and defaults
    # in one of these paths, if set
    #   --conf_file=       : use $self->{ARGV}->{conf_file} (which could be an arrayref )
    #   ACT_CONFIG_conf_file= : comma separated list of files

    my $conf_path = $self->{ARGV}->{conf_path};
    if ( ! $conf_path ) {
	$conf_path = ( $skip_env ? undef : $ENV{ACT_CONFIG_conf_path} );
	if ( !$conf_path ) {
	    ERROR( "Neither ACT_CONFIG conf_path env var nor --conf_path set");



( run in 2.161 seconds using v1.01-cache-2.11-cpan-437f7b0c052 )