NAME

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

VERSION

    This document describes App::Easer version 0.010.

SYNOPSIS

       #!/usr/bin/env perl
       use v5.24;
       use experimental 'signatures';
       use App::Easer 'run';
       my $app = {
          commands => {
             MAIN => {
                name => 'main app',
                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 ($global, $conf, $args) {
                   my $foo = $conf->{foo};
                   say "Hello, $foo!";
                   return 0;
                },
                'default-child' => '', # run execute by default
             },
          },
       };
       exit run($app, [@ARGV]);

    Call examples:

       $ ./example.pl
       Hello, bar!
    
       $ ./example.pl --foo World
       Hello, World!
    
       $ ./example.pl commands
       $ perl lib/App/Easer.pm commands
                  help: print a help message
              commands: list sub-commands
    
       $ ./example.pl help
       this is the main app
    
       Description:
           Yes, this really is the main app
    
       Options:
                   foo: 
                        command-line: mandatory string option
                                      --foo <value>
                                      -f <value>
                        environment : FOO
                        default     : bar
    
       Sub commands:
                  help: print a help message
              commands: list sub-commands
    
       $ ./example.pl help help
       print a help message
    
       Description:
           print help for (sub)command
    
       This command has no options.
    
       $ ./example.pl help commands
       list sub-commands
    
       Description:
           Print list of supported sub-commands
    
       This command has no options.
    
       $ ./example.pl inexistent
       cannot find sub-command 'inexistent'
    
       $ ./example.pl help inexistent
       cannot find sub-command 'inexistent'

