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.xml") }; } 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 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: /home/httpd/configs site search order Configuration files F, F, and F will be located in the F 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, F, and F 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 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 - 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 - 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 - 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 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 - 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 configurations that are defined in F, 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, Ebrad@smithart.netE (AKA Impossible Robot) =head1 COPYRIGHT Copyright (C) 2001-2002 P.B. Smithart =cut