NAME

App::Easer::V2 - Simplify writing (hierarchical) CLI applications

VERSION

This document describes App::Easer::V2 version {{[ version ]}}.

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 through dd and then to warn.

  • 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 useing the module:

  • -command

    The package from where App::Easer::V2 is used is a command and inherits from App::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 is used inherits from App::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 a name 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 its new 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 named MyApp::Cmd*, e.g. MyApp::CmdFoo and MyApp::CmdBar and ignores classed named MyApp::Common or MyApp::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 the execute 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 no environment key is present in the option's specification, or when the environment key points to value 1.

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 the execute of the command itself should be run.

If set to -default, the default_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, and tree sub-commands.

This can come handy when default_child/fallback_to are set to -self and the three words help, commands, and tree 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 and pass_through are added too, although the latter only temporarily to allow for chaining (unrecognized options might still trigger an exception). If allow_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, and tree.

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], where filename can NOT contain the character :. The file will be opened and if the binmode part is provided, binmode() will be called on the resulting filehandle with the provided value.

    If the filename part is - or -stdout (case insensitive), then STDOUT will be used. If filename is -stderr (case insensitive), then STDERR 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:

args

call Params::Validate::validate_pos on the residual_args (see "OPTIONS").

config

call Params::Validate::validate on the collected merged configuration (see "OPTIONS").

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, and tree) 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.

See "Application High Level View".

allow_residual_options
my $boolean = $self->allow_residual_options;
$self->allow_residual_options(0); # disable
$self->allow_residual_options(1); # enable

See "Application High Level View".

auto_environment
my $boolean = $self->auto_environment;
$self->auto_environment(0); # disable
$self->auto_environment(1); # enable

See "Application High Level View".

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.

See "Application High Level View".

children_prefixes
my $aref = $self->children_prefixes;
$self->children_prefixes(\@new_prefixes);

See "Application High Level View".

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 or config_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 and sequence 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);

See "Application High Level View".

description
my $text = $self->description;
$self->description($updated_description);

See "Application High Level View".

environment_prefix
my $prefix = $self->environment_prefix;
$self->environment_prefix($new_prefix);

See "Application High Level View".

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 an environment key and it has a name, that is returned, otherwise if it is 1 then an environment variable is generated from envoronment_prefix and name.

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.

See "Application High Level View".

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 version 2.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 version 2.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

See "Application High Level View".

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 or usage. The former generates the help including the Description section, the latter does not include it.

getopt_config
my $aref = $self->getopt_config;
$self->getopt_config(\@custom_getopt_long_configuration);

See "Application High Level View".

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.

See "Application High Level View".

help
my $text = $self->help;
$self->help($new_help_line);

See "Application High Level View".

help_channel
my $ch = $self->help_channel;
$self->help_channel($new_channel);

See "Application High Level View".

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 its new 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 via load_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 with require 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.

See "Application High Level View".

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 from getopt, or from environment. 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 and postamble. When present, they will be printed respectively before and after the help text generated automatically from options.

params_validate
my $href_or_undef = $self->params_validate;
$self->params_validate($new_pv_conf);

See "Application High Level View".

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 string usage; the latter prints a shorter message, i.e. it does not print the Description 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 either merged (the default value) or override.

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 for App::Easer::V2 is kept in a sub-hash pointed by the slot 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 in GetOpt::Long. The first alias of an option is also set as the option's name in case key name 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 configuration environment_prefix and the option's name in case it is set to value 1 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 named final_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 to merged_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.