NAME
CLI::Startup - Simple initialization for command-line scripts
VERSION
Version 0.22
SYNOPSIS
CLI::Startup
can export a single method, startup()
, into the caller's namespace. It transparently handles config files, defaults, and command-line options.
use CLI::Startup 'startup';
# Returns the merged results of defaults, config file
# and command-line options.
my $options = startup({
'opt1=s' => 'Option taking a string',
'opt2:i' => 'Optional option taking an integer',
...
});
It also supports an object-oriented interface with much more scope for customization. The basic usage looks like this:
use CLI::Startup;
# Parse command line and read config
$app = CLI::Startup->new({
usage => '[options] [other args ...]', # Optional
options => $optspec,
default_settings => $defaults,
});
$app->init;
# Combined command line, config file and default options. Almost
# always what you want.
$opts = $app->get_options;
# Information about the current invocation of the calling script:
$opts = $app->get_raw_options; # Actual command-line options
$conf = $app->get_config; # Options set in config file
$dflt = $app->get_default_settings; # Wired-in script defaults
Most scripts can then use $opts
for all their customization needs.
You can also hide extra data in the config file, and access it through $app-
get_config>. The app settings will be stored in a section of the config file named "default," so the rest of the file is yours to do with as you wish. See the example implemetation of an rsync
wrapper, below, for one use of this.
DESCRIPTION
Good command-line scripts always support command-line options using Getopt::Long, and should support default configuration in a file in a standard format like YAML, JSON, XML, INI, etc. At minimum it 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.
Any of these auto-generated options, except --help
, can be disabled by including the option's name in the hashref with a false value in place of the help text.
An additional hashref can be passed with default values for the various options.
CLI::Startup
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 INI file parsing to support hash-valued options of the form:
[default]
hash=a=1, b=2, c=3
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',
'map=s%' => 'Some name/value pairs mapping something to something',
'x|y=i' => 'Integer --x, could also be called --y',
'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
DEFAULT OPTIONS
Here is a help message printed by CLI::Startup
for a script that defines no command-line options:
usage: test [options]
Options:
--help - Print this help message
Aliases: -h
--manpage - Print the manpage for this script
Aliases: -H
--rcfile - Config file to load
--rcfile-format - Format to write the config file
--verbose - Print verbose messages
Aliases: -v
--version - Print version information and exit
Aliases: -V
--write-rcfile - Write the current options to config file
The options work as follows:
- --help | -h
-
Prints a help message like the one above. The message is dynamically created using the
Getopt::Long
-style optspec and the help text. It automatically identifies negatable options, and lists aliases for options that have any. - --manpage | -H
-
This option is a poor-man's version of the
--more-help
option some scripts provide: when the--manpage
option is used,CLI::Startup
callsperldoc
on the script itself. - --rcfile
-
This option lets the user specify the .rc file from which to load saved arguments. Any options in that file will override the app defaults, and will in turn be overridden by any command-line options specified along with
--rcfile
. - --rcfile-format
-
Allows the user to specify the formay of the .rc file when saving a new one using the
--write-rcfile
option. Valid values are: INI, XML, JSON, YAML, PERL. The value is not case sensitive. - --verbose | -v
-
This option lets the user request verbose output. There's a certain amount of extra magic behind this "option," in order to support the prevailing paradigms:
If you specify
--verbose
, thenget_options()
will return a hash with its 'verbose' key set to 1.If you specify
--verbose=N
or--verbose N
, the 'verbose' key returned byget_options()
will have the value N.If you specify
-vvv
or-v -v
, etc., the 'verbose' key will have a value equal to the number of 'v' options that were specified.If you use a mixture of
-v
and--verbose
options, the total values will be added up.The point of this is to support
-vvv
,--verbose
, and--verbose=5
style options. It allows the user to perform pathological combinations of them all, but the end result should do what the user meant. When generating verbose output, you can check the value ofget_options()->{verbose}
and print if it's non-zero, or print if it exceeds some threshold. - --version | -V
-
Note the capital
-V
, as distinct from the lowercase-v
that is the alias of--verbose
. This option prints the version, as found in$::VERSION
, along with the path to the script and the Perl version. - --write-rcfile
-
This option takes the fully-processed options, including the command line, any .rc file options, and the app defaults, and writes the results back to the specified .rc file. This can be used to save and reuse invocations: build up the command line that you want, and then tack on the
--write-rcfile
option to save it. Next time you run the script with no options (or with--rcfile PATH
if the .rc file isn't the default one), it will use the saved options.
COMMAND LINE PROCESSING
CLI::Startup
ultimately calls Getopt::Long::GetOptions()
, so processing works almost exactly like you're used to. The only major difference is in the handling of array and hash argument types.
When the option spec is something like 'email=s@', that means the --email
option can be specified multiple times. The arguments are then collected in an array, so $app-
get_options->{email}> will be an array reference. In addition, though, CLI::Startup
will step through each of the arguments and parse them as CSV. As a result, the following are equivalent:
foo --bar=baz --bar=qux --bar=quux
foo --bar=baz,qux --bar=quux
foo --bar=baz,qux,quux
Similarly, when the option spec is something like 'map=s%', that means that the --map
option is hash-valued, and $app-
get_options->{map}> will be a hash reference. The name/value pairs are delimited by an equals sign, and either separated by commas or given in separate repetitions of the option, like so:
foo --bar=baz=1 --bar=qux=2
foo --bar=baz=1,qux=2
# etc...
If you're running the foo
script, in this case, you probably want to omit the equals sign between the option and its argument, to reduce confusion, like so:
foo --bar baz=1,qux=2
Also note that Getopt::Long
is configured with the settings posix_default
, gnu_compat
, and bundling
. That means most confusing options are turned off, like specifying options first, and their arguments in a big list at the end, or other things that you hopefully don't want to do anyway. Specifically,
Abbreviated versions of option names are not recognized.
Options can't be started with a '+' in lieu of '--'.
You can use
--opt=
to specify empty options.Options must be immediately followed by their arguments.
Single-character options can be combined:
-las
for-l -a -s
.Option names are case insensitive.
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: $OS_ERROR");
} 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';
# Simple version--almost a drop-in replacement for
# Getopt::Long:
my $options = startup({
'opt1=s' => 'Option taking a string',
'opt2:i' => 'Optional option taking an integer',
...
});
# More complicated version, using more CLI::Startup
# features.
my $options = startup({
usage => '[options] [module ...]',
options => $optspec,
default_settings => $defaults,
});
Process command-line options specified in the argument hashref. The argument is passed to CLI::Startup-
new>, so anything valid there is valid here.
Scripts using this function automatically respond to to the --help
option, or to invalid options, by printing a help message and exiting. They can also write a config file and exit, or any of the other default features provided by CLI::Startup
.
If it doesn't exit, it 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
The following methods are available when the object-oriented interface is used.
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. The referece returned by this sub is to a clone of the settings, so although its contents can be modified, it will have no effect on the $app
object.
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. 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. Keys are option specifications in the Getopt::Long
syntax, and values are the short descriptions to be printed in a usage summary. See set_optspec
for an example, and see Getopt::Long
for the full syntax.
Note that this optspec does include the default options supplied by CLI::Startup
.
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, to be printed in the usage message. CLI::Startup
will automatically add the default options to the optspec you supply, so get_optspec()
will generally not return the same structure spec that you gave to get_optspec()
.
It is a fatal 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 built-in default of $HOME/.SCRIPTNAMErc
, but is in turn overridden by the --rcfile
option supported automatically by CLI::Startup
.
It is a fatal 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 string for the script. Useful if the command options are followed by positional parameters; otherwise a default usage message is supplied automatically.
It is a fatal error to call set_usage()
after calling init()
.
set_write_rcfile
$app->set_write_rcfile( \&rcfile_writing_sub );
sub rcfile_writing_sub
{
($app, $filename) = @_;
$config_data = $app->get_options_as_defaults;
# Do stuff with $config_data, and write it to
# $filename in the desired format.
}
A code reference for writing out the rc file, in case the default file formats aren't good enough. Setting this to undef
disables the command-line options --write-rcfile
and --rcfile-format
. Those options are also disabled if reading rc files is disabled by setting the rcfile
attribute to anything that evaluates to false.
For now, your writer will have to write in one of the formats supported by CLI::Startup
, so this feature is mostly useful for providing prettier output, with things like nice formatting and helpful explanatory comments.
It is a fatal error to call set_write_rcfile()
after calling init()
.
SUBROUTINES/METHODS
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
options => \%options,
defaults => \%defaults,
});
Create a new CLI::Startup
object to process the options defined in \%options
. Options not specified on the command line or in a config file will be set to the value contained in \%defaults
, if any.
Setting the rcfile
option to a false value disables the --rcfile
option, which in turn prevents the script from reading config files.
Setting the write_rcfile
option to a false value disables writing config files with the --write-rcfile
option, but does not disable reading config files created some other way.
BUILD
An internal method called by new()
.
DEMOLISH
An internal method, called when an object goes out of scope.
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.
After init()
is called, most of the write accessors will throw a fatal exception. It's not quite true that this object becomes read-only, but you can think of it that way: the object has done its heavy lifting, and now exists mostly to answer questions about the app's configuration.
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. Identical to calling CORE::warn
, except for the formatting.
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. This is exactly the same as calling CORE::die
, except for the standardized formatting of the message. It also suppresses any backtrace, by postpending a newline to your message.
die_usage
$app->die_usage if $something_wrong;
$app->die_usage($message);
Print a help message and exit. This is called internally if the user supplies a --help
option on the command-line. If the optional $message
is specified, then precedes the usage message with something like:
program_name: FATAL: $message
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 the variable $VERSION
is defined in the top-level scope.
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.
The file format can be specified by the --rcfile-format
option, and must be one of: ini, yaml, json, xml, and perl. By default this method will attempt to use .ini file format, because it's the simplest and most readable for most option specification needs. If the module Config::INI::Writer
isn't installed, it will fall back on perl format, which looks like the output of Data::Dumper
.
The prettiest formats are ini, yaml, and perl. The others will tend to be harder to read.
The simplest format for users is ini. It's good enough, if you don't have complicated command-line options, or additional data hidden in your config files.
The most powerful formats are json and yaml, of which yaml is the most readable. It will let you put pretty much any data structure you desire in your config files.
It's a fatal error to call write_rcfile()
before calling init()
.
get_options_as_defaults
$options = $app->get_options_as_defaults;
Returns the same hashref as $app-
get_config> would do, except that $options-
{default}> is overridden with the current settings of the app. This is a helper method if you write a function to write config files in some format not natively supported by CLI::Startup
. It lets you freeze the state of the current command line as the default for future runs.
This sub will also strip out any of the auto-generated options, like --help
and --rcfile
, since they don't belong in a config file.
AUTHOR
Len Budney, <len.budney at gmail.com>
BUGS AND LIMITATIONS
CLI::Startup
tries reasonably to keep things consistent, but it doesn't stop you from shooting yourself in the foot if you try at all hard. For example: it will let you specify defaults for nonexistent options; it will let you use a hashref as the default value for a boolean option; etc..
For now, you should also not define aliases for default options. If you try to define an option like 'help|?', expecting to get --help
and --?
as synonyms, something will break. Basically, CLI::Startup
isn't quite smart enough to recognize that your help option is a suitable replacement for the builtin one.
You can delete default options by supplying the primary name of the option with a help-text value that evaluates to false. CLI::Startup
will delete that option from the defaults before merging with your options. If you try that with --help
, though, CLI::Startup
will put that option back in. Its main use is to disable .rc file handing.
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-2017 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.