DESCRIPTION

    NOTE: this software should be considered "late alpha" maturity. I'm
    mostly happy with the interface, but there are still a few things that
    might get changed. Anyway, if you find a release of App::Easer to work
    fine for you, it's fair to assume that you will not need to get a newer
    one later.

    App::Easer provides the scaffolding for implementing hierarchical
    command-line applications in a very fast way.

    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.

    An application is defined through a hash reference (or something that
    can be transformed into a hash reference, like a JSON-encoded string
    containing an object, or Perl code) with the description of the
    different aspects of the application and its commands.

    By default, only commands need to be provided, each including metadata
    for generating a help message, taking parameters from the command line
    or other sources (e.g. environment variables), sub-commands, etc., as
    well as the actual code to run when the command must be run.

 Application High Level View

    The following YAML representation gives a view of the structure of an
    application managed by App::Easer:

       factory:
          create: 芦executable禄
          prefixes: 芦hash or array of hashes禄
       configuration:
          collect:   芦executable禄
          merge:     芦executable禄
          specfetch: 芦executable禄
          validate:  芦executable禄
          sources:   芦array禄
          'auto-children':    芦false or array禄
          'help-on-stderr':   芦boolean禄
          'auto-leaves':      芦boolean禄
          'auto-environment': 芦boolean禄
       commands:
          cmd-1:
             芦command definition禄
          cmd-2:
             芦command definition禄
          MAIN:
             芦command definition禄

    Strictly speaking, only the commands section is needed in defining an
    application; all other parts only deal with customizing the behaviour
    of App::Easer itself and take sensible defaults when not provided.

 Anatomy of a run

    When an application is run, the following high level algorithm is
    followed, assuming the initial command is defined as MAIN:

      * the specification of the command is fetched, either from a
      configuration hash or by some other method, according to the
      specfetch hook;

      * option values for that command are gathered, consuming part of the
      command-line arguments;

      * the configuration is optionally validated;

      * a commit hook is optionally called, allowing an intermediate
      command to perform some actions before a sub-command is run;

      * a sub-command is searched and, if present, the process restarts
      from the first step above

      * when the final sub-command is reached, its execute function is run.

 Factory and Executables

       factory:
          create: 芦executable禄
          prefixes: 芦hash or array of hashes禄

    Many customization options appear as 芦executable禄.

    At the basic level, these can be just simple references to a sub. In
    this case, it is used directly.

    When they are provided in some other form, though, a factory function
    is needed to turn that alternative representation into a sub reference.

    App::Easer comes with a default factory function (described below) that
    should cover most of the needs. It is possible to override it by
    setting the value for key create inside factory; the default function
    is used to generate this new factory, which is then installed to parse
    all other 芦executable禄 values in the definition.

    The default factory manages the following representations:

      * sub references are passed through directly;

      * strings are first filtered according to the mapping/mappings
      provided by the field prefixes in factory, then parsed to get the
      name of a package and optionally the name of a sub in that package
      (each field that carries an 芦executable禄 is associated to a default
      sub name).

    The prefixes can be either a hash reference, or an array of hashes. The
    latter allows setting an order for substitutions, e.g. to make sure
    that prefix :: is tried first if there is also prefix : defined:

       prefixes:
          - '::' : 'What::Ever::'
          - ':' :  'My::App::'

    Otherwise, the unordered nature of Perl hashes would risk that the
    expansion associated to : is tried first, spoiling the result and
    making it unpredictable.

    By default, the + character prefix is associated to a mapping into
    functions in App::Easer starting with stock_. As an example, the string
    +CmdLine is expanded into App::Easer::stock_CmdLine, which happens to
    be an existing function (used in parsing command-line options). It is
    possible to suppress this expansion by setting a mapping from + to + in
    the prefixes, although this will deviate from the normal working of
    App::Easer.

 Configuration Parsing Customization

       configuration:
          name:      芦string禄
          collect:   芦executable禄
          merge:     芦executable禄
          namenv:    芦executable禄
          specfetch: 芦executable禄
          validate:  芦executable禄
          sources:   芦array禄
          'auto-children':    芦false or array禄
          'help-on-stderr':   芦boolean禄
          'auto-leaves':      芦boolean禄
          'auto-environment': 芦boolean禄

    The name configuration allows setting a name for the application, which
    can e.g. be used to generate automatic names for environment variables
    to be associated to command options.

    One of the central services provided by App::Easer is the automatic
    gathering of options values from several sources (command line,
    environment, commands upper in the hierarchy, defaults). Another
    service is the automatic handling of two sub-commands help and commands
    to ease navigating in the hierarchy and get information on the
    (sub)commands.

    The configuration is collected by a function provided by App::Easer
    that can be optionally overridden by setting a different executable for
    collect under configuration. This of course requires re-implementing
    the options value gathering from scratch. Calling convention:

       sub ($app, $spec, $args)
       # $app:  hash ref with the details on the whole applications
       # $spec: hash ref with the specification of the command
       # $args: array ref with residual (command line) arguments

    This function is expected to return a list with two items, the first a
    hash reference with the collected configuration options, the second an
    array reference with the residual arguments. The function is called
    according to the following calling convention:

       sub ($app, $cspec, $ospec)
       # $app:   hash ref with the details on the whole applications
       # $cspec: hash ref with the specification of the command
       # $ospec: hash ref with the specification of the option

    The merge executable allows setting a function that merges several
    hashes together. The default implementation operates at the higher
    level of the hashes only, giving priority to the first hashes provided
    (in order). Calling convention:

       sub (@list_of_hashes_to_merge) # returns a hash reference

    The namenv executable allows setting a function that generates the name
    of environment variables based on options specifications. By default a
    stock_NamEnv function is used (aliased to +NamEnv) is used, generating
    the name of the environment variable by uppercasing the string
    generated by the application's name and the option name, joined by an
    underscore character.

    The specfetch executable allows setting a function to perform
    resolution of a command identifier (as e.g. stored in the children) or
    an upper command) into a specification. By default the internal
    function corresponding to the executable specification string
    +SpecFromHash is used, insisting that the whole application is entirely
    pre-assembled in the specification hash/object; it's also possible to
    use +SpecFromHashOrModule for allowing searching through modules too.

    The validate executable allows setting a validator. By default the
    validation is performed using Params::Validate (if available, it is
    anyway loaded only when needed).

    It is possible to set several sources for gathering options values,
    setting them using the sources array. By default it is set to the
    ordered list with +Default, +CmdLine, +Environment, and +Parent, ,
    meaning that options from the command line will have the highest
    precedence, then the environment, then whatever comes from the parent
    command configuration, then default values if present. This can be set
    explicitly with +DefaultSources.

    As an alternative, sources can be set to +SourcesWithFiles, which adds
    +JsonFileFromConfig and +JsonFiles to the ones above. The former looks
    for a configuration named config (or whatever is set as config-option
    in the overall configuration hash) to load a JSON file with additional
    configurations; the latter looks for a list of JSON files to try in
    config-files inside the configuration hash.

      Although the +Default source is put first, it actually acts as the
      one with the least precedence by how it is coded and how the merging
      algorithm is implemented. From a practical point of view it's like it
      were put last, but is put first instead so that its defaults can be
      applied as options are gathered along the way.

      One case where this comes handy is in managing a --config option to
      pass a configuration file name to load some external file for
      additional configurations (e.g. sources option +SourcesWithFiles). In
      it, default configuration must still appear with the least
      precedence, but still it can be handy to set a default file to load
      upon starting, which means that it's handy to have this default at
      hand before the configuration files are supposed to be loaded.

    As anticipated, the help and commands sub-commands are automatically
    generated and associated to each command by default (more or less). If
    this is not the desired behaviour, it is possible to either disable the
    addition of the auto-children completely (by setting a false value), or
    provide an array of children names that will be added automatically to
    each command (again, more or less).

    It should be noted that both validate and sources are also part of the
    specific setup for each command. As such, they will be rarely set at
    the higher configuration level and the whole configuration section can
    normally be left out of an application's definition.

    Option help-on-stderr allows printing the two stock helper commands
    help and commands on standard error instead of standard output (which
    is the default).

    Option auto-leaves allows setting any command that has no explicit
    sub-command as a leaf, which prevents it from getting a help and a
    commands sub-command (or whatever has been put to override them). As of
    version 0.007002 this is set to a true value by default, but can still
    be set to a false value if the automatic sub-commands above are deemed
    necessary for commands that have no explicit children in the hierarchy.

    Option auto-environment turns on automatic addition of environment
    variables to options, by setting the associated setting to 1. This can
    also be set locally in a command.

 Commands Specifications

    All commands are stored in a hash of hashes, where the key represents
    an internal identifier for the command, which is then used to build the
    hierarchy (each command can have a children element where these
    identifier are listed).

    The command definition is a hash with the following shape:

       name: foo
       help: foo the bar
       description: foo allows us to foo the bar
       supports: ['foo', 'Foo']
    
       options:
         - name: whip
           getopt: 'whip|w=s'
           environment: FOO_WHIP
           default: gargle
           help: 'beware of the whip'
       auto-environment: 0
       allow-residual-options: 0
       sources: ['+CmdLine', '+Environment', '+Parent', '+Default']
    
       collect:  芦executable禄
       merge:    芦executable禄
       validate: ... 芦executable禄 or data structure...
       commit:   芦executable禄
       execute:  芦executable禄
    
       children: ['foo.bar', 'baz']
       default-child: 'foo.bar'
       dispatch: 芦executable禄
       fallback: 芦executable禄
       fallback-to: 'baz'
       fallback-to-default: 1
       leaf: 0
       no-auto: 1

    The following keys are supported:

    name

    help

    description

      These do what they advertise, and are used when building the help for
      the command automatically.

    supports

      This indicates all the different variants of the command that are
      allowed, i.e. the actual strings that trigger the selection of this
      command while looking for a suitable candidate;

    options

      This is a list of options, each with metadata useful to gather values
      for the option. The actual content is dependent of what sources are
      then used. The name sub-field is used in the automatic help
      generation; other sub-options are self-explanatory (getopt,
      environment, and default).

    allow-residual-options

      This boolean indicates whether additional options in the command are
      allowed; it is tied to +CmdLine and getopt and defaults to false.

      It means that if a command accepts option --foo only, calling the
      command with --foo --bar will result in an error and --bar will not
      be tried as a sub-command.

      Reasons to disable this (by setting this option to true) might be if
      a leaf command will then use the rest of the argument list to e.g.
      call an external program.

    sources

      This is the list of sources to gather values for options. It defaults
      to whatever has been set in the top-level configuration of the
      application, or +CmdLine +Environment +Parent +Default by default.

      It might be helpful to override this setting in the MAIN entry point
      command, e.g. to add the loading of a configuration file once and for
      all.

      Items are executables, i.e. sub references or names that will be
      resolved into sub references through the factory.

    collect

    merge

      These allow overriding the internal default behaviour of App::Easer

    validate

      This can be a sub reference called to perform the validation, or a
      hash that, when the default validator is in effect, will be used to
      call Params::Validate.

    commit

      This optional callback is invoked just after the parsing of the
      configuration and its optional validation. It shouldn't be normally
      needed, but it allows a "former" command to perform actions before
      the search mechanism investigates further down looking for the target
      command.

      Calling convention:

         sub ($app, $spec, $args)
         # $app:  hash ref with the details on the whole applications
         # $spec: hash ref with the specification of the command
         # $args: array ref with residual (command line) arguments

      The configuration that has been assembled up to the specific command
      can be retrieved at $app->{configs}[-1].

    execute

      This is the callback that is called when the command is selected for
      execution.

      Calling convention:

         sub ($app, $opts, $args)
         # $app:  hash ref with the details on the whole applications
         # $opts: hash ref with options for the command
         # $args: array ref with residual (command line) arguments

    children

      This is a list of children, i.e. allowed sub-commands, specified by
      their identifier in the commands hash. This list is normally enriched
      with sub-commands help and commands automatically, unless the
      automatic children have been changed or disabled. It is possible to
      mark a command as a leaf (missing also sub-commands help/commands) by
      setting this parameter to a false value, otherwise it must be an
      array;

    default-child

      When the arguments list is exhausted, this option allows setting the
      last sub-command name. By default it is help, but this can be
      overridden. Setting this to a false value disables looking for a
      sub-command, so it allows addressing the command itself directly.

      This must be the key associated to a child in the commands mapping,
      i.e. the same name that is put in the children array.

    dispatch

      For commands with children, this completely overrides the child
      search mechanism by calling a custom executable, which is expected to
      return the name of a sub-command or the empty list is case the
      specific command's execute should be called instead.

      Calling convention:

         sub ($app, $spec, $args)
         # $app:  hash ref with the details on the whole applications
         # $spec: hash ref with the specification of the command
         # $args: array ref with residual (command line) arguments

      The configuration that has been assembled up to the specific command
      can be retrieved at $app->{configs}[-1].

      The return value must be either an empty list/undef or the name of a
      children (actually it can be any command).

    fallback

    fallback-to

    fallback-to-default

      For commands with children, these options allows figuring out a
      fallback command to execute if no child can be found. This allows
      building Do What I Mean interfaces where e.g. a sub-command should be
      selected by default.

      As an example, suppose the application has a search and a stats
      sub-commands, where the search is expected to be invoked the vast
      majority of times:

         myapp search this
         myapp search that is foo
         myapp stats

      It's tempting at this point to get rid of the search word to speed
      things up, while still preserving the sub-commands resolution
      mechanism:

         myapp this
         myapp that is foo
         myapp stats

      In the first two cases, App::Easer would normally look for
      sub-commands this and that respectively, failing. With a fallback,
      though, it's possible to select another command and implement the
      DWIM interface.

      fallback is an executable that is expected to return the name of a
      child (or actually any command) or the empty list/undef, in which
      case the current command's execute will be used instead. This is the
      most flexible way of doing the fallback.

      Calling convention:

         sub ($app, $spec, $args)
         # $app:  hash ref with the details on the whole applications
         # $spec: hash ref with the specification of the command
         # $args: array ref with residual (command line) arguments

      The configuration that has been assembled up to the specific command
      can be retrieved at $app->{configs}[-1].

      fallback-to sets the name of a children (or actually any command) as
      a static string. It can also be set to undef, which means that the
      current command's execute should be used instead.

      fallback-to-default selects whatever default is set for the command;
      it is a boolean-ish option.

    leaf

      This is an alternative, hopefully more readable, way to set the
      command as a leaf and avoid considering any sub-command, including
      the auto-generated ones.

    no-auto

      This option disables the automatic addition of automatically
      generated sub-commands.

      If set to an array reference, all items in the array will be filtered
      out from the list of automatically added sub-commands. If set to the
      string *, all automatic sub-commands will be ignored.

FUNCTIONS

    The following functions can be optionally imported.

 d

       d(['whatever', {hello => 'world'}]);

    Dump data on standard error using Data::Dumper.

 run

       run($application, \@args);
    
       # hash data structure
       run({...}, \@ARGV);
    
       # filename or string, in JSON or Perl
       run('/path/to/app.json', \@ARGV);
       run('/path/to/app.pl', \@ARGV);
       run(\$app, \@ARGV);
    
       # filehandle, data in JSON or Perl
       run(\*DATA, \@ARGV)

    Run an application.

    Takes two positional parameters:

      * an application definition, which can be provided as:

	* hash reference ("native" format)

	* reference to a string, containing either a JSON or a Perl
	definition for the application.

	For the JSON alternative, the string must contain a JSON object so
	that the parsing returns a reference to a hash.

	For the Perl alternative, the text is evaled and must return a
	reference to a hash.

	* a string with a file path, pointing to either a JSON or a Perl
	file. The file is loaded and treated as described above for the
	reference to a string case;

	* a filehandle, allowing to load either a JSON or a Perl file. The
	content is treated as described above for the reference to a string
	case.

      * the command-line arguments to parse (usually taken from @ARGV),
      provided as a reference to an array.

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.