Hi all

UPDATE

This module has now been released to CPAN as Config::Loader. It has been extended to handle YAML, JSON, XML, INI, Config::General and Perl config files, a la Config::Any. Also, there is now an OO and a functional interface, and a number of callbacks to allow customization of the local data merging process.

I'm looking for feedback on a YAML-based config module that I have written and (clearly) find useful. It loads YAML data recursively from the specified directory (see below for more). I am considering releasing it to CPAN. I would like feedback, please, on:

This module has 5 goals:

UPDATE : Why not Config::YAML

In response to Limbic~Region's comment below, Config::YAML is pretty much just a wrapper around the YAML module. It doesn't meet the goals I list above:

### UPDATE END ###

The configuration files

Configuration files can be stored in their own directory tree or mixed up with other files (but why would you do this?). When loading your config data, Burro::Config starts at the directory specified at startup and looks for any file ending in .conf and at any subdirectories.

The name of the file or subdirectory is used as the first key. So:

global.conf: username : admin password : 12345

would be stored as :

$Config = { global => { username => 'admin', password => '12345', } }

Subdirectories are processed before the current directory, so you can have a directory and a config file with the same name, and the values will be merged into a single hash, so for instance, you can have:

confdir: syndication/ --data_types/ --traffic.conf --headlines.conf --data_types.conf syndication.conf

The config items in syndication.conf will be added to (or overwrite) the items loaded into the syndication namespace via the subdirectory called syndication.

Overriding config locally

The situation often arises where it is necessary to specify different config values on different machines. For instance, the database host on a dev machine may be different from the host on the live application.

Instead of changing this data during dev and then having to remember to change it back before putting the new code live, we have a mechanism for overriding config locally in a local.conf file and then, as long as that file never gets uploaded to live, you are protected.

You can put a file called local.conf in any sub-directory, and the data in this file will be merged with the existing data. For instance, if we have:

confdir: db.conf local.conf

and db.conf has :

connections: default_settings: host: localhost table: abc password: 123

And in local.conf:

db: connections: default_settings: password: 456

the resulting configuration will look like this:

db: connections: default_settings: host: localhost table: abc password: 456

Using the module

This module is always subclassed, as the configuration lives as a singleton in the subclass eg:
file: MyApp/Config.pm: ---------------------- package MyApp::Config; use base 'Burro::Config'; file: (eg) startup.pl: ---------------------- ## package main; # this loads the configuration data from the provided directory # the dir is only ever specified once use MyApp::Config '/path/to/config/dir'; file: MyApp/Other/Module.pm --------------------------- package MyApp::Other::Module; # this imports the sub C() into the current package to give acce +ss # to the configuration data use MyApp::Config; @dbs = C('db.server_pool");

However, for the purposes of trying the module out, if you would like to avoid creating a separate file to subclass Burro::Config, you could do the following:

package MyApp::Config; our @ISA=('Burro::Config'); use Burro::Config(); package main; use lib ('/path/to/Burro'); MyApp::Config->import('/opt/apache/sites/iAnnounce/config'); # normally wouldn't want C() to be exported package main # but for testing purposes MyApp::Config->import(); print C('my.config.value');

Required packages:

The code

I'd appreciate any feedback. thanks

