App-Easer

 view release on metacpan or  search on metacpan

lib/App/Easer/V2.pod  view on Meta::CPAN

=pod

=for vim
   vim: tw=72 ts=3 sts=3 sw=3 et ai :

=encoding utf8

=head1 NAME

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

=head1 VERSION

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

=head1 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

lib/App/Easer/V2.pod  view on Meta::CPAN


call C<Params::Validate::validate> on the collected I<merged>
configuration (see L</OPTIONS>).

=back

=item C<sources>

Array of items.

See L</OPTIONS>.

=item C<validate>

Sub reference for performing validation. Will be called during the
validation phase and passed the command object instance:

   $validation_sub->($self);

If set, L</params_validate> is ignored.

=back

The following YAML representation gives an overview of the elements that
define an application managed by C<App::Easer::V2>, highlighting the
necessary or I<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.

=head1 App::Easer::V2::Command METHODS

When a command is created, it is (usually) an instance of class
C<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).

=over

=item C<auto_children>

   my @classes = $self->auto_children;
   my @objects = $self->auto_children(1);

Return a list of the automatic children (C<help>, C<commands>, and
C<tree>) as inflatable children, i.e. as fully qualified class names. If
an optional true value is passed, children are inflated into objects.

=item C<auto_commands>

   my $instance = $self->auto_commands;

Returns an instance representing the C<commands> sub-command.

=item C<auto_help>

   my $instance = $self->auto_help;

Returns an instance representing the C<help> sub-command.

=item C<auto_tree>

   my $instance = $self->auto_tree;

Returns an instance representing the C<tree> sub-command.

=item C<aliases>

   my @aliases = $self->aliases;
   my @modified = $self->aliases(\@new_aliases);

Gets/sets the aliases. If not set, C<name> is used.

See L</Application High Level View>.

=item C<allow_residual_options>

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

See L</Application High Level View>.

lib/App/Easer/V2.pod  view on Meta::CPAN

See L</Application High Level View>.

=item C<inflate_children>

   my @instances = $self->inflate_children(@hints);

Turn a list of C<@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 C<hashy_class>.

An array reference assumes that the first element in the array is a
valid C<hashy_class> and the rest of the items are passed to its C<new>
method.

Anything else is considered a string containing a class to instantiate.

Actual instantiation is done using method C<instantiate>.

Direct use of this method is a liability.

=item C<inherit_options>

   my @options = $self->inherit_options(@options_names);

Look for matching names in a parent's options. This does technically
I<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.

=item C<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 C<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).

=item C<instantiate>

   my $instance = $self_or_package->instantiate($class, @args);

This method loads class C<$class> via C<load_module> and returns:

   $class->new(@args);

This is a class method.

=item C<is_root>

   say 'root command!' if $self->is_root;

Return I<true> if the command is the I<root>, i.e. if it has no parent.
Available after version C<2.007001>.

=item C<list_children>

   my @children = $self->list_children;

Return the I<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 C<force_auto_children> is set to a
I<true> value).

=item C<load_module>

   my $module_name_copy = $self->load_module($module_name);

Load C<$module_name> with C<require> and return the name itself. The
module name can only be provided using C<::> as separators. No specific
bug from the past of Perl is addressed.

=item C<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 C<@inputs> are supposed to be provided in
priority order, where those coming first take precedence over those
coming next.

=item C<name>

   my $name = $self->name;
   $self->name($new_name);

Get/set name of command. If not present, the first item of C<aliases> is
used. If this is not set too, or empty, string C<** no name **> is used.

See L</Application High Level View>.

=item C<name_for_option>

   my $opt_name = $self->name_for_option($opt_spec_hashref);

Get the option's name, either from its C<name> key, or deriving it from
C<getopt>, or from C<environment>. Returns C<~~~> if none of them works.

=item C<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 C<$class . '::app_easer_spec'>, if set.

=item C<options>

   my @options_aoh = $self->options;

lib/App/Easer/V2.pod  view on Meta::CPAN

   }

Method C<final_commit_stack> is only available (predictably) inside
method/callback C<final_commit>; its availability elsewhere is not
guaranteed.

=head2 Specification Inheritance vs. Value Acquisition

C<App::Easer> provides two different ways of I<sharing> options between
parents and children.

One way of doing this is by means of source C<+Parent>
(L<< /Option Values Collection (C<sources>) >>: this allows I<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 I<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 I<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 C<transmit> to a true value in the option's
speficiation. By default, options are I<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 C<transmittable> in the parent command can be inherited, while
C<parent-only> can not. Child command C<foo> does indeed inherit the
option, thanks to the C<+parent> option that allows inheriting
everything that the parent sets with a true C<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 C<+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 I<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
C<transmit_exact> in the option's specification in the parent.

=head2 Accessing Collected Option Values

Assuming the application object is C<$self>, there are a few methods to
get the configurations:

=over

=item * C<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.

=item * C<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 I<false>
by Perl) gets the I<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 I<complicated> data structure where the
C<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.



( run in 0.394 second using v1.01-cache-2.11-cpan-5511b514fd6 )