The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Perinci::CmdLine::Base - Base class for Perinci::CmdLine{Classic,::Lite}

VERSION

This document describes version 1.12 of Perinci::CmdLine::Base (from Perl distribution Perinci-CmdLine-Lite), released on 2015-04-25.

DESCRIPTION

PROGRAM FLOW (NORMAL)

If you execute run(), this is what will happen, in order:

  • Detect if we are running under tab completion mode

    This is done by checking the existence of special environment varibles like COMP_LINE or COMMAND_LINE (tcsh). If yes, then jump to "PROGRAM FLOW (TAB COMPLETION)". Otherwise, continue.

  • Run hook_before_run, if defined

    This hook (and every other hook) will be passed a single argument $r, a hash that contains request data (see "REQUEST KEYS").

    Some ideas that you can do in this hook: XXX.

  • Parse command-line arguments (@ARGV) and set action

    If read_env attribute is set to true, and there is environment variable defined to set default options (see documentation on read_env and env_name attributes) then the environment variable is parsed and prepended first to the command-line, so it can be parsed together. For example, if your program is called foo and environment variable FOO_OPT is set to --opt1 --opt2 val. When you execute:

     % foo --no-opt1 --trace 1 2

    then @ARGV will be set to <('--opt1', '--opt2', 'val', '--no-opt1', '--trace', 1, 2)>. This way, command-line arguments can have a higher precedence and override setting from the environment variable (in the example, --opt1 is negated by --no-opt1).

    Currently, parsing is done in two steps. The first step is to extract subcommand name. Because we want to allow e.g. cmd --verbose subcmd in addition to cmd subcmd (that is, user is allowed to specify options before subcommand name) we cannot simply get subcommand name from the first element of @ARGV but must parse command-line options. Also, we want to allow user specifying subcommand name from option cmd --cmd subcmd because we want to support the notion of "default subcommand" (subcommand that takes effect if there is no subcommand specified).

    In the first step, since we do not know the subcommand yet, we only parse common options and strip them. Unknown options at this time will be passed through.

    If user specifies common option like --help or <--version>, then action will be set to (respectively) help and version and the second step will be skipped. Otherwise we continue the the second step and action by default is set to call.

    At the end of the first step, we already know the subcommand name (of course, if subcommand name is unknown, we exit with error) along with subcommand spec: its URL, per-subcommand settings, and so on (see the subcommands attribute). If there are no subcommands, subcommand name is set to '' (empty string) and the subcommand spec is filled from the attributes, e.g. url, summary, <tags>, and so on.

    We then perform a meta Riap request to the URL to get the Rinci metadata. After that, hook_after_get_meta is run if provided. From the Rinci metadata we get list of arguments (the args property). From this, we generate a spec of command-line options to feed to Getopt::Long. There are some conversions being done, e.g. an argument called foo_bar will become command-line option --foo-bar. Command-line aliases from metadata are also added to the Getopt::Long spec.

    It is also at this step that we read config file (if read_config attribute is true). We run hook_before_read_config_file first. Some ideas to do in this hook: setting default config profile.

    We then pass the spec to Getopt::Long::GetOptions, we get function arguments.

    We then run hook_after_parse_argv. Some ideas to do in this hook: XXX.

    Function arguments that are still missing can be filled from STDIN or files, if the metadata specifies cmdline_src property (see Rinci::function for more details).

  • Delegate to action_$action method

    Before running the action_$action method, hook_before_action is called e.g. to allow changing/fixing action, last chance to check arguments, etc.

    After we get the action from the previous step, we delegate to separate action_$action method (so there is action_version, action_help, and so on; and also action_call). These methods also receive $r as their argument and must return an enveloped result (see Rinci::function for more details).

    Result is put in $r->{res}.

    hook_after_action is then called e.g. to preformat result.

  • Run hook_format_result

    Hook must set $r->{fres} (formatted result).

    If result has cmdline.skip_format result metadata property, then this step is skipped and $r->{fres} is simply taken from $r->{res}[2].

  • Run hook_display_result

    This hook is used by XXX.

  • Run hook_after_run, if defined

    Some ideas to do in this hook: XXX.

  • Exit (or return result)

    If exit attribute is true, will exit() with the action's envelope result status. If status is 200, exit code is 0. Otherwise exit code is status minus 300. So, a response [501, "Not implemented"] will result in exit code of 201.

    If exit attribute is false, will simply return the action result ($r->{res}). And will also set exit code in $r->{res}[3]{'x.perinci.cmdline.base.exit_code'}.

