http://qs1969.pair.com?node_id=146246
Category: Miscellaneous
Author/Contact Info Brad Smithart AKA impossiblerobot
Description: SiteConfig - Perl module for XML-based site-wide configuration files

SiteConfig was created as a simple way to manage site-wide configuration information, allowing multiple applications to share various sub-sets of the overall settings.

Though I've been using this for a while, I've never posted it because there are so many configuration management modules on CPAN. But I've found it so useful that I decided others might like it too.

I've used it for things I had not anticipated; for example, loading a hash from an XML file of state names and abbreviations.

For more information, read the POD (at the end of the module code -- though normally I keep the POD for this module in a separate file). I would appreciate any suggestions/comments that would help me improve this.
package SiteConfig;

# by Brad Smithart AKA Impossible Robot
# $Revision: 1.8 $

use strict;
use XML::Simple;

my $inc_dir = $INC{'SiteConfig.pm'};
$inc_dir =~ s!/SiteConfig.pm$!!;
$inc_dir = '.' if $inc_dir eq 'SiteConfig.pm';

my %SiteConfig = %{ XMLin("$inc_dir/SiteConfig.xml") };
my @configs = @{ $SiteConfig{'config'} };
my $xml_path = $SiteConfig{'path'};

sub import
{
    my $pkg = shift;
    my @exports = @_;
    my ($caller_pkg) = caller;
    my %export_ok;
    foreach my $sym (@configs)
    {
        $export_ok{$sym} = 1;
    }
    # Check for set-up hash-ref
    if (ref($exports[0]))
    {
        my %setup = %{shift @exports};
        # Stub for later adding setup overrides, for example:
        # $xml_path = $setup{'path'} if $setup{'path'};
    }
    no strict 'refs';
    foreach my $sym (@exports)
    {
        if ($export_ok{$sym})
        {
            *{ "${caller_pkg}::${sym}" } = \%{ XMLin("$xml_path/$sym.x
+ml") };
        }
        else
        {
            warn ("Bad config requested: $sym\n");
        }
    }
}

sub getarray
{
    my $var = shift;
    my $array = ref($var) ? $var : [$var];
    return @{ $array };
}

sub write
{
    my %configs = @_;
    foreach (keys %configs)
    {
        XMLout($configs{$_},
              xmldecl => 1,
              rootname => $_,
              outputfile => "$xml_path/$_.xml",
              noattr => 1,
              ) or die $!;
    }
}

1;

__END__
######################################################################
# SiteConfig POD
# (I usually keep this POD in a separate file.)
######################################################################

=head1 NAME

SiteConfig - Perl Module for XML-based site-wide configuration files

=head1 SYNOPSIS

  # Read configuration files:
  use SiteConfig qw( site search );

  # Access configuration values:
  print "$site{name}\n";
  print "$search{title}\n";

  # Access array values:
  my @fields = SiteConfig::getarray($site{'fields'});

  #####

  # Write configuration file:

  use SiteConfig;

  my %site = ( name => 'My site',
               dir  => '/home/httpd/html',
             )

  SiteConfig::write( site => \%site );

=head1 PREREQUISITES

SiteConfig uses XML::Simple to read and write its XML-based
configuration files.

=head1 DESCRIPTION

SiteConfig was created as a simple way to manage site-wide
configuration information, allowing multiple applications to
share various sub-sets of the overall settings.
(For more information, see the L</"DESIGN CONSIDERATIONS"> section).
SiteConfig uses XML configuration files to store this information.

=head2 CONFIGURING SiteConfig

Configuration information for SiteConfig is stored in the file
'SiteConfig.xml', which must exist in the same directory as the
SiteConfig module itself.

This file contains the path to all the other SiteConfig XML files,
and entries for each valid configuration:

 <SiteConfig>
   <path>/home/httpd/configs</path>
   <config>site</config>
   <config>search</config>
   <config>order</config>
 </SiteConfig>

Configuration files F<site.xml>, F<search.xml>, and F<order.xml>
will be located in the F</home/httpd/configs> directory.

=head2 READING CONFIGURATION FILES

Reading configuration information is as simple as 'use'ing the
SiteConfig module, listing the configuration hashes to be imported.

For example:

  use SiteConfig qw( site search order );

This loads the SiteConfig module and imports the I<%site>, I<%search>,
and I<%order> hashes from the F<site.xml>, F<search.xml>, and
F<order.xml> configuration files.

=head2 ACCESSING CONFIGURATION INFORMATION

Each configuration hash can be used as a normal hash, and can contain
nested data.

Normal scalar values can be accessed directly:

  print $search{'title'};

Nested hash references can be de-referenced normally:

  print $search{'labels'}{'page'};

Nested array references require special treatment. Because SiteConfig
has no way to distinguish a scalar value from a single-element array,
it is necessary to force array context where needed using the
SiteConfig::getarray() function:

  my @fields = SiteConfig::getarray($site{'fields'};

  or

  print join(', ', SiteConfig::getarray($site{'fields'});

=head2 WRITING CONFIGURATION FILES

Writing a configuration file requires defining a configuration hash,
then calling the SiteConfig::write() function with the base name of
the configuration file and a reference to the configuration hash as
arguments.
(Of course, you can also create a configuration file by hand.)

For example:

  my %search = ( title  => 'Search my site',
                 dir    => '/home/httpd/html',
                 fields => [ 'page', 'URL', 'description' ],
                 labels => { page        => 'Page',
                             URL         => 'Web',
                             description => 'Description',
                           }
               )

  SiteConfig::write( search => \%search );

This creates a configuration file called F<search.xml> containing
the information in %search.

=head2 EXPORTS

Exports configuration hashes as requested.

=head1 DESIGN CONSIDERATIONS

The design of the SiteConfig module was driven by four
overriding concepts:

=over 4

=item *

Transparent interface - Simply adding a 'use' statement makes
configuration variables available to the program. Developers should
not have to learn new functions to be able to use the configuration
data.

=item *

XML storage - Configuration information is stored in a standard,
extensible format that allows arbitrary nested structures and is
easy to manipulate (with XML::Simple)

=item *

Shared but selective configuration files - Individual applications
should be able to pull in sets of shared configuration data, but
not pull in information that is unnecessary.

=item *

Control - Since configuration data is shared site-wide, individual
developers should not be arbitrarily creating configuration
files/structures.

=back

SiteConfig was conceived in an environment where I managed
inexperienced Perl programmers, who were unfamiliar with Perl's
OOP features, and not entirely comfortable with references.

If a new configuration scheme were to be adopted, it would have to
be as simple or simpler than the previously used configuration
method: requiring in files of Perl variables.

Here's how I worked my way through my four criteria:

B<Transparency> - I found that most of the CPAN configuration modules
either restricted the data structure that could be stored or
required essentially learning (and setting) a large variety of
options to get the functionality I wanted. Simplifying these modules
would require sub-classing or creating a wrapper around them. But it
was easier to just write a module that did what I wanted. (I was
also enamored with the idea of a "'use' it and forget it"
interface.)

I was not entirely successful, however, in creating a transparent
interface, since I needed the getarray() function to force array
context on single-element arrays.

B<XML Storage> - I have never used XML in an application before, but
XML::Simple makes it so easy that I had to try it. XML::Simple could
almost be used alone for this task, if it weren't for my other
considerations.

B<Shared but selective configs> - We had been in the habit of creating
configuration files for each specific application. However, there
was often quite a bit of overlap, and when we changed a setting, we
often had to make sure that we changed it in B<all> the appropriate
configuration files. One option would have been a monolithic file
containing all the configuration information for a site. But a more
modular approach seemed more reasonable.

B<Control> - Since multiple developers designing multiple applications
will be using the data stored in the config files, SiteConfig was
created to allow centralized approval of config file names and
structures (and, implicitly, data structures). A list of usable
configurations is defined in SiteConfig.xml, and write access to
that file can be restricted, giving some measure of control.

Currently, SiteConfig is a little dangerous, since although it will
only I<read> configurations that are defined in F<SiteConfig.xml>,
it will not only write a config file with any name to the config
directory, but will overwrite any existing file of the same name.

The current solution is to use OS file permissions to prevent
accidental overwriting.

=head1 AUTHOR

P.B. Smithart, E<lt>brad@smithart.netE<gt>

(AKA Impossible Robot)

=head1 COPYRIGHT

Copyright (C) 2001-2002 P.B. Smithart

=cut