The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

CLI::Startup - Simple initialization for command-line scripts

VERSION

Version 0.08

DESCRIPTION

Good command-line scripts always support command-line options using Getopt::Long, and should support default configuration in a .rc file, such as Config::Simple supports. At minimum that should include a --help option that explains the other options. Supporting all this takes quite a bit of boilerplate code. In my experience, doing it right takes several hundred lines of code that are practically the same in every script.

CLI::Startup is intended to factor away almost all of that boilerplate. In the common case, all that's needed is a single hashref listing the options (using Getopt::Long syntax) as keys, and a bit of help text as values. CLI::Startup will automatically generate the command-line parsing, reading of an optional config file, merging of the two, and creation of a hash of the actual settings to be used for the current invocation. It automatically prints a usage message when it sees invalid options or the --help option. It automatically supports an option to save the current settings in an rc file. It supports a --version option that prints $::VERSION from the calling script, and a --manpage option that prints the formatted POD, if any, in the calling script. All the grunt work is handled for you.

CLI::Startup also supports additional options to disable any of those options except --help, which is always supported, and to specify default options. It slightly enhances Getopt::Long behavior by allowing repeatable options to be specified either with multiple options or with a commalist honoring CSV quoting conventions. It also enhances Config::Simple behavior by supporting options with hashes as values, and by unflattening the contents of INI files into a two-level hash.

For convenience, CLI::Support also supplies die() and warn() methods that prepend the name of the script and postpend a newline.

    use CLI::Startup;

    my $app = CLI::Startup->new({
        'infile=s'   => 'An option for specifying an input file',
        'outfile=s'  => 'An option for specifying an output file',
        'password=s' => 'A password to use for something',
        'email=s@'   => 'Some email addresses to notify of something',
        'verbose'    => 'Verbose output flag',
        'lines:i'    => 'Optional - the number of lines to process',
        'retries:5'  => 'Optional - number of retries; defaults to 5',
        ...
    });

    # Process the command line and resource file (if any)
    $app->init;

    # Information about the current invocation of the calling
    # script:
    my $opts = $app->get_raw_options;       # Actual command-line options
    my $conf = $app->get_config;            # Options set in config file
    my $dflt = $app->get_default_settings;  # Wired-in script defaults

    # Get the applicable options for the current invocation of
    # the script by combining, in order of decreasing precedence:
    # the actual command-line options; the options set in the
    # config file; and any wired-in script defaults.
    my $opts = $app->get_options;

    # Print messages to the user, with helpful formatting
    $app->die_usage();      # Print a --help message and exit
    $app->print_manpage();  # Print the formatted POD for this script and exit
    $app->print_version();  # Print version information for the calling script
    $app->warn();           # Format warnings nicely
    $app->die();            # Die with a nicely-formatted message

EXAMPLES

The following is a complete implementation of a wrapper for rsync. Since rsync doesn't support a config file, this wrapper provides that feature in 33 lines of code (according to sloccount). Fully 1/3 of the file is simply a list of the rsync command-line options in the definition of $optspec; the rest is just a small amount of glue for invoking rsync with the requested options.

  #!/usr/bin/perl

  use File::Rsync;
  use CLI::Startup;
  use List::Util qw{ reduce };
  use Hash::Merge qw{ merge };

  # All the rsync command-line options
  $optspec = {
      'archive!'     => 'Use archive mode--see manpage for rsync',
      ...
      'verbose+'     => 'Increase rsync verbosity',
  };

  # Default settings
  $defaults = {
      archive  => 1,
      compress => 1,
      rsh      => 'ssh',
  };

  # Parse command line and read config
  $app = CLI::Startup->new({
      usage            => '[options] [module ...]',
      options          => $optspec,
      default_settings => $defaults,
  });
  $app->init;

  # Now @ARGV is a list of INI-file groups: run rsync for
  # each one in turn.
  do {
      # Combine the following, in this order of precedence:
      # 1) The actual command-line options
      # 2) The INI-file group requested in $ARGV[0]
      # 3) The INI-file [default] group
      # 4) The wired-in app defaults

      $options = reduce { merge($a, $b) } (
          $app->get_raw_options,
          $config->{shift @ARGV} || {},
          $app->get_config->{default},
          $defaults,
      );

      my $rsync = File::Rsync->new($options);

      $rsync->exec({
          src    => delete $options->{src},
          dest   => delete $options->{dest},
      }) or $app->warn("Rsync failed for $source -> $dest: $!");

  } while @ARGV;

My personal version of the above script uses strict and warnings, and includes a complete manpage in POD. The POD takes up 246 lines, while the body of the script contains only 67 lines of code (again according to sloccount). In other words, 80% of the script is documentation.

CLI::Startup saved a ton of effort writing this, by abstracting away the boilerplate code for making the script behave like a normal command-line utility. It consists of approximately 425 lines of code (sloccount again), so the same script without CLI::Startup would have been more than seven times longer, and would either have taken many extra hours to write, or else would lack the features that this version supports.

EXPORT

If you really don't like object-oriented coding, or your needs are super-simple, CLI::Startup optionally exports a single sub: startup().

startup

  use CLI::Startup 'startup';

  my $options = startup({
    'opt1=s' => 'Option taking a string',
    'opt2:i' => 'Optional option taking an integer',
    ...
  });

Process command-line options specified in the argument hash. Automatically responds to to the --help option, or to invalid options, by printing a help message and exiting. Otherwise returns a hash (or hashref, depending on the calling context) of the options supplied on the command line. It also automatically checks for default options in a resource file named $HOME/.SCRIPTNAMErc and folds them into the returned hash.

