#======================================================================== # # Badger::Config # # DESCRIPTION # A central configuration module. # # AUTHOR # Andy Wardley # #======================================================================== package Badger::Config; use Badger::Debug ':dump'; use Badger::Class version => 0.01, debug => 0, import => 'class', base => 'Badger::Prototype', utils => 'blessed numlike', constants => 'HASH ARRAY CODE DELIMITER', auto_can => 'can_configure', messages => { get => 'Cannot fetch configuration item <1>.<2> (<1> is <3>)', }; sub init { my ($self, $config) = @_; my $data = $self->{ data } = $config->{ data } || $config; my $class = $self->class; # merge all $ITEMS in package variables with those listed in # $config->{ items } and all other $config keys. my $items = $self->class->list_vars( ITEMS => delete($config->{ items }), keys %$data ); # store hash lookup table marking valid items $items = $self->{ item } = { map { $_ => 1 } map { split DELIMITER } @$items }; # load up all the configuration items from package variables # # TODO: We need different init rules here with fallbacks. This should # be merged in with the code in Badger::Class::Config, or rather B:C:C # should define a config schema. foreach my $item (keys %$items) { $data->{ $item } = $config->{ $item } || $class->any_var( uc $item ); $self->debug("config set $item => ", $data->{ $item }, "\n") if DEBUG; } if (DEBUG) { $self->debug("config items: ", $self->dump_data($self->{ items })); $self->debug("config data: ", $self->dump_data($self->{ data })); } return $self; } sub get { my $self = shift; my @names = map { ref $_ eq ARRAY ? @$_ : split /\W+/ } @_; my $name = shift @names; my $data = $self->{ data }->{ $name }; my ($last, $method); $self->debug("get: ", join(' / ', @names)) if DEBUG; while (defined $data && @names) { $data = $data->() if ref $data eq CODE; $name = shift @names; if (ref $data eq HASH) { $data = $data->{ $name }; } elsif (ref $data eq ARRAY && numlike $name) { $data = $data->[$name]; } elsif (blessed $data && (my $method = $data->can($name))) { $data = $method->($data); } else { return $self->error_msg( get => $last, $name, ref $last || 'text' ); } } return $data; } sub set { my $self = shift; my $name = shift; my $data = @_ == 1 ? shift : { @_ }; $self->{ data }->{ $name } = $data; $self->{ item }->{ $name } = 1; return $data; } sub can_configure { my ($self, $name) = @_; $self = $self->prototype unless ref $self; return unless $name && $self->{ item }->{ $name }; return sub { return @_ > 1 ? shift->set( $name => @_ ) # set : $self->{ data }->{ $name }; # get }; } 1; __END__ =head1 NAME Badger::Config - configuration module =head1 SYNOPSIS use Badger::Config; my $config = Badger::Config->new( user => { name => { given => 'Arthur', family => 'Dent', }, email => [ 'arthur@dent.org', 'dent@heart-of-gold.com', ], }, planet => { name => 'Earth', description => 'Mostly Harmless', }, ); # fetch top-level data item - these both do the same thing my $user = $config->user; # shortcut method my $user = $config->get('user'); # generic get() method # fetch nested data item - these all do the same thing print $config->get('user', 'name', 'given'); # Arthur print $config->get('user.name.family'); # Dent print $config->get('user/email/0'); # arthur@dent.org print $config->get('user email 1'); # dent@heart-of-gold.com =head1 DESCRIPTION This is a quick hack to implement a placeholder for the L module. A config object is currently little more than a blessed hash with an AUTOLOAD method which allows you to get/set items via methods. Update: this has been improved a little since the above was written. It's still incomplete, but it's being worked on. =head1 METHODS =head2 new() Constructor method to create a new L object. Configuration data can be specified as the C named parameter: my $config = Badger::Config->new( data => { name => 'Arthur Dent', email => 'arthur@dent.org', }, ); The C parameter can be used to specify the names of other valid configuration values that this object supports. my $config = Badger::Config->new( data => { name => 'Arthur Dent', email => 'arthur@dent.org', }, items => 'planet friends', ); Any data items defined in either C or C can be accessed via methods. print $config->name; # Arthur Dent print $config->email; # arthur@dent.org print $config->planet || 'Earth'; # Earth As a shortcut you can also specify configuration data direct to the method. my $config = Badger::Config->new( name => 'Arthur Dent', email => 'arthur@dent.org', ); You should avoid this usage if there is any possibility that your configuration data might contain the C or C items. =head2 get($name) Method to retrieve a value from the configuration. my $name = $config->get('name'); This can also be used to fetch nested data. You can specify each element as a separate argument, or as a string delimited with any non-word characters. For example, given the following configuration data: my $config = Badger::Config->new( user => { name => { given => 'Arthur', family => 'Dent', }, email => [ 'arthur@dent.org', 'dent@heart-of-gold.com', ], }, ); You can then access data items using any of the following syntax: print $config->get('user', 'name', 'given'); # Arthur print $config->get('user.name.family'); # Dent print $config->get('user/email/0'); # arthur@dent.org print $config->get('user email 1'); # dent@heart-of-gold.com In addition to accessing list and hash array items, the C will call subroutine references and object methods, as shown in this somewhat contrived example: # a trivial object class package Example; use base 'Badger::Base'; sub wibble { return 'wobble'; } package main; # a config with a function that returns a hash containing an object my $config = Badger::Config->new( function => sub { return { object => Example->new(), } } ); print $config->get('function.object.wibble'); # wobble =head2 set($name,$value) Method to store a value in the configuration. $config->set( friend => 'Ford Prefect' ); $config->set( friends => ['Ford Prefect','Trillian','Marvin'] ); At present this does I allow you to set nested data items in the way that the L method does. =head1 INTERNAL METHODS =head2 can_configure($name) Internal method used to generate accessor methods on demand. This is installed using the L hook in L. =head1 AUTHOR Andy Wardley L =head1 COPYRIGHT Copyright (C) 2001-2009 Andy Wardley. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut # Local Variables: # mode: perl # perl-indent-level: 4 # indent-tabs-mode: nil # End: # # vim: expandtab shiftwidth=4: