NAME
CLI::Startup - Simple initialization for command-line scripts
VERSION
Version 0.06
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()
.
print_manpage
$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.
print_version
$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:
RT: CPAN's request tracker
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
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.