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.
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 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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 :
1 2 3 4 5 6 7 8 9 10
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
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);
}