docs/cmdopts.mdwn
author Myhailo Danylenko <isbear@isbear.org.ua>
Fri, 09 Dec 2016 03:14:57 +0200
changeset 93 d59df8a9413d
parent 82 06d4a9185902
permissions -rw-r--r--
[cmake] Fix ESCDELAY detection


**New commands interface for MCabber**

[[!toc levels=2]]

# Overview

New command interface was designed with next goals in mind:

 * Share as much argument checking code as possible.
 * Remove cumbersome parsing code from commands.
 * Encourage adding options and switches to commands.
 * Use the same rules, when handling arguments everywhere.
 * Integrate and improve completion system.
 * Add common generic stuff, like '--help'.
 * Try to still be lightweight.
 * Try to still be readable.

It is built around static structure, "command description".  User can add or
remove these structures to list of commands.  *FIXME* more

## Command description struct, 'cmdopts_t'

[[!format c """// --------------------------------------------------------------

typedef struct cmdopts_struct cmdopts_t;

typedef enum {
    cmd_default = 0x0000,
    cmd_safe    = 0x0001,
} cmd_flags_t;

struct cmdopts_struct {
    const char          *name;
    const cmd_flags_t   flags;
    const cmd_checker_t check;
    const cmd_handler_t handle;
    const cmdopt_t      *opts;
    const cmdarg_t      *args;
    const cmdopts_t     *cmds;
    const gpointer      userdata;
    size_t              valno;
};

// --------------------------------------------------------------     """     ]]

This struct describes command as a whole and links to argument descriptions.
This struct is also used to describe individual subcommands, as they are quite
similar to normal command, because they can have their own options, arguments
and subcommands.  The fields of this struct:

 * 'name' - name of the command or subcommand.

 * 'flags' - currently there's only one flag:

    + 'cmd_safe' -  command is safe to execute in main mcabberrc during
      initialization.  Have no meaning for subcommands.
    + 'cmd_default' - default value of no flags enabled.
 
 * 'check' - execution environment checker for command.  This callback is used
   to do general checks before even parsing command line.  You can write your
   own checker or use standard ones, for example - 'cmd_check_online', that
   checks, if you are currently online.

 * 'handle' - command function.  It is executed only if command line was
   successfully parsed and argument values passed the type checking.  Unused in
   subcommands.

 * 'opts' - pointer to the array of 'cmdopt_t' structs, describing command-line
   options ("-f bar") and switches ("-x"), that this command accepts.  Array
   must end with option, that have 0 as short option character.
 
 * 'args' - similarly, pointer to the array of 'cmdarg_t' structs, that describe
   command-line positional arguments (in order).  Array must end with argument,
   that have NULL name.
 
 * 'cmds' - pointer to the array of subcommands of this command (or subcommand).
   How parser switches to subcommands we will describe later.  Array must end
   with subcommand, that have NULL name.
 
 * 'userdata' - arbitrary pointer, where you can put some data, that should
   accompany this command or subcommand.  Unused by parser.

 * 'valno' - this is internal value, that is initialized at command definition
   time, you should not modify it.  Currently unused in subcommands.

## Command function, 'cmd_handler_t'

[[!format c """// --------------------------------------------------------------

typedef gchar *(*cmd_handler_t) (const cmdopts_t *command,
                                 cmdarg_value_t  *values);

// --------------------------------------------------------------     """     ]]

Command function is passed it's command definition struct (mainly to give it
access to userdata) and dynamically allocated array of parsed argument values.

It should return NULL in case all went smoothly, or dynamically allocated error
string, that will be g_free()'d after displaying.

So, as you can see, command definition should give parser info on what can
appear in command line and how to map all these options and arguments to array
of values.

## Option description struct, 'cmdopt_t'

[[!format c """// --------------------------------------------------------------

typedef struct {
    const char stortopt;
    const char *longopt;
    cmdarg_t   arg;
} cmdopt_t;

// --------------------------------------------------------------     """     ]]

This struct just adds short option character and long option name to generic
argument struct, that we'll look at right now.

## Argument description struct, 'cmdarg_t'