PROGRAM FLOW (TAB COMPLETION)

If program is detected running in tab completion mode, there is some differences in the flow. First, @ARGV is set from COMP_LINE (or COMMAND_LINE) environment variable. Afterwards, completion is done by calling Perinci::Sub::Complete's complete_cli_arg.

The result is then output to STDOUT (resume from Run hook_format_result step in the normal program flow).

REQUEST KEYS

The various values in the $r hash/stash.

  • orig_argv => array

    Original @ARGV at the beginning of run().

  • common_opts => hash

    Value of common_opts attribute, for convenience.

  • action => str

    Selected action to use. Usually set from the common options.

  • format => str

    Selected format to use. Usually set from the common option --format.

  • read_config => bool

    This is set in run() to signify that we have tried to read config file (this is set to true even though config file does not exist). This is never set to true when read_config attribute is set, which means that we never try to read any config file.

  • read_env => bool

    This is set in run() to signify that we will try to read env for default options. This settng can be turned off e.g. in common option no_env. This is never set to true when read_env attribute is set to false, which means that we never try to read environment.

  • config => hash

    This is set in the routine that reads config file, containing the config hash. It might be an empty hash (if there is on config file to read), or a hash with sections as keys and hashrefs as values (configuration for each section). The data can be merged from several existing config files.

  • read_config_files => array

    This is set in the routine that reads config file, containing the list of config files actually read, in order.

  • config_paths => array of str

  • config_profile => str

  • parse_argv_res => array

    Enveloped result of parse_argv().

  • ignore_missing_config_profile_section => bool (default 1)

    This is checked in the parse_argv(). To aid error checking, when a user specifies a profile (e.g. via --config-profile FOO) and config file exists but the said profile section is not found in the config file, an error is returned. This is to notify user that perhaps she mistypes the profile name.

    But this checking can be turned off with this setting. This is sometimes used when e.g. a subclass wants to pick a config profile automatically by setting $r->{config_profile} somewhere before reading config file, but do not want to fail execution when config profile is not found. An example of code that does this is Perinci::CmdLine::fatten.

  • subcommand_name => str

    Also set by parse_argv(). The subcommand name in effect, either set explicitly by user using --cmd or the first command-line argument, or set implicitly with the default_subcommand attribute. Undef if there is no subcommand name in effect.

  • subcommand_name_from => str

    Also set by parse_argv(). Tells how the subcommand_name request key is set. Value is either --cmd (if set through --cmd common option), arg (if set through first command-line argument), default_subcommand (if set to default_subcommand attribute), or undef if there is no subcommand_name set.

  • subcommand_data => hash

    Also set by parse_argv(). Subcommand data, including its URL, summary (if exists), and so on. Note that if there is no subcommand, this will contain data for the main command, i.e. URL will be set from url attribute, summary from summary attribute, and so on. This is a convenient way to get what URL and summary to use, and so on.

  • skip_parse_subcommand_argv => bool

    Checked by parse_argv(). Can be set to 1, e.g. in common option handler for --help or --version to skip parsing @ARGV for per-subcommand options.

  • args => hash

    Also taken from parse_argv() result.

  • meta => hash

    Result of get_meta().

  • dry_run => bool

    Whether to pass -dry_run special argument to function.

  • res => array

    Enveloped result of action_ACTION().

  • fres => str

    Result from hook_format_result().

  • output_handle => handle

    Set by select_output_handle() to choose output handle. Normally it's STDOUT but can also be pipe to pager (if paging is turned on).

  • naked_res => bool

    Set to true if user specifies --naked-res.

