--- a/cmdopts.diff Mon Mar 18 02:26:50 2013 +0200
+++ b/cmdopts.diff Fri Mar 22 02:09:13 2013 +0200
@@ -37,9 +37,980 @@
* misc:
* fix help for /buffer date
+diff -r 1b0b563a81e6 mcabber/doc/commands_HOWTO.mdwn
+--- /dev/null Thu Jan 01 00:00:00 1970 +0000
++++ b/mcabber/doc/commands_HOWTO.mdwn Fri Mar 22 01:56:17 2013 +0200
+@@ -0,0 +1,967 @@
++
++**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.
++
++ * 'args' - similarly, pointer to the array of 'cmdarg_t' structs, that describe
++ command-line positional arguments (in order).
++
++ * 'cmds' - pointer to the array of subcommands of this command (or subcommand).
++ How parser switches to subcommands we will describe later.
++
++ * '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) (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_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 {
++ cmdarg_t *src;
++ cmdval_flags_t flags;
++ union {
++ guint uint;
++ gint sint;
++ guint swc;
++ const gchar *roarg;
++ gchar *arg;
++ 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 (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 (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);
++...
++
++[[!format c """ // -------------------------------------------------------------
++
++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 (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 (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 (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 (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 (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.
++
++## 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: -->
diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_buffer.txt
--- a/mcabber/doc/help/cs/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Přesune se o [n] řádků nahoru (výchozí: polovina obrazovky).
/buffer down [n]
@@ -51,7 +1022,7 @@
Přesune se na procentuální pozici n%.
diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_del.txt
--- a/mcabber/doc/help/cs/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -60,7 +1031,7 @@
Smaže aktuální kontakt ze seznamu kontaktů (rosteru) a zruší povolení oznamování o stavu daného kontaktu (autorizaci) na obou stranách.
diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_move.txt
--- a/mcabber/doc/help/cs/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [skupina]
@@ -71,7 +1042,7 @@
Tip: V módu rozhovoru lze použít "/roster alternate" pro skok na přesunutý kontakt.
diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_rename.txt
--- a/mcabber/doc/help/cs/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME jméno
@@ -83,7 +1054,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_buffer.txt
--- a/mcabber/doc/help/de/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/de/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Scrollt den Puffer um n Zeilen hoch. Gibt man keine Zahl an, scrollt er um einen halben Bildschirm
/buffer down [n]
@@ -95,7 +1066,7 @@
Springe zur Position "n" im Chatpuffer
diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_del.txt
--- a/mcabber/doc/help/de/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/de/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -104,7 +1075,7 @@
Löscht den gerade ausgewählten Buddy vom Roster. Außerdem werden die automatischen Presence Benachrichtigungen vom/zum Buddy gestoppt.
diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_move.txt
--- a/mcabber/doc/help/de/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/de/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,6 +1,7 @@
- /MOVE [groupname]
@@ -116,7 +1087,7 @@
Tipp: Wenn der Chatmodus aktiviert ist, kannst du "/roster alternate" benutzen um zu dem gerade bewegten Buddy zu springen.
diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_rename.txt
--- a/mcabber/doc/help/de/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/de/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -128,7 +1099,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_buffer.txt
--- a/mcabber/doc/help/en/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/en/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Scroll the buffer up [n] lines (default: half a screen)
/buffer down [n]
@@ -140,7 +1111,7 @@
Jump to position %n of the buddy chat buffer
diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_del.txt
--- a/mcabber/doc/help/en/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/en/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -150,7 +1121,7 @@
+Delete the current buddy or one, specified with [jid] from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_move.txt
--- a/mcabber/doc/help/en/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/en/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groupname]
@@ -161,7 +1132,7 @@
Tip: if the chatmode is enabled, you can use "/roster alternate" to jump to the moved buddy.
diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_rename.txt
--- a/mcabber/doc/help/en/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/en/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -173,7 +1144,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_buffer.txt
--- a/mcabber/doc/help/fr/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Défile vers le haut de [n] lignes (par défaut un demi écran)
/buffer down [n]
@@ -185,7 +1156,7 @@
Va à la position n% du tampon
diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_del.txt
--- a/mcabber/doc/help/fr/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -194,7 +1165,7 @@
Supprime le contact sélectionné du roster, supprime notre abonnement à ses notifications de présence et supprime son abonnement aux nôtres.
diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_move.txt
--- a/mcabber/doc/help/fr/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groupname]
@@ -205,7 +1176,7 @@
Astuce : si le mode discussion (chatmode) est activé, vous pouvez utiliser "/roster alternate" pour vous positionner sur le contact que vous venez de déplacer.
diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_rename.txt
--- a/mcabber/doc/help/fr/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nom
@@ -217,7 +1188,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_buffer.txt
--- a/mcabber/doc/help/it/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/it/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Fa scorrere indietro il buffer di [n] linee (default: metà schermo)
/buffer down [n]
@@ -229,7 +1200,7 @@
Salta alla posizione %n del buffer di chat corrente
diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_del.txt
--- a/mcabber/doc/help/it/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/it/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -238,7 +1209,7 @@
Elimina il contatto corrente dal roster, cancellando la sottoscrizione alle reciproche notifiche della propria presenza.
diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_move.txt
--- a/mcabber/doc/help/it/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/it/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [gruppo]
@@ -249,7 +1220,7 @@
Trucco: se la modalità chat è abilitata, puoi usare "/roster alternate" per spostarti sul contatto appena mosso.
diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_rename.txt
--- a/mcabber/doc/help/it/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/it/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nome
@@ -261,7 +1232,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_buffer.txt
--- a/mcabber/doc/help/nl/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Scroll de buffer [n] regels omhoog (standaard: een half scherm)
/buffer down [n]
@@ -273,7 +1244,7 @@
Spring naar positie %n in de buddy chat buffer
diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_del.txt
--- a/mcabber/doc/help/nl/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -282,7 +1253,7 @@
Verwijder de actieve buddy uit ons roster, en zet het wederzijds toezenden van status veranderingen stop.
diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_move.txt
--- a/mcabber/doc/help/nl/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [groepsnaam]
@@ -293,7 +1264,7 @@
Tip: indien chatmode actief is, kun je "/roster alternate" gebruiken om direct naar de verplaatste buddy te springen.
diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_rename.txt
--- a/mcabber/doc/help/nl/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME naam
@@ -305,7 +1276,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_del.txt
--- a/mcabber/doc/help/pl/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -314,7 +1285,7 @@
Usuwa aktualnie zaznaczoną osobę z rostera, usuwa subskrypcję powiadomienia dostępności u danej osoby oraz u nas.
diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_move.txt
--- a/mcabber/doc/help/pl/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [nazwa grupy]
@@ -325,7 +1296,7 @@
Podpowiedź: jeśli jest włączony tryb czatu, możesz użyć "/roster alternate" aby skoczyć do przeniesionej osoby.
diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_rename.txt
--- a/mcabber/doc/help/pl/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME nazwa
@@ -337,7 +1308,7 @@
+Optionally you can use one of --jid, --group or --name to select object, different from current.
diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_buffer.txt
--- a/mcabber/doc/help/ru/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Перемещает на [n] строк вверх в буфере (истории переписки) (по умолчанию: половина экрана)
/buffer down [n]
@@ -349,7 +1320,7 @@
Перемещает на позицию %n в текущем буфере (истории переписки)
diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_del.txt
--- a/mcabber/doc/help/ru/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -359,7 +1330,7 @@
+Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_move.txt
--- a/mcabber/doc/help/ru/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,6 +1,7 @@
- /MOVE [groupname]
@@ -371,7 +1342,7 @@
diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_rename.txt
--- a/mcabber/doc/help/ru/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME name
@@ -383,7 +1354,7 @@
+Для указания обьекта, отличного от текущего, можно использовать опции --jid, --group и --name.
diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_buffer.txt
--- a/mcabber/doc/help/uk/hlp_buffer.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_buffer.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_buffer.txt Fri Mar 22 01:56:17 2013 +0200
@@ -25,7 +25,7 @@
Посунути буфер вверх на n рядків (якщо не вказано - пів екрану).
/buffer down [n]
@@ -395,7 +1366,7 @@
Перейти до вказаної у процентах позиції.
diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_del.txt
--- a/mcabber/doc/help/uk/hlp_del.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_del.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_del.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,4 @@
- /DEL
@@ -405,7 +1376,7 @@
+Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_move.txt
--- a/mcabber/doc/help/uk/hlp_move.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_move.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_move.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,5 +1,6 @@
- /MOVE [група]
@@ -417,7 +1388,7 @@
Примітка: в режимі розмови можна використати "/roster alternate", щоб перейти до нового місця контакту контакту.
diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_rename.txt
--- a/mcabber/doc/help/uk/hlp_rename.txt Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_rename.txt Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_rename.txt Fri Mar 22 01:56:17 2013 +0200
@@ -1,4 +1,6 @@
- /RENAME ім'я
@@ -428,7 +1399,7 @@
+Опції --jid, --group та --name дозволяють перейменовувати об’єкти, відмінні від поточного.
diff -r 1b0b563a81e6 mcabber/mcabber/commands.c
--- a/mcabber/mcabber/commands.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/commands.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/commands.c Fri Mar 22 01:56:17 2013 +0200
@@ -19,7 +19,7 @@
* USA
*/
@@ -472,7 +1443,7 @@
+
+//static void room_bookmark(gpointer bud, char *arg);
+
-+#define BUILTIN_COUNT 8
++#define BUILTIN_COUNT 9
+static cmdopts_t def_roster,
+ def_color,
+ def_status,
@@ -481,8 +1452,8 @@
+ def_del,
+ def_group,
+ def_say;
++ def_msay,
+#if 0
-+ def_msay,
+ def_say_to,
+ def_buffer,
+ def_clear,
@@ -597,8 +1568,8 @@
+ cmd_list[5] = &def_del;
+ cmd_list[6] = &def_group;
+ cmd_list[7] = &def_say;
++ cmd_list[8] = &def_msay;
+#if 0
-+ cmd_list[8] = &def_msay;
+ cmd_list[9] = &def_say_to;
+ cmd_list[10] = &def_buffer;
+ cmd_list[11] = &def_clear;
@@ -2342,7 +3313,7 @@
struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
if (note) {
display_and_free_note(note, bjid);
-@@ -800,484 +1377,618 @@
+@@ -800,484 +1377,622 @@
}
}
@@ -2386,7 +3357,7 @@
+ NULL,
+ do_roster,
+ NULL,
-+ (cmdarg_t[2]){{"subcommand", pos_roster_scmd, cmdarg_subcmd | cmdarg_check, NULL, NULL},{NULL}},
++ (cmdarg_t[2]){{"subcommand", pos_roster_scmd, cmdarg_subcmd | cmdarg_chreq, NULL, NULL},{NULL}},
+ (cmdopts_t[25]){
+ SCMD_ROSTER(bottom, NULL),
+ SCMD_ROSTER(top, NULL),
@@ -2701,12 +3672,12 @@
+ NULL,
+ do_color,
+ NULL,
-+ (cmdarg_t[2]){{ "subcommand", pos_color_scmd, cmdarg_subcmd | cmdarg_check, NULL, NULL },{NULL}},
++ (cmdarg_t[2]){{ "subcommand", pos_color_scmd, cmdarg_subcmd | cmdarg_chreq, NULL, NULL },{NULL}},
+ (cmdopts_t[4]){
+ {"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_default, NULL, &cmdarg_type_bjidmask },
-+ { "color|-", pos_color_roster_color, cmdarg_default, NULL, &cmdarg_type_color },
++ { "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}
+ }, NULL, (gpointer)scmd_color_roster},
+ {"muc", cmd_default, NULL, NULL, NULL, (cmdarg_t[3]){
@@ -2737,7 +3708,7 @@
} else {
- if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
- scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-+ if (!wildcard || !*wildcard || !color || !*color) {
++ if ((!wildcard) || (!color)) {
+ // freaking "clear" :(
+ return g_strdup ("Missing argument.");
} else {
@@ -2858,7 +3829,8 @@
+
+typedef enum {
+ pos_status_status = 0,
-+ pos_status_message = 1,
++ pos_status_jid = 1,
++ pos_status_message = 2,
+} pos_status_t;
+
+static const string2enum_t s2e_status2[] = {
@@ -2915,7 +3887,9 @@
+ cmd_safe,
+ NULL,
+ do_status,
-+ NULL,
++ (cmdopt_t[2]){
++ {'t', "to", {"jid", pos_status_jid, cmdarg_required, NULL, &cmdarg_type_fjid}},
++ },
+ (cmdarg_t[3]){
+ {"status", pos_status_status, cmdarg_chreq, "show", &cmdarg_type_status_status, (gpointer)s2e_status2},
+ {"message", pos_status_message, cmdarg_eol, NULL, &cmdarg_type_nonspace},
@@ -2937,7 +3911,8 @@
+ if (!xmpp_is_online())
+ scr_LogPrint(LPRINT_NORMAL, "You are currently not connected...");
+ scr_check_auto_away(TRUE);
-+ xmpp_setstatus(values[pos_status_status].value.uint, NULL,
++ xmpp_setstatus(values[pos_status_status].value.uint,
++ values[pos_status_jid].value.arg,
+ values[pos_status_message].value.arg, FALSE);
}
- arg = to_utf8(arg);
@@ -3380,7 +4355,7 @@
{
char *bare_jid, *rp;
char *hmsg;
-@@ -1285,6 +1996,7 @@
+@@ -1285,6 +2000,7 @@
gint retval = 0;
int isroom;
gpointer xep184 = NULL;
@@ -3388,7 +4363,7 @@
if (!xmpp_is_online()) {
scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-@@ -1299,11 +2011,15 @@
+@@ -1299,11 +2015,15 @@
return 1;
}
if (check_jid_syntax((char*)fjid)) {
@@ -3406,7 +4381,7 @@
// We must use the bare jid in hk_message_out()
rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
if (rp)
-@@ -1354,8 +2070,7 @@
+@@ -1354,8 +2074,7 @@
// send_message(msg, subj, type_overwrite)
// Write the message in the buddy's window and send the message on
// the network.
@@ -3416,7 +4391,7 @@
{
const char *bjid;
char *jid;
-@@ -1378,34 +2093,13 @@
+@@ -1378,34 +2097,13 @@
else
jid = g_strdup(bjid);
@@ -3453,7 +4428,7 @@
scr_set_chatmode(TRUE);
scr_show_buddy_window();
-@@ -1424,80 +2118,137 @@
+@@ -1424,135 +2122,190 @@
}
buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -3498,9 +4473,69 @@
+ return NULL;
}
-+#if 0
-+
- static void do_msay(char *arg)
+-static void do_msay(char *arg)
++//
++// /msay
++//
++
++static gchar *do_msay (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++ scmd_msay_begin, scmd_msay_verbatim,
++ scmd_msay_send, scmd_msay_send_to,
++ scmd_msay_toggle, scmd_msay_toggle_verbatim,
++ scmd_msay_abort,
++} scmd_msay_t;
++
++typedef enum {
++ pos_msay_scmd = 0,
++ pos_msay_subject = 1,
++ pos_msay_jid = 1,
++ pos_msay_msgtype = 2,
++} pos_msay_t;
++
++static cmdopts_t def_msay = {
++ "msay",
++ cmd_default,
++ NULL,
++ do_msay,
++ NULL,
++ (cmdarg_t[2]){
++ { "subcommand", pos_msay_scmd, cmdarg_subcmd|cmdarg_chreq, NULL, NULL },
++ {NULL},
++ },
++ (cmdopts_t[8]){
++ { "begin", cmd_default, NULL, NULL, NULL,
++ (cmdarg_t[2]){{"subject", pos_msay_subject, cmdarg_eol, NULL, &cmdarg_type_nonspace}, {NULL}},
++ NULL, (gpointer)scmd_msay_begin },
++ { "verbatim", cmd_default, NULL, NULL, NULL,
++ (cmdarg_t[2]){{"subject", pos_msay_subject, cmdarg_eol, NULL, &cmdarg_type_nonspace}, {NULL}},
++ NULL, (gpointer)scmd_msay_verbatim },
++ { "send", cmd_default, NULL, NULL,
++ (cmdopt_t[5]){
++ {'t', "to", {"jid", pos_msay_jid, cmdarg_required, NULL, &cmdarg_type_fjid}},
++ {'n', "normal", {"normal", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_normal}},
++ {'h', "headline", {"headline", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_headline}},
++ {'d', "default", {"default", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_not_set}},
++ {0}
++ },
++ NULL, NULL, (gpointer)scmd_msay_send, 0 },
++ { "send_to", cmd_default, NULL, NULL,
++ (cmdopt_t[4]){
++ {'n', "normal", {"normal", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_normal}},
++ {'h', "headline", {"headline", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_headline}},
++ {'d', "default", {"default", pos_msay_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_not_set}},
++ {0}
++ },
++ (cmdarg_t[2]){{"jid", pos_msay_jid, cmdarg_chreq, NULL, &cmdarg_type_fjid}, {NULL}},
++ NULL, (gpointer)scmd_msay_send_to },
++ { "toggle", cmd_default, NULL, NULL, NULL, NULL, NULL, (gpointer)scmd_msay_toggle },
++ { "toggle_verbatim", cmd_default, NULL, NULL, NULL, NULL, NULL, (gpointer)scmd_msay_toggle_verbatim },
++ { "abort", cmd_default, NULL, NULL, NULL, NULL, NULL, (gpointer)scmd_msay_abort },
++ },
++};
++
++static gchar *do_msay (cmdopts_t *command, cmdarg_value_t *values)
{
- /* Parameters: begin verbatim abort send send_to */
- char **paramlst;
@@ -3517,71 +4552,20 @@
- scr_LogPrint(LPRINT_NORMAL, "(Use \"%s begin\" to enter "
- "multi-line mode...)", mkcmdstr("msay"));
- goto do_msay_return;
-+ enum msay_scmd_t {
-+ msay_scmd_begin, msay_scmd_verbatim,
-+ msay_scmd_send, msay_scmd_send_to,
-+ msay_scmd_toggle, msay_scmd_toggle_verbatim,
-+ msay_scmd_abort,
-+ } subcmd;
-+ cmdopts_t options = {
-+ "msay",
-+ NULL,
-+ (cmdarg_t[1]){
-+ // subcommand
-+ { CMDOPT_SUBCOMMAND | CMDOPT_REQUIRED | CMDOPT_LAST, { .cmd = NULL } },
-+ },
-+ (cmdopts_t[7]){
-+ { "begin", NULL,
-+ (cmdarg_t[1]){
-+ // subject
-+ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+ },
-+ NULL, (gpointer)msay_scmd_begin, 0 },
-+ { "verbatim", NULL,
-+ (cmdarg_t[1]){
-+ // subject
-+ { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+ },
-+ NULL, (gpointer)msay_scmd_verbatim, 0 },
-+ { "send",
-+ (cmdopt_t[2]){
-+ { CMDOPT_SWITCH, 'n', "normal", { .swc = 0 } },
-+ { CMDOPT_SWITCH | CMDOPT_LAST, 'h', "headline", { .swc = 0 } },
-+ },
-+ NULL, NULL, (gpointer)msay_scmd_send, 0 },
-+ { "send_to",
-+ (cmdopt_t[2]){
-+ { CMDOPT_SWITCH, 'n', "normal", { .swc = 0 } },
-+ { CMDOPT_SWITCH | CMDOPT_LAST, 'h', "headline", { .swc = 0 } },
-+ },
-+ (cmdarg_t[1]){
-+ // jid
-+ { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
-+ },
-+ NULL, (gpointer)msay_scmd_send_to, 0 },
-+ { "toggle", NULL, NULL, NULL, (gpointer)msay_scmd_toggle, 0 },
-+ { "toggle_verbatim", NULL, NULL, NULL,
-+ (gpointer)msay_scmd_toggle_verbatim, 0 },
-+ { "abort", NULL, NULL, NULL, (gpointer)msay_scmd_abort, CMDOPT_LAST },
-+ },
-+ };
+ const char *msg;
+
-+ if (cmdopts_parse(arg, &options))
-+ return;
-+
-+ subcmd = (enum msay_scmd_t) options.args[0].value.cmd -> userdata;
-+
-+ if (subcmd == msay_scmd_toggle) {
++ subcmd = (scmd_msay_t) (values[pos_msay_scmd].src -> userdata);
++
++ if (subcmd == scmd_msay_toggle) {
+ if (scr_get_multimode())
-+ subcmd = msay_scmd_send;
++ subcmd = scmd_msay_send;
+ else
-+ subcmd = msay_scmd_begin;
-+ } else if (subcmd == msay_scmd_toggle_verbatim) {
++ subcmd = scmd_msay_begin;
++ } else if (subcmd == scmd_msay_toggle_verbatim) {
+ if (scr_get_multimode())
-+ subcmd = msay_scmd_send;
++ subcmd = scmd_msay_send;
+ else
-+ subcmd = msay_scmd_verbatim;
++ subcmd = scmd_msay_verbatim;
}
- if (!strcasecmp(subcmd, "toggle")) {
@@ -3597,11 +4581,11 @@
- }
-
- if (!strcasecmp(subcmd, "abort")) {
-+ if (subcmd == msay_scmd_abort) {
++ if (subcmd == scmd_msay_abort) {
if (scr_get_multimode())
scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode.");
scr_set_multimode(FALSE, NULL);
- goto do_msay_return;
+- goto do_msay_return;
- } else if ((!strcasecmp(subcmd, "begin")) ||
- (!strcasecmp(subcmd, "verbatim"))) {
- bool verbat;
@@ -3609,10 +4593,11 @@
- if (!strcasecmp(subcmd, "verbatim")) {
- scr_set_multimode(2, subj_utf8);
- verbat = TRUE;
-+ } else if (subcmd == msay_scmd_begin || subcmd == msay_scmd_verbatim) {
-+ gchar *subject = options.args[0].value.cmd -> args[0].value.arg;
-+
-+ if (subcmd == msay_scmd_verbatim) {
++ return NULL;
++ } else if (subcmd == scmd_msay_begin || subcmd == scmd_msay_verbatim) {
++ gchar *subject = values[pos_msay_subject].value.arg;
++
++ if (subcmd == scmd_msay_verbatim) {
+ scr_set_multimode(2, subject);
} else {
- scr_set_multimode(1, subj_utf8);
@@ -3622,26 +4607,31 @@
scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.",
- verbat ? "VERBATIM " : "");
-+ subcmd == msay_scmd_verbatim ? "VERBATIM " : "");
++ subcmd == scmd_msay_verbatim ? "VERBATIM " : "");
scr_LogPrint(LPRINT_NORMAL, "Select a buddy and use \"%s send\" "
"when your message is ready.", mkcmdstr("msay"));
- if (verbat)
-+ if (subcmd == msay_scmd_verbatim)
++ if (subcmd == scmd_msay_verbatim)
scr_LogPrint(LPRINT_NORMAL, "Use \"%s abort\" to abort this mode.",
mkcmdstr("msay"));
- g_free(subj_utf8);
- goto do_msay_return;
- } else if (strcasecmp(subcmd, "send") && strcasecmp(subcmd, "send_to")) {
- scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
- goto do_msay_return;
+- goto do_msay_return;
++ return NULL;
}
- /* send/send_to command */
-+ /* msay_scmd_send or msay_scmd_send_to */
++ /* scmd_msay_send or scmd_msay_send_to */
if (!scr_get_multimode()) {
- scr_LogPrint(LPRINT_NORMAL, "No message to send. "
-@@ -1508,49 +2259,47 @@
+- scr_LogPrint(LPRINT_NORMAL, "No message to send. "
++ return g_strdup_printf ("No message to send. "
+ "Use \"%s begin\" first.", mkcmdstr("msay"));
+- goto do_msay_return;
+ }
+
scr_set_chatmode(TRUE);
scr_show_buddy_window();
@@ -3683,34 +4673,27 @@
- send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg));
- g_free(msg_utf8);
+ if ((msg = scr_get_multiline())) {
-+ msgtype_t msg_type = msgtype_not_set;
-+
-+ if (options.args[0].value.cmd -> opts[0].value.swc) // n
-+ msg_type = msgtype_normal;
-+ else if (options.args[0].value.cmd -> opts[1].value.swc) // h
-+ msg_type = msgtype_headline;
-+
-+ if (subcmd == msay_scmd_send_to) {
-+ const char *jid = options.cmds[3].args[0].value.arg;
++ msgtype_t msg_type = (msgtype_t) (values[pos_msay_msgtype].src -> userdata);
++
++ if (subcmd == scmd_msay_send_to) {
++ const char *jid = values[pos_msay_jid].value.arg;
+
+ // Let's send to the specified JID. We leave now if there
+ // has been an error (so we don't leave multi-line mode).
+ if (send_message_to(jid, msg, scr_get_multimode_subj(),
+ msg_type, FALSE))
-+ goto do_msay_return;
++ return NULL;
+ } else { // Send to currently selected buddy
+ gpointer bud;
+
+ if (!current_buddy) {
-+ scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?");
-+ goto do_msay_return;
++ return g_strdup ("Whom are you talking to?");
+ }
+
+ bud = BUDDATA(current_buddy);
+ if (!(buddy_gettype(bud) &
+ (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_ROOM))) {
-+ scr_LogPrint(LPRINT_NORMAL, "This is not a user.");
-+ goto do_msay_return;
++ return g_strdup ("This is not a user.");
+ }
+
+ buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -3720,14 +4703,17 @@
+
scr_set_multimode(FALSE, NULL);
scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode.");
-+
- do_msay_return:
+-do_msay_return:
- free_arg_lst(paramlst);
-+ cmdopts_free(&options);
++
++ return NULL;
}
++#if 0
// load_message_from_file(filename)
-@@ -1566,7 +2315,7 @@
+ // Read the whole content of a file.
+ // The data are converted to UTF8, they should be freed by the caller after
+@@ -1566,7 +2319,7 @@
char *next_utf8_char;
size_t len;
@@ -3736,7 +4722,7 @@
if (!fd || fstat(fileno(fd), &buf)) {
scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
-@@ -1634,130 +2383,103 @@
+@@ -1634,130 +2387,103 @@
static void do_say_to(char *arg)
{
@@ -3923,7 +4909,7 @@
}
// buffer_updown(updown, nblines)
-@@ -1775,27 +2497,10 @@
+@@ -1775,27 +2501,10 @@
scr_buffer_scroll_up_down(updown, nblines);
}
@@ -3951,7 +4937,7 @@
t = from_iso8601(date, 0);
if (t)
scr_buffer_date(t);
-@@ -1804,98 +2509,156 @@
+@@ -1804,98 +2513,156 @@
"not correctly formatted or invalid.");
}
@@ -4190,7 +5176,7 @@
}
static void do_info(char *arg)
-@@ -2033,29 +2796,20 @@
+@@ -2033,29 +2800,20 @@
}
}
@@ -4229,7 +5215,7 @@
// Enter chat mode
scr_set_chatmode(TRUE);
-@@ -2075,12 +2829,12 @@
+@@ -2075,12 +2833,12 @@
rstatus = buddy_getstatus(bud, p_res->data);
rst_msg = buddy_getstatusmsg(bud, p_res->data);
@@ -4244,7 +5230,7 @@
enum imrole role = buddy_getrole(bud, p_res->data);
enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
bool showaffil = (affil != affil_none);
-@@ -2096,12 +2850,12 @@
+@@ -2096,12 +2854,12 @@
snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
(char*)p_res->data);
scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
@@ -4259,7 +5245,7 @@
enum imrole role = buddy_getrole(bud, p_res->data);
enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
-@@ -2145,16 +2899,69 @@
+@@ -2145,16 +2903,69 @@
static void do_rename(char *arg)
{
@@ -4334,7 +5320,7 @@
bjid = buddy_getjid(bud);
group = buddy_getgroupname(bud);
type = buddy_gettype(bud);
-@@ -2162,11 +2969,13 @@
+@@ -2162,11 +2973,13 @@
if (type & ROSTER_TYPE_SPECIAL) {
scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
@@ -4349,7 +5335,7 @@
return;
}
-@@ -2181,90 +2990,117 @@
+@@ -2181,90 +2994,117 @@
// }
//}
@@ -4498,7 +5484,7 @@
} else {
// This is a local item, we move it without adding to roster.
guint msgflag;
-@@ -2276,7 +3112,7 @@
+@@ -2276,7 +3116,7 @@
msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
if (msgflag)
roster_msg_setflag(bjid, FALSE, FALSE);
@@ -4507,7 +5493,7 @@
if (msgflag)
roster_msg_setflag(bjid, FALSE, TRUE);
if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
-@@ -2285,8 +3121,7 @@
+@@ -2285,8 +3125,7 @@
}
}
@@ -4517,7 +5503,7 @@
update_roster = TRUE;
}
-@@ -2468,50 +3303,33 @@
+@@ -2468,50 +3307,33 @@
static void do_rawxml(char *arg)
{
@@ -4588,7 +5574,7 @@
}
// check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
-@@ -2815,6 +3633,8 @@
+@@ -2815,6 +3637,8 @@
free_arg_lst(paramlst);
}
@@ -4597,7 +5583,7 @@
void cmd_room_leave(gpointer bud, char *arg)
{
gchar *roomid, *desc;
-@@ -2833,6 +3653,8 @@
+@@ -2833,6 +3657,8 @@
g_free(roomid);
}
@@ -4606,7 +5592,7 @@
static void room_nick(gpointer bud, char *arg)
{
if (!buddy_getinsideroom(bud)) {
-@@ -2874,7 +3696,7 @@
+@@ -2874,7 +3700,7 @@
fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
g_free (nick_utf8);
msg = to_utf8(arg);
@@ -4615,7 +5601,7 @@
g_free(fjid_utf8);
g_free(msg);
free_arg_lst(paramlst);
-@@ -3052,6 +3874,8 @@
+@@ -3052,6 +3878,8 @@
free_arg_lst(paramlst);
}
@@ -4624,7 +5610,7 @@
// cmd_room_whois(..)
// If interactive is TRUE, chatmode can be enabled.
// Please note that usernick is expected in UTF-8 locale iff interactive is
-@@ -3146,6 +3970,8 @@
+@@ -3146,6 +3974,8 @@
free_arg_lst(paramlst);
}
@@ -4633,7 +5619,7 @@
static void room_bookmark(gpointer bud, char *arg)
{
const char *roomid;
-@@ -3290,6 +4116,207 @@
+@@ -3290,6 +4120,207 @@
static void do_room(char *arg)
{
@@ -4841,7 +5827,7 @@
char **paramlst;
char *subcmd;
gpointer bud;
-@@ -3347,7 +4374,7 @@
+@@ -3347,7 +4378,7 @@
cmd_room_leave(bud, arg);
} else if (!strcasecmp(subcmd, "names")) {
if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
@@ -4850,7 +5836,7 @@
} else if (!strcasecmp(subcmd, "nick")) {
if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
room_nick(bud, arg);
-@@ -4162,5 +5189,6 @@
+@@ -4162,5 +5193,6 @@
}
mcabber_set_terminate_ui();
}
@@ -4859,8 +5845,8 @@
/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
diff -r 1b0b563a81e6 mcabber/mcabber/commands.h
--- a/mcabber/mcabber/commands.h Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/commands.h Mon Mar 18 02:16:22 2013 +0200
-@@ -5,32 +5,338 @@
++++ b/mcabber/mcabber/commands.h Fri Mar 22 01:56:17 2013 +0200
+@@ -5,32 +5,345 @@
#include <mcabber/config.h>
@@ -5013,20 +5999,20 @@
+
+// command description
+struct cmdopts_struct {
-+ const char *name; // [user,req] command name (error messages, help, subcommands)
-+ cmd_flags_t flags; // [user,req] safe
-+ cmd_checker_t check; // [user,req] checker routine
-+ cmd_handler_t handle; // [user,req] main command processing function
-+ cmdopt_t *opts; // [user,req] options
-+ cmdarg_t *args; // [user,req] arguments
-+ cmdopts_t *cmds; // [user,req] subcommands
-+ gpointer userdata; // [user]
-+ size_t valno; // internal, number of values to allocate
++ const char *name; // [user,req] command name (error messages, help, subcommands)
++ const cmd_flags_t flags; // [user,req] safe
++ const cmd_checker_t check; // [user,req] checker routine
++ const cmd_handler_t handle; // [user,req] main command processing function
++ const cmdopt_t *opts; // [user,req] options
++ const cmdarg_t *args; // [user,req] arguments
++ const cmdopts_t *cmds; // [user,req] subcommands
++ gconstpointer userdata; // [user]
++ size_t valno; // internal, number of values to allocate
+};
+// positional/option argument description
+struct cmdarg_struct {
+ const char *name; // [user,req] argument name - errors, help (unused for switches, but must be initialized)
-+ const guint pos; // [user,req] value positional number
++ const size_t pos; // [user,req] value positional number
+ const cmdarg_flags_t flags; // [user,req] catchall, plain, check, required, subcommand, switch
+ const char *defval; // [user,req] default value (unused for switches)
+ const cmdarg_type_t *type; // [user,req] type cbs - checker and completor (unused for switches and subcommands)
@@ -5115,6 +6101,13 @@
+cmd_result_t process_line(const char *line);
+
+//
++// Command checkers
++//
++
++// checks if connection is available
++gchar *cmd_check_online (cmdopts_t *command, cmdarg_value_t *values);
++
++//
+// Standard argument types
+//
+
@@ -5221,7 +6214,7 @@
diff -r 1b0b563a81e6 mcabber/mcabber/hooks.c
--- a/mcabber/mcabber/hooks.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/hooks.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/hooks.c Fri Mar 22 01:56:17 2013 +0200
@@ -638,10 +638,9 @@
scr_LogPrint(LPRINT_LOGNORM, "Running hook-post-connect...");
@@ -5250,7 +6243,7 @@
diff -r 1b0b563a81e6 mcabber/mcabber/roster.c
--- a/mcabber/mcabber/roster.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/roster.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/roster.c Fri Mar 22 01:56:17 2013 +0200
@@ -1586,13 +1586,14 @@
// Look for a buddy whose name or jid contains string.
// Search begins at current_buddy; if no match is found in the the buddylist,
@@ -5291,7 +6284,7 @@
}
diff -r 1b0b563a81e6 mcabber/mcabber/screen.c
--- a/mcabber/mcabber/screen.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/screen.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/screen.c Fri Mar 22 01:56:17 2013 +0200
@@ -3630,7 +3630,7 @@
{
scr_check_auto_away(TRUE);
@@ -5361,7 +6354,7 @@
}
diff -r 1b0b563a81e6 mcabber/mcabber/settings.c
--- a/mcabber/mcabber/settings.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/settings.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/settings.c Fri Mar 22 01:56:17 2013 +0200
@@ -183,28 +183,12 @@
if ((*line == '\n') || (*line == '\0') || (*line == '#'))
continue;
@@ -5398,7 +6391,7 @@
fclose(fp);
diff -r 1b0b563a81e6 mcabber/mcabber/xmpp_iq.c
--- a/mcabber/mcabber/xmpp_iq.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/xmpp_iq.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/mcabber/xmpp_iq.c Fri Mar 22 01:56:17 2013 +0200
@@ -71,20 +71,20 @@
struct adhoc_status {
char *name; // the name used by adhoc
@@ -5447,7 +6440,7 @@
"Status has been changed");
diff -r 1b0b563a81e6 mcabber/modules/beep/beep.c
--- a/mcabber/modules/beep/beep.c Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/modules/beep/beep.c Mon Mar 18 02:16:22 2013 +0200
++++ b/mcabber/modules/beep/beep.c Fri Mar 22 01:56:17 2013 +0200
@@ -31,6 +31,7 @@
static void beep_init (void);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/cmdopts.mdwn Fri Mar 22 02:09:13 2013 +0200
@@ -0,0 +1,967 @@
+
+**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.
+
+ * 'args' - similarly, pointer to the array of 'cmdarg_t' structs, that describe
+ command-line positional arguments (in order).
+
+ * 'cmds' - pointer to the array of subcommands of this command (or subcommand).
+ How parser switches to subcommands we will describe later.
+
+ * '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) (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_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 {
+ cmdarg_t *src;
+ cmdval_flags_t flags;
+ union {
+ guint uint;
+ gint sint;
+ guint swc;
+ const gchar *roarg;
+ gchar *arg;
+ 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 (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 (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);
+...
+
+[[!format c """ // -------------------------------------------------------------
+
+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 (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 (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 (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 (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 (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.
+
+## 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: -->