pericmd 023: Subcommands

Complex/large CLI applications usually have subcommands, for example: git, svn, dzil. This lets the program be organized more neatly into separate categories. Each subcommand has their own set of options, aside from some common options. Some CLI applications even have “nested subcommands”, where a subcommand will have its own subsubcommand, and so on.

Perinci::CmdLine supports subcommands, but it currently does not support nested subcommands (I very rarely see CLI apps that have nested subcommands though).

To specify subcommands, you set the subcommands attribute to a hash of subcommand names and subcommand specs. Each subcommand spec is in turn a hash and must contain at least url (function name) and can contain summary, description, and a few other stuffs.

Here’s a full example taken from pause, a CLI app distributed with WWW::PAUSE::Simple:

use 5.010;
use strict;
use warnings;

use Perinci::CmdLine::Any;

my $prefix = '/WWW/PAUSE/Simple/';
Perinci::CmdLine::Any->new(
    url => $prefix,
    subcommands => {
        upload     => { url => "${prefix}upload_file" },
        list       => { url => "${prefix}list_files" },
        ls         => {
            url => "${prefix}list_files",
            summary => 'Alias for list',
        },
        delete     => { url => "${prefix}delete_files" },
        rm         => {
            url => "${prefix}delete_files",
            summary => 'Alias for delete',
        },
        undelete   => { url => "${prefix}undelete_files" },
        reindex    => { url => "${prefix}reindex_files" },
        password   => { url => "${prefix}set_password" },
        #'account-info' => { url => "${prefix}set_account_info" },
    },
    log => 1,
)->run;

The CLI app defines 6 subcommands (upload, list, delete, undelete, reindex, password) along with two aliases (ls for list, and rm for delete).

Let’s see the help message for this app:

% pause -h
pause - An API for PAUSE

Usage:
  pause --help (or -h, -?)
  pause --subcommands
  pause --version (or -v)
  pause [options]
Subcommands:
  delete
  list
  ls
  password
  reindex
  rm
  undelete
  upload
Options:
  --config-path=s     Set path to configuration file
  --config-profile=s  Set configuration profile to use
  --debug             Set log level to debug
  --format=s          Choose output format, e.g. json, text
  --help, -h, -?      Display this help message
  --json              Set output format to json
  --log-level=s       Set log level
  --naked-res         When outputing as JSON, strip result envelope
  --no-config         Do not use any configuration file
  --quiet             Set log level to quiet
  --subcommands       List available subcommands
  --trace             Set log level to trace
  --verbose           Set log level to info
  --version, -v       

The summary for the application (“An API for PAUSE”) is taken from this package variable in WWW::PAUSE::Simple:

$SPEC{':package'} = {
    v => 1.1,
    summary => 'An API for PAUSE',
};

This is referred to from the url attribute of the Perinci::CmdLine::Any object, which is set to /WWW/PAUSE/Simple/ (this refers to what is called the package entity, instead of function entity, and the above $SPEC{':package'} hash supplies the Rinci metadata for the package.

As explained before, each subcommand’s url refers to a function entity. For example the delete subcommand has its url set to /WWW/PAUSE/Simple/delete_files, and we have a subroutine delete_files in WWW::PAUSE::Simple, along with its metadata:

$SPEC{delete_files} = {
    v => 1.1,
    summary => 'Delete files',
    description => <<'_',

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.

_
    args => {
        %common_args,
        %file_arg,
    },
};

Information in this metadata is used when we ask for help message for this subcommand:

% pause delete -h
pause delete - Delete files

Usage:
  pause delete --help (or -h, -?)
  pause delete --version (or -v)
  pause delete [options] <file> ...

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.
Options:
  --config-path=s     Set path to configuration file
  --config-profile=s  Set configuration profile to use
  --debug             Set log level to debug
  --file-json=s       File name/wildcard pattern (JSON-encoded)
  --file=s@*          File name/wildcard pattern (=arg[0-])
  --format=s          Choose output format, e.g. json, text
  --help, -h, -?      Display this help message
  --json              Set output format to json
  --log-level=s       Set log level
  --naked-res         When outputing as JSON, strip result envelope
  --no-config         Do not use any configuration file
  --password=s*       PAUSE password
  --quiet             Set log level to quiet
  --trace             Set log level to trace
  --username=s*       PAUSE ID
  --verbose           Set log level to info
  --version, -v       

The same will be shown for rm:

% pause rm --help
pause rm - Alias for delete

Usage:
  pause rm --help (or -h, -?)
  pause rm --version (or -v)
  pause rm [options] <file> ...

When a file is deleted, it is not immediately deleted but has
scheduled_for_deletion status for 72 hours, then deleted. During that time, the
file can be undeleted.
Options:
  --config-path=s     Set path to configuration file
  --config-profile=s  Set configuration profile to use
  --debug             Set log level to debug
  --file-json=s       File name/wildcard pattern (JSON-encoded)
  --file=s@*          File name/wildcard pattern (=arg[0-])
  --format=s          Choose output format, e.g. json, text
  --help, -h, -?      Display this help message
  --json              Set output format to json
  --log-level=s       Set log level
  --naked-res         When outputing as JSON, strip result envelope
  --no-config         Do not use any configuration file
  --password=s*       PAUSE password
  --quiet             Set log level to quiet
  --trace             Set log level to trace
  --username=s*       PAUSE ID
  --verbose           Set log level to info
  --version, -v       

Subcommand name is taken from the first (non-option) argument. Common options (like –format, –help, –version, etc) will be parsed before getting subcommand name, so you can also say pause --help upload aside from pause upload --help.

Unknown subcommand will of course result in an error:

% pause foo
ERROR 500: Unknown subcommand: foo

Subcommand must always be specified, unless when we define a default subcommand (this will be covered in a next blog post).

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s