In this article, I'm going to discuss advanced file-based configuration for Perl programs using the CPAN AppConfig module. For an introduction to AppConfig and the rationale behind using a configuration management module, see my previous "Cultured Perl" installment on this topic.
Some of the code in this second article draws upon code in the first article without explaining the details. In order to understand the material I'm going to cover, you'll need to read the first article.
Before you begin, you should have Perl 5.005 or later on your system, and the CPAN AppConfig module installed on your system (see Resources later in this article for links to these packages if needed).
Often forgotten in the rush of development, data validation is the vaccine your program must take now so it won't keel over and die later. Users expect your program to handle their input, and you should expect the unexpected.
Fortunately, you can use the AppConfig data validation routines. You can either reject bad data, or abort the entire program when you find unsavory data, depending on the PEDANTIC setting given at the time you initialize AppConfig.
Listing 1. Validate configuration values
# SSN: must be a string with exactly 9 digits
# LIMITED: must be a string key existing in the hash %limited_hash
$config->define(
# more complex subroutine validation
'SSN' => { ARGCOUNT => ARGCOUNT_ONE,
VALIDATE => sub
{
my $varname = shift @_;
my $value = shift @_;
# only succeed with 9 digits in $value
return (9 == ($value =~ tr/0-9//));
}
},
# limited values, must be 'alpha' or 'beta'
'LIMITED' => { ARGCOUNT => ARGCOUNT_ONE,
VALIDATE => sub
{
my $varname = shift @_;
my $value = shift @_;
my %limited_hash = ( alpha => 1, beta => 2 );
return exists $limited_hash{$value};
}
},
);
|
Note that the VALIDATE subroutine is much more limited than the ACTION subroutines (described in the next section). The AppConfig::State instance is not passed to VALIDATE subroutines, so we can't access other variables in our configuration easily. This was probably done intentionally, so an adventurous programmer doesn't place AppConfig::State into an indeterminate state.
The AppConfig module makes it easy to trigger functions when a variable's value is set. The ACTION subroutine is called after the value is set, whereas the VALIDATE subroutine is invoked before the value is set. You should not use ACTION to set the value of the current variable. You will start an infinite loop if you are not careful. Try the following, for instance:
Listing 2. An infinite autoaction loop -- DO NOT USE!
$config->define(
'TRIGGER' => { ARGCOUNT => ARGCOUNT_ONE,
ACTION => sub # autoaction
{
my $config = shift @_;
my $varname = shift @_;
my $value = shift @_;
print "$varname = $value\n";
$value++;
$config->TRIGGER($value);
}
}
);
|
The code in Listing 2 will run forever. You can put safeguards in place with external variables, but that would get messy very quickly. Instead, do the postprocessing of your data after all the data has been read in.
Autoactions will be explored further in a future article, when we discuss how a variable's value can set off triggers for other variables. There you will see how to set a variable's value safely from within its own trigger for mutually exclusive variables.
Modifying AppConfig hash and array variables
It's pretty obvious how to modify scalar values in an AppConfig object:
you just call $config->VARIABLE(value).
Arrays and hashes, however, are more complex. The AppConfig object will give you an array or hash reference when you ask for a variable's value. You have to modify the data in the object, not the data that's passed to you. You can't remove a single element; you have to remove all of them and then reinsert the wanted ones.
You can modify arrays like this:
Listing 3. Modifying arrays
$config->define('HOSTS' => { ARGCOUNT => ARGCOUNT_LIST });
# add a value to the array named HOSTS
$config->HOSTS("jove");
# list the values in the HOSTS array
print "$_\n" foreach @{$config->HOSTS};
# reset the array to 0 entries
$config->_default("HOSTS");
|
There's no API for directly accessing the values of arrays. You can do it by accessing internal AppConfig::State data or the array reference returned to you as the value of the array variable, but it is not recommended that you do so.
Hash entries are, similarly to arrays, easy to set but difficult to remove. Also, they are limited to scalar strings or undef for the values.
Listing 4. Modifying Hashes
$config->define('PHONE' => { ARGCOUNT => ARGCOUNT_HASH });
# add values to the hash named PHONE
$config->PHONE("jimmy=1-800-453-2211");
# unusual cases
$config->PHONE("equals=a=b"); # the value will be "a=b"
$config->PHONE("harry="); # the value will be a string of length 0 ('')
$config->PHONE("harry"); # the value will be undef
# list the sorted keys and their values in the PHONE hash
print "$_ => ", $config->PHONE()->{$_}, "\n" foreach sort keys %{$config->PHONE};
# you can also do it with Data::Dumper
print Dumper $config->PHONE;
# reset the hash to 0 entries
$config->_default("PHONE");
|
Just like arrays, there is no API for directly accessing hash values. You can do it by accessing internal AppConfig::State data or the hash reference returned to you as the value of the hash variable, but it is not recommended that you do so.
Radio buttons are familiar to all GUI users: a series of mutually exclusive options are defined. For instance, the type of a file could be "executable," "directory," or "special" (anything else). You can handle this with numeric types; for instance, executable is 1, directory is 2, and special is 3. With radio buttons, though, when any of the three options is set, the other two are automatically set to 0.
The implementation relies on a binary state of the option (on or off). When the option is off, nothing extra needs to be done. When the option is on, the other two options will be set to 0.
Setting all three options to 0 is acceptable; it can be treated as an unknown state (so there are four possible states: file, directory, special, and unknown).
Listing 5. Radio buttons
my $config = AppConfig->new();
$config->define(
# mutually exclusive options
'FILE' => { ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ACTION => \&toggle_type },
'DIRECTORY' => { ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ACTION => \&toggle_type },
'SPECIAL' => { ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 1, ACTION => \&toggle_type },
);
# {{{ toggle_type: do right when file type is specified (3 mutually exclusive options)
sub toggle_type
{
my $self = shift @_;
my $varname = shift @_;
my $value = shift @_;
return unless $value; # do nothing if we're being turned off
# turn off all options except the one that invoked this action
$self->set($_, 0) foreach grep !/^$varname$/i, qw/FILE DIRECTORY SPECIAL/;
}
|
The key to the code above is that is works along with all other AppConfig
usage, and does not require a new module. It can go right into your code.
It could have been implemented differently if the internals of
AppConfig::State were accessed (so, for instance, we wouldn't have to
invoke set() ), but for our example of three
mutually exclusive settings, that was not necessary.
Here is the code that would modify the internals of AppConfig::State if the options are more complex. The important thing is how we modify the values of variables directly in the internals of AppConfig::State -- something we're not supposed to do normally.
This example will automatically set the WINNER and LOSER variables when the TIMES hash has more than one entry. It is a complete example you can run.
Note also how complex the interplay of variables can be. When is it safe to set WINNER and LOSER? Should WINNER and LOSER, when set directly, use the TIMES hash, or should they modify values directly in the AppConfig::State internals? This is for only three variables. Imagine how complex it can get for a small program that wishes to use interdependencies! You should look into finite state machines if you have lots of options that trigger other options or actions. FSMs are best suited to that sort of complex environment.
Listing 6. Interdependent options
#!/usr/bin/perl -w
use strict;
use AppConfig qw/:argcount/;
my $config = AppConfig->new();
$config->define(
'TIMES' => { ARGCOUNT => ARGCOUNT_HASH, ACTION => \&toggle_option },
'WINNER' => { ARGCOUNT => ARGCOUNT_ONE, ACTION => \&cheat_action },
'LOSER' => { ARGCOUNT => ARGCOUNT_ONE, ACTION => \&cheat_action },
'CHEATERS' => { ARGCOUNT => ARGCOUNT_NONE },
);
$config->TIMES("Johnny Boy = .444");
$config->TIMES("Johnny Appleseed = 2.45");
$config->TIMES("Johnny Be Good = 8.002");
print "Winner: ", $config->WINNER, "\n";
print "Loser: ", $config->LOSER, "\n";
$config->WINNER("Johnny Be Good");
print "Winner: ", $config->WINNER, "\n";
print "Loser: ", $config->LOSER, "\n";
$config->LOSER("Johnny Be Good");
print "Winner: ", $config->WINNER, "\n";
print "Loser: ", $config->LOSER, "\n";
# {{{ toggle_option: toggle interdependent options
sub toggle_option
{
my $self = shift @_;
my $varname = shift @_;
my $value = shift @_;
my @sorted = sort { $self->TIMES()->{$a} <=> $self->TIMES()->{$b} } keys %{$self->TIMES};
if (scalar @sorted > 1) # we need more than 1 member to have a winner and a loser
{
# we have to set winner and loser directly to avoid the cheat_action
$self->{VARIABLE}->{winner} = $sorted[0];
$self->{VARIABLE}->{loser} = $sorted[-1];
}
}
# }}}
# {{{ cheat_action: set TIMES hash value so it's always the winner or the loser
sub cheat_action
{
my $self = shift @_;
my $varname = shift @_;
my $value = shift @_;
my $time;
$varname = uc $varname; # make sure we match the variable name
if ($varname eq 'WINNER')
{
# set the time to 0 unconditionally
$time = 0;
}
elsif ($varname eq 'LOSER')
{
# set the time to the sum of all the times (it will always be the worst time, then)
$time += $_ foreach values %{$config->TIMES()};
}
# trigger a resorting by inserting the newly wanted time
$self->TIMES("$value=$time");
}
# }}}
|
The advanced techniques shown here should help you improve the configuration of your own programs. Don't be afraid to experiment with your own AppConfig extensions!
- Read Ted's other Perl articles in the "Cultured Perl" series on developerWorks.
- Check out CPAN.org for all the
Perl modules you ever wanted.
- Go to Perl.com for Perl
information and related resources.
- You can get more information on the AppConfig module, by Andy Wardley, at CPAN.org.
- Using WebSphere Studio? Read Integrating Rational ClearCase LT and WebSphere Studio Application Developer, a technical article that covers software configuration management using the version of ClearCase included with WebSphere Studio Application Developer.
- Find more Linux articles and tutorials on developerWorks.
Teodor Zlatanov graduated with an M.S. in computer engineering from Boston University in 1999. He has worked as a programmer since 1992, using Perl, Java, C, and C++. His interests are in open source work on text parsing, 3-tier client-server database architectures, UNIX system administration, CORBA, and project management.
Comments (Undergoing maintenance)





