Getopt modules 13: App::Options

About this mini-article series. Each day for 24 days, I will be reviewing a module that parses command-line options (such module is usually under the Getopt::* namespace). First article is here.

In the previous article, I reviewed Getopt::ArgvFile which enables you to read options from config file. There's another module which used to be my favorite when it comes to reading options from command-line as well as config files and environment: App::Options.

App::Options is a module by Stephen Adkins (SPADKINS). The first release is in 2004 and the latest release as of this writing is version 1.12 in 2010. Unlike Getopt::ArgvFile which must be combined with Getopt::Long, App::Options does it all alone.

Modules like App::Options are terribly convenient. You just need one specification of list of options you want to accept and the module will parse the values from various sources, in specific order like you would expect in a typical Unix program. You are then presented with the final result in a hash. In the case of App::Options, the final hash is %App::options.

Here's an example of how to use App::Options, taken from one of my scripts called phpbb3-post:

use App::Options (
    option => {
        base_url => { type => 'string', required => 1, description => 'Address of phpBB3 site'},
        username => { type => 'string', required => 1, description => 'Username to login to phpBB3 site'},
        password => { type => 'string', required => 1, description => 'Password to login to phpBB3 site'},

        forum_id => { type => 'int', required => 1, },
        topic_id => { type => 'int', required => 0, },
        delay => { type => 'int', required => 0, default => 4, description => 'Number of seconds to wait between posting'},
        bbcode => { type => 'bool', required => 0, default => 0, description => 'Whether to interpret BBCode'},
        log_level => { type => 'string', required => 0, default => 'DEBUG' },
        obfuscate_link => { type => 'bool', required => 0, default => 0, },
    },
);

If you run the script:

% phpbb3-post –help
Error: "base_url" is a required option but is not defined
Error: "forum_id" is a required option but is not defined
Error: "password" is a required option but is not defined
Error: "username" is a required option but is not defined
Usage: phpbb3-post [options] [args]
–help print this message (also -?)
–base_url=<value> [undef] (string) Address of phpBB3 site
–bbcode=<value> [0] (bool) Whether to interpret BBCode
–delay=<value> [4] (int) Number of seconds to wait between posting
–forum_id=<value> [undef] (int)
–log_level=<value> [DEBUG] (string)
–obfuscate_link=<value> [0] (bool)
–password=<value> [undef] (string) Password to login to phpBB3 site
–topic_id=<value> [undef] (int)
–username=<value> [undef] (string) Username to login to phpBB3 site

The values of the options can be specified directly from the command-line, e.g.:

% phpbb3-post –base_url=https://example.com/forum/ –username=foo –password=secret \
–forum_id=10 < post.txt

or, some of them can be stored in a configuration file (like password, which is not apt to be specified via command-line). App::Options searches config files in several locations, from per-user .app directory ($HOME/.app/PROG_NAME.conf, $HOME/.app/app.conf), to program directory ($PROG_DIR/PROG_NAME.conf, $PROG_DIR/app.conf), until global directory /etc/app/app.conf. The location of configuration file can be changed via –option_file command-line option or disabled via –no_option_file.

The configuration file is INI-like but with some differences. There is a concept of config profiles to let you store multiple sets of options in a single file which can be selected via –profile. For example:

[profile=site1]
site=https://SITE1/
username=USER1
password=PASS1

[profile=site2]
site=https://SITE2/
username=USER2
password=PASS2

However, the module has its own peculiarities that over the years finally made me develop my own solution to replace it.

First of all, it encourages putting the specification in the use statement at compile-time phase, which interferes with perl -c. You suddenly cannot check your script simply with perl -c YOURSCRIPT anymore, as options checking is still done and you'll still need to provide all the required options or supply a config file. This can be remedied by changing the code from:

use App::Options (option => { ... });

into:

use App::Options ();
App::Options->import(option => { ... });

which delays the option parsing to the runtime phase. This is not documented though (instead the documentation seems to be out-of-date and mentions the init() method which no longer exists in the code).

Second, you need to use this syntax to provide an option value on the command-line:

–name=VALUE

The more common syntax:

–name VALUE

is not accepted and the error message is not clear when you do this.

The third is more minor and purely personal preference: I prefer the option like foo_bar to become –foo-bar on the command-line (that is, the underscores become dashes). Or at least support both –foo-bar and –foo_bar. App::Options only supports the later.

The fourth: I don't like the order that App::Options searches configuration files. Since I deploy applications as Perl distributions, I'd much prefer the config files to be in the usual $HOME/.config/ or $HOME or finally /etc. I'd hate my applications to become "special" or "different" and need to have their configs put in $HOME/.app or /etc/app. The ordering is currently fixed and cannot be customized.

My Perinci::CmdLine steals a few ideas from App::Options, mainly the INI-like configuration format and the concept of config profiles. But with all my itches scratched. You can also try Smart::Options (to be reviewed later) which also supports config files, plus subcommands.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s