views:

280

answers:

8

I find the following anti-pattern repeated in my Perl scripts: the script contains some machine/setup specific settings which I store in-line as constants in the script whereas the rest of the script is general in nature:

#!/usr/bin/perl

use strict;
use warnings;

# machine specific settings at the start of the script.
my $SETTING_1 = "foo";
my @SETTING_2 = ("123", "456");
my $SETTING_3 = "something";

# general part of script follows.
...

This pattern is somewhat okay when running on one machine, but as soon as I want to distribute the script to multiple machines the trouble starts since I must keep track so that I do not overwrite the settings part with new updates in the general part.

The correct solution is obviously to have one general script file and have it read a configuration file which is specific to the environment that the script runs in.

My question is: What CPAN module would you recommend for solving this problem? Why?

+1  A: 

The usual low-tech method is to simply do EXPR a configuration file. Have you looked into this?

ephemient
This provides no error checking whatsoever, and allows execution of arbitrary code. Never evaluate the contents of expressions or files, when their contents can simply be read into variables instead.
Ether
@Ether The `do` method is built-in, easy, and commonly used. Many large or popular packages still use it in favor of safer but more complex configuration schemes, for example http://gna.org/projects/savane/ and http://packages.debian.org/sbuild and Net::Config (included in the Perl distribution). It's perfectly fine as long as you trust the user creating the configuration files.
ephemient
+2  A: 

I prefer YAML and YAML::XS for configuration data. It's simple, readable, and has bindings for almost any programming language. Another popular choice is Config::General.

eugene y
+6  A: 

For configuration files, I like to use YAML. Simple, cross-platform, human-readable, and no danger of your configuration accidentally morphing into an actual program.

friedo
I prefer YAML::Tiny as it's lightweight and pure-Perl (making it easy to bundle if necessary).
Michael Carman
Is it human-writable?
mobrule
@mobrule: I'd call it human-editable. It's trivial to change existing values. It's a little harder to create a YAML file by hand from scratch.
Michael Carman
Re, yaml: check out http://yaml.org The layout of the main page is actually in yaml. It's kinda neat. :)
Robert P
YAML gets really poor for human editing if your config data is structured (point of reference: Catalyst). Providing YAML examples in POD documentation is also a pain because POD represents verbatim blocks with indentation and perldoc *renders them indented* -- and YAML is sensitive to indentation.
hobbs
+2  A: 

The Config:Properties library is good for reading and writing key/value pair property files.

john
+4  A: 

My favorite is Config::Std. I like the way it handles multi-line and multi-part configuration values.

You have to be careful when a variable is potentially multi-valued: If a single value exists in the configuration file, it will store the value in a scalar; if multiple values exist, you will get an array reference.

I find it convenient to have two configuration files: One for values that describe the operating environment (where to find libraries etc) and another for user-modifiable behavior.

I also like to write a wrapper around it. For example (updated to include autogenerated read-only accessors):

#!/usr/bin/perl

package My::Config;
use strict; use warnings;

use Config::Std;
use FindBin qw($Bin);
use File::Spec::Functions qw( catfile );

sub new {
    my $class = shift;
    my ($config_file) = @_;

    $config_file = catfile($Bin, 'config.ini');
    read_config $config_file => my %config;

    my $object = bless \%config => $class;

    $object->gen_accessors(
        single => {
            install => [ qw( root ) ],
        },
        multi => {
            template => [ qw( dir ) ],
        },
    );

    return $object;
}

sub gen_accessors {
    my $config = shift;
    my %args = @_;

    my $class = ref $config;

    {
        no strict 'refs';
        for my $section ( keys %{ $args{single} } ) {
            my @vars = @{ $args{single}->{$section} };
            for my $var ( @vars ) {
                *{ "${class}::${section}_${var}" } = sub {
                    $config->{$section}{$var};
                };
            }
        }

        for my $section ( keys %{ $args{multi} } ) {
            my @vars = @{ $args{multi}->{$section} };
            for my $var ( @vars ) {
                *{ "${class}::${section}_${var}" } = sub {
                    my $val = $config->{$section}{$var};
                    return [ $val ] unless 'ARRAY' eq ref $val;
                    return $val;
                }
            }
        }
    }

    return;
}

package main;

use strict; use warnings;

my $config = My::Config->new;

use Data::Dumper;
print Dumper($config->install_root, $config->template_dir);
C:\Temp> cat config.ini
[install]
root = c:\opt

[template]
dir = C:\opt\app\tmpl
dir = C:\opt\common\tmpl

Output:

C:\Temp> g.pl
$VAR1 = 'c:\\opt';
$VAR2 = [
          'C:\\opt\\app\\tmpl',
          'C:\\opt\\common\\tmpl'
        ];
Sinan Ünür
+1 Very helpful -- thanks. Seems like there should be a module that would generate the methods like `template_dirs` for us. I stumbled across `Config::General`, notably its `-ExtendedAccess` option. Never used it, but it looks interesting.
FM
@FM I did not know about `Config::General`. It does look interesting but I find that sometimes general solutions can be a little too general. One can, of course, auto-generate accessors for scalar valued options and potentially multi-valued options separately.
Sinan Ünür
+1  A: 

At the risk of being laughed out of class, one solution is to store the config in XML (or for more adventurous, JSON). Human-consumable, interoperable outside of Perl, doesn't have to live on local PC (both XML and JSON can be requested off of a "config URL") and a bunch of standard modules (XML::Simple is usually good enough for config XML files) exist on CPAN.

DVK
A: 

Don't tie yourself to a format -- use Config::Any, or for a little more whizbang DWIM factor, Config::JFDI (which itself wraps Config::Any). With them you buy yourself the ability to support INI, YAML, XML, Apache-style config, and more.

Config::JFDI builds on this by trying to capture some of the magic of Catalyst's config loader: merging of instance-local config with app-wide config, environment variable support, and a limited macro facility (__path_to(foo/bar)__ comes in handy surprisingly often.)

hobbs
+1  A: 

For simple configuration like this, especially for trivial things where I don't expect this data to change in the real world, I often simply use YAML. The simplicity cannot be beat:

First, write your Perl data structure containing your configuration.

use YAML;

my $SETTINGS = {
    '1' => "foo",
    '2' => ["123", "456"],
    '3' => "something",
};

Then, pass it to YAML::DumpFile();

YAML::DumpFile("~/.$appname.yaml", $SETTINGS);

Delete the data structure and replace it with

my $SETTINGS = YAML::LoadFile("~/.$appname.yaml");

And then forget about it. Even if you don't know or want to learn YAML syntax, small changes to the config can be made by hand and more major ones can be done in Perl and then re-dumped to YAML.

Sorpigal