Welcome to little lamb

Code » limb » master » tree

[master] / src / doc / command.h / command.h.0.md

% limb manual
% command.h(0)
% limb 0.1.0
% 2023-07-24

# NAME

command.h - helpers for implementing command-based application


# SYNOPSIS

    #include <limb/command.h>


# DESCRIPTION

The header defines helpers to implement a command-based application.

## Types

The following types are defined :

: *main_fn*
:: Prototype for the command's main function.

## Structures

The following structures are defined :

: *struct command*
:: A structure to define a command and help showing its help screen.

## Functions

The following functions are defined :

: [dirnocommand](3)
:: To terminate a process execution showing the application's help screen, or
:: the usage line.

: [getcommandordie](3)
:: To parse the command's name and return the corresponding *struct command*.

## Macros

The following macros are defined :

: *COMMAND*(`name`, `desc`, `usage`, `help`)
:: To define a new command.

: *N_COMMANDS*
:: To be used after defining the commands, to define `n_commands` with the
:: number of commands.

# HOW DOES IT WORK?

The idea behind those helpers is to create an application that will be
command-based, that is its first argument is to be the name of the command to
run. Think [git](1) for example.

## Writing a new command

To help with the basics of such an interface, notably handling the help screens,
one can use this header. Then, to create a new command, say *test*, one could
write a file `test.c` like so :

```c
#include <limb/command.h>
#include <limb/exitcode.h>

COMMAND(test, "Short description of the test command",
        "[OPTION..] [<name>]",
" -n, --dry-run                         Dry mode, i.e. don't actually run the test\n"
);


int
test_main(int argc, const char *argv[], const char *env[], const char usage[], void *ctx_)
{
    if (argc > 1)
        diecmdusage(EX_USAGE, usage, &command_test);
    return 0;
}
```

The macro `COMMAND`() defines a new structure named `command_test` which holds
pointers to the different strings to be a short description, usage line and help
screen of the command, respectively.

It also declares the command's main function which must follow the following
prototype :

`int (*main_fn) (int argc, const char *argv[], const char *env[], const char usage[], void *ctx)`

Much like a regular's `main` function, including the environment, and adding as
extra argument the usage prefix to be shown if needed (for e.g; global options),
and a pointer intended to allow a context to be passed from the main program,
i.e. the actual `main`() function.

Though we don't do anything here, the first thing to do would likely be parsing
the command line, which would only contain options passed to this command; i.e.
specified /after/ the command's name on the command line.

Options specified /before/ would have been parsed earlier on, and might have
resulted in things set up in the given `ctx`.

And in case of usage error, [diecmdusage](3) is used to handle printing the
usage line for this command.

We now need to "register" this command, like all the others.

## Registering commands

To know which commands are available, a simple array of *struct command* needs
to be constructed. Those were defined via the *COMMAND*() macro seen earlier.

A simple `commands.c` file could look like this :

```c
#include <limb/command.h>

extern struct command command_test;

struct command *commands[] = {
    &command_test,
    0
};

N_COMMANDS;
```

Here comes the declaration of out structure/command, which is then added to the
array listing all commands. They will be listed in the same order in the
application's help screen, so one might want to keep them grouped by purposes or
alphabetically ordered.

The `N_COMMANDS` simply defines the unsigned `n_commands` variable, holding the
number of commands n the `commands` array.

At this point, we're ready to work on our main `main`().

## Main application

Now we can start working on our main file, and the actual entry point of the
application.

Here we'll have a function to handle parsing of the command line. This will
actually only process options /before/ the command's name. This is intended for
the *--help* option as well as any global options.

It will also use the [dienocommand](3) function to show the application's help
screen, automatically listing all available commands, with their descriptions,
at the end.

Lastly the [getcommandordie](3) function is used to parse the command's name,
which may have been abbreviated, and return the corresponding structure. If no
command matches the given name (or more than one does), an error and usage line
is shown and program execution terminated.

After handling the global options, the application can then simply call the
command's own `main` function, or use [diecmdhelp](3) to show the command's
help screen if needed.

```c
#include <limb/command.h>
#include <limb/exitcode.h>
#include <limb/parseopt.h>
#include <limb/output.h>

enum {
    OPT_HELP    = 1 << 0,
    OPT_GLOBAL  = 1 << 1,
};

static struct command *
parse_cmdline(int *argc, const char **argv[], unsigned *ctx)
{
    const char usage[] = "[-h] [-G] command [...]";
    const struct option options[] = {
        OPTION_ARG_REQ ('G', "global",                  0, OPTID_SHORTOPT),
        OPTION_ARG_NONE('h', "help",                    0, OPTID_SHORTOPT),
        OPTION_DONE
    };
    struct parseopt po = { 0 };

    int c;
    while ((c = parseopt(*argc, *argv, options, 0, &po))) switch (c) {
        case 'G':
            *ctx |= OPT_GLOBAL;
            break;
        case 'h':
            *ctx |= OPT_HELP;
            break;
        case -1:
            dieusage(EX_USAGE, usage);
        default:
            die(EX_SOFTWARE, "unexpected return value ", PUTMSG_INT(c), " from parseopt");
    };

    /* no command specified */
    if (po.cur == *argc)
        dienocommand(EX_USAGE, usage, (*ctx & OPT_HELP) ?
" -h, --help                            Show (command's) help screen and exit\n"
: NULL);

    *argc -= po.cur;
    *argv += po.cur;

    return getcommandordie(EX_USAGE, usage, **argv);
}

int
main(int argc, const char *argv[], const char *env[])
{
    const char usage[] = "[-G] ";
    unsigned ctx = 0;

    struct command *command = parse_cmdline(&argc, &argv, &ctx);

    if (ctx & OPT_HELP)
        diecmdhelp(0, usage, command);

    return command->main(argc, argv, env, usage, &ctx);
}
```