Fugu

Fugu::CLI(3p) Perl Library Manual Fugu::CLI(3p)

Fugu::CLI - subcommand dispatch for a command-line tool

    use Fugu::CLI;
    my $cli = Fugu::CLI->new(
        name    => 'mytool',
        usage   => '[-c file] <command>',
        options => { 'config|c=s' => 'the configuration file' },
        commands => {
            status => {
                summary => 'report the state',
                run     => sub ($cli, @argv) {
                    say 'running';
                    return Fugu::CLI::EXIT_SUCCESS;
                },
            },
        },
    );
    exit $cli->run(@ARGV);

Fugu::CLI dispatches the subcommands of a command-line tool. A tool declares its global options once and its commands in a table. The module parses the global options, finds the command, parses that command's own options, and calls it. Every command in a tool would otherwise repeat the same Getopt::Long setup.

Output has two channels, and they are not the same channel. Data that a script reads goes to standard output; a command prints it itself. Diagnostics go to the logger, which writes to standard error. Thus "mytool list --names | xargs" keeps working when the tool starts to warn about something.

new(%args) creates a dispatcher.

These are the arguments:

"commands"
The subcommand table, as a hash reference. This argument is necessary.
"name"
The program name in the usage line and in diagnostics. The default is "cli".
"options"
The global options, as a hash reference whose keys are Getopt::Long specifications and whose values describe them.
"usage"
The one line that follows "usage: <name>".
"epilogue"
Text that follows the command list in the help. Put examples here.
"log"
The logger. The default is "Fugu::Log->default".

Each entry of "commands" maps a name to a hash reference:

"run"
The body, as a code reference. This key is necessary. The body receives the dispatcher and the remaining arguments, and returns an exit code.
"usage"
The argument summary of this command.
"summary"
One line for the command list.
"options"
This command's own options, in the same form as "options".

run(@argv) parses, dispatches, and returns the exit code of the command.

An unknown command, a bad option, and a missing command all give "EXIT_INVALID_ARGS" with the usage line on standard error.

A command name of "help", and an empty argument list, print the help and return "EXIT_SUCCESS". Asking for help is not a failure. Every command takes "--help", whether it declared the option or not.

option($name) returns a parsed option value. The name is the first name of the specification, so "config|c=s" arrives as "config".

Global options and the running command's options share one namespace. A tool that gives the same name two meanings is a tool nobody can use.

options() returns every parsed option as a hash reference.

command() returns the name of the command that is running.

name() returns the program name.

log() returns the logger. A command body reports through it.

print_help($command) prints the help for one command, or for the whole tool. The help goes to standard output, because the user asked for it.

usage_error() prints the usage line on standard error.

command_usage_error($command) prints the usage line of one command on standard error.

The module defines the codes that every tool shares:

"EXIT_SUCCESS"
0
"EXIT_ERROR"
1
"EXIT_INVALID_ARGS"
2
"EXIT_CONFIG_ERROR"
3
"EXIT_TIMEOUT"
7

A caller can import "EXIT_SUCCESS" and "EXIT_ERROR" with "use Fugu::CLI qw(EXIT_SUCCESS EXIT_ERROR)".

A code that belongs to one tool is defined by that tool, not here, and starts above these.

new() returns a dispatcher. run() returns an exit code. option() returns a value or "undef". options() returns a hash reference. name() and command() return a string. log() returns a logger.

This example declares a command with its own option:

    list => {
        summary => 'list the cached images',
        usage   => '[--names]',
        options => { 'names' => 'print names only' },
        run     => sub ($cli, @argv) {
            for my $entry (@entries) {
                say $cli->option('names')
                    ? $entry->{name}
                    : sprintf('%-20s %s', $entry->{name}, $entry->{size});
            }
            return Fugu::CLI::EXIT_SUCCESS;
        },
    },

new() dies when the command table is absent, and when a command has no "run" code reference. Both are programming errors that a tool hits on its first run.

run() does not die. Every input failure gives "EXIT_INVALID_ARGS" and a usage line.

Fugu::Config, Fugu::Log, Getopt::Long

Dick Olsson <hi@senzilla.io>

The module does not catch a die from a command body. A body that must not kill the tool catches its own errors.

Global options must come before the command name, and a command's own options after it. The parser stops at the first argument that is not an option, which is how a command can take an option name that the tool also uses.

2026-10-16 OpenBSD