package Burro::Config; use strict; use warnings FATAL => 'all', NONFATAL => 'redefine'; use File::Glob qw(:glob); use YAML::Syck(); BEGIN { $YAML::Syck::ImplicitUnicode = 1; } use Storable(); use Data::Alias qw(deref); =head1 NAME Burro::Config - access configuration data across your application =head1 SYNOPSIS ------------------------- file: MyApp/Config.pm: package MyApp::Config; use base 'Burro::Config'; file: (eg) startup.pl: # package main; use MyApp::Config '/path/to/config/dir'; file: MyApp/Other/Module.pm; package MyApp::Other::Module; use MyApp::Config; @dbs = C('db.server_pool"); ------------------------- $dbs = MyApp::Config::copy_C('db.server_pool'); ($value,$label) = MyApp::Config->value_label('uk "United Kingdom"); =head1 DESCRIPTION Burro::Config is a configuration module which has five aims: =over =item * Store all configuration in an easy to read (see L<YAML>) format with a flexible structured layout. See L</"Config file layout"> =item * Provide a simple, easy to read, concise way of accessing the configura +tion values. See L</"Using config in applications"> =item * Specify the location of the configuration files only once per application, so that it requires minimal effort to relocate. See L</"Implementing Burro::Config"> =item * Allow different applications (eg web sites in mod_perl) each to have t +heir own configuration data, without having to pass an object around. =item * Provide a way for overriding configuration values on a particular machine, so that differences between (eg) the dev environment and the live environment do not get copied over accidentally. See L</"Overriding config locally"> =item * Load all config at startup so that (eg in the mod_perl environment) th +e data is shared between all child processes. See L</"Using config in ap +plications"> =back =head2 Implementing Burro::Config Burro::Config is never used directly by applications. It should alway +s be subclassed. The configuration data gets stored in the subclassing +module. package MyApp::Config; use base 'Burro::Config'; package MyApp::Other::Module; use MyApp::Config; Only C<MyApp::Config> uses C<Burro::Config> directly. All the other modules which require access to the config data use C<MyApp::Config>. B<Important :> Whenever either C<Burro::Config> or C<MyApp::Config> ar +e 'use'd, they should not be followed by empty parentheses. Their functionality relies on C<sub import()> being called. So: use Burro::Config; NOT use Burro::Config(); =head2 Config file layout Burro::Config uses L<YAML> for its configuration files. Configuration files can be stored in their own directory tree or mixed up with other files (but why would you do this?). When loading your config data, Burro::Config starts at the directory specified at startup (see L</"Using config in applications">) and look +s for any file ending in C<.conf> and at any subdirectories. The name of the file or subdirectory is used as the first key. So: global.conf: username : admin password : 12345 would be stored as : $Config = { global => { username => 'admin', password => '12345', } } Subdirectories are processed before the current directory, so you can have a directory and a config file with the same name, and the values will be merged into a single hash, so for instance, you can have: confdir: syndication/ --data_types/ --traffic.conf --headlines.conf --data_types.conf syndication.conf The config items in syndication.conf will be added to (or overwrite) the items loaded into the syndication namespace via the subdirectory called syndication. =head2 Overriding config locally The situation often arises where it is necessary to specify different config values on different machines. For instance, the database host on a dev machine may be different from the host on the live application. Instead of changing this data during dev and then having to remember to change it back before putting the new code live, we have a mechanis +m for overriding config locally in a C<local.conf> file and then, as lon +g as that file never gets uploaded to live, you are protected. You can put a file called C<local.conf> in any sub-directory, and the data in this file will be merged with the existing data. For instance, if we have: confdir: db.conf local.conf and db.conf has : connections: default_settings: host: localhost table: abc password: 123 And in local.conf: db: connections: default_settings: password: 456 the resulting configuration will look like this: db: connections: default_settings: host: localhost table: abc password: 456 =head2 Using config in applications =over =item Specifying the config directory The config directory can only be specified once, the very first time that C<MyApp::Config> is 'use'd. For instance: use MyApp::Config '/opt/myapp/config'; This statement causes the config files in that directory and sub-direc +tories to be loaded. Every other module which needs access to the config, just needs: use MyApp::Config; # NOTE : no parenthese () =item Refering to config Whenever C<use MyApp::Config;> is used in a module, the function C<C() +> is imported into the module's namespace. The config values can be accessed with dot notation, as in : $value = C('key1.key2.keyn'); $password = C('db.connections.default_settings.password'); C<key> can be the key of a hash, or the index of an array. $host3 = C('db.server_pool.2'); The return value is context-sensitive. If a scalar, it will return a s +calar. If the value is an array or a hash, then it will return a ref if calle +d in scalar context, or the array or hash if called in list context. $hosts_array_ref = C('db.server_pool'); @hosts = C('db.server_pool'); If the specified key is not found, then an error is thrown. =back =head1 METHODS =over =item C<new()> $conf = Burro::Config->new($config_dir); new() instantiates a config object, loads the config from the directory specified, and returns the object. You should never need to call it specifically - this happens automatically when a subclass inherits from this module. =cut #========================================== sub new { #========================================== my $proto = shift; my $class = ref $proto || $proto; my $self = { _memo => {} }; bless( $self, $class ); my $dir = shift || ''; if ( $dir && -d $dir && -r _ ) { $dir =~ s|/?$|/|; $self->{config_dir} = $dir; $self->load_config(); return $self; } else { $class->error( 'Configuration directory not specified when creating a new + config object' ); } return $self; } =item C<C()> $val = $self->C('key1.key2.keyn'); $val = $self->C('key1.key2.keyn',$hash_ref); OR, more usually, in a module that inherits from this $val = C('key1.key2.keyn'); $val = C('key1.key2.keyn',$hash_ref); C() is used for accessing a configuration value stored either internally in a hash_ref in the module that inherits from this module, or passed in in the parameter list. Normally, the user will only ever use C() as a function and not as a method, because the modules that end up 'use'ing this module have the function C() exported to their namespace. 'key1.key2.keyn' is just a simpler way of writing: $C->{key1}->{key2}->{keyn} $$C{key1}{key2}{keyn} OR (eg) $C->{key1}->[2]->{keyn} $$C{key1}[2]{keyn} The return values are context-sensitive, so if called in list context, a list will be returned, otherwise an array_ref or a hash_ref will be returned. A scalar value will always be returned as a scalar. So for example: $password = C('database.main.password'); @countries = C('lists.countries'); $countries_array_ref = C('lists.countries'); etc =cut #========================================== sub C { #========================================== my $self = shift; my $path = shift; $path = '' unless defined $path; my ( $config, @keys ); # If a private hash is passed in use that if (@_) { $config = $_[0]; @keys = split( /\./, $path ); $config = $self->_walk_path( $config, 'PRIVATE', \@keys ); } # Otherwise use the stored config data else { # Have we previously memoised this? if ( exists $self->{_memo}->{$path} ) { $config = deref $self->{_memo}->{$path}; } # Not memoised, so get it manually else { $config = $self->{config}; (@keys) = split( /\./, $path ); $config = $self->_walk_path( $config, '', \@keys ); $self->{_memo}->{$path} = \$config; } } return wantarray && ref $config ? ( deref $config) : $config } =item C<_walk_path()> =cut #=================================== sub _walk_path { #=================================== my $self = shift; my ( $config, $key_path, $keys ) = @_; foreach my $key (@$keys) { next unless defined $key && length($key); if ( ref $config eq 'ARRAY' && $key =~ /^[0-9]+/ && exists $config->[$key] ) { $config = $config->[$key]; $key_path .= '.' . $key; next; } elsif ( ref $config eq 'HASH' && exists $config->{$key} ) { $config = $config->{$key}; $key_path .= '.' . $key; next; } $self->error("Invalid key '$key' specified for '$key_path'\n") +; } return $config; } =item C<copy_C()> This works exactly the same a L</"C()"> but it performs a deep clone of the data before returning it. This means that the returned data can be changed without affecting the data stored in the $conf object; The data is deep cloned, using Storable, so the bigger the data, the m +ore performance hit. That said, Storable's dclone is very fast. =cut #========================================== sub copy_C { #========================================== my $self = shift; my $data = $self->Burro::Config::C(@_); return Storable::dclone($data); } =item C<value_label()> ($value,$label) = MyApp::Config->value_label('value "Label"'); This is a convenience method for storing key/value pairs in a scalar... Easier by example: For instance, when generating a list of values for a drop down list in a form, there are 3 bits of information: =over =item * The descriptive text e.g. 'Spain' =item * The value e.g. 'es' =item * The order =back So the values for this drop down list could be specified in YAML as follows: countries: - eg "Egypt" - es "Spain" - fr "France" - uk Because, in the above example, 'uk' doesn't have a label specified, C<value_label('uk')> would return ('uk','uk') The label must be enclosed between double quotes, and double quotes can be embedded within the label without escaping them. The value (or key) cannot contain double quotes. =cut #========================================== sub value_label { #========================================== my $self = shift; my $data = shift || ''; my ( $value, $label ) = ( $data =~ /^(.*?)\s*(?:"(.+)")?$/ ); $label ||= $value; return ( $value, $label ); } =item C<load_config()> Public method for loading (or reloading) the config files located in the directory specified at object creation (see L</"new()">). Returns the config hash ref. =cut #========================================== sub load_config { #========================================== my $self = shift; return $self->{config} = $self->_load_config(); } =item C<_load_config()> $hash_ref = $self->_load_config([$directory]); Private method that starts in the config directory specified at object creation (see L</"new()">) and recurses through all the sub-directories, looking for C<*.conf> files. Also, see L</"Config fil +e layout">. Returns the config hash_ref. =cut #========================================== sub _load_config { #========================================== my $self = shift; my $dir = shift || $self->{config_dir}; my $config = {}; my @config_files = sort { $a cmp $b } grep { !/\/local.conf$/ } glob( $dir . "* +" ); foreach my $config_file (@config_files) { my ( $data, $name ); if ( -f $config_file && $config_file =~ /\.conf$/ ) { $data = $self->_load_config_file($config_file); ($name) = ( $config_file =~ m|.*/(.*)\.conf$| ); } elsif ( -d $config_file ) { $data = $self->_load_config( $config_file . '/' ); ($name) = ( $config_file =~ m|.*/(.*)$| ); } else { next; } if ( exists $config->{$name} ) { map { $config->{$name}->{$_} = $data->{$_} } keys %$data; } else { $config->{$name} = $data; } } if ( -e $dir . 'local.conf' ) { my $data = $self->_load_config_file( $dir . 'local.conf' ); $config = $self->_merge_local( $config, $data ); } return $config; } =item C<_merge_local()> $merged_config = $self->_merge_local($config_hash,$local_config_ha +sh); Private method used for merging the contents of a C<local.conf> file i +nto the main configuration. Hashes are merged, arrays are overwritten. =cut #========================================== sub _merge_local { #========================================== my $self = shift; my $config = shift; my $local = shift; foreach my $key ( keys %$local ) { if ( ref $local->{$key} eq 'HASH' && exists $config->{$key} ) { $config->{$key} = $self->_merge_local( $config->{$key}, $local->{$key} + ); } else { $config->{$key} = $local->{$key}; } } return $config; } =item C<_load_config_file()> $config_hash = $self->_load_config_file($filename); Private method which parses the YAML file and throws an error if it is + not correctly formatted. =cut #========================================== sub _load_config_file { #========================================== my $self = shift; my $config_file = shift; my $data; eval { $data = YAML::Syck::LoadFile($config_file); }; if ($@) { $self->error( "Error loading config file $config_file:\n\n" . +$@ ); } return $data; } =item C<import()> MyApp::Config->import($config_dir); C<import()> does all the magic, as follows: package MyApp::Config; use base 'Burro::Config'; package main; use MyApp::Config '/path/to/config/dir'; --> The first time that MyApp::Config is used, the config dir must be specified. * This calls MyApp::Config->new('/path/to/config/dir'), which loads the config hash and returns the config object ($co +nf). * $conf is stored in $MyApp::Config::Config. * MyApp::Config::C() and MyApp::Config::copy_C() are set up as functions which call the related methods, using the config object singleton $MyApp::Config::Config * C() is exported to the caller package (in this case, 'main') * It also sets up the sub MyApp::Config::import() package MyApp::Other::Module; use MyApp::Config; * This calls MyApp::Config::import() automatically, which export the function C() to the caller package. =cut #========================================== sub import { #========================================== my $class = shift; my $dir = shift || ''; no strict 'refs'; my $var = $class . "::Config"; ${$var} = $class->new($dir); # Export C, copy_C to the subclass *{ $class . "::C" } = eval "sub {return \$$var->Burro::Config::C(\@_)}"; *{ $class . "::copy_C" } = eval "sub {return \$$var->Burro::Config::copy_C(\@_)}"; # Create a new import sub in the subclass *{ $class . "::import" } = eval ' sub { my $callpkg = caller(0); no strict \'refs\'; *{$callpkg."::C"} = \&' . $class . '::C; }'; } # Added basic error method to replace my framework's exception mechani +sm # for the purposes of code review on PM #========================================== sub error { #========================================== my $proto = shift; my $class = ref $proto || $proto; my $error = shift; die "$class error : $error"; } =back =head1 SEE ALSO L<Burro::Exception> =head1 TODO =over 4 =item * Add multiple config sources, especially find a way to specify how to load from databases =item * Add optional expiry times, but this negates the idea of sharing the co +nfig between child processes =item * Add a HUP signal handler or reloading config. Would also need to add an interface for modules to register subs to be called when the config is reloaded. =item * Optionally use cache for sharing config between machines. =back =head1 BUGS None known =head1 AUTHOR Clinton Gormley, E<lt>clinton@traveljury.comE<gt> =head1 COPYRIGHT AND LICENSE Copyright (C) 2006 by Clinton Gormley This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.7 or, at your option, any later version of Perl 5 you may have available. =cut 1

