Dave Baird
Dave Baird

Reputation: 163

How to elegantly combine Getopt::Long with a config file when the config file can be specified on the command line

I want to process script options with these rules:

  1. Options can be provided on the command line or in a config file or both
  2. Command line processing is done with Getopt::Long
  3. Options on the command line take precedence over those in the config file
  4. The config file can be specified on the command line
  5. Ideally, only using core modules

Without requirement 4, I can just process the config file first, then the command line, allowing the later options to overwrite the earlier. Elegant.

But if the config file is specified on the command line, I need to throw it all away and start again, which makes everything suddenly messy and complicated.

Is there an elegant perl idiom for this?

Upvotes: 1

Views: 257

Answers (1)

Marty
Marty

Reputation: 2808

Rule 5 complicates things... However, modules loaded via -M on the command line are loaded before modules mentioned in your script - including Getopt::Long. Therefore, we can rig something up where the default config is stored in a short, custom module;

# DefaultConf.pm
use strict;
use warnings;

while (<DATA>) {
    chomp ;                              # Remove the newline
    s/ \s* \#.* //x ;                    # Strip comments
    next if / ^ \s* $ /x ;               # Ignore blank lines
    my @a = split(" ", $_, 2);           # Break into 2 pieces and unshift
    unshift(@ARGV, $_) for reverse @a ;  # onto ARGV in reverse order
}

# print "===\n", join(" ", @ARGV), "\n", "===\n";  # debug

1;
__DATA__

# Default App  config data here
--verbose
-n  # single old-style switch with comment

# another comment
--files file1.txt, file2.txt

Text after __DATA__ is ignored by perl but is available to the programmer via the DATA filehandle. Here we can store our default config for the script with one restriction - only one option (with arguments) per line. Comments can be used either on the end of an option specification or for a whole line using the perl comment character, '#'.

This is used from the command line like so:

perl -MDefaultConf my_app.pl --verbose=0 --files file12.txt --log

my_app.pl loads Getopt::Long as normal but as our custom module is loaded via -M on the command line, it is loaded first. As a result, we can fiddle with @ARGV before Getopt::Long gets its greedy little hands on it. Thereafter, the actual option processing is done by Getopt::Long, so the option syntax at the end of our custom module is exactly the same.

Hopefully the inline comments make the code self explanatory - the only trickery is when there are two "pieces" (say, an option and its single argument), we need to push them onto the front of @ARGV (unshift - not push) in reverse order so that if the same option is mentioned on the command line, it is processed latter and therefore takes precedence.

Using this method you could have numerous config files for different "run situations" selected via the -M option or, as requested, you can run without any of them using CLI for options, or a combination where the CLI options take precedence.

One final comment about rule 5 - only core modules. Because of this restriction, you have to use this (or similar) module - which is NOT a core module. And although its small and easy to understand, it has no tests and it hasn't been deployed in hundreds of places as say, Config::Tiny has. Config::Tiny is under 100 lines long (excluding blanks and comments) and can be fully understood in about an hour. Of course, everyone's environment and constraints are different - it's just something to consider.

Upvotes: 1

Related Questions