# HG changeset patch # User Myhailo Danylenko # Date 1363910953 -7200 # Node ID 93088d0c8140da11126a4d3ed379ccc9230a59ef # Parent 07e696e91b6f30bc9d12308552961469b4b11197 Add documentation for cmdopts diff -r 07e696e91b6f -r 93088d0c8140 cmdopts.diff --- 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. ++ ++ 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 @@ -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); diff -r 07e696e91b6f -r 93088d0c8140 docs/cmdopts.mdwn --- /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. + + diff -r 07e696e91b6f -r 93088d0c8140 docs/index.mdwn --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/index.mdwn Fri Mar 22 02:09:13 2013 +0200 @@ -0,0 +1,9 @@ + +[[!meta title="mcabber-patches"]] + +# My MQ queue for MCabber + +Set of patches, that I use with mcabber ncurses xmpp client. + +[ [[!hg mcabber-patches desc="SOURCE"]] ] [ [[cmdopts tutorial|cmdopts]] ] +