If you want any fancy configuration, or you want to customize any behaviors, then you need to use the object-oriented interface.

ACCESSORS

get_config

  $config = $app->get_config;

Returns the contents of the resource file as a hashref. This attribute is read-only; it is set when the config file is read, which happens when $app-init()> is called.

It is a fatal error to call get_config() before init() is called.

get_default_settings

  $defaults = $app->get_default_settings;

Returns default settings as a hashref. Default settings are applied with lower precedence than the rcfile contents, which is in turn applied with lower precedence than command-line options.

set_default_settings

  $app->set_default_settings(\%settings);

Set the default settings for the command-line options.

It is a fatal error to call set_default_settings() after calling init().

get_initialized

  $app->init unless $app->get_initialized();

Read-only flag indicating whether the app is initialized. This is used internally; you probably shouldn't need it since you should only be calling $app-init()> once, near the start of your script.

get_options

  my $options = $app->get_options;

Read-only: the command options for the current invocation of the script. This includes the actual command-line options of the script, or the defaults found in the config file, if any, or the wired-in defaults from the script itself, in that order of precedence.

Usually, this information is all your script really cares about this. It doesn't care about $app-get_config> or $app-get_optspec> or any other building blocks that were used to ultimately build $app-get_options>.

It is a fatal error to call get_options() before calling init().

get_optspec

  my $optspec = $app->get_optspec();

Returns the hash of command-line options. See set_optspec for an example, and see Getopt::Long for the full syntax.

set_optspec

  $app->set_optspec({
    'file=s'  => 'File to read',    # Option with string argument
    'verbose' => 'Verbose output',  # Boolean option
    'tries=i' => 'Number of tries', # Option with integer argument
    ...
  });

Set the hash of command-line options. The keys use Getopt::Long syntax, and the values are descriptions for printing in the usage message.

It is an error to call set_optspec() after calling init().

get_raw_options

  $options = $app->get_raw_options;

Returns the options actually supplied on the command line--i.e., without adding in any defaults from the rcfile. Useful for checking which settings were actually requested, in cases where one option on the command line disables multiple options from the config file.

get_rcfile

  my $path = $app->get_rcfile;

Get the full path of the rcfile to read or write.

set_rcfile

  $app->set_rcfile( $path_to_rcfile );

Set the path to the rcfile to read or write. This overrides the build-in default of $HOME/.SCRIPTNAMErc, but is in turn overridden by the --rcfile option supported automatically by CLI::Startup.

It is an error to call set_rcfile() after calling init().

get_usage

  print "Usage: $0: " . $app->get_usage . "\n";

Returns the usage string printed as part of the --help output. Unlikely to be useful outside the module.

set_usage

  $app->set_usage("[options] FILE1 [FILE2 ...]");

Set a usage message for the script. Useful if the command options are followed by positional parameters; otherwise a default usage message is supplied automatically.

It is an error to call set_usage() after calling init().

set_write_rcfile

  $app->set_write_rcfile( \&rcfile_writing_sub );

A code reference for writing out the rc file, in case it has extra options needed by the app. Setting this to undef disables the --write-rcfile command-line option. This option is also disabled if reading rc files is disabled by setting the rcfile attribute to undef.

It is an error to call set_write_rcfile() after calling init().

SUBROUTINES/METHODS

die

  $app->die("die message");
  # Prints the following, for script "$BINDIR/foo":
  # foo: FATAL: die message

Die with a nicely-formatted message, identifying the script that died.

die_usage

  $app->die_usage if $something_wrong;

Print a help message and exit. This is called internally if the user supplies a --help option on the command-line.

init

  $app  = CLI::Startup->new( \%optspec );
  $app->init;
  $opts = $app->get_options;

Initialize command options by parsing the command line and merging in defaults from the rcfile, if any. This is where most of the work gets done. If you don't have any special needs, and want to use the Perl fourish interface, the startup() function basically does nothing more than the example code above.

new

  # Normal: accept defaults and specify only options
  my $app = CLI::Startup->new( \%options );

  # Advanced: override some CLI::Startup defaults
  my $app = CLI::Startup->new(
    rcfile       => $rcfile_path, # Set to false to disable rc files
    write_rcfile => \&write_sub,  # Set to false to disable writing
    optspec => \%options,
  );

Create a new CLI::Startup object to process the options defined in \%options.

BUILD

An internal method called by new().

  $app->print_manpage;

Prints the formatted POD contained in the calling script. If there's no POD content in the file, then the --help usage is printed instead.

  $app->print_version;

Prints the version of the calling script, if defined.

warn

  $app->warn("warning message");
  # Prints the following, for script "$BINDIR/foo":
  # foo: WARNING: warning message

Print a nicely-formatted warning message, identifying the script by name.

write_rcfile

  $app->write_rcfile();      # Overwrite the rc file for this script
  $app->write_rcfile($path); # Write an rc file to a new location

Write the current settings for this script to an rcfile--by default, the rcfile read for this script, but optionally a different file specified by the caller. The automatic --write-rcfile option always writes to the script specified in the --rcfile option.

It's a fatal error to call write_rcfile() before calling init().

AUTHOR

Len Budney, <len.budney at gmail.com>

BUGS

Please report any bugs or feature requests to bug-cli-startup at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CLI-Startup. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc CLI::Startup

You can also look for information at:

ACKNOWLEDGEMENTS

LICENSE AND COPYRIGHT

Copyright 2011 Len Budney.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.