CONFIGURATION

Configuration can be used to set function arguments as well as some common options.

Configuration is currently in the IOD (basically INI) format.

By default these paths are searched: $HOME/.config/$prog_name.conf, $HOME/$prog_name.conf, /etc/$prog_name.conf. The location can be customized from command-line option --config-path.

All existing configuration files will be read in order, and the result merged if more than one files exist.

Section names map to subcommand names. For application that does not have subcommand, you can put parameters outside any section, e.g.:

 param=val
 otherparam=val
 ...

For application that has subcommands, put parameters inside section with the same name as the subcommand name:

 [subcommand1]
 param=val
 ...

 [subcommand2]
 param2=val
 ...

Or you can also put some parameters outside the section which will be used for all subcommands:

 commonarg=val

 [subcommand1]
 param1=val
 ...

 [subcommand2]
 param2=val
 ...

A configuration file can also have (multiple) profiles, to allow multiple configuration to be stored in a single file. Section names can have "profile=PROFILENAME" suffix to mark it as belonging to a certain profile. Parameters in sections with matching "profile=PROFILENAME" will be read. Parameters in sections without any profile names will still be read. Example:

 a=0
 b=0
 d=9

 [profile=p1]
 a=1
 b=2

 [profile=p2]
 a=10
 b=20

 [subcommand1 profile=p1]
 c=3

 [subcommand1 profile=p2]
 c=1

If you run:

 % cmd subcommand1

then your subcommand1 function will get: a=0, b=0, d=9.

 % cmd subcommand1 --config-profile p1

then your subcommand1 function will get: a=1, b=2, c=3, d=9. If you run:

 % cmd subcommand1 --config-profile p2

then your subcommand1 function will get: a=10, b=20, c=30, d=9.

Parameter names map to function argument names or common option. If a common option name clashes with a function argument name, the function argument is accessible using the NAME.arg syntax. For example, log_level is a common option name. If your function also has a log_level argument, to set this function argument, you write:

 log_level.arg=blah

ATTRIBUTES

actions => array

Contains a list of known actions and their metadata. Keys should be action names, values should be metadata. Metadata is a hash containing these keys:

common_opts => hash

A hash of common options, which are command-line options that are not associated with any subcommand. Each option is itself a specification hash containing these keys:

  • category (str)

    Optional, for grouping options in help/usage message, defaults to Common options.

  • getopt (str)

    Required, for Getopt::Long specification.

  • handler (code)

    Required, for Getopt::Long specification. Note that the handler will receive <($geopt, $val, $r)> (an extra $r).

  • usage (str)

    Optional, displayed in usage line in help/usage text.

  • summary (str)

    Optional, displayed in description of the option in help/usage text.

  • show_in_usage (bool or code, default: 1)

    A flag, can be set to 0 if we want to skip showing this option in usage in --help, to save some space. The default is to show all, except --subcommand when we are executing a subcommand (obviously).

  • show_in_options (bool or code, default: 1)

    A flag, can be set to 0 if we want to skip showing this option in options in --help. The default is to 0 for --help and --version in compact help. Or --subcommands, if we are executing a subcommand (obviously).

  • order (int)

    Optional, for ordering. Lower number means higher precedence, defaults to 1.

A partial example from the default set by the framework:

 {
     help => {
         category        => 'Common options',
         getopt          => 'help|h|?',
         usage           => '--help (or -h, -?)',
         handler         => sub { ... },
         order           => 0,
         show_in_options => sub { $ENV{VERBOSE} },
     },
     format => {
         category    => 'Common options',
         getopt      => 'format=s',
         summary     => 'Choose output format, e.g. json, text',
         handler     => sub { ... },
     },
     undo => {
         category => 'Undo options',
         getopt   => 'undo',
         ...
     },
     ...
 }

