pileofrogs has asked for the wisdom of the Perl Monks concerning the following question:

I've just started playing around with AppConfig, and I love it!

As I'm working though, I realize there's a bit of a chicken-and-egg problem, and this isn't specific to AppConfig.

Say I want to use hard-coded default values, config file values and command line arg values. I want config file values to override default values and I want command line args to override both defaults and config file values.

The trick is, I want to specify the config file on the command line. That means I need to get the command line first even though it should be last. The obvious solution is to save the command line values somewhere and re-add them after adding the defaults and config file values.

Is there a better way to handle this?

-Thanks
Pileofrogs

Update:

Here's what I've come up with, and yes there is a default config file.

  1. Set values to default, including name of config file
  2. Read command line args and store somewhere
  3. If command line specifies config file, and config file does not exist, blow chunks. (If no config file is specified, and the default config file doesn't exist, that's OK.)
  4. If command line specifies config file (and we haven't blown chunks yet) set value of config file
  5. If config file exists, process and set values
  6. Set values from command line that we stored earlier
  • Comment on command line args - a chicken and egg problem

Replies are listed 'Best First'.
Re: command line args - a chicken and egg problem
by GrandFather (Saint) on Apr 12, 2006 at 01:24 UTC

    grep for the configuration file and if you find it, splice it out of @ARGV. Then process the configuration file followed by the command line processing.


    DWIM is Perl's answer to Gödel
Re: command line args - a chicken and egg problem
by Zaxo (Archbishop) on Apr 12, 2006 at 05:13 UTC

    If you can live with a modification of your command-line argument rules, you can make this all pretty simple.

    Rule 2b: The configuration file overrides command-line options occuring before it is specified, is overridden by those occuring after.

    With that, you only need to process the config file as soon as it is specified. To get the behavior you wanted, your user would just have to name the config file first.

    After Compline,
    Zaxo

      That works nicely unless you have a default config file. You can't process it as the zeroth argument, because it shouldn't be read at all if a config file option is given on the command line. And it can't be processed conditionally after the command-line args because then it would override the command line.

      I have run into this several times too, but I don't remember what I ended up with. Probably something different and slightly wrong each time. I guess you could do something like:

      my %opts = ( 'config' => 'default.config', 'other1' => 'default1', 'other2' => 'default2' ); %opts = (%opts, process_command_line()); %opts = (read_config_file($opts{config}), %opts);
      which might be better spelled
      my %defaults = ( 'other1' => 'default1', 'other2' => 'default2' ); my %from_cmdline = process_command_line(); my $config = $from_cmdline{config} || "default.config"; my %from_config = read_config_file($config); my %opts = (%defaults, %from_config, %from_cmdline);
      I wouldn't use either, because I always use Getopt::Long. So perhaps:
      my %defaults = ( param1 => 'default1', param2 => 'default2' ); my $config = 'default.config'; GetOptions("config|c=s" => \$config, "param1=s" => \$opt{param1}, "param2=s" => \$opt{param2}); %defaults = (%defaults, read_config_file($config)); while (my ($param, $value) = each %defaults) { $opt{$param} = $value unless defined $opt{$param}; }
      which doesn't feel very satisfying. Maybe it would be better to use Zaxo's rule 2b and fold them together?
      my %opts = ( param1 => 'default1', param2 => 'default2' ); my $use_default_config = 1; GetOptions("config|c=s" => sub { my ($param, $value) = @_; $use_default_config = 0; read_config_file(\%opt, $value); }, "param1=s" => \$opt{param1}, "param2=s" => \$opt{param2}); read_config_file(\%opt, 'default.config') if $use_default_config;
      Feels about right.

      Update: I just read through the POD for AppConfig. I agree; it sounds really nice! But it looks like it doesn't expose enough to implement what you want. Seems like a good opportunity to submit a patch.

Re: command line args - a chicken and egg problem
by jkeenan1 (Deacon) on Apr 12, 2006 at 01:40 UTC
    I faced this problem when I was revising modulemaker, the command-line utility that comes with ExtUtils::ModuleMaker. There are default values that come with the module; the user can save personal default values to a file which will override the original default values; and the user can then supply values via command-line options or the interactive mode.

    It was quite challenging to figure it all out, and I haven't had to pay attention to it for several months, so I can't recite how it goes off the top of my head. But you're welcome to poke around in the source and see if it gives you some leads.

    Jim Keenan
Re: command line args - a chicken and egg problem
by Helter (Chaplain) on Apr 13, 2006 at 15:12 UTC
    My typical command line parsing goes something like this:
    use vars qw/ $opt_help $opt_one $opt_two $opt_config /; sub parseCommandLine(); parseCommandLine(); ...... ...... sub parseCommandLine() { &getOptions( 'help|h', 'config|c=s', 'one', 'two=s' ) or $opt_help += 1; if( defined( $opt_help ) ) { &printUsage(); exit(0); } if( defined( $opt_config ) ) { &parse_config( $opt_config ); } if( defined( $opt_one )) { # do extra setup required for option 1 } else { $opt_one = 'default_opt_one'; } unless( defined( $opt_two ) ) { $opt_two = 'default_opt_two'; } # Do stuff needed for option 2. } sub parse_config( ) { my $config_file = shift(); # read file # use file to set $opt_one, $opt_two }
    Hope this helps...Untested code of course :)