Config-Merge

 view release on metacpan or  search on metacpan

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

    OO:
       @hosts     = $config->('db.hosts.session');
       $hosts_ref = $config->('db.hosts.session');
       $host_1    = $config->('db.hosts.session.0');

    Functional:
       @hosts     = C('db.hosts.session');
       $hosts_ref = C('db.hosts.session');
       $host_1    = C('db.hosts.session.0');

These lookups are memo'ised, so lookups are fast.

If the specified key is not found, then an error is thrown.

=head1 MINIMISING MEMORY USE

The more configuration data you load, the more memory you use. In order to
keep the memory use as low as possible for mod_perl (or other forking
applications), the configuration data should be loaded at startup in the
parent process.

As long as the data is never changed by the children, the configuration hash
will be stored in shared memory, rather than there being a separate copy in each
child process.

(See L<http://search.cpan.org/~pgollucci/mod_perl-2.0.3/docs/user/performance/mpm.pod>)

=head1 METHODS

=over

=item C<new()>

    $conf = Config::Merge->new($config_dir);

new() instantiates a config object, loads the config from
the directory specified, and returns the object.

=cut

#===================================
sub new {
#===================================
    my $proto = shift;
    my $class = ref $proto || $proto;

    my $self = {};
    bless( $self, $class );

    my $params
        = @_ > 1              ? {@_}
        : ref $_[0] eq 'HASH' ? shift()
        :                       { path => shift() };

    # Emit debug messages
    $self->{debug} = $params->{debug} ? 1 : 0;

    die "Parameter 'sort' must be a coderef"
        if exists $params->{sort} && ref $params->{sort} ne 'CODE';

    # Setup callbacks
    $self->_init_callback( $_, $params->{$_} )
        foreach qw(skip is_local load_as sort);

    my $path = $params->{path}
        or die( "Configuration directory not specified when creating a new "
            . "'$class' object" );

    if ( $path && -d $path && -r _ ) {

        $path =~ s|/?$|/|;
        $self->{config_dir} = $path;
        $self->load_config();

        return $self;
    }
    else {
        die( "Configuration directory '$path' not readable when creating a new "
                . "'$class' object" );
    }
    return $self;
}

=item C<C()>

  $val = $config->C('key1.key2.keyn');
  $val = $config->C('key1.key2.keyn',$hash_ref);

C<Config::Merge> objects are overloaded so that this also works:

  $val = $config->('key1.key2.keyn');
  $val = $config->('key1.key2.keyn',$hash_ref);

Or, if used in the functional style (see L</"USING Config::Merge">):

  $val = C('key1.key2.keyn');
  $val = C('key1.key2.keyn',$hash_ref);

C<key1> etc can be keys in a hash, or indexes of an array.

C<C('key1.key2.keyn')> returns everything from C<keyn> down,
so you can use the return value just as you would any normal Perl variable.

The return values are context-sensitive, so if called
in list context, an array ref or hash ref will be returned as lists.
Scalar values, code refs, regexes and blessed objects will always be returned
as themselves.

So for example:

  $password = C('database.main.password');
  $regex    = C('database.main.password_regex');

  @countries = C('lists.countries');
  $countries_array_ref = C('lists.countries');

  etc

If called with a hash ref as the second parameter, then that hash ref will be
examined, rather than the C<$config> data.

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

the file / dir. By default, it returns the C<$name> for main
config files, or C<''> for local files.

The decision is made based on the name of the file / dir, without
any extension.

If C<load_as()> returns an empty string, then each key in the file/tree
is merged separately. This is how the C<local.*> files work by default.
See L</"OVERRIDING CONFIG LOCALLY">.

For instance:

   main.yaml:
     key1:  value
     key2:  value

   db.yaml:
     key3:  value
     key4:  value

   local.yaml:
     main:
        key1: new_value
     db:
        key4: new_value

To use C<load_as()>, you can either subclass it, or pass in a parameter
to new:

=over

=item C<qr/(regex)/>

The regex will be checked against the file/dir name, and if it matches
then it returns the string captured in the regex, otherwise it returns
the original name.

=item C<sub {}> or subclassed C<is_local>

   sub {
       my ($self,$name,$is_local) = @_;
       ...make decision...
       return 'string';   # string is used as the keyname
       return '';         # acts like local.* (see above)
       return undef;      # don't load this file/dir
   }

=back

Also, see L</"EXAMPLE USING is_local() AND load_as()">.

=cut

#===================================
sub load_as {
#===================================
    my ( $self, $filename, $local ) = @_;
    return $local ? '' : $filename;
}

my %callbacks = (
    CODE  => \&_init_code_callback,
    HASH  => \&_init_hash_callback,
    ARRAY => \&_init_array_callback,
);

=item EXAMPLE USING C<is_local()> AND C<load_as()>

For instance, instead of using C<local.*> files, you may want to
keep versioned copies of local configs for different machines, and so use:

   app.yaml
   app-(dev1.domain.com).yaml
   app-(dev2.domain.com).yaml

You would implement this as follows:

    my $config = Config::Merge->new(
        path        => '/path/to/config',

        # If matches 'xxx-(yyy)'
        is_local    => sub {
            my ( $self, $name ) = @_;
            return $name=~/- [(] .+ [)]/x ? 1 : 0;
        },

        # If local and matches 'xxx-(hostname)', return xxx
        load_as => sub {
            my ( $self, $name, $is_local ) = @_;
            if ($is_local) {
                if ( $name=~/(.*) - [(] ($hostname) [)] /x ) {
                    return  $1;
                }
                return undef;
            }
            return $name;
        }
    );

See C<examples/advanced.pl> for a working illustration.

=item C<sort()>

    $c = Config::Merge->new(
            path   => '/path/to/config',
            sort   => sub {}
    );

By default, directory entries are sorted alphabetically, with
directories before filenames.

This would be the order for these directory entries:

  api/
  api-(dev1)/
  api.yaml
  api-(dev1).yaml

To override this, you can subclass C<sort()> or pass it in as a
parameter to new:

   sub {
       my ($self,$names_array_ref) = @_
       ...sort...
       return $names_array_ref;
   }

=cut

#===================================
sub sort {
#===================================
    my ( $self, $names ) = @_;
    s/[.]([^.]+$)/ .$1/ foreach @$names;
    $names = [ sort { $a cmp $b } @$names ];
    s/ [.]([^.]+$)/.$1/ foreach @$names;
    return $names;
}

=item C<debug()>

    my $config = Config::Merge->new(
        path        => '/path/to/config',
        debug       => 1 | 0
    );

If C<debug> is true, then Config::Merge prints out an explanation
of what it is doing on STDERR.

=back

=cut

#===================================
sub debug {
#===================================
    my $self = shift;
    print STDERR ( join( "\n", @_, '' ) )
        if $self->{debug};
    return 1;
}

#===================================
sub _init_callback {
#===================================
    my ( $self, $callback, $check ) = @_;

    # If nothing set, use default or subclassed version
    unless ($check) {
        $self->{$callback} = $self->can($callback);
        $self->debug("Using default or subclassed $callback()");
        return;
    }

    $check = [$check]
        unless exists $callbacks{ ref $check };

    $self->debug( 'Using ' . ( ref $check ) . " handler for $callback()" );

    $self->{$callback} = $callbacks{ ref $check }->( $check, $callback );
    return;
}

#===================================
sub _init_code_callback {
#===================================
    return $_[0];
}

#===================================
sub _init_hash_callback {
#===================================
    my ( $check, $callback ) = @_;
    die "load_as() cannot be a hashref"
        if $callback eq 'load_as';
    return sub {
        my $self  = shift;
        my $param = shift;
        return exists $check->{$param};
    };
}

#===================================
sub _init_array_callback {
#===================================
    my ( $check, $callback ) = @_;
    if ( $callback eq 'load_as' ) {
        die "load_as() must contain a single regex"
            unless @$check == 1;
        my $regex = $check->[0];
        return sub {
            my $self     = shift;
            my $filename = shift;
            return $filename =~ m/$regex/
                ? $1
                : $filename;
        };
    }

    foreach my $value (@$check) {
        $value ||= '';
        die "'$value' is not a regular expression"
            unless ref $value eq 'Regexp';
    }
    return sub {
        my $self  = shift;
        my $value = shift;
        foreach my $regex (@$check) {
            return 1 if $value =~ m/$regex/;
        }
        return 0;
    };
}

=head1 SEE ALSO

L<Storable>, L<Config::Any>, L<Config::Any::YAML>,
L<Config::Any::JSON>, L<Config::Any::INI>, L<Config::Any::XML>,
L<Config::Any::General>



( run in 1.904 second using v1.01-cache-2.11-cpan-d8267643d1d )