The default contains: help (getopt help|h|?), version (getopt version|v), action (getopt action), format (getopt format=s), format_options (getopt format-options=s), json). If there are more than one subcommands, this will also be added: list (getopt list|l). If dry-run is supported by function, there will also be: dry_run (getopt dry-run). If undo is turned on, there will also be: undo (getopt undo), redo (getopt redo), history (getopt history), clear_history (getopt clear-history).

Sometimes you do not want some options, e.g. to remove format and format_options:

 delete $cmd->common_opts->{format};
 delete $cmd->common_opts->{format_options};
 $cmd->run;

Sometimes you want to rename some command-line options, e.g. to change version to use capital -V instead of -v:

 $cmd->common_opts->{version}{getopt} = 'version|V';

Sometimes you want to add subcommands as common options instead. For example:

 $cmd->common_opts->{halt} = {
     category    => 'Server options',
     getopt      => 'halt',
     summary     => 'Halt the server',
     handler     => sub {
         my ($go, $val, $r) = @_;
         $r->{subcommand_name} = 'shutdown';
     },
 };

This will make:

 % cmd --halt

equivalent to executing the 'shutdown' subcommand:

 % cmd shutdown

completion => code

Will be passed to Perinci::Sub::Complete's complete_cli_arg(). See its documentation for more details.

default_subcommand => str

Set subcommand to this if user does not specify which to use (either via first command-line argument or --cmd option). See also: get_subcommand_from_arg.

get_subcommand_from_arg => int (default: 1)

The default is 1, which is to get subcommand from the first command-line argument except when there is default_subcommand defined. Other valid values are: 0 (not getting from first command-line argument), 2 (get from first command-line argument even though there is default_subcommand defined).

description => str

exit => bool (default: 1)

formats => array

Available output formats.

pass_cmdline_object => bool (default: 0)

Whether to pass special argument -cmdline containing the cmdline object to function. This can be overriden using the pass_cmdline_object on a per-subcommand basis.

In addition to -cmdline, -cmdline_r will also be passed, containing the $r per-request stash/hash (see "REQUEST KEYS").

Passing the cmdline object can be useful, e.g. to call action_help(), to get the settings of the Perinci::CmdLine, etc.

program_name => str

Default is from PERINCI_CMDLINE_PROGRAM_NAME environment or from $0.

riap_client => float (default: 1.1)

Specify Riap protocol version to use. Will be passed to riap_client_args.

riap_client => obj

Set to Perinci::Access (or compatible) instance. PC::Lite uses lighter version Perinci::Access::Lite.

riap_version => float (default: 1.1)

Will be passed to Riap client constructor as well.

riap_client_args => hash

Arguments to pass to Perinci::Access constructor. This is useful for passing e.g. HTTP basic authentication to Riap client (Perinci::Access::HTTP::Client):

 riap_client_args => {handler_args => {user=>$USER, password=>$PASS}}

subcommands => hash | code

Should be a hash of subcommand specifications or a coderef.

Each subcommand specification is also a hash(ref) and should contain these keys:

  • url (str, required)

    Location of function (accessed via Riap).

  • summary (str, optional)

    Will be retrieved from function metadata at url if unset

  • description (str, optional)

    Shown in verbose help message, if description from function metadata is unset.

  • tags (array of str, optional)

    For grouping or categorizing subcommands, e.g. when displaying list of subcommands.

  • log_any_app (bool, optional)

    Whether to load Log::Any::App, default is true. For subcommands that need fast startup you can try turning this off for said subcommands. See "LOGGING" for more details.

  • use_utf8 (bool, optional)

    Whether to issue "binmode(STDOUT, ":utf8")". See "LOGGING" for more details.

  • undo (bool, optional)

    Can be set to 0 to disable transaction for this subcommand; this is only relevant when undo attribute is set to true.

  • show_in_help (bool, optional, default 1)

    If you have lots of subcommands, and want to show only some of them in --help message, set this to 0 for subcommands that you do not want to show.

  • pass_cmdline_object (bool, optional, default 0)

    To override pass_cmdline_object attribute on a per-subcommand basis.

  • args (hash, optional)

    If specified, will send the arguments (as well as arguments specified via the command-line). This can be useful for a function that serves more than one subcommand, e.g.:

     subcommands => {
         sub1 => {
             summary => 'Subcommand one',
             url     => '/some/func',
             args    => {flag=>'one'},
         },
         sub2 => {
             summary => 'Subcommand two',
             url     => '/some/func',
             args    => {flag=>'two'},
         },
     }

    In the example above, both subcommand sub1 and sub2 point to function at /some/func. But the function can differentiate between the two via the flag argument being sent.

     % cmdprog sub1 --foo 1 --bar 2
     % cmdprog sub2 --foo 2

    In the first invocation, function will receive arguments {foo=>1, bar=>2, flag=>'one'} and for the second: {foo=>2, flag=>'two'}.