[[!format c """// --------------------------------------------------------------

typedef struct cmdarg_struct cmdarg_t;

typedef enum {
    cmdarg_default  = 0x0000,
    cmdarg_catchall = 0x0001,
    cmdarg_plain    = 0x0002,
    cmdarg_check    = 0x0004,
    cmdarg_required = 0x0008,
    cmdarg_subcmd   = 0x0010,
    cmdarg_switch   = 0x0020,
    cmdarg_eol      = 0x0003, // catchall + plain
    cmdarg_chreq    = 0x000C, // check + required
    cmdarg_special  = 0x0030, // subcmd + switch
    cmdarg_trigger  = 0x0024, // switch + check
} cmdarg_flags_t;

struct cmdarg_struct {
    const char           *name;
    const guint          pos;
    const cmdarg_flags_t flags;
    const char           *defval;
    const cmdarg_type    *type;
    gconstpointer        chkdata;
    gconstpointer        userdata;
};

// --------------------------------------------------------------     """     ]]

This struct stores information about mapping between command-line entity
(switch, option, argument, subcommand) and element in 'values' array.  First,
let's briefly describe fields, and then walk through their use in different
entities.

Fields:

 * 'name' - argument name, mainly used for help (not implemented yet) and error
   messages.
 
 * 'pos' - this is the index in 'values' array, where argument value should be
   stored.
 
 * 'flags' - various tweaks to parsing process:

    + 'catchall' - argument value will catch everything, remaining on command
      line, even if unescaped spaces will appear in it.
    + 'plain' - do not treat backslashes ('\') and quotes ('"') as escaping
      characters.
    + 'check' - call argument value checker (defined in 'type' field), even if
      argument was not assigned the value during the parsing process.
    + 'required' - if mentioned checker will return error, with this flag set,
      this error will be considered fatal, and command function will not be
      called.
    + 'subcmd' - this argument is subcommand.
    + 'switch' - this argument is part of option definition, and this option
      have no argument (but it still needs to store switch state somewhere).
    + 'eol' - shortcut for "rest of command line without modification".
    + 'chreq' - shortcut for "argument must have a valid value".
    + 'special' - this is special type of argument - subcommand or switch.
 
 * 'defval' - default value for argument in "unchecked" form - i.e., in the form
   of string, as it appears on command line (we'll discuss type checkers and
   what can they do to value later).  Before parsing command line, parser will
   assign this value to corresponding value structure.

 * 'type' - pointer to structure, describing argument type.

 * 'chkdata' - if type needs some additional info, it is a place to supply it.

 * 'userdata' - place for arbitrary info, that should accompany this argument or
   option.  Unused by parser.

Now, let's discuss general case - option with argument or positional argument.

When parser encounters such argument, it checks 'catchall' and 'plain' flags and
crops argument value from command line accordingly, then it assigns it to the
'value.arg' field of 'cmdarg_value_t' struct in array 'values' with index, given
in 'pos'.  Also it marks such value as 'visited' and puts a link to argument
description into value's 'src' field.  More about effect of these actions later,
in 'cmdarg_value_t' section.

However, if argument struct is a part of option description struct, and have
flag 'switch' set, parser will not try to parse value.  Instead it will increase
the count in 'value.swc' in corresponding 'cmdarg_value_t' struct.  Argument
name for switches is essentially ignored, but for safety sake it is recommended
to duplicate switch long option name here.  Flags 'catchall' and 'plain'
obviously have no effect.  Flag 'check' makes switch a trigger, that flips
between 'on' and 'off' for every occurence of flag on command line.  Flags
'required' and 'subcmd' also have no effect for obvious reasons.  Default value
is ignored for switches, they always start with count 0 (off).  Since switch is
internal type, argument type field is ignored as well.  Flags and source fields
of value are updated as usual.

If flag 'subcmd' is set for positional argument, parser crops argument value
according to flags as usual, but instead of assigning it, walks through list of
subcommands in command description struct and compares obtained value to
subcommand names.  If it finds corresponding subcommand, it assigns pointer to
subcommand description struct to 'value.cmd' field of corresponding
'cmdarg_value_t' struct and updates it's source and flags.  Then, instead of
proceeding with parsing process, it recursively calls parser on remaining part
of command line, passing subparser subcommand description.  Note, that if
subcommand parser will end parsing before hitting end of command line, parser
will proceed with parsing arguments for main command.  Default value and
argument type fields are ignored for subcommands.

Now let's take a look at value structure, that you'll be dealing with the most
in the actual code.

## Argument value structure, 'cmdarg_value_t'

[[!format c """// --------------------------------------------------------------

typedef struct cmdarg_value_struct cmdarg_value_t;

typedef enum {
    cmdval_default = 0x0000,
    cmdval_visited = 0x0100,
    cmdval_freeme  = 0x0200,
} cmdval_flags_t;

struct cmdarg_value_struct {
    const cmdarg_t *src;
    cmdval_flags_t flags;
    union {
        guint           uint;
        gint            sint;
        guint           swc;
        const gchar     *roarg;
        gchar           *arg;
        const cmdopts_t *cmd;
        struct {
            gpointer    bud;
            gchar       *resource;
        } rjid;
        gpointer        ptr;
    } value;
};

// --------------------------------------------------------------     """     ]]

Command may happen to be called recursively - i.e., something in command may
cause the event, that will cause another call to the same command, thus we
cannot store actual values in command/argument definition struct, and have to
allocate dynamic memory for them.  This struct is designed exactly for this.
Let's take a look at it's fields:

 * 'src' - this points to the argument description, from which this struct is
   holding value right now (note, that value can be initialized several times
   during parsing process from different arguments).
 
 * 'flags' - to hold parser and typechecker marks:

    + 'visited' - parser uses this to track values, initialized from command
      line as opposed to values, holding default value.
    + 'freeme' - used by argument type checker to tell parser, that it needs to
      call value destructor callback on value.
 
 * 'value' - union, that contains various possible forms of value:

    + 'uint'  - generic unsigned integer.
    + 'sint'  - generic signed integer.
    + 'swc'   - switch occurence count.
    + 'roarg' - XXX read-only string - default value or like.
    + 'arg'   - generic string value (read-write).
    + 'cmd'   - pointer to subcommand description struct.
    + 'rjid'  - roster jid value - pair of roster buddy and resource string.
    + 'ptr'   - used for anything, that does not fits into these types.

To better understand, how these fields are used, let's walk through parsing
process.

## Parsing

Parser starts by allocating memory for values array, and then initializing it by
walking through command description, looking at options and arguments and
assigning default values to corresponding entries in array.  It also puts
pointer to the argument description into value's 'src' field.  Thus, all used in
command description values will have this field initialized, even if they were
not specified on command line.  This comes handly later, when checking for
reqired value presence.  For switches parser just sets the counter to zero.
Note, that parser does not descend into subcommands at this stage.  It does the
same procedure for subcommand, but later, when it already knows which subcommand
is selected.  Also note, that if several arguments have the same value index,
parser will use latest encountered one to initialize the value.  This is used
for default value in "argument clustering", that I'll show you later.

Then parser calls command environment checker callback (if present), and if it
returns error - terminates the process right now.  Note, that subcommands can
also have checkers.

Next parser does its job of parsing command line.  Each time it extracts
argument value, it into 'value.arg' field of corresponding value entry and
pointer to argument description struct into 'src' field.  Also it sets 'visited'
flag on value.  At this stage value is still just unchecked string, except for
special argument types.  For switch occurence count in 'value.swc' gets
increased each time argument was specified.  Note however, that if several
switches use the same value index ("clustered switches"), counter gets reset,
when switches change one another in sequence - i.e. "-e -s -s -s" will count as
three "-s", but "-s -s -e -s" will count as only one "-s".  For subcommands
parser checks for corresponding subcommand in 'cmds' list, assigns it to
'value.cmd' and recursively passes the end of command line to be parsed with
subcommand description.  Note, that for subcommands parser does "checking on the
spot" - if parsed argument value does not match any subcommand, and argument
have 'required' flag set, it raises error immediately (if flag is not set, it
merely assigns NULL and proceeds parsing according to current command
description).

Then parser walks through array of values and performs value checking.  Note,
that all values, that need checking at this point should have 'src' field
initialized - either set at default value assignment step, or during parsing,
so, parser knows properties of value's argument.  Parser only pays attention to
the values, that either have 'visited' flag set (i.e. provided by user) or that
have 'check' flag in argument description (useful for mandatory arguments or
default values, that need convesion).  If value corresponds to a switch, and
argument have 'check' flag set, switch occurence count is replaced by remainder
of division it by 2 (this way switch behaves like trigger).  If it is a
subcommand, and it have 'required' flag set, parser checks, if it have non-NULL
value.  If it is usual argument (option or positional), and it does have 'type',
that have 'check' callback set, parser calls this checker, passing it value
structure (again, value structure contains pointer to argument description, so,
checker can access 'chkdata' field, supplied by user).  If checker returns error
string and argument have 'required' flag set, parser raises error.  If flag is
not set, parser just prints warning and proceeds with checking.

If checking was successful, parser calls command function, providing it with
command description and values array.  This function can also return error, but
at this stage it does not change process, only causes error message to be
printed.

And finally parser frees allocated resources - walks through values array,
calling argument type 'free' callback on values, that have 'freeme' flag set and
then frees the array itself.

## Argument type, 'cmdarg_type_t'

[[!format c """// --------------------------------------------------------------

typedef struct cmdarg_type_struct cmdarg_type_t;

typedef gchar *(*cmdarg_checker_t) (cmdarg_value_t *value);
typedef void (*cmdarg_destructor_t) (cmdarg_value_t *value);
// FIXME: this one is still not designed
typedef void (*cmdarg_completor_t) (void);

struct cmdarg_type_struct {
    cmdarg_checker_t    check;
    cmdarg_destructor_t free;
    cmdarg_completor_t  complete;
};

// --------------------------------------------------------------     """     ]]

As you can see, argument type is nothing more than a set of callbacks:

 * 'check' - check parsed argument value for conformance to type rules, possibly
   replace with something different, like corresponding integer value or roster
   buddy pointer.

 * 'free' - if type checker may need to free some data afterwards, this callback
   should be set to corresponding function, and each time checker really needs
   value to be freed, it should set flag 'freeme' on value.

 * 'complete' - *FIXME* not yet designed callback, that will return list of
   possible completions according to given prefix.

After parsing command line parser performs argument value checking, that's where
it calls 'check' callbacks.   Checker is given pointer to value structure, that
it needs to check.  Checker can modify value string (except when it is default
value, but you have to supply your default values so, that they do not need
modifying) or completely replace it with another string or even non-string
object.  If checker uses some resources (eg.  allocates memory for replacement
value), it can set the flag 'freeme' on value to request call to value
destructor, when values array will be freed.  If checker needs some additional
data (eg. it is some generic checker, that needs list of valid values or other
parameters), these data can be supplied in 'chkdata' field.  Checker function
should return NULL on success or error string, that will be g_free()'d by
parser.  Take note, that if argument does not have 'reqired' flag set, parser
will ignore checker error, so, it is recommended to nullify invalid value before
returning error (but it is not required).

# Examples

When writing description for a command, first thing, you have to do - is to
determine, which values your command can get from user.  You don't have to be
shy - new interface is designed to encourage commands to be as flexible and
option-rich as possible.  

Second - consider, which ones are to be specified as positional arguments, and
which should become options or switches.

Next you will want to decide, which checks and restrictions should you put on
values.  Essentially, determine value type.

And then you can begin writing command definition.  So, let's start with
something simple.

## Single-argument no-checks command

Let's look at command like /say, that have only one argument, that should be
passed as is, and with no restrictions:

    /ex1 message...

Definition for such command will look like:

[[!format c """// --------------------------------------------------------------

// command function predeclaration
gchar *do_ex1 (const cmdopts_t *command, cmdarg_value_t *values);

// command arguments definition
cmdopts_t def_ex1 = {
    "ex1",              // command name
    cmd_default,        // flags
    NULL,               // checker
    do_ex1,             // command function
    NULL,               // list of options - none
    (cmdarg_t[2]){      // list of arguments
        {
            "message",  // argument name
            0,          // value index
            // flags:
            // - plain:    do not interpret quotes and escapes
            // - catchall: do not end argument on unescaped spaces
            cmdarg_plain | cmdarg_catchall,
            NULL,       // default value
            NULL,       // argument type
        },
        {NULL}          // this is an argument list end marker
    },
    NULL,               // list of subcommands - none
};

// Command function gets shown above command description (we don't need it) and
// argument value list.  Returns error message or NULL.
gchar *do_ex1 (const cmdopts_t *command, cmdarg_value_t *values)
{
    gchar *message = values[0].value.arg;
    // now do something with message:
    // - check, if message was given at all
    if (!message || !*message)
        // return error, it will be printed, prepended by command name
        return g_strdup("You need to specify a message!");
    // - use the value
    scr_log_print (LPRINT_NORMAL, "Got the message: \"%s\".", message);
    // no error occured
    return NULL;
}

...
// register our command
cmd_define (&def_ex1);
...
// remove command
cmd_undef (&def_ex1);
...

// --------------------------------------------------------------     """     ]]

A lot of things to do to achieve a simple goal - does not look quite appealing
so far.  Still, let's tweak our example a bit.

Remember the third step - decide, which checks should apply to our argument.
Now, look at our command - we check, if message is NULL or if message is empty.
But imagine, that user has given us a message " " - it's of no big use to us,
so, probably, we should also strip leading/trailing spaces before doing the
check.  That's where argument types come into play.  We can write argument
checker for that! But luckily, we already have built-in standard checker, that
does exactly what we need - checks if string contains non-space characters.  All
we need to do - to specify '&cmdarg_type_nonspace' as argument type and remove
our check inside of the command:

[[!format c """// --------------------------------------------------------------

...
cmdopts_t def_ex1 = {
    "ex1",
    cmd_default,
    NULL,
    do_ex1,
    NULL,
    (cmdarg_t[2]){
        {
            "message",
            0,
            // flags:
            // - plain: do not interpret quotes and escapes
            // - catchall: do not end argument on unescaped spaces
            // - check: always invoke checker, even if user omitted the argument
            // - required: terminate processing, if checker returns error
            // a lot of flags, but we can use shortcuts:
            // - eol = plain + catchall: get everything to the end of line as is
            // - chreq = check + required: argument needs to have a valid value
            cmdarg_eol | cmdarg_chreq,
            NULL,
            // strip spaces, check if result have non-zero length
            &cmdarg_type_nonspace,
        },
        {NULL}
    },
    NULL,
};

gchar *do_ex1 (const cmdopts_t *command, cmdarg_value_t *values)
{
    scr_log_print (LPRINT_NORMAL, "Got the message: \"%s\".",
                   values[0].value.arg);
    return NULL;
}
...

// --------------------------------------------------------------     """     ]]

Ok, that's a little bit better.  Now let's move on to something more complex.

## Switches

Let's add switches '-s' and '-l', that will define, where to print the message
to - to log or to screen.  For that we will need another two value indices - one
for each switch.

[[!format c """// --------------------------------------------------------------

...
cmdopts_t def_ex1 = {
    "ex1",
    cmd_default,
    NULL,
    do_ex1,
    (cmdopt_t[3]){
        {                    // first switch: [-s|--screen]
            's',             // short option name
            "screen",        // long option name
            {
              "screen",      // argument name - unused
              1,             // value position
              // flags:
              // - switch: this is switch
              cmdarg_switch,
              NULL,          // default value - unused
              NULL,          // type - unused
            }
        },
                             // second switch: [-l|--log]
        { 'l', "log", { "log", 2, cmdarg_switch, NULL, NULL } },
        {0}                  // end of list marker
    },
    (cmdarg_t[2]){
        { "message", 0, cmdarg_eol | cmdarg_chreq, NULL,
                                                   &cmdarg_type_nonspace },
        {NULL}
    },
    NULL,
};

gchar *do_ex1 (const cmdopts_t *command, cmdarg_value_t *values)
{
    // default value
    guint whereto = LPRINT_NORMAL;
    // -s is default, so, check, that it is not set before checking for -l
    if (!values[1].value.swc)
        if (values[2].value.swc)
            whereto = LPRINT_LOG;
    scr_log_print (whereto, "Got the message: \"%s\".", values[0].value.arg);
    return NULL;
}
...

// --------------------------------------------------------------     """     ]]

Ok, that works, but what if user have aliases, and wants last specified option
to override the value? Currently, if -s was once specified, -l will not have any
effect, regardless of count or position in command line.  Not that good.  Let's
use the trick, that I call "argument clustering".  We'll specify the same value
index for both switches.  Since 'value' struct have the pointer to the argument,
it was initialized from last time, we can recognize, which switch was used last.
By default this pointer points to the last argument with this index in command
definition - we can use that to specify default value.  Now, to identify
switches we can use argument names, but 'argument' struct contains userdata
field, where we can put our LPRINT_* constants and just use it directly.  So,
with clustered switches, we will have:

[[!format c """// --------------------------------------------------------------

...
cmdopts_t def_ex1 = {
    "ex1",
    cmd_default,
    NULL,
    do_ex1,
    (cmdopt_t[3]){
        // Set both argument indices to 1, specify our constants in userdata
        // field.  Screen is default value, thus, it goes last.
        { 'l', "log",    { "log",    1, cmdarg_switch, NULL, NULL, NULL,
                             (gpointer)LPRINT_LOG    } },
        { 's', "screen", { "screen", 1, cmdarg_switch, NULL, NULL, NULL,
                             (gpointer)LPRINT_NORMAL } },
        {0}
    },
    (cmdarg_t[2]){
        { "message", 0, cmdarg_eol | cmdarg_chreq, NULL,
                                                   &cmdarg_type_nonspace },
        {NULL}
    },
    NULL,
};

gchar *do_ex1 (const cmdopts_t *command, cmdarg_value_t *values)
{
    scr_log_print ((guint)values[1].src -> userdata,
                   "Got the message: \"%s\".", values[0].value.arg);
    return NULL;
}
...

// --------------------------------------------------------------     """     ]]

That's much better.  This trick may be quite useful not only with switches, but
also with options, sometimes even clustering options with arguments can be
handy.

## Options

Options are not much different from normal arguments, except there you'll see
'check' and 'required' mostly only in combination with default value - otherwise
it defeats the purpose - to be optional.

[[!format c """// --------------------------------------------------------------

// TODO:
//   example (not really used as of now - were too complex to deal using old
//   interface).

// --------------------------------------------------------------     """     ]]

## Subcommands

Now, let's discuss another internal argument type - subcommand.  Since
subcommands are quite common in mcabber, and since they have quite big impact on
parsing process, they were made a part of parser.

Currently, only positional arguments can be subcommands.  You can have options or
other arguments precede them, though in practice there's no examples of that so
far.

So, to indicate, that argument is a subcommand, you just add flag 'subcmd'.
When parser will encounter such argument, it will look up command structure with
specified name in the list 'cmds' of command definition and proceed parsing,
using that command definition instead of main one.  A good example of command
with several completely different subcommands would be '/color', so, let's look:

[[!format c """// --------------------------------------------------------------

static gchar *do_color (const cmdopts_t *command, cmdarg_value_t *values);

// We will put these values in subcommand definition 'userdata' fields
// to simplify the task of determining, which subcommand was actually selected.
typedef enum {
    scmd_color_roster,
    scmd_color_mucnick,
    scmd_color_muc,
} scmd_color_t;

// We define value inedxes as enum to make value access expressions
// self-explanatory.
typedef enum {
    pos_color_scmd          = 0,
    pos_color_roster_status = 1,
    pos_color_roster_jid    = 2,
    pos_color_roster_color  = 3,
    pos_color_muc_room      = 1,
    pos_color_muc_mode      = 2,
    pos_color_nick_nick     = 1,
    pos_color_nick_color    = 2,
} pos_color_t;

// This is a helper struct for cmdarg_type_string2enum checker.
// The checker will compare value to strings and replace value.arg
// with corresponding value.uint.
static const string2enum_t s2e_color_muc[] = {
    { "on",     MC_ALL    },
    { "off",    MC_OFF    },
    { "preset", MC_PRESET },
    { "-",      MC_REMOVE },
    { NULL,     0         },
};

static cmdopts_t def_color = {
    "color",
    // This command is allowed in main config file during initialization, thus
    // it have the 'safe' flag.
    cmd_safe,
    NULL,
    do_color,
    // no options
    NULL,
    // only one argument - subcommand
    (cmdarg_t[2]){{ "subcommand", pos_color_scmd, cmdarg_subcmd | cmdarg_chreq,
                                                           NULL, NULL },{NULL}},
    // three subcommands
    (cmdopts_t[4]){
        // First subcommand - 'roster' with three arguments.
        {"roster", cmd_default, NULL, NULL, NULL, (cmdarg_t[4]){
              { "statusmask|clear", pos_color_roster_status, cmdarg_chreq, NULL,
                           &cmdarg_type_color_statusmask, (gpointer)"ofdna_?" },
              { "jidmask",          pos_color_roster_jid,    cmdarg_check, NULL,
                           &cmdarg_type_bjidmask },
              { "color|-",          pos_color_roster_color,  cmdarg_check, NULL,
                           &cmdarg_type_color    },
              {NULL}
            },
            // Subcommand can have its own subcommands, but in this case we
            // don't have them.
            NULL,
            // We use userdata to determine, which subcommand was selected.
            (gpointer)scmd_color_roster},
        // Second subcommand - 'muc' with two arguments.
        {"muc", cmd_default, NULL, NULL, NULL, (cmdarg_t[3]){
              { "roomjid",         pos_color_muc_room, cmdarg_chreq, NULL,
                           &cmdarg_type_color_roomjid },
              { "on|off|preset|-", pos_color_muc_mode, cmdarg_chreq, "on",
                           &cmdarg_type_string2enum, (gpointer)s2e_color_muc },
              {NULL}
            }, NULL, (gpointer)scmd_color_muc},
        // Third subcommand - 'mucnick' also with two arguments.
        {"mucnick", cmd_default, NULL, NULL, NULL, (cmdarg_t[3]){
              { "nick",    pos_color_nick_nick,  cmdarg_chreq, NULL,
                           &cmdarg_type_nick  },
              { "color|-", pos_color_nick_color, cmdarg_chreq, NULL,
                           &cmdarg_type_color },
              {NULL}
            }, NULL, (gpointer)scmd_color_mucnick},
        {NULL}
    },
};

static gchar *do_color (const cmdopts_t *options, cmdarg_value_t *values)
{
    scmd_color_t subcmd =
                  (scmd_color_t) (values[pos_color_scmd].value.cmd -> userdata);

    if (subcmd == scmd_color_roster) {
        const gchar *status   = values[pos_color_roster_status].value.arg;
        const gchar *wildcard = values[pos_color_roster_jid].value.arg;
        const gchar *color    = values[pos_color_roster_color].value.arg;
        if (!strcmp(status, "clear")) { // Not a color command, clear all
            scr_roster_clear_color();
            update_roster = TRUE;
        } else {
            // Unfortunately, due to "clear" case not taking any arguments,
            // we cannot check for argument presence using 'required' flag.
            if ((!wildcard) || (!color)) {
              return g_strdup ("Missing argument.");
            } else {
              update_roster = scr_roster_color(status, wildcard, color) ||
                              update_roster;
            }
        }
    } else if (subcmd == scmd_color_muc) {
        scr_muc_color ( values[pos_color_muc_room].value.arg,
                        values[pos_color_muc_mode].value.uint );
    } else { // scmd_color_mucnick
        scr_muc_nick_color( values[pos_color_nick_nick].value.arg,
                            values[pos_color_nick_color].value.arg );
    }

    return NULL;
}

// --------------------------------------------------------------     """     ]]

Here you also see a lot of new types:

 * 'color_statusmask' - specific to "/color" command wrapper over generic type
   'charset'.  This type allows only word "clear" or string, composed of
   specified in 'chkdata' field characters, in this case tese are, of course,
   "ofdna_?".

 * 'bjidmask' - this type only provides completion, otherwise it is the same as
   'nonspace'.

 * 'color' - checks value to be a valid name of mcabber color.

 * 'color_roomjid' - specific to "/color" command wrapper over type 'bjid'.
   Type allows the string "*" or a valid bare jid.

 * 'string2enum' - generic type, that converts string into unsigned integer
   value according to given in 'chkdata' dictionary of name-value pairs.

 * 'nick' - as 'bjidmask' - only provides completion.

Note, that although here I used value indexes 1 and 2 several times for
different arguments, it is not "clustering", as they are used in different
subcommands.

## Argument types

Let's take a look at simple checker, that we've encountered first - 'nonspace':

[[!format c """// --------------------------------------------------------------

// Checker gets parsed value string in 'value.arg', argument description in
// 'src' and returns error string or NULL.
gchar *cmdarg_check_nonspace (cmdarg_value_t *arg)
{
    // current value
    gchar *val = arg -> value.arg;

    // was value given at all?
    if (val) {
        // skip leading spaces
        while (isspace (*val))
            val ++;
        if (*val) { // valid arg - string contains non-space symbols
            // reassing value in case of stripped leading space
            arg -> value.arg = val;
            // strip trailing space
            while (*val)
                val ++;
            while (isspace (*val))
                val --;
            val ++;
            // Note: this needs write access, so, default value cannot contain
            // trailing spaces!
            if (*val)
                *val = '\0';
            // no error, argument is valid
            return NULL;
        }
    }

    // Returned error may be ignored by parser, if 'required' flag is not set on
    // argument, so, we nullify argument to ensure, that invalid value will not
    // be passed to command.
    arg -> value.arg = NULL;
    return g_strdup ("Non-space value required.");
}

// type definition
const cmdarg_type_t cmdarg_type_nonspace = {
    // our checker
    cmdarg_check_nonspace,
    // freeing callabck - no need for that
    NULL,
    // completion callabck - none, we can't know, what string may contain
    NULL,
};

// --------------------------------------------------------------     """     ]]

Quite simple, I hope.  Now, let's look at more complex type - 'fjid':

[[!format c """// --------------------------------------------------------------

// This checker checks syntax of fjid and expands "current-buddy" expressions
// "." and "./resource".
gchar *cmdarg_check_fjid (cmdarg_value_t *arg)
{
    gchar *error = NULL;

    // We're using nonspace checker to check our value - empty string is not a
    // valid jid.
    if (!(error = cmdarg_check_nonspace(arg))) {
        // Now, we're sure, that we have non-space string
        const char *fjid = arg -> value.arg;

        // We check for "current-buddy" expression.
        if (fjid[0] == '.' &&
                       (fjid[1] == JID_RESOURCE_SEPARATOR || fjid[1] == '\0')) {
            const char *jid;
            if (!current_buddy) {
              error = g_strdup_printf ("No buddy selected.");
            } else if (!(jid = buddy_getjid(BUDDATA(current_buddy)))) {
              error = g_strdup_printf ("Current buddy have no jid.");
            } else if (fjid[1] == '\0') {
              arg -> value.roarg = jid;
            } else {
              // We allocate value - we will need to free it, so, we as well set
              // 'freeme' flag.
              arg -> value.arg = g_strdup_printf ("%s%c%s",
                                         jid, JID_RESOURCE_SEPARATOR, fjid + 2);
              arg -> flags    |= cmdval_freeme;
            }
        // this is jid - check, that it is valid
        } else if (check_jid_syntax(fjid)) {
            error = g_strdup_printf ("Jid <%s> is invalid.", fjid);
        }
    }

    // As before, nullify value in case of error
    if (error)
        arg -> value.arg = NULL;
    return error;
}

// Free callback will be called only if freeme flag is set, so, we can
// just g_free() value without any checks.
void cmdarg_free_gfree (cmdarg_value_t *arg)
{
    g_free (arg -> value.arg);
}

const cmdarg_type_t cmdarg_type_fjid = {
    // checker
    cmdarg_check_fjid,
    // freer
    cmdarg_free_gfree,
    // completor, while possible, is not implemented, as whole completion system is
    // not yet designed.
    NULL,
};

// --------------------------------------------------------------     """     ]]

If possible, you are encouraged to re-use existing checkers - for example, bjid
checker uses fjid checker to expand "current-buddy" expressions and check
syntax, and only strips resource afterwards:

[[!format c """// --------------------------------------------------------------

gchar *cmdarg_check_bjid (cmdarg_value_t *arg)
{
    gchar *error = NULL;

    if (!(error = cmdarg_check_fjid(arg))) {
        gchar *res = strchr (arg -> value.arg, JID_RESOURCE_SEPARATOR);
        if (res != NULL)
            *res = '\0';
    }

    // Error can only happen inside fjid callback, that will nullify argument
    // for us.
    return error;
}

const cmdarg_type_t cmdarg_type_bjid = {
    cmdarg_check_bjid,
    // may need to free value - we're using fjid checker internally
    cmdarg_free_gfree,
    NULL,
};

// --------------------------------------------------------------     """     ]]

So far we've only modified string in value.  But checkers are not limited to
this, for example, uint checker performs atoi() on value and assigns resulting
number to value.uint.  Take a look at definition of cmdarg_value_t struct -
value is actually a union of different types of value.  If you need something
different from existing - you can always allocate your own struct and use
value.ptr.  However, if you think, that your case is generic enough - contact
mcabber developers, we'll consider adding more variants there.  Maybe we'll even
add your argument type to built-in types.

<!-- vim: se ts=4 sw=4 et filetype=markdown tw=80: -->