Hack 17. Collect Configuration Information


Save and re-use configuration information.

Some code you write needs configuration information when you build and install it. For example, consider a program that can use any of several optional and conflicting plug-ins. The user must decide what to use when she builds the module, especially if some of the dependencies themselves have dependencies.

When you run your tests and the code in general, having this information available in one spot is very valuableyou can avoid expensive and tricky checks if you hide everything behind a single, consistent interface.

How do you collect and store this information? Ask the user, and then write it into a simple configuration module!

The Hack

Both Module::Build and ExtUtils::MakeMaker provide user prompting features to ask questions and get answers. The benefit of this is that they silently accept the defaults during automated installations. Users at the keyboard can still answer a prompt, while users who just want the software to install won't launch the installer, turn away, and return an hour later to find that another prompt has halted the process in the meantime.

Module::Build is easier to extend, so here's a simple subclass that allows you to specify questions, default values, and configuration keys before writing out a standard module containing this information:

package Module::Build::Configurator; use strict; use warnings; use base 'Module::Build'; use SUPER; use File::Path; use Data::Dumper; use File::Spec::Functions; sub new {     my ($class, %args) = @_;     my $self           = super( );     my $config         = $self->notes( 'config_data' ) || { };     for my $question ( @{ $args{config_questions} } )     {         my ($q, $name, $default) = map { defined $_ ? $_ : '' } @$question;         $config->{$name}         = $self->prompt( $q, $default );     }     $self->notes( 'config_module', $args{config_module} );     $self->notes( 'config_data',   $config );     return $self; } sub ACTION_build {     $_[0]->write_config( );     super( ); } sub write_config {     my $self      = shift;     my $file      = $self->notes( 'config_module' );     my $data      = $self->notes( 'config_data' );     my $dump      = Data::Dumper->new( [ $data ], [ 'config_data' ] )->Dump;     my $file_path = catfile( 'lib', split( /::/, $file . '.pm' ) );     my $path      = ( splitpath( $file_path ) )[1];     mkpath( $path ) unless -d $path;     my $package   = <<END_MODULE;     package $file;     my $dump     sub get_value     {         my (\\$class, \\$key) = \\@_;         return unless exists \\$config_data->{ \\$key };         return               \\$config_data->{ \\$key };     }     1; END_MODULE     $package =~ s/^\\t//gm;     open( my $fh, '>', $file_path )         or die "Cannot write config file '$path': $!\\n";     print $fh $package;     close $fh; } 1;

The module itself is a straightforward subclass of Module::Build. It overrides new( ) to collect the config_module argument (containing the name of the configuration module to write) and to loop over every configuration question (specified with config_questions). The latter argument is an array reference of array references containing the name of the question to ask, the name of the value as stored in the configuration module, and the default value of the question, if any. In an unattended installation, the prompt( ) method will return the default value rather than interrupt the process.

The module also overrides ACTION_build( ), the method that perl ./Build runs, to write the configuration file. The write_config( ) method takes the hash of configuration data created in new( ) (and stored with Module::Build's handy notes( ) mechanism), serializes it with Data::Dumper, and writes it and a module skeleton to the necessary path under lib/.

Note the use of File::Spec::Functions and File::Path to improve file handling and to make sure that the destination directory exists for the configuration module.


Using the Hack

To use the hack, write a Build.PL file as normal:

use Module::Build::Configurator; my $build = Module::Build::Configurator->new(     module_name      => 'User::IrisScan',     config_module    => 'User::IrisScan::Config',     config_questions =>     [         [ 'What is your name?',                        'name', 'Anouska' ],         [ 'Rate yourself as a spy from 1 to 10.',    'rating', '10'      ],         [ 'What is your eye color?',              'eye_color', 'blue'    ],     ], ); $build->create_build_script( );

This file builds a distribution for the User::IrisScan module. Run it to see the prompts:

$ perl Build.PL What is your name? [Anouska] Faye Rate yourself as a spy from 1 to 10. [10] 8 What is your eye color? [blue] blue Deleting Build Removed previous script 'Build' Creating new 'Build' script for 'User-IrisScan' version '1.28' $

Now look at lib/User/IrisScan/Config.pm:

package User::IrisScan::Config; my $config_data = {              'eye_color' => 'blue',              'name' => 'Faye',              'rating' => '8'            }; sub get_value {     my ($class, $key) = @_;     return unless exists $config_data->{ $key };     return               $config_data->{ $key }; } 1;

You can use this configuration module within your tests or within the program now as normal. If you're clever, you can even check for this module when upgrading your software. If it exists, use the configured values there for the defaults. Your users will love you.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net