Subcommands can also be a coderef, for dynamic list of subcommands. The coderef will be called as a method with hash arguments. It can be called in two cases. First, if called without argument name (usually when doing --subcommands) it must return a hashref of subcommand specifications. If called with argument name it must return subcommand specification for subcommand with the requested name only.

summary => str

tags => array of str

url => str

Required if you only want to run one function. URL should point to a function entity.

Alternatively you can provide multiple functions from which the user can select using the first argument (see subcommands).

read_env => bool (default: 1)

Whether to read environment variable for default options.

env_name => str

Environment name to read default options from. Default is from program name, upper-cased, sequences of dashes/nonalphanums replaced with a single underscore, plus a _OPT suffix. So if your program name is called cpandb-cpanmeta the default environment name is CPANDB_CPANMETA_OPT.

read_config => bool (default: 1)

Whether to read configuration files.

config_dirs => array of str

Which directories to look for configuration file. The default is to look at the user's home and then system location. On Unix, it's [ "$ENV{HOME}/.config", $ENV{HOME}, "/etc"]. If $ENV{HOME} is empty, getpwuid() is used to get home directory entry.

config_filename => str

Configuration filename. The default is program_name . ".conf". For example, if your program is named foo-bar, config_filename will be foo-bar.conf.

METHODS

$cmd->run() => ENVRES

The main method to run your application. See "PROGRAM FLOW" for more details on what this method does.

$cmd->do_completion() => ENVRES

Called by run().

$cmd->parse_argv() => ENVRES

Called by run().

$cmd->get_meta($r, $url) => ENVRES

Called by parse_argv() or do_completion(). Subclass has to implement this.

HOOKS

All hooks will receive the argument $r, a per-request hash/stash. The list below is by order of calling.

$cmd->hook_before_run($r)

Called at the start of run(). Can be used to set some initial values of other $r keys. Or setup the logger.

$cmd->hook_before_read_config_file($r)

Only called when read_config attribute is true.

$cmd->hook_after_read_config_file($r)

Only called when read_config attribute is true.

$cmd->hook_after_get_meta($r)

Called after the get_meta method gets function metadata, which normally happens during parsing argument, because parsing function arguments require the metadata (list of arguments, etc).

PC:Lite as well as PC:Classic use this hook to insert a common option --dry-run if function metadata expresses that function supports dry-run mode.

PC:Lite also checks the deps property here. PC:Classic doesn't do this because it uses function wrapper (Perinci::Sub::Wrapper) which does this.

$cmd->hook_after_parse_argv($r)

Called after run() calls parse_argv() and before it checks the result. $r-{parse_argv_res}> will contain the result of parse_argv(). The hook gets a chance to, e.g. fill missing arguments from other source.

Note that for sources specified in the cmdline_src property, this base class will do the filling in after running this hook, so no need to do that here.

PC:Lite uses this hook to give default values to function arguments $r->{args} from the Rinci metadata. PC:Classic doesn't do this because it uses function wrapper (Perinci::Sub::Wrapper) which will do this as well as some other stuffs (validate function arguments, etc).

$cmd->hook_before_action($r)

Called before calling the action_ACTION method. Some ideas to do in this hook: modifying action to run ($r->{action}), last check of arguments ($r->{args}) before passing them to function.