Replies are listed 'Best First'.
Re: RFC: A YAML config module to be posted to CPAN
by hossman (Prior) on May 12, 2007 at 19:30 UTC

    Didn't read the code, just the write up, a couple of points of feedback based on your usage description...

    1. You mention in your update that an advantage your module has over Config::YAML is syntax...

      $host = $c->get_dbconfig->{servers}[2]{host}; as opposed to: $host = C('dbconfig.servers.2.host');

      ..it's not clear to me why you consider that an advantage. it's shorter there's no doubt, but there's a lot of information in the first case that's not in the second case: mainly an easy way to disambiguate between "i want the second item from the section named 'servers'" and "i want the section named '2' in the section named 'servers'"

    2. It would be helpful to know what types of error handling/messages does your mod produce when config values can't be found -- or when a value can be found, but it is not in the right context (ie: i ask for 'dbconfig.servers.2.host' but 'servers' is a hash, or 'host' is a single item ut i've asked for an array. (these are examples of the types of errors i'm worried about since you use your own string based lookup syntax instead of letting people rely on standard perl sigls and error messages.

    3. The use of singletons and the need to create a subclass seems obnoxious and unnecessary. if the goal is to let people both: 1) load a config set in one place, and refer to it from elsewhere in the code without passing an object refrence arround; 2) load multiple config sets; then instead of making them create a seperate package for each set, why not let them declare a global (or package) variable for each set and assuign an instance of your object to it. that way they can control the scoping...

      use Burro::Config # i want this config to be private... my $privconf = Burro::Config->parse('/opt/privconf/'); my $passs = $privconf->c('foo.bar.2.user.password'); # i want this config to be available to other packages... $conf = Burro::Config->parse('/opt/publicconf/');
      ..it's not clear to me why you consider that an advantage. it's shorter there's no doubt, but there's a lot of information in the first case that's not in the second case: mainly an easy way to disambiguate between "i want the second item from the section named 'servers'" and "i want the section named '2' in the section named 'servers'"

      The inspiration came from Template::Toolkit's way of dereferencing variables - it's clean, easy to read and, (imho) perfect for configuration. Assuming your YAML is correct, there will never be a confusion between 'the second item from the section named servers' and 'the section named 2'. The reason for this is that the value at 'dbconfig.servers' is either a scalar, a hash ref, or an array ref, so assuming it is not a scalar, it has to be one of the two - not ambiguous. My module doesn't do validate the underlying YAML - it assumes that you know what you're doing. In order to validate it, you could use Kwalify to check the data against a schema. I've been using this module for over a year, and it has never been an issue for me.

      It would be helpful to know what types of error handling/messages does your mod produce when config values can't be found -- or when a value can be found, but it is not in the right context (ie: i ask for 'dbconfig.servers.2.host' but 'servers' is a hash, or 'host' is a single item ut i've asked for an array. (these are examples of the types of errors i'm worried about since you use your own string based lookup syntax instead of letting people rely on standard perl sigls and error messages.

      In your example, the '2' could equally be the third element in an array, or a key called '2'. So the module doesn't differentiate between these. There is really only one error at data lookup time, and that is: "unknown key '2' at 'dbconfig.servers'". So the key either exists or it doesn't, and if it doesn't, it throws an exception. As I said above, this has never been a problem for me. I think, because you're working with configuration data, you generally know what it looks like before you run the program. It's not the same as (eg) parsing user submitted data from a website.

      The use of singletons and the need to create a subclass seems obnoxious and unnecessary. if the goal is to let people both: 1) load a config set in one place, and refer to it from elsewhere in the code without passing an object refrence arround; 2) load multiple config sets; then instead of making them create a seperate package for each set, why not let them declare a global (or package) variable for each set and assuign an instance of your object to it. that way they can control the scoping...

      Yes... I think I might have gone a bit overboard with the whole minimalism thing, when I was writing it. I wanted it to just work, be shared between mod_perl modules, and require the minimum work to get access to it.

      In practice, it is really easy to use

      • the only slight annoyance being that you have to create a file as follows:

        file: My/Config.pm: -------------------- package My::Config; use base 'Burro::Config'; 1;
      • Then you load the data once (eg for mod_perl, in startup.pl):

        File: startup.pl: ----------------- use My::Config '/path/to/config/dir';
      • Then for any module that needs access to the config, you just add this to module file:

        File: My/Module.pm: ------------------- use My::Config; $value = C('key1.key2.etc...');

      I fully admit that I'm doing some stuff in the import sub which may not be the best idea, I'd welcome feedback on that. But the fact is that it works, and it is easy to use and easy to maintain (eg when moving code around)

      So I'm wondering if others would find it sufficiently useful for me to release it to CPAN?

      Clint

      PS Thanks for taking the time to read and respond - I realise it was a long loooong post.

        Assuming your YAML is correct, there will never be a confusion between 'the second item from the section named servers' and 'the section named 2'. The reason for this is that the value at 'dbconfig.servers' is either a scalar, a hash ref, or an array ref, so assuming it is not a scalar, it has to be one of the two - not ambiguous.

        The YAML in question will be a config file, which means it's likely that it will be created by a hand by a person -- and not auto generated by code, so assuming it is "correct" is a bad assumption. Frankly I can't possibly imagine how you can consider that syntax unambiguous: you tell me, is "a.b.2.3" referring to $conf->{'a'}->{'b'}->{'2'}->{'3'} or $conf->{'a'}->{'b'}->[2]->[3] ?

        ...because you're working with configuration data, you generally know what it looks like before you run the program. It's not the same as (eg) parsing user submitted data from a website.

        You may know what the data looks like, because you wrote the config parsing module, and the config files, and the apps that use the config parsing module. Joe Blo who writes an app using your module may not understand it as well, and John Smith who uses Joe Blo's app and needs to write the config files for it REALLY may not understand what's going on.

        In practice, it is really easy to use ... the only slight annoyance being that you have to create a file as follows ... Then you load the data once (eg for mod_perl, in startup.pl) ... Then for any module that needs access to the config, you just add this to module file

        That does not sound easy to use ... particularly the part about having to create a seperate mini little perl module for each config dir i want to read from, or the fact that i have to use the module one way in exactly one palce in my app, but every other place in my app i have to use it a different way.

