NAME
App::Easer::V2 - Simplify writing (hierarchical) CLI applications
VERSION
This document describes App::Easer::V2 version 2.012.
SYNOPSIS
#!/usr/bin/env perl
use v5.24;
use experimental 'signatures';
use App::Easer V2 => 'run';
my $app = {
aliases => ['foo'],
help => 'this is the main app',
description => 'Yes, this really is the main app',
options => [
{
name => 'foo',
help => 'option foo!',
getopt => 'foo|f=s',
environment => 'FOO',
default => 'bar',
},
],
execute => sub ($instance) {
my $foo = $instance->config('foo');
say "Hello, $foo!";
return 0;
},
default_child => '-self', # run execute by default
children => [
{
aliases => ['bar'],
help => 'this is a sub-command',
description => 'Yes, this is a sub-command',
execute => sub { 'Peace!' },
},
],
};
exit run($app, $0, @ARGV);
Call examples:
$ ./example.pl
Hello, bar!
$ ./example.pl --foo World
Hello, World!
$ ./example.pl commands
sub-commands for ./example.pl
bar: this is a sub-command
help: print a help command
commands: list sub-commands
$ ./example.pl help
this is the main app
Description:
Yes, this really is the main app
Can be called as: foo
Options:
foo: option foo!
command-line: mandatory string option
--foo <value>
-f <value>
environment: FOO
default: bar
Sub-commands:
bar: this is a sub-command
help: print a help command
commands: list sub-commands
$ ./example.pl help help
print a help command
Description:
Print help for (sub)command
Can be called as: help
This command has no option
No sub-commands
$ ./example.pl help commands
list sub-commands
Description:
Print list of supported sub-commands
Can be called as: commands
This command has no option
No sub-commands
$ ./example.pl inexistent
cannot find sub-command 'inexistent'
$ ./example.pl help inexistent
cannot find sub-command 'inexistent'
DESCRIPTION
NOTE: THIS DOCUMENT HAS TO BE REVIEWED TO MAKE IT EXPLICIT THAT IT REFERS TO VERSION 2 OF THE API.
App::Easer::V2 provides the scaffolding for implementing hierarchical command-line applications in a very fast way. This is Version 2 of the provided API, which does everything described below.
It makes it extremely simple to generate an application based on specific interfacing options, while still leaving ample capabilities for customising the shape of the application. As such, it aims at making simple things easy and complex things possible, in pure Perl spirit.
There are multiple ways to define an application. The two extremes are:
a hash reference with all the needed elements inside (see "SYNOPSIS" for an example)
a sub-class of
App::Easer::V2::Command
with methods overriding the needed elements where the programmer sees fit.
App::Easer::V2 allows also to leverage both aspects at the same time for the same command, e.g. setting some parts through a hash reference and other parts with overriding methods.
Top-Level Functions
The following functions are exported by App::Easer::V2
:
appeaser_api
my $api_version = appeaser_api();
Return string
V2
.d
d($whatever);
Sends
$whatever
throughdd
and then towarn
.dd
my $dumped = dd($whatever);
Sends
$whatever
through Data::Dumper and returns it.run
my $exit_code = run($cmd_hashref, $0, @ARGV);
Run a command with the provided parameters (first the name of the command, using
$0
, then the command-line arguments).
In addition to importing the functions above, the following keys are also supported when use
ing the module:
-command
The package from where
App::Easer::V2
isuse
d is a command and inherits fromApp::Easer::V2::Command
, as well as registering as a command. This is the combined effect of-inherit
and-register
below.-inherit
The package from where
App::Easer::V2
isuse
d inherits fromApp::Easer::V2::Command
. Usually-command
is the right one though.-register
Register this package as a valid (sub)command.
-spec
Provide a specification hash reference to set many attributes of the specific command. This hash reference is saved into the package's variable
app_easer_spec
.
The options above allow easily define a new class for a command like this:
use App::Easer::V2 -command => -spec => { ... };
Anatomy of a run
Running an application can be done in several ways, depending on how the application is represented:
# application defined as a hashref or arrayref
use App::Easer V2 => 'run';
exit run($app, $0, @ARGS);
# application defined in a class
use MyApp;
exit MyApp->new->run($0, @ARGS);
When an application is run, the following high level algorithm is applied:
Options are gathered from different sources, like e.g. the command-line, environment variables, parent commands (in lower levels of the command hierarchy), files, or defaults.
A commit hook is called, if present, allowing an intermediate command to perform some actions before a sub-command is run. This hook has been anticipated before validation in V2.
A validation hook is called, if present, allowing check of the provided configuration, e.g. to spot incompatible options.
A sub-command is searched if possible and, if present, the process restarts from the first bullet above with this sub-command.
When the final sub-command is reached, the execute hook is run.
Application High Level View
The following list provides all keys/method names that are recognized in the definition of an application with App::Easer::V2
. Items markes as Executable
can be provided as sub references or with overriding the corresponding method in the derived class.
aliases
-
Array of strings.
The array contains alias names for the command. The first alias is also considered the
name
in case aname
is missing. allow_residual_options
-
Boolean, defaults to false.
Signal that the parsing of the command line must not fail if residual, unrecognized options are found. This allows e.g. building wrappers around external commands without the need to re-implement all of their options.
auto_environment
-
Boolean, defaults to false.
Signal that all options without an
environment
key should get one set to value 1, triggering auto-generation of the environment variable name. children
-
Array of items.
Provides a list of children for the command. Children can be provided as hash references (whose structure is the same as anytyhing supported by
hashy_class
), array references (whose first item is the name of a class and the rest is provided to itsnew
method), a string (which leads to a class name that can be instantiated without parameters), or a blessed object.Anything that does not eventually produce a
App::Easer::V2::Command
should be considered a liability. children_prefixes
-
Array of strings. Defaults to
[$pkg . '::Cmd']
.Each string in the array is used as a prefix to look for sub-commands in
@INC
.Assuming the main command is implemented in class
MyApp
, the default value looks for classes namedMyApp::Cmd*
, e.g.MyApp::CmdFoo
andMyApp::CmdBar
and ignores classed namedMyApp::Common
orMyApp::Utils
. commit
-
Executable with signature:
sub commit_function ($app) { ... }
where
$app
is the blessed object for the application.Perform the commit hook for the application, which is called immediately after the collection of options for the command and before looking further down for sub-commands or calling the
execute
method. Every weird action like fiddling with the command-line arguments etc. should be done here. default_child
-
String. Defaults to
help
.The name of a sub-command to invoke if no non-option values are set on the command line for a non-leaf command.
If set to
-self
, lack of an explicit child name on the command line means that theexecute
of the command itself should be run.See also
fallback_to
. description
-
String.
A verbose form of
help
, usually providing details about how the command works. This is used when generating the whole help for the command. environment_prefix
-
String.
A prefix that is applied to an options's name to derive an environment variable name. Used when
auto_environment
is true and noenvironment
key is present in the option's specification, or when theenvironment
key points to value1
. execute
-
Executable.
fallback_to
-
String.
The name of a sub-command to invoke if the first non-option command-line argument is not recognized as a children command for non-leaf commands.
If set to
-self
, lack of a valid child name on the command line means that theexecute
of the command itself should be run.If set to
-default
, thedefault_child
is used.If set to a non-negative integer n, the n-th child is used from the list of children.
If set to a string, the corresponding child is used.
Otherwise, an exception is raised.
See also
default_child
. force_auto_children
-
Boolean. Defaults to false.
Force the generation of the so-called auto-children even for leaf commands (i.e. commands without sub-commands either in
children
or from the class hierarchy).Auto-children are the
help
,commands
, andtree
sub-commands.This can come handy when
default_child
/fallback_to
are set to-self
and the three wordshelp
,commands
, andtree
are not possible valid values as arguments for the leaf command itself (e.g. when it does not use non-option command line arguments). getopt_config
-
Array of strings. See below for defaults.
A list of strings to use to configure
Getopt::Long
for the specific command.By default it is set to
gnu_getopt
. If a command is not a leaf,require_order
andpass_through
are added too, although the latter only temporarily to allow for chaining (unrecognized options might still trigger an exception). Ifallow_residual_options
is true,pass_through
is added. hashy_class
-
String. Defaults to
App::Easer::V2::Command
.The class name to use for inflating applications defined as hash references into objects.
Anything different from the default is a liability.
help
-
A concise form of help, ideally a single line to explain what the (sub-)command does. It is used when generating the whole help for the command, as well as the list of sub-commands of the help for the parent command.
help_channel
-
String. Defaults to
-STDOUT:encoding(UTF-8)
.Set the output channel for automatic commands
help
,commands
, andtree
.It can be set to:
a sub reference, which will be called with the following signature:
sub channel ($cmd_object, @stuff_to_print);
a filehandle, used to print out stuff
a reference to a scalar, where the output will be placed
a string of the form
filename[:binmode]
, wherefilename
can NOT contain the character:
. The file will be opened and if thebinmode
part is provided,binmode()
will be called on the resulting filehandle with the provided value.If the
filename
part is-
or-stdout
(case insensitive), thenSTDOUT
will be used. Iffilename
is-stderr
(case insensitive), thenSTDERR
will be used.
name
-
String.
Name of the command. If absent, the first item in the
alias
array is used. options
-
Array of items.
See "OPTIONS".
params_validate
-
Hash reference or
undef
. Ignored if "validate" is set.If passed as a hash reference, two keys are supported:
sources
-
Array of items.
See "OPTIONS".
validate
-
Sub reference for performing validation. Will be called during the validation phase and passed the command object instance:
$validation_sub->($self);
If set, "params_validate" is ignored.
The following YAML representation gives an overview of the elements that define an application managed by App::Easer::V2
, highlighting the necessary or strongly suggested ones at the beginning:
aliases: «array of strings»
execute: «executable»
help: «string»
options: «array of hashes»
allow_residual_options: «boolean»
auto_environment: «boolean»
children: «array of hashes»
children_prefixes: «array of strings»
commit: «executable»
default_child: «string»
description: «string»
environment_prefix: «string»
fallback_to: «string»
force_auto_children: «boolean»
hashy_class: «string»
help_channel: «string»
name: «string»
params_validate: «hash»
sources: «array of items»
validate: «executable»
As anticipated, it's entirely up to the user to decide what style is best, i.e. define applications through metadata only, through object-oriented derivation, or through a mix of the two. The following examples are aimed at producing the same application:
# metadata (mostly)
my $app_as_metadata = {
aliases => [qw< this that >],
help => 'this is the application, but also that',
options => [ { getopt => 'foo|f=s', default => 'bar' } ],
execute => sub ($app) {
say 'foo is ', $app->config('foo');
return 0;
},
};
# class only
package ThisThatApp;
use App::Easer::V2 '-command';
sub aliases ($self) { return [qw< this that >] }
sub help ($self) { return 'this is the application, but also that' }
sub options ($self) { [ { getopt => 'foo|f=s', default => 'bar' } ] }
sub execute ($self) {
say 'foo is ', $self->config('foo');
return 0;
}
# mixed style
package ThisThatMixedApp;
use App::Easer::V2 -command => -spec => {
aliases => [qw< this that >],
help => 'this is the application, but also that',
options => [ { getopt => 'foo|f=s', default => 'bar' } ],
};
sub execute ($self) {
say 'foo is ', $self->config('foo');
return 0;
}
The last style allows keeping data mostly as data, while leaving the freedom to implement the logic as proper methods, which can be beneficial for e.g. sharing common logic among several commands.
App::Easer::V2::Command METHODS
When a command is created, it is (usually) an instance of class App::Easer::V2::Command
or a descendant. As such, it has the methods explained in the following list, which at the moment appear as public ones although some might be hidden in the future (stable ones are expressely marked so).
auto_children
-
my @classes = $self->auto_children; my @objects = $self->auto_children(1);
Return a list of the automatic children (
help
,commands
, andtree
) as inflatable children, i.e. as fully qualified class names. If an optional true value is passed, children are inflated into objects. auto_commands
-
my $instance = $self->auto_commands;
Returns an instance representing the
commands
sub-command. auto_help
-
my $instance = $self->auto_help;
Returns an instance representing the
help
sub-command. auto_tree
-
my $instance = $self->auto_tree;
Returns an instance representing the
tree
sub-command. aliases
-
my @aliases = $self->aliases; my @modified = $self->aliases(\@new_aliases);
Gets/sets the aliases. If not set,
name
is used. allow_residual_options
-
my $boolean = $self->allow_residual_options; $self->allow_residual_options(0); # disable $self->allow_residual_options(1); # enable
auto_environment
-
my $boolean = $self->auto_environment; $self->auto_environment(0); # disable $self->auto_environment(1); # enable
call_name
-
my $string = $self->call_name; $self->call_name('override, for no reason apparently...');
Returns a string with the name used to call the sub command (useful to figure out which of the
aliases
was used).The method can also be used to set this name, if needed. This is not guaranteed to be future-proof.
children
-
my @direct_children = $self->children; $self->children(\@new_direct_children);
Get/set the list of direct children. Other children might be collected automatically from the class hierarchy.
Use of this method is discouraged and not future-proof.
children_prefixes
-
my $aref = $self->children_prefixes; $self->children_prefixes(\@new_prefixes);
collect
-
$self->collect;
Collect options values from the configured sources, see "OPTIONS".
Not assumed to be used directly. Overloading is a liability.
commit
-
$self->commit;
Commit the collected options.
Not assumed to be used directly.
It can be overloaded to provide a custom behaviour, which can e.g. modify
residual_args
orconfig_hash
to drive following steps of looking for a sub-command. config
-
my $value = $self->config($key); # returns a scalar anyway my @values = $self->config(@keys);
Retrieve collected option values.
config_hash
-
my $merged_hash = $self->config_hash; my $detailed = $self->config_hash(1);
Get the collected option values as a hash.
In the basic case, a merged hash is returned, with values taken from sources according to their priorities.
In the advanced case where a true value is passed as optional argument, a hash with two keys
merged
andsequence
is returned. The explanation of the data format is beyond the scope of this manual page. default_child
-
my $dc = $self->default_child; $self->default_child($new_name);
description
-
my $text = $self->description; $self->description($updated_description);
environment_prefix
-
my $prefix = $self->environment_prefix; $self->environment_prefix($new_prefix);
environment_variable_name
-
my $name = $self->environment_variable_name($opt_hash);
Get the environment variable name set for the specific option described by
$opt_hash
. If it contains anenvironment
key and it has a name, that is returned, otherwise if it is1
then an environment variable is generated fromenvoronment_prefix
andname
. execute
-
$self->execute;
Code that executes the main logic of the command.
If a sub reference was provided with
execute
in the application definition, that is called receiving the object as its only parameter. Otherwise, the intended use is to override this method in a derived class for a command.It is not meant to be called directly.
execution_reason
-
my $reason = $self->execution_reason;
A string representing the reason why the specific
execute
method/callback was selected, can be one of:-default
The command was selected as the default child set for a command.
-fallback
The command was selected as a result of the
fallback
method.-leaf
The command is a leaf and has no sub-commands.
fallback
-
my $name = $self->fallback;
Select a fallback child, or the invoking command itself (returning
-self
) as the command that should be investigated next or executed.This method is called when there are unrecognized non-option command-line arguments that lead to no child of a non-leaf command. The selection is performed using
fallback_to
. fallback_to
-
my $name = $self->fallback_to; $self->fallback_to($new_fallback);
Get/set the fallback string for figuring out the fallback in case of need. Used by
fallback
. final_collect
-
$self->final_collect($callback_sub); $self->final_collect;
Can be set either as a callback or as a method override. Not supposed to be called directly.
See "Sources as hash reference". Available after version
2.007001
(not included). final_commit
-
$self->final_commit($callback_sub); $self->final_commit;
Can be set either as a callback or as a method override. Not supposed to be called directly.
See "New commit finalization:
final_commit
". Available after version2.007001
(not included). final_commit_stack
-
my @sequence = $self->final_commit_stack;
Sequence of command objects during the
final_commit
phase.See "New commit finalization:
final_commit
". Available after version2.007001
(not included). find_child
-
my ($child_instance, @child_args) = $self->find_child;
Look for a child to hand execution over. Returns an child instance or undef (which means that the
$self
is in charge of executing something). This implements the most sensible default, deviations will have to be coded explicitly.This method is called by
run
and is not expected to be called elsewhere. It use is discouraged and not future-proof.Returns a list with the selected children and the arguments to feed to its
run
method:-
(undef, '-leaf')
if no child exists. In this case, the caller command's
execute
method should be used. -
($instance, @args)
if a child is found in
$args[0]
. -
($instance, '-default')
if the default child is returned.
-
(undef, '-fallback')
in case $self is the fallback and should be used for calling the
execute
method. -
($instance, '-fallback', @args)
in case the fallback is returned, with the residual unparsed arguments.
-
find_matching_child
-
my $instance = $self->find_matching_child($name);
Find and instantiate a child matching
$name
, either directly or as an alias. force_auto_children
-
my $boolean = $self->force_auto_children; $self->force_auto_children(0); # disable $self->force_auto_children(1); # enable
full_help_text
-
my $full_help = $self->full_help_text; my $usage_help = $self->full_help_text('usage');
Get the full auto-generated help text for the command, e.g. to print it.
It can be passed an additional string paramter, which can be
help
orusage
. The former generates the help including theDescription
section, the latter does not include it. getopt_config
-
my $aref = $self->getopt_config; $self->getopt_config(\@custom_getopt_long_configuration);
hashy_class
-
my $class = $self->hashy_class; $self->hashy_class($new_class);
Setting this to anything different from
App::Easer::V2::Command
is a liability, although it might make sense to set to a derived class to provide a common set of methods useful for the specific application. help
-
my $text = $self->help; $self->help($new_help_line);
help_channel
-
my $ch = $self->help_channel; $self->help_channel($new_channel);
inflate_children
-
my @instances = $self->inflate_children(@hints);
Turn a list of
@hints
into corresponding instances.A blessed hint is always returned unmodified, assuming it's already a valid instance. No check on the class is performed.
A hash reference is inflated using
hashy_class
.An array reference assumes that the first element in the array is a valid
hashy_class
and the rest of the items are passed to itsnew
method.Anything else is considered a string containing a class to instantiate.
Actual instantiation is done using method
instantiate
.Direct use of this method is a liability.
inherit_options
-
my @options = $self->inherit_options(@options_names);
Look for matching names in a parent's options. This does technically not put these options inside the command, but it is used internally to achieve this goal.
This method is not meant to be called directly and is subject to become a private method, so its usage is discouraged and not future-proof.
inject_config
-
$self->inject_configs($hash_of_configurations); $self->inject_configs($hash_of_configurations, $priority);
Add an additional hash reference of configurations to the ones collected via sources. This is most useful within a
commit
callback/method, where options have been collected for the specific command level and allows applying custom ways of setting default values without the need to code an ad-hoc source.Default priority is set to 1000 (which is normally "very later", i.e. considered only as a fallback after everything else).
instantiate
-
my $instance = $self_or_package->instantiate($class, @args);
This method loads class
$class
viaload_module
and returns:$class->new(@args);
This is a class method.
is_root
-
say 'root command!' if $self->is_root;
Return true if the command is the root, i.e. if it has no parent. Available after version
2.007001
. list_children
-
my @children = $self->list_children;
Return the full list of children for a command, including ones gathered in the class tree and auto-generated ones (these are added only if there are other children or if
force_auto_children
is set to a true value). load_module
-
my $module_name_copy = $self->load_module($module_name);
Load
$module_name
withrequire
and return the name itself. The module name can only be provided using::
as separators. No specific bug from the past of Perl is addressed. merge_hashes
-
my $merged = $self->merge_hashes(@inputs);
Takes a list of input hash references and generates a merged version of all of them. Hashes in
@inputs
are supposed to be provided in priority order, where those coming first take precedence over those coming next. name
-
my $name = $self->name; $self->name($new_name);
Get/set name of command. If not present, the first item of
aliases
is used. If this is not set too, or empty, string** no name **
is used. name_for_option
-
my $opt_name = $self->name_for_option($opt_spec_hashref);
Get the option's name, either from its
name
key, or deriving it fromgetopt
, or fromenvironment
. Returns~~~
if none of them works. new
-
my $instance = $class->new(@spec); my $other = $class->new(\%spec);
Instantiate a new object, using the provided values.
In derived classes, additional specifications are also taken from the package variable
$class . '::app_easer_spec'
, if set. options
-
my @options_aoh = $self->options; $self->options(\@new_options);
See "Application High Level View" and "OPTIONS". The returned list if passed through
resolve_options
anyway, e.g. to inherit options. options_help
-
my $options_help = $self->options_help; $self->options_help($new_help);
Set text to be printed for the options.
If set to a plain string, that string is all that will be printed, i.e. no automatic help text from
options
will be generated.If set to a hash reference, two keys are supported:
preamble
andpostamble
. When present, they will be printed respectively before and after the help text generated automatically fromoptions
. params_validate
-
my $href_or_undef = $self->params_validate; $self->params_validate($new_pv_conf);
parent
-
my $parent_command = $self->parent;
Get the parent command.
ref_to_sub
-
my $sub = $self->ref_to_sub($locator);
Turn a locator for a sub into a proper reference to a sub. The
$locator
can be:a sub reference, that is returned unmodified;
an array reference with two items inside, i.e. a class/module name followed by a method/function name
a string pointing to the function, either fully qualified as in
My::Class::function
or as a plain name that will be looked for in the class of$self
.
residual_args
-
my @residual_args = $self->residual_args; $self->residual_args(\@new_args);
Access the list of residual (i.e. unparsed) arguments from the command line.
resolve_options
-
my @options = $self->resolve_options($single_specification);
Expand a
$single_specification
into one or more options. Basic optiosn specifications are hash references, but they might be strings like+parent
(for inheriting all that is transmitted from a parent command) or regular expressions to bulk import options based on their names. root
-
my $root_command = $self->root;
Generalization of "parent" to go all the way up to the topmost command of the chain. Available after version
2.007001
. run
-
$self->run($command_name, @arguments);
Run a command. The first parameter is the command name, it should probably be set to
$0
when calling the topmost command.Running means parsing all options and potentially looking for sub-commands, until one is found.
Returns whatever the
execute
method of the selected sub-command returns. run_help
-
$self->run_help; $self->run_help('usage');
Run the
auto_help
command, which should print out the help for the command itself. This is not usually needed, but comes handy to implement printing the help not via a sub-command but honoring a command-line option, like this:sub execute ($self) { return $self->run_help if $self->config('help'); ... }
It is possible to pass the string
help
(default) or the stringusage
; the latter prints a shorter message, i.e. it does not print theDescription
section. set_config
-
$self->set_config(foo => 'bar');
Set a new value for a configuration, overriding what has been found out from the several input sources.
set_config_hash
-
$self->set_config_hash($new_merged_config); $self->set_config_hash($new_overall, 1);
Set a new value for the whole configuration hash.
In the first form (no second parameter, or second parameter set to a false value), only the merged configuration hash is set, i.e. what is returned by a call like
$instance->configuration_hash
. In practical terms, it replaces what can be taken when setting "config_hash_key" to eithermerged
(the default value) oroverride
.The second form allows setting the whole underlying data structure that can be taken by a call like
$instance->configuration_hash(1)
. This allows messing up with the whole process. slot
-
my $hashref = $self->slot; $self->slot($new_data);
App::Easer::V2
uses a blessed hash reference to manage all data related to an object representing a command. To minimize overlapping with a user's object data, all data forApp::Easer::V2
is kept in a sub-hash pointed by theslot
key, like this:bless { $class_name => { ... all actual App::Easer::V2 stuff here ... } }, $hashy_class;
This method gives access to this slot and can be overridden to keep this data elsewhere, e.g. in an array element or in an inside-out object, without the need to re-implement all accessors.
slurp
-
my $contents = $self->slurp($filename); my $contents_2 = $self->slurp($filename, '<:raw');
Get the whole contents from a file, optionally specifying the mode for openining the file. By default, UTF-8 encoding is assumed and enforced.
Use of this method is discouraged and not future-proof.
sources
-
my @sources = $self->sources; $self->sources(\@new_sources_list);
See "Application High Level View" and "OPTIONS".
validate
-
$self->validate(\&validation_sub); $self->validate;
Performs validation.
When a validation sub is set (either calling with a parameter, or setting it via
validate
in "new") it will be called, receiving$self
as the only parameter:$sub->($self);
The validator is supposed to throw an exception upon validation failure.
If no validation sub is set, "params_validate" is looked for. If present, validation is applied according to it, using Params::Validate.
OPTIONS
The main capability provided by App::Easer
is the flexibility to handle options, collecting them from several sources and merging them together according to priorities.
Supported options are set in an array of hashes (or strings) pointed by the options
key. Each hash in the array sets the details regarding a single option. When provided as string, the option is inherited from a parent, allowing the sub-command to expose the same option as the parent. This gives freedom to put options and values either in the parent or in the descendant command, providing flexibility.
This is a YAML overview of how to set one option as a hash:
name: «string»
help: «string»
transmit: «boolean»
transmit_exact: «boolean»
getopt: «string»
environment: «string» or 1
default: any value
A few keys inside the hash regard the option itself, like:
name
name of the option. This is not mandatory if
getopt
is present, which gets the name automatically from the first alias of the option itself;help
some help about the option.
transmit
boolean to set whether an option can be easily "inherited" by a sub-command (without the need to put all attributes of the option once again).
transmit_exact
boolean to set whether the option must be spelled exactly to be inherited (no inheritance via a regular expression).
Option Values Collection (sources
)
The collection is performed thanks to sources, which can be set with the corresponding sources
key or method (depending on the style). App::Easer
comes with several sources for getting configurations from a variety of places, but still leaves the door open to add more customized ones.
Up to version 2.007001
included, the sources
key could only point to an array reference holding a list of sources. After that release, it has been expanded to also accept a hash reference, allowing for a new and more sophisticated handling of option values collection. In either case, anyway, processing arrives at a point where there is a list of sources to be processed as described below; using the array ref keeps backwards compatibility with the old behaviour. See "Sources as hash reference" for additional details on the new way.
Sources are considered with respect to two different ordering methods: their place in the array pointed by key sources
and their priority. The former sets the order in which data from each source is collected, the latter sets the precedence while assembling conflicting data from several different sources (lower priorities means higher precedence).
Each source can be set either as a $locator
(see ahead) or as an array reference with the following structure:
[ $locator, @args ]
The $locator
can be a reference to a sub, which is used as the source itself, or a string that allows getting the source sub to handle the specific source. Strings starting with the +
character are reserved for sources provided by App::Easer::V2
natively.
If $locator
is a plain string, it is possible to set the priority directly inside it with =NN
(e.g. +Default=100
). It's not necessary to set the priority; if missing, it will be assumed to be 10 more than the previous one in the array (with the first item starting at 10). This also means that, by default, the ordering of sources also doubles down as the ordering of precedence.
The @args
part can provide additional arguments to the specific source; its applicability is dependent on the source itself. As the only exception, if the first item of @args
is a hash reference, it will be removed from the array and used to gather additional meta-options used directly by App::Easer
. At the moment, this is an alternative way to set the priority of the specific source using the key priority
. This means that the following examples are equivalent:
# priority in source name, like anywhere else
[ '+FromTrail=90', qw< defaults foo baz > ],
# priority in meta-options first-arguments hash reference
[ '+FromTrail', {priority => 90}, qw< defaults foo baz > ],
After the $locator
has been resolved to a sub reference $located_sub
, and the first item in @args
has been analyzed and possibly removed (because it contains meta-options as described above), the sub reference is invoked like follows:
$located_sub->($command, $args_arrayref, $inputs_arrayref);
where:
$command
is the instance of the (sub-)command under analysis$args_arrayref
is a reference to an array containing the remaining arguments provided via@args
in the source definition (when the array reference format[ $locator @args ]
form has been used). This array reference does not contain the meta-options.$inputs_arrayref
is a reference to an array holding the input command-line parameters (at least what's left of them after processing by previous sources).
Unless the source is supposed to process command-line arguments, it should just ignore the third parameter and focus on the first two.
It's not necessary to set the sources
explicitly, as by default the following configuration is assumed:
+CmdLine +Environment +Parent=70 +Default=100
where the respective priorities are, in order, 10, 20, 70, and 100.
Sources provided out of the box by App::Easer::V2
are:
+CmdLine
This source gathers options from the command line arguments. It is set using the
getopt
key, according to the rules explained inGetOpt::Long
. The first alias of an option is also set as the option's name in case keyname
is missing.+LastCmdLine
(After version
2.007001
, excluded)This source returns the same configuration hash as that determined by the last
+CmdLine
that was executed within a specific command level.This source is supposed to be used only in relation to the new hash-based way of setting sources (see "Sources as hash reference").
+Environment
This source gets values from environment variables. It is set using the
environment
key, which can be set to either the name of the environment variable (this is case sensitive) or automatically from higher-level configurationenvironment_prefix
and the option's name in case it is set to value1
exactly.This source expliticly ignores inherited options, to prevent overwriting previously set options. It is possible to override this:
[ '+Environment', include_inherited => 1 ]
Another alternative to do this is use
+FinalEnvironment
.+FinalEnvironment
This source works the same as
+Environment
, except that it works also for options that have been inherited. This is particularly useful when using "Sources as hash reference".+Parent
This source gets values from a parent command (it only applies to sub-commands).
+Default
Get a default value from the
default
sub-key of the option.This source expliticly ignores inherited options, to prevent overwriting previously set options. It is possible to override this:
[ '+Default', include_inherited => 1 ]
Another alternative to do this is use
+FinalDefault
.+FinalDefault
This source works the same as
+Default
, except that it works also for options that have been inherited. This is particularly useful when using "Sources as hash reference".+JsonFileFromConfig
Get keys/values from a JSON file, whose path is pointed by a key in the collected options. By default this key is
config
. To set a different key, pass this source as an array reference with the source name and the key, like this:[ JsonFileFromConfig => 'jcnf' ]
+JsonFiles
Get keys/values from a few JSON files (it's OK if they do not exist). To set the files to try, pass this source as an array reference:
[ JsonFiles => @paths ]
+FromTrail
Get keys/values from a sub-hash of the already collected options (e.g. after loading them from a configuration file). The trail to the position of the sub-hash is provided like this:
[ FromTrail => qw< topcmd subcmd additional_values > ]
It is possible to set a default value for the option used by JsonFileFromConfig
, as long as the +Default
source is placed before JsonFileFromConfig
. In this case, it's usually necessary to assign a lower priority value to JsonFileFromConfig
to make sure values read from there take precedence over the defaults:
sources => [qw<
+CmdLine=10 +Environment=20 +Parent=30 +Default=100
+JsonFileFromConfig=40
>];
This is the main reason why the priority has been detached from order of appearance.
Sources as hash reference
Up to version 2.007001
, the sources
key can only be set to an array reference holding a list of sources. Each command level can have its own list, or rely on the default one provided by App::Easer
.
This setup works in many situation but becomes too rigid when dealing with inherited options. As an example, suppose that we define an option loglevel
allowing to set the log level (e.g. info, debug, trace, ...) for our suite of commands. At this point, either we limit the option at the highest level only (then setting the logger configuration in the topmost command's commit
), or we have to wait until the leaf command is determined, which implies that every leaf command must repeat the code to set the logger's configuration.
Additionally, this setup might not play well with options that load configurations from files, because some files might be loaded based on command line configurations that appear in upper commands but might eventually be overridden in lower commands. So, in the following example, we want to be able to only load second.json
and disregard first.json
:
myprg --config first.json foo --config second.json
For this reason, release past 2.007001
(which is not included) also support an alternative way of configuring sources
, namely through a hash reference that supports three keys:
current
-
(mandatory) this is the equivalent of the old behaviour, i.e. a list of sources that is used to figure out the order and precedence of collecting options.
next
-
(optional) this allows setting the list of sources for the children sub-commands (unless the sub-command has one already configured), i.e. its
current
when it will be called. It can be a straight list or a sub reference, in which case it will be called to determine the list.When absent, by default the children inherits the same
current
list as the parent command. final
-
(optional) this allows setting a list of sources that is used at the very end of the child determination process, i.e. when the program determines the descendant sub-command for which the
execute
method/callback will be called, in a new sub-phase aptly namedfinal_collect
.
With the new hash-based configuration, the determination of the right sub-command proceeds like before, using the current
list of sources at each step (the current
for a sub-command might have been set using the next
of the parent command). During this phase, the commit
method/callback is called at every level just like before.
After the search for the right command to execute
is finished, method final_collect
is called, using the final
list of the hash reference. This allows deferring the collection of data from non-commmand-line sources at the very end.
The default configuration when adopting the new hash-based sources
is the following:
{
current => [ qw< +CmdLine +Parent > ],
final =>
[ qw< +LastCmdLine +Parent +FinalEnvironment +FinalDefault > ],
}
In this configuration, all command levels share the same forward configuration, only gathering command-line parameters, with later sub-commands taking precedence (+CmdLine
has higher priority than +Parent
).
After the right sub-command to execute is determined, then the latest command line is taken (it is not parsed again), then the parent (like before, it only contains command-line parameters from upper commands), then the environment, then the defaults.
In summary: all command-line parameters with precedence from right to left, then the environment, then defaults.
It is easy to also add the +JsonFileFromConfig
source, like in the following example:
{
current => [ qw< +CmdLine +Parent > ],
final =>
[
qw<
+LastCmdLine=10
+Parent=20
+FinalEnvironment=30
+FinalDefault=100
+JsonFileFromConfig=40
>
],
}
Priorities for the first three sources in final
are set explicitly but would take the same values implicitly, they have been put in the example only for clarity.
As before, we put the +FinalDefault
option before the +JsonFileFromConfig
so that we can read the default value for the config
option (if present), while still giving stuff from the file precendence over the defaults.
After this information gathering phase, method/callback final_commit
is called (much like commit
is called after collect
).
It is possible to set key final_collect
or override it as a method, for maximum flexibility. Doing this is a liability.
New commit finalization: final_commit
in the new "Sources as hash reference" way of setting sources
, after the final roundup of options done by final_collect
, the final_commit
method/callback is executed.
By default, this triggers calling it all up the commands ladder, i.e. from the leaf command that will be executed up to the root command. This allows setting "final configurations" (like a logger) in the final_commit
method of the topmost command, once and for all.
It is anyway possible to set callbacks or override the method at each intermediate or at the leaf level, of course. In this case, the return value of the method/callback will be used to determine whether to continue up the ladder or not; true values will continue the call chain, false values will interrupt it.
It's also possible to break the chain of calls by setting the key to a false value.
It MUST be considered that the method is specific to each command, which usually does not have an updated view of the whole configuration, because that is only known to the leaf command's object. For this reason, method/callback final_commit
can also call method final_commit_stack
, which provides a list of all command objects that are involved in the chain, starting from the leaf command down towards the root.
For this reason, the way to get the full configuration in the topmost command is the following:
sub final_commit ($self) {
my ($leaf) = $self->final_commit_stack;
my $final_config = $leaf->config_hash;
...
}
Method final_commit_stack
is only available (predictably) inside method/callback final_commit
; its availability elsewhere is not guaranteed.
Specification Inheritance vs. Value Acquisition
App::Easer
provides two different ways of sharing options between parents and children.
One way of doing this is by means of source +Parent
("Option Values Collection (sources
)": this allows absorbing configurations from a parent inside a child. Every options that has been set in the parent command is copied into the child, subject to priorities etc.
Another way of doing this is by inheriting options specifications by a child from its parent. This makes the parent's option appear also as a child's option, which might ease the life of users because they would not need to set options exactly in the first command that supports it.
The latter behaviour has two halves: one in the parent and the other in the child command.
Parent commands can declare which option specifications can be inherited by children by setting transmit
to a true value in the option's speficiation. By default, options are not set as inheritable.
Children can then inherit options by using string or regular expressions instead of hash references. In this way, a child can cherry-pick which options from the parent make sense and ignore the other ones.
Consider the following example:
my $app = {
aliases => [ 'main' ],
options => [
{
getopt => 'transmittable',
help => 'option available in parent, transmit => 1',
transmit => 1,
},
{
getopt => 'parent-only',
help => 'option available in parent only',
transmit => 0,
},
],
children => [
{
aliases => [ 'foo' ],
options => [ '+parent',
{
getopt => 'child-only',
help => 'option available in child only',
},
],
execute => sub ($self) {
say "foo says: ",
$self->config('transmittable') ? 'transmit' : 'no way';
},
},
],
};
Option transmittable
in the parent command can be inherited, while parent-only
can not. Child command foo
does indeed inherit the option, thanks to the +parent
option that allows inheriting everything that the parent sets with a true transmit
.
An example run of the application above makes it clear that the option can be set both in the parent and in the child command:
$ perl prova.pl foo
foo says: no way
# command-line option is set in parent command
$ perl prova.pl --transmittable foo
foo says: transmit
# command-line option is set in child command
$ perl prova.pl foo --transmittable
foo says: transmit
Option string +parent
inherits everything; it's also possible to set exact strings or regular expressions (also as strings). In case of regular expressions, it's furthermore possible for the parent to restrict inheritance of an option only when the name is provided exactly (i.e. not as a regular expression); this is done setting transmit_exact
in the option's specification in the parent.
Accessing Collected Option Values
Assuming the application object is $self
, there are a few methods to get the configurations:
config
my $value = $self->config('foo'); my @values = $self->config(@multiple_keys);
get whatever value was computed after collecting values from all sources and taking the one with the best (i.e. lowest) priority.
config_hash
my $merged_hash = $self->config_hash; my $complete_hash = $self->config_hash(1);
The first form (or any where the only parameter is considered false by Perl) gets the merged form, i.e. where each key has one value associated, based on the priorities set for the sources.
The second form returns a more complicated data structure where the
sequence
key points to an array reference containing all details about data gathering. Explanation of this data structure is beyond the scope of this manual page.The second form cam be used in case the provided mechanism of prioritization is not sufficient.
residual_args
my @args = $self->residual_args;
Get all arguments from the command line that were not parsed (and hence left for a sub-command or just unparsed).
The merged configuration returned by config_hash
is regulated by option config_hash_key
. As of release 2.008
, it's possible to set it to one of the following:
merged
the default value, providing whatever is coded as default. As of version
v2.008
this corresponds tomerged_legacy
below.merged_legacy
provide a backwards-compatible behaviour (pre-
v2.008
).v2.008
use a different way of coalescing values across different commands/sub-commands. The configuration is collected taking into account the priority of each source; for same-priority sources, sub-commands take precedence over ancestors.
override
use whatever has been set explicity via "set_config_hash". Note that setting a configuration explicitly means that the value pointed at by key
merged
is changed too.
See also "set_config" and "set_config_hash".
MIXED STUFF
Managing help as a command-line option
To get help
about a command, App::Easer::V2 provides a help
sub-command out of the box most of the times. The exception is for leaf commands, which do not have sub-commands.
After version 2.007001
(not included), in addition to the help
command there is also a usage
command, which provides the same output as help
but without the Description
section. The usage
sub-command is managed as an alias of help
; it is possible to remove this alias by setting @App::Easer::V2::Command::Help::aliases
to the string help
alone.
This is not a big deal with hierarchical applications, because it's possible to invoke the help
sub-command of the parent command and pass the name of the sub-command we need help with:
# print help about `topcmd`
$ topcmd help
# print help about `subcmd` under `topcmd`
$ topcmd help subcmd
On the other hand, for non-hierarchical applications, the only available command is also a leaf and this hinders getting a meaningful, auto-generated help text off the shelf.
In this case, it's possible to include an option for getting help, like this:
my $app = {
options => [
{
getopt => 'help|h!',
help => 'print help on the command',
},
...
Then, inside the execute
sub, it's possible to run the help
sub-command explicitly:
sub execute ($self) {
return $self->run_help if $self->config('help');
# ... normal code for "execute"
# ...
}
As of versions after 2.007001
(not included) it is possible to pass an additional parameter to run_help
with string usage
, to get a shorter help which does not include the Description
section.
BUGS AND LIMITATIONS
Minimum perl version 5.24.
Report bugs through GitHub (patches welcome) at https://github.com/polettix/App-Easer.
AUTHOR
Flavio Poletti <flavio@polettix.it>
COPYRIGHT AND LICENSE
Copyright 2021 by Flavio Poletti <flavio@polettix.it>
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.