PC:Lite uses this hook to validate function arguments. PC:Classic does not do this because it uses function wrapper which already does this.

$cmd->hook_after_action($r)

Called after calling action_ACTION method. Some ideas to do in this hook: preformatting result ($r->{res}).

$cmd->hook_format_result($r)

The hook is supposed to format result in $res-{res}> (an array).

All direct subclasses of PC:Base do the formatting here.

$cmd->hook_display_result($r)

The hook is supposed to display the formatted result (stored in $r-{fres}>) to STDOUT. But in the case of streaming output, this hook can also set it up.

All direct subclasses of PC:Base do the formatting here.

$cmd->hook_after_run($r)

Called at the end of run(), right before it exits (if exit attribute is true) or returns $r-{res}>. The hook has a chance to modify exit code or result.

SPECIAL ARGUMENTS

Below is list of special arguments that may be passed to your function by the framework. Per Rinci specification, these are prefixed by - (dash).

-dry_run => bool

Only when in dry run mode, to notify function that we are in dry run mode.

-cmdline => obj

Only when pass_cmdline_object attribute is set to true. This can be useful for the function to know about various stuffs, by probing the framework object.

-cmdline_r => hash

Only when pass_cmdline_object attribute is set to true. Contains the $r per-request hash/stash. This can be useful for the function to know about various stuffs, e.g. parsed configuration data, etc.

-cmdline_src_ARGNAME => str

This will be set if argument is retrieved from file, stdin, stdin_or_file, stdin_or_files, or stdin_line.

-cmdline_srcfilenames_ARGNAME => array

An extra information if argument value is retrieved from file(s), so the function can know the filename(s).

METADATA PROPERTY ATTRIBUTE

This module observes the following Rinci metadata property attributes:

cmdline.default_format => STR

Set default output format (if user does not specify via --format command-line option).

RESULT METADATA

This module interprets the following result metadata property/attribute:

attribute: cmdline.exit_code => int

Instruct to use this exit code, instead of using (function status - 300).

attribute: cmdline.result => any

Replace result. Can be useful for example in this case:

 sub is_palindrome {
     my %args = @_;
     my $str = $args{str};
     my $is_palindrome = $str eq reverse($str);
     [200, "OK", $is_palindrome,
      {"cmdline.result" => ($is_palindrome ? "Palindrome" : "Not palindrome")}];
 }

When called as a normal function we return boolean value. But as a CLI, we display a more user-friendly message.

attribute: cmdline.default_format => str

Default format to use. Can be useful when you want to display the result using a certain format by default, but still allows user to override the default.

attribute: cmdline.page_result => bool

If you want to filter the result through pager (currently defaults to $ENV{PAGER} or less -FRSX), you can set cmdline.page_result in result metadata to true.

For example:

 $SPEC{doc} = { ... };
 sub doc {
     ...
     [200, "OK", $doc, {"cmdline.page_result"=>1}];
 }

attribute: cmdline.pager => STR

Instruct to use specified pager instead of $ENV{PAGER} or the default less or more.

attribute: cmdline.skip_format => bool (default: 0)

When we want the command-line framework to just print the result without any formatting.

attribute: x.perinci.cmdline.base.exit_code => int

This is added by this module, so exit code can be tested.

ENVIRONMENT

  • PAGER => str

    Like in other programs, can be set to select the pager program (when cmdline.page_result result metadata is active). Can also be set to '' or 0 to explicitly disable paging even though cmd.page_result result metadata is active.

  • PERINCI_CMDLINE_PROGRAM_NAME => STR

    Can be used to set CLI program name.

HOMEPAGE

Please visit the project's homepage at https://metacpan.org/release/Perinci-CmdLine-Lite.

SOURCE

Source repository is at https://github.com/sharyanto/perl-Perinci-CmdLine-Lite.

BUGS

Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-CmdLine-Lite

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.

AUTHOR

perlancar <perlancar@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by perlancar@cpan.org.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.