Re: RFC: A YAML config module to be posted to CPAN
by Limbic~Region (Chancellor) on May 12, 2007 at 15:02 UTC
    clinton,
    I have not read your post beyond checking to see if it addresses the existing Config::YAML module. It doesn't appear to so I would suggest that you do that.

    Cheers - L~R

Re: RFC: A YAML config module to be posted to CPAN
by clinton (Priest) on Jun 01, 2007 at 16:56 UTC
    This is a follow up to my previous RFC (RFC: A YAML config module to be posted to CPAN).

    I have now released this config module as Config::Loader version 1.

    Config::Loader reads in (and merges) a configuration directory tree, which can contain files with YAML, XML, JSON, INI, Perl and Config::General config data. (Thanks castaway for the pointer to Config::Any.)

    (WARNING - I had a version 0.01 on CPAN from 2004 which worked very differently - look for version 1.00)

    • Individual keys in the real config data can be overwritten for a dev environment though one (or many) local.(yaml/json/ini/etc) file(s). See the examples/browser.pl

    • I've taken hossman's concerns into account by providing an OO interface in addition to the functional interface, and I've made it easier to use the functional style, so:

      OO style ------------------------------------------------------- use Config::Loader(); my $config = Config::Loader->new('/path/to/config'); @hosts = $config->('db.hosts.session'); -------------------------------------------------------
      OR
      Functional style (auto generates your own config class) ------------------------------------------------------- # On startup use Config::Loader('My::Config' => '/path/to/config'); # Then, in any module where you want to use the config package My::Module; use My::Config; @hosts = C('db.hosts.sesssion'); -------------------------------------------------------

    • The config data can be accessed as a normal Perl variable, or for ease of reading, I provide a method for using Template Toolkit style notation, for instance:

      OO style ------------------------------------------------------- @hosts = $config->('db.hosts.session'); $hosts_ref = $config->('db.hosts.session'); $host_2 = $config->('db.hosts.session.1'); @cloned_hosts = $config->clone('db.hosts.session'); # deep clones +the data -------------------------------------------------------
      Functional style ------------------------------------------------------- @hosts = C('db.hosts.sesssion'); $hosts_ref = C('db.hosts.sesssion'); $host_2 = C('db.hosts.session.1'); @cloned_hosts = My::Config::clone('db.hosts.session'); # deep cl +ones the data $config = My::Config::object; # returns + the stored config object -------------------------------------------------------
      These lookups are memo'ised for speed.

    • I have used overload to make these two equivalent:

      $config->C('db.hosts.session'); $config->('db.hosts.session');
      Is this overkill? How far back is overloading &{} supported?

    I would appreciate any feedback / comments / use of the module.

    thanks

    clint