[cmdopts] Convert /buffer, merge subcmd_t and cmdopts_t, auto-print error
authorMyhailo Danylenko <isbear@ukrpost.net>
Tue, 26 Feb 2013 23:51:38 +0200
changeset 71 2bdd3252d918
parent 70 e2ef34130809
child 72 99d64d6ebe89
[cmdopts] Convert /buffer, merge subcmd_t and cmdopts_t, auto-print error
cmdopts.diff
--- a/cmdopts.diff	Tue Feb 26 01:22:22 2013 +0200
+++ b/cmdopts.diff	Tue Feb 26 23:51:38 2013 +0200
@@ -15,13 +15,195 @@
     * -n(--dryrun) switch for debugging purposes
   * /group uses parser
   * /say uses parser
-    * say_cmd()'s second argument is now LmMessageSubType
+    * say_cmd()'s second argument is now of new type msgtype_t
   * /msay uses parser
     * scr_multi* now store multiline in utf8
+  * /buffer uses parser
+    * fix help for /buffer date
 
+diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_buffer.txt
+--- a/mcabber/doc/help/cs/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Přesune se o [n] řádků nahoru (výchozí: polovina obrazovky).
+ /buffer down [n]
+  Přesune se o [n] řádků dolů (výchozí: polovina obrazovky).
+-/buffer date [datum]
++/buffer date datum
+  Přesune se na první řádek po datu [datum] (formát: "RRRR-mm-dd").
+ /buffer % n
+  Přesune se na procentuální pozici n%.
+diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_del.txt
+--- a/mcabber/doc/help/cs/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ 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 92fa48ef53c9 mcabber/doc/help/de/hlp_buffer.txt
+--- a/mcabber/doc/help/de/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/de/hlp_buffer.txt	Tue Feb 26 23:50:10 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]
+  Scrollt den Puffer um n Zeilen runter. Gibt man keine Zahl an, scrollt er um einen halben Bildschirm
+-/buffer date [date]
++/buffer date date
+  Springe zu der ersten Zeile nach dem Datum, welches im Format "JJJJ-mm-tt" anstatt [date] angegeben werden muss
+ /buffer % n
+  Springe zur Position "n" im Chatpuffer
+diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_del.txt
+--- a/mcabber/doc/help/de/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/de/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ Löscht den gerade ausgewählten Buddy vom Roster. Außerdem werden die automatischen Presence Benachrichtigungen vom/zum Buddy gestoppt.
+diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_buffer.txt
+--- a/mcabber/doc/help/en/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/en/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Scroll the buffer up [n] lines (default: half a screen)
+ /buffer down [n]
+  Scroll the buffer down [n] lines (default: half a screen)
+-/buffer date [date]
++/buffer date date
+  Jump to the first line after the specified [date] in the chat buffer (date format: "YYYY-mm-dd")
+ /buffer % n
+  Jump to position %n of the buddy chat buffer
+diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_del.txt
+--- a/mcabber/doc/help/en/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/en/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+-Delete the current buddy from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
++Delete the current buddy or one, specified with [jid] from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
+diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_buffer.txt
+--- a/mcabber/doc/help/fr/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Défile vers le haut de [n] lignes (par défaut un demi écran)
+ /buffer down [n]
+  Défile vers le bas de [n] lignes (par défaut un demi écran)
+-/buffer date [date]
++/buffer date date
+  Va à la première ligne après la [date] dans le tampon actuel (format: "aaaa-mm-jj")
+ /buffer % n
+  Va à la position n% du tampon
+diff -r 92fa48ef53c9 mcabber/doc/help/fr/hlp_del.txt
+--- a/mcabber/doc/help/fr/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ Supprime le contact sélectionné du roster, supprime notre abonnement à ses notifications de présence et supprime son abonnement aux nôtres.
+diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_buffer.txt
+--- a/mcabber/doc/help/it/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/it/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Fa scorrere indietro il buffer di [n] linee (default: metà schermo)
+ /buffer down [n]
+  Fa scorrere avanti il buffer di [n] linee (default: metà schermo)
+-/buffer date [data]
++/buffer date data
+  Salta alla prima linea successiva alla [data] specificata nel buffer di chat (formato della data: "YYYY-mm-dd")
+ /buffer % n
+  Salta alla posizione %n del buffer di chat corrente
+diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_del.txt
+--- a/mcabber/doc/help/it/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/it/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ Elimina il contatto corrente dal roster, cancellando la sottoscrizione alle reciproche notifiche della propria presenza.
+diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_buffer.txt
+--- a/mcabber/doc/help/nl/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Scroll de buffer [n] regels omhoog (standaard: een half scherm)
+ /buffer down [n]
+  Scroll de buffer [n] regels omlaag (standaard: een half scherm)
+-/buffer date [datum]
++/buffer date datum
+  Spring naar de eerste regel na de aangeduide [datum] in de chat buffer (datum formaat: "YYYY-mm-dd")
+ /buffer % n
+  Spring naar positie %n in de buddy chat buffer
+diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_del.txt
+--- a/mcabber/doc/help/nl/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ Verwijder de actieve buddy uit ons roster, en zet het wederzijds toezenden van status veranderingen stop.
+diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_del.txt
+--- a/mcabber/doc/help/pl/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+ Usuwa aktualnie zaznaczoną osobę z rostera, usuwa subskrypcję powiadomienia dostępności u danej osoby oraz u nas.
+diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_buffer.txt
+--- a/mcabber/doc/help/ru/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Перемещает на [n] строк вверх в буфере (истории переписки) (по умолчанию: половина экрана)
+ /buffer down [n]
+  Перемещает на [n] строк вниз в буфере (истории переписки) (по умолчанию: половина экрана)
+-/buffer date [date]
++/buffer date date
+  Перемещает в первой строке после определенной даты [date] в буфере (истории переписки) (формат даты: "год-месяц-день", для примера "2006-01-01")
+ /buffer % n
+  Перемещает на позицию %n в текущем буфере (истории переписки)
+diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_del.txt
+--- a/mcabber/doc/help/ru/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+-Удаляет текущего пользователя из списка контактов, отключает уведомления о его статусе и отключает уведомления пользователя о вашем статусе.
++Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
+diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_buffer.txt
+--- a/mcabber/doc/help/uk/hlp_buffer.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_buffer.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -25,7 +25,7 @@
+  Посунути буфер вверх на n рядків (якщо не вказано - пів екрану).
+ /buffer down [n]
+  Посунути буфер вниз на n рядків (якщо не вказано - пів екрану).
+-/buffer date [дата]
++/buffer date дата
+  Перейти до першого повідомлення після дати (дата вигляду РРРР-ММ-ДД).
+ /buffer % процент
+  Перейти до вказаної у процентах позиції.
+diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_del.txt
+--- a/mcabber/doc/help/uk/hlp_del.txt	Sun Jan 27 00:40:37 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_del.txt	Tue Feb 26 23:50:10 2013 +0200
+@@ -1,4 +1,4 @@
+ 
+- /DEL
++ /DEL [-n|--dryrun] [jid]
+ 
+-Потерти поточний контакт зі списку. На додачу відписатися від його повідомлень про статус і відписати його від ваших.
++Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
 diff -r 92fa48ef53c9 mcabber/mcabber/commands.c
 --- a/mcabber/mcabber/commands.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.c	Tue Feb 26 01:21:26 2013 +0200
++++ b/mcabber/mcabber/commands.c	Tue Feb 26 23:50:10 2013 +0200
 @@ -502,7 +502,9 @@
    if (!iscmd && scr_get_multimode() == 2
        && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
@@ -43,7 +225,7 @@
 +      scr_append_multiline(utf8);
      else
 -      say_cmd((char*)line, 0);
-+      say_cmd(utf8, LM_MESSAGE_SUB_TYPE_NOT_SET);
++      say_cmd(utf8, msgtype_not_set);
 +    g_free(utf8);
      return 0;
    }
@@ -75,7 +257,7 @@
    } else {      // Display a note
      struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
      if (note) {
-@@ -819,175 +818,253 @@
+@@ -819,175 +818,228 @@
  /* All these do_*() functions will be called with a "arg" parameter */
  /* (with arg not null)                                              */
  
@@ -92,7 +274,6 @@
 -  if (!subcmd || !*subcmd) {
 -    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
 -    free_arg_lst(paramlst);
--    return;
 +  enum roster_scmd_t {
 +    roster_scmd_bottom, roster_scmd_top, roster_scmd_up, roster_scmd_down,
 +    roster_scmd_group_prev, roster_scmd_group_next,
@@ -107,19 +288,19 @@
 +    roster_scmd_hide, roster_scmd_show, roster_scmd_toggle,
 +  } subcmd;
 +#define ROSTER_SCMD_NOARG(NAME) \
-+      { 0, #NAME, { NULL, NULL, NULL, NULL }, (gpointer)roster_scmd_##NAME }
++      { #NAME, NULL, NULL, NULL, (gpointer)roster_scmd_##NAME, 0 }
 +// all of them have at most one argument
 +#define ROSTER_SCMD(NAME, FLAGS, VALUE) \
-+      { 0, #NAME, { NULL, \
-+                    (cmdarg_t[1]){ \
-+                      { CMDOPT_LAST | FLAGS, { .arg = VALUE } } \
-+                    }, NULL, NULL }, (gpointer)roster_scmd_##NAME }
++      { #NAME, NULL, \
++        (cmdarg_t[1]){{ CMDOPT_LAST | FLAGS, { .arg = VALUE } }}, \
++        NULL, (gpointer)roster_scmd_##NAME, 0 }
 +  cmdopts_t options = {
++    "roster",
 +    NULL,
 +    (cmdarg_t[1]){
 +      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } }
 +    },
-+    (subcmd_t[23]){
++    (cmdopts_t[23]){
 +      ROSTER_SCMD_NOARG(bottom),
 +      ROSTER_SCMD_NOARG(top),
 +      ROSTER_SCMD(up,               0, "1"), // num lines
@@ -142,27 +323,22 @@
 +      ROSTER_SCMD(resource_unlock,  0, NULL), // resource/jid
 +      ROSTER_SCMD_NOARG(hide),
 +      ROSTER_SCMD_NOARG(show),
-+      { CMDOPT_LAST, "toggle", { NULL, NULL, NULL, NULL },
-+        (gpointer)roster_scmd_toggle },
++      { "toggle", NULL, NULL, NULL, (gpointer)roster_scmd_toggle,
++        CMDOPT_LAST },
 +    },
 +    NULL,
 +  };
 +  gchar *arg;
 +
-+  {
-+    const char *error = cmdopts_parse (args, &options);
-+    if (error != NULL) {
-+      scr_log_print (LPRINT_NORMAL, error);
-+      return;
-+    }
++  if (cmdopts_parse(args, &options))
+     return;
++
++  subcmd = (enum roster_scmd_t) (options.args[0].value.cmd -> userdata);
++  if (options.args[0].value.cmd -> args != NULL) {
++    arg = options.args[0].value.cmd -> args[0].value.arg;
    }
  
 -  if (!strcasecmp(subcmd, "top")) {
-+  subcmd = (enum roster_scmd_t) (options.args[0].value.cmd -> userdata);
-+  if (options.args[0].value.cmd -> options.args != NULL) {
-+    arg = options.args[0].value.cmd -> options.args[0].value.arg;
-+  }
-+
 +  if (subcmd == roster_scmd_top) {
      scr_roster_top();
      update_roster = TRUE;
@@ -221,111 +397,42 @@
 -    if (!arg || !*arg) {
 -      scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?");
 -      free_arg_lst(paramlst);
+-      return;
+-    }
 +  } else if (subcmd == roster_scmd_search) {
-+    scr_roster_search(arg);
-+    update_roster = TRUE;
+     scr_roster_search(arg);
+     update_roster = TRUE;
+-  } else if (!strcasecmp(subcmd, "up")) {
 +  } else if (subcmd == roster_scmd_up) {
-+    roster_updown(-1, arg);
+     roster_updown(-1, arg);
+-  } else if (!strcasecmp(subcmd, "down")) {
 +  } else if (subcmd == roster_scmd_down) {
-+    roster_updown(1, arg);
+     roster_updown(1, arg);
+-  } else if (!strcasecmp(subcmd, "group_prev")) {
 +  } else if (subcmd == roster_scmd_group_prev) {
-+    scr_roster_prev_group();
+     scr_roster_prev_group();
+-  } else if (!strcasecmp(subcmd, "group_next")) {
 +  } else if (subcmd == roster_scmd_group_next) {
-+    scr_roster_next_group();
+     scr_roster_next_group();
+-  } else if (!strcasecmp(subcmd, "note")) {
 +  } else if (subcmd == roster_scmd_note) {
-+    roster_note(arg);
+     roster_note(arg);
+-  } else if (!strcasecmp(subcmd, "resource_lock")) {
 +  } else if (subcmd == roster_scmd_resource_lock) {
-+    roster_resourcelock(arg, TRUE);
+     roster_resourcelock(arg, TRUE);
+-  } else if (!strcasecmp(subcmd, "resource_unlock")) {
 +  } else { // roster_scmd_resource_unlock
-+    roster_resourcelock(arg, FALSE);
+     roster_resourcelock(arg, FALSE);
+-  } else
+-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+-  free_arg_lst(paramlst);
 +  }
 +
 +  cmdopts_free(&options);
-+}
-+
-+void do_color(char *arg)
-+{
-+  enum color_scmd_t {
-+    color_scmd_roster,
-+    color_scmd_mucnick,
-+    color_scmd_muc,
-+  } subcmd;
-+  cmdopts_t options = {
-+    NULL,
-+    (cmdarg_t[1]){
-+      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
-+    },
-+    (subcmd_t[3]){
-+      { 0, "roster",
-+        {
-+          NULL,
-+          (cmdarg_t[3]){
-+            { CMDOPT_REQUIRED, { .arg = NULL } }, // status mask or "clear"
-+            { 0,               { .arg = NULL } }, // jid mask
-+            { CMDOPT_LAST,     { .arg = NULL } }, // color
-+          },
-+          NULL,
-+          NULL,
-+        },
-+        (gpointer)color_scmd_roster,
-+      },
-+      { 0, "muc",
-+        {
-+          NULL,
-+          (cmdarg_t[2]){
-+            { CMDOPT_REQUIRED, { .arg = NULL } }, // jid
-+            { CMDOPT_LAST,     { .arg = "on" } }, // on/off/preset/-
-+          },
-+          NULL,
-+          NULL,
-+        },
-+        (gpointer)color_scmd_muc,
-+      },
-+      { CMDOPT_LAST, "mucnick",
-+        {
-+          NULL,
-+          (cmdarg_t[2]){
-+            { CMDOPT_REQUIRED,               { .arg = NULL } }, // nick
-+            { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } }, // color
-+          },
-+          NULL,
-+          NULL,
-+        },
-+        (gpointer)color_scmd_mucnick,
-+      },
-+    },
-+    NULL,
-+  };
-+
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-       return;
-     }
--    scr_roster_search(arg);
--    update_roster = TRUE;
--  } else if (!strcasecmp(subcmd, "up")) {
--    roster_updown(-1, arg);
--  } else if (!strcasecmp(subcmd, "down")) {
--    roster_updown(1, arg);
--  } else if (!strcasecmp(subcmd, "group_prev")) {
--    scr_roster_prev_group();
--  } else if (!strcasecmp(subcmd, "group_next")) {
--    scr_roster_next_group();
--  } else if (!strcasecmp(subcmd, "note")) {
--    roster_note(arg);
--  } else if (!strcasecmp(subcmd, "resource_lock")) {
--    roster_resourcelock(arg, TRUE);
--  } else if (!strcasecmp(subcmd, "resource_unlock")) {
--    roster_resourcelock(arg, FALSE);
--  } else
--    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
--  free_arg_lst(paramlst);
--}
--
--void do_color(char *arg)
--{
+ }
+ 
+ void do_color(char *arg)
+ {
 -  char **paramlst;
 -  char *subcmd;
 -
@@ -336,8 +443,46 @@
 -  if (!subcmd || !*subcmd) {
 -    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
 -    free_arg_lst(paramlst);
--    return;
-   }
++  enum color_scmd_t {
++    color_scmd_roster,
++    color_scmd_mucnick,
++    color_scmd_muc,
++  } subcmd;
++  cmdopts_t options = {
++    "color",
++    NULL,
++    (cmdarg_t[1]){
++      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++    },
++    (cmdopts_t[3]){
++      { "roster", NULL,
++        (cmdarg_t[3]){
++          { CMDOPT_REQUIRED, { .arg = NULL } }, // status mask or "clear"
++          { 0,               { .arg = NULL } }, // jid mask
++          { CMDOPT_LAST,     { .arg = NULL } }, // color
++        },
++        NULL, (gpointer)color_scmd_roster, 0,
++      },
++      { "muc", NULL,
++        (cmdarg_t[2]){
++          { CMDOPT_REQUIRED, { .arg = NULL } }, // jid
++          { CMDOPT_LAST,     { .arg = "on" } }, // on/off/preset/-
++        },
++        NULL, (gpointer)color_scmd_muc, 0,
++      },
++      { "mucnick", NULL,
++        (cmdarg_t[2]){
++          { CMDOPT_REQUIRED,               { .arg = NULL } }, // nick
++          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } }, // color
++        },
++        NULL, (gpointer)color_scmd_mucnick, CMDOPT_LAST,
++      },
++    },
++  };
++
++  if (cmdopts_parse(arg, &options))
+     return;
+-  }
 -
 -  if (!strcasecmp(subcmd, "roster")) {
 -    char *status, *wildcard, *color;
@@ -352,9 +497,9 @@
 +  subcmd = (enum color_scmd_t) options.args[0].value.cmd -> userdata;
 +
 +  if (subcmd == color_scmd_roster) {
-+    const gchar *status   = options.cmds[0].options.args[0].value.arg;
-+    const gchar *wildcard = options.cmds[0].options.args[1].value.arg;
-+    const gchar *color    = options.cmds[0].options.args[2].value.arg;
++    const gchar *status   = options.cmds[0].args[0].value.arg;
++    const gchar *wildcard = options.cmds[0].args[1].value.arg;
++    const gchar *color    = options.cmds[0].args[2].value.arg;
 +    if (!strcmp(status, "clear")) { // Not a color command, clear all
        scr_roster_clear_color();
        update_roster = TRUE;
@@ -396,8 +541,8 @@
 -            scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
 -        }
 +  } else if (subcmd == color_scmd_muc) {
-+    const gchar *muc  = options.cmds[1].options.args[0].value.arg;
-+    const gchar *mode = options.cmds[1].options.args[1].value.arg;
++    const gchar *muc  = options.cmds[1].args[0].value.arg;
++    const gchar *mode = options.cmds[1].args[1].value.arg;
 +    if (!strcmp(muc, "."))
 +      if (!(muc = CURRENT_JID))
 +        scr_LogPrint(LPRINT_NORMAL, "No JID selected");
@@ -431,8 +576,8 @@
 -    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
 -  free_arg_lst(paramlst);
 +  } else { // color_scmd_mucnick
-+    const gchar *nick  = options.cmds[2].options.args[0].value.arg;
-+    const gchar *color = options.cmds[2].options.args[1].value.arg;
++    const gchar *nick  = options.cmds[2].args[0].value.arg;
++    const gchar *color = options.cmds[2].args[1].value.arg;
 +    scr_muc_nick_color(nick, color);
 +  }
 +
@@ -454,7 +599,7 @@
    enum imstatus st;
  
    if (!xmpp_is_online())
-@@ -1000,15 +1077,15 @@
+@@ -1000,15 +1052,15 @@
    if (!recipient)
      scr_check_auto_away(TRUE);
  
@@ -475,7 +620,7 @@
    if      (!strcasecmp(status, IMSTATUS_OFFLINE))       st = offline;
    else if (!strcasecmp(status, IMSTATUS_ONLINE))        st = available;
    else if (!strcasecmp(status, IMSTATUS_AVAILABLE))     st = available;
-@@ -1020,65 +1097,87 @@
+@@ -1020,64 +1072,76 @@
    else if (!strcasecmp(status, IMSTATUS_NOTAVAILABLE))  st = notavail;
    else if (!strcasecmp(status, IMSTATUS_FREE4CHAT))     st = freeforchat;
    else if (!strcasecmp(status, "message")) {
@@ -511,6 +656,7 @@
  {
 -  if (!*arg) {
 +  cmdopts_t options = {
++    "status",
 +    NULL,
 +    (cmdarg_t[2]){
 +      // status
@@ -519,16 +665,10 @@
 +      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
 +    },
 +    NULL,
-+    NULL,
 +  };
 +
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-+  }
++  if (cmdopts_parse(arg, &options))
++    return;
 +
 +  if (options.args[0].value.arg == NULL) {
      const char *sm = xmpp_getstatusmsg();
@@ -550,6 +690,7 @@
  {
 -  char **paramlst;
 +  cmdopts_t options = {
++    "status_to",
 +    NULL,
 +    (cmdarg_t[3]){
 +      // jid
@@ -560,7 +701,6 @@
 +      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = ""   } },
 +    },
 +    NULL,
-+    NULL,
 +  };
    char *fjid, *st, *msg;
 -  char *jid_utf8 = NULL;
@@ -574,24 +714,18 @@
 -    scr_LogPrint(LPRINT_NORMAL,
 -                 "Please specify both a Jabber ID and a status.");
 -    free_arg_lst(paramlst);
--    return;
 +
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-   }
- 
++  if (cmdopts_parse(arg, &options))
+     return;
+-  }
++
 +  fjid = options.args[0].value.arg;
 +  st   = options.args[1].value.arg;
 +  msg  = options.args[2].value.arg;
-+
+ 
    // Allow things like /status_to "" away
    if (!*fjid || !strcmp(fjid, "."))
-     fjid = NULL;
-@@ -1086,15 +1185,13 @@
+@@ -1086,15 +1150,13 @@
    if (fjid) {
      // The JID has been specified.  Quick check...
      if (check_jid_syntax(fjid)) {
@@ -608,7 +742,7 @@
      }
    } else {
      // Add the current buddy
-@@ -1105,144 +1202,205 @@
+@@ -1105,144 +1167,190 @@
    }
  
    if (fjid) {
@@ -637,6 +771,7 @@
 -  char *id, *nick;
 -  char *jid_utf8 = NULL;
 +  cmdopts_t options = {
++    "add",
 +    NULL,
 +    (cmdarg_t[2]){
 +      // jid
@@ -645,7 +780,6 @@
 +      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
 +    },
 +    NULL,
-+    NULL,
 +  };
 +  gchar *jid, *nick;
  
@@ -664,13 +798,8 @@
 -    id = NULL;
 -
 -  if (id) {
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-+  }
++  if (cmdopts_parse(arg, &options))
++    return;
 +
 +  jid  = options.args[0].value.arg;
 +  nick = options.args[1].value.arg;
@@ -729,6 +858,7 @@
 -    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
 -                 "the currently-selected buddy will be deleted.");
 +  cmdopts_t options = {
++    "del",
 +    (cmdopt_t[1]){
 +      { CMDOPT_SWITCH | CMDOPT_LAST, 'n', "dryrun", { .swc = 0 } },
 +    },
@@ -736,7 +866,6 @@
 +      { CMDOPT_LAST, { .arg = "."  } }, // jid
 +    },
 +    NULL,
-+    NULL,
 +  };
 +  gchar *jid;
 +  gpointer buddy;
@@ -746,30 +875,9 @@
      return;
    }
  
--  if (!current_buddy)
--    return;
--  bjid = buddy_getjid(BUDDATA(current_buddy));
--  if (!bjid)
--    return;
--
--  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) {
--    // This is a chatroom
--    if (buddy_getinsideroom(BUDDATA(current_buddy))) {
--      scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error) {
-+      scr_log_print(LPRINT_NORMAL, error);
-       return;
-     }
-   }
- 
--  // Close the buffer
--  scr_buffer_purge(1, NULL);
--
--  scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid);
--  xmpp_delbuddy(bjid);
--  scr_update_buddy_window();
++  if (cmdopts_parse(arg, &options))
++    return;
++
 +  jid = options.args[0].value.arg;
 +
 +  if (jid && (!*jid || !strcmp(jid, ".")))
@@ -824,37 +932,66 @@
 +  }
 +
 +  cmdopts_free(&options);
- }
- 
- static void do_group(char *arg)
- {
++}
++
++static void do_group(char *arg)
++{
 +  enum group_scmd_t {
 +    group_scmd_unfold = 0,
 +    group_scmd_fold   = 1,
 +    group_scmd_toggle = -1,
 +  } subcmd;
 +  cmdopts_t options = {
++    "group",
 +    NULL,
 +    (cmdarg_t[1]){
 +      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
 +    },
 +    // all of them have one argument - group name
 +#define GROUP_SUBCMD(NAME, REALNAME, FLAGS) \
-+      { FLAGS, #NAME, { NULL, (cmdarg_t[1]){ \
++      { #NAME, NULL, (cmdarg_t[1]){ \
 +          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } }, \
-+        }, NULL, NULL }, (gpointer)group_scmd_##REALNAME }
-+    (subcmd_t[5]){
++        }, NULL, (gpointer)group_scmd_##REALNAME, FLAGS }
++    (cmdopts_t[5]){
 +      GROUP_SUBCMD(expand, unfold, 0),
 +      GROUP_SUBCMD(unfold, unfold, 0),
 +      GROUP_SUBCMD(shrink, fold,   0),
 +      GROUP_SUBCMD(fold,   fold,   0),
 +      GROUP_SUBCMD(toggle, toggle, CMDOPT_LAST),
 +    },
-+    NULL,
 +  };
 +  gchar *name;
-   gpointer group = NULL;
-   guint leave_buddywindow;
++  gpointer group = NULL;
++  guint leave_buddywindow;
++
+   if (!current_buddy)
+     return;
+-  bjid = buddy_getjid(BUDDATA(current_buddy));
+-  if (!bjid)
++
++  if (cmdopts_parse(arg, &options))
+     return;
+-
+-  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_ROOM) {
+-    // This is a chatroom
+-    if (buddy_getinsideroom(BUDDATA(current_buddy))) {
+-      scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
+-      return;
+-    }
+-  }
+-
+-  // Close the buffer
+-  scr_buffer_purge(1, NULL);
+-
+-  scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", bjid);
+-  xmpp_delbuddy(bjid);
+-  scr_update_buddy_window();
+-}
+-
+-static void do_group(char *arg)
+-{
+-  gpointer group = NULL;
+-  guint leave_buddywindow;
 -  char **paramlst;
 -  char *subcmd;
 -  enum { group_toggle = -1, group_unfold = 0, group_fold = 1 } group_state = 0;
@@ -863,10 +1000,10 @@
 -    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
 -    return;
 -  }
- 
-   if (!current_buddy)
-     return;
- 
+-
+-  if (!current_buddy)
+-    return;
+-
 -  paramlst = split_arg(arg, 2, 0); // subcmd, [arg]
 -  subcmd = *paramlst;
 -  arg = *(paramlst+1);
@@ -883,18 +1020,12 @@
 -  else {
 -    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
 -    goto do_group_return;
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-   }
+-  }
 -
 -  if (arg && *arg) {
 +  
 +  subcmd = (enum group_scmd_t) options.args[0].value.cmd -> userdata;
-+  name   = options.args[0].value.cmd -> options.args[0].value.arg;
++  name   = options.args[0].value.cmd -> args[0].value.arg;
 +
 +  if (name && *name) {
      GSList *roster_elt;
@@ -905,7 +1036,7 @@
      if (roster_elt)
        group = buddy_getgroup(roster_elt->data);
    } else {
-@@ -1264,16 +1422,16 @@
+@@ -1264,20 +1372,20 @@
      goto do_group_return;
    }
  
@@ -925,7 +1056,20 @@
  }
  
  static int send_message_to(const char *fjid, const char *msg, const char *subj,
-@@ -1299,8 +1457,7 @@
+-                           LmMessageSubType type_overwrite, bool quiet)
++                           msgtype_t msg_type, bool quiet)
+ {
+   char *bare_jid, *rp;
+   char *hmsg;
+@@ -1285,6 +1393,7 @@
+   gint retval = 0;
+   int isroom;
+   gpointer xep184 = NULL;
++  LmMessageSubType type_overwrite = LM_MESSAGE_SUB_TYPE_NOT_SET;
+ 
+   if (!xmpp_is_online()) {
+     scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
+@@ -1299,11 +1408,15 @@
      return 1;
    }
    if (check_jid_syntax((char*)fjid)) {
@@ -935,7 +1079,30 @@
      return 1;
    }
  
-@@ -1382,30 +1539,9 @@
++  if (msg_type == msgtype_normal)
++    type_overwrite = LM_MESSAGE_SUB_TYPE_NORMAL;
++  else if (msg_type == msgtype_headline)
++    type_overwrite = LM_MESSAGE_SUB_TYPE_HEADLINE;
++
+   // We must use the bare jid in hk_message_out()
+   rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
+   if (rp)
+@@ -1354,8 +1467,7 @@
+ //  send_message(msg, subj, type_overwrite)
+ // Write the message in the buddy's window and send the message on
+ // the network.
+-static void send_message(const char *msg, const char *subj,
+-                         LmMessageSubType type_overwrite)
++static void send_message(const char *msg, const char *subj, msgtype_t msgtype)
+ {
+   const char *bjid;
+   char *jid;
+@@ -1378,34 +1490,13 @@
+   else
+     jid = g_strdup(bjid);
+ 
+-  send_message_to(jid, msg, subj, type_overwrite, FALSE);
++  send_message_to(jid, msg, subj, msgtype, FALSE);
    g_free(jid);
  }
  
@@ -960,14 +1127,14 @@
 -}
 -
 -void say_cmd(char *arg, int parse_flags)
-+void say_cmd(char *arg, LmMessageSubType msgtype)
++void say_cmd(char *arg, msgtype_t msgtype)
  {
    gpointer bud;
 -  LmMessageSubType msgtype = LM_MESSAGE_SUB_TYPE_NOT_SET;
  
    scr_set_chatmode(TRUE);
    scr_show_buddy_window();
-@@ -1424,80 +1560,150 @@
+@@ -1424,80 +1515,131 @@
    }
  
    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -981,6 +1148,7 @@
  static void do_say(char *arg) {
 -  say_cmd(arg, 1);
 +  cmdopts_t options = {
++    "say",
 +    (cmdopt_t[2]){
 +      { CMDOPT_SWITCH,               'n', "normal",   { .swc = 0 } },
 +      { CMDOPT_SWITCH | CMDOPT_LAST, 'h', "headline", { .swc = 0 } },
@@ -990,25 +1158,18 @@
 +        { .arg = NULL } },
 +    },
 +    NULL,
-+    NULL,
 +  };
-+  LmMessageSubType mtype = LM_MESSAGE_SUB_TYPE_NOT_SET;
++  msgtype_t msgtype = msgtype_not_set;
++
++  if (cmdopts_parse(arg, &options))
++    return;
 +
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-+  }
++  if (options.opts[0].value.swc)
++    msgtype = msgtype_normal;
++  else if (options.opts[1].value.swc)
++    msgtype = msgtype_headline;
 +
-+  if (options.opts[0].value.swc) {
-+    mtype = LM_MESSAGE_SUB_TYPE_NORMAL;
-+  } else if (options.opts[1].value.swc) {
-+    mtype = LM_MESSAGE_SUB_TYPE_HEADLINE;
-+  }
-+
-+  say_cmd(options.args[0].value.arg, mtype);
++  say_cmd(options.args[0].value.arg, msgtype);
 +
 +  cmdopts_free(&options);
  }
@@ -1037,85 +1198,78 @@
 +    msay_scmd_abort,
 +  } subcmd;
 +  cmdopts_t options = {
++    "msay",
 +    NULL,
 +    (cmdarg_t[1]){
 +      // subcommand
 +      { CMDOPT_SUBCOMMAND | CMDOPT_REQUIRED | CMDOPT_LAST, { .cmd = NULL } },
 +    },
-+    (subcmd_t[7]){
-+      { 0, "begin",
-+        { NULL,
-+          (cmdarg_t[1]){
-+            // subject
-+            { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+          },
-+          NULL, NULL, }, (gpointer)msay_scmd_begin },
-+      { 0, "verbatim",
-+        { NULL,
-+          (cmdarg_t[1]){
-+            // subject
-+            { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
-+          },
-+          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, 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, NULL }, (gpointer)msay_scmd_send_to },
-+      { 0, "toggle",
-+        { NULL, NULL, NULL, NULL },
-+        (gpointer)msay_scmd_toggle },
-+      { 0, "toggle_verbatim",
-+        { NULL, NULL, NULL, NULL },
-+        (gpointer)msay_scmd_toggle_verbatim },
-+      { CMDOPT_LAST, "abort",
-+        { NULL, NULL, NULL, NULL },
-+        (gpointer)msay_scmd_abort },
++    (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 },
 +    },
-+    NULL,
 +  };
 +  const char *msg;
 +
-+  {
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-   }
- 
--  if (!strcasecmp(subcmd, "toggle")) {
++  if (cmdopts_parse(arg, &options))
++    return;
++
 +  subcmd = (enum msay_scmd_t) options.args[0].value.cmd -> userdata;
 +
 +  if (subcmd == msay_scmd_toggle) {
-     if (scr_get_multimode())
--      subcmd = "send";
++    if (scr_get_multimode())
 +      subcmd = msay_scmd_send;
-     else
--      subcmd = "begin";
--  } else if (!strcasecmp(subcmd, "toggle_verbatim")) {
++    else
 +      subcmd = msay_scmd_begin;
 +  } else if (subcmd == msay_scmd_toggle_verbatim) {
-     if (scr_get_multimode())
--      subcmd = "send";
++    if (scr_get_multimode())
 +      subcmd = msay_scmd_send;
-     else
--      subcmd = "verbatim";
++    else
 +      subcmd = msay_scmd_verbatim;
    }
  
+-  if (!strcasecmp(subcmd, "toggle")) {
+-    if (scr_get_multimode())
+-      subcmd = "send";
+-    else
+-      subcmd = "begin";
+-  } else if (!strcasecmp(subcmd, "toggle_verbatim")) {
+-    if (scr_get_multimode())
+-      subcmd = "send";
+-    else
+-      subcmd = "verbatim";
+-  }
+-
 -  if (!strcasecmp(subcmd, "abort")) {
 +  if (subcmd == msay_scmd_abort) {
      if (scr_get_multimode())
@@ -1130,7 +1284,7 @@
 -      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 -> options.args[0].value.arg;
++    gchar *subject = options.args[0].value.cmd -> args[0].value.arg;
 +
 +    if (subcmd == msay_scmd_verbatim) {
 +      scr_set_multimode(2, subject);
@@ -1161,7 +1315,7 @@
  
    if (!scr_get_multimode()) {
      scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
-@@ -1508,49 +1714,47 @@
+@@ -1508,49 +1650,47 @@
    scr_set_chatmode(TRUE);
    scr_show_buddy_window();
  
@@ -1203,15 +1357,15 @@
 -      send_message(msg_utf8, scr_get_multimode_subj(), scan_mtype(&arg));
 -      g_free(msg_utf8);
 +  if ((msg = scr_get_multiline())) {
-+    LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET;
++    msgtype_t msg_type = msgtype_not_set;
 +
-+    if (options.args[0].value.cmd -> options.opts[0].value.swc) // n
-+      msg_type = LM_MESSAGE_SUB_TYPE_NORMAL;
-+    else if (options.args[0].value.cmd -> options.opts[1].value.swc) // h
-+      msg_type = LM_MESSAGE_SUB_TYPE_HEADLINE;
++    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].options.args[0].value.arg;
++      const char *jid = options.cmds[3].args[0].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).
@@ -1247,24 +1401,30 @@
  }
  
  //  load_message_from_file(filename)
-@@ -1634,130 +1838,110 @@
+@@ -1566,7 +1706,7 @@
+   char *next_utf8_char;
+   size_t len;
+ 
+-  fd = fopen(filename, "r");
++  fd = fopen(filename, "r"); // FIXME g_from_utf8
+ 
+   if (!fd || fstat(fileno(fd), &buf)) {
+     scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
+@@ -1634,130 +1774,103 @@
  
  static void do_say_to(char *arg)
  {
 -  char **paramlst;
 -  char *fjid, *msg_utf8;
-+  char *fjid;
-   char *msg;
+-  char *msg;
 -  char *unescaped_msg = NULL;
 -  char *uncompletedfjid = NULL;
 -  char *file = NULL;
-+  char *file;
-+  gchar *freeme  = NULL; // fjid
-+  gchar *freeme2 = NULL; // msg
-   LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET;
+-  LmMessageSubType msg_type = LM_MESSAGE_SUB_TYPE_NOT_SET;
 -  bool quiet = FALSE;
 -  bool eval = FALSE;
 +  cmdopts_t options = {
++    "say_to",
 +    (cmdopt_t[5]){
 +      { CMDOPT_SWITCH, 'n', "normal",   { .swc = 0 }    },
 +      { CMDOPT_SWITCH, 'h', "headline", { .swc = 0 }    },
@@ -1279,8 +1439,11 @@
 +      { CMDOPT_LAST | CMDOPT_PLAIN | CMDOPT_CATCHALL, { .arg = NULL } },
 +    },
 +    NULL,
-+    NULL,
 +  };
++  char *fjid, *msg, *file;
++  gchar *freeme  = NULL; // fjid
++  gchar *freeme2 = NULL; // msg
++  msgtype_t msg_type = msgtype_not_set;
  
    if (!xmpp_is_online()) {
      scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
@@ -1293,15 +1456,10 @@
 -  if (!*paramlst) {  // No parameter?
 -    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
 -    free_arg_lst(paramlst);
--    return;
-+  { // parse arguments
-+    const char *error = cmdopts_parse(arg, &options);
-+    if (error != NULL) {
-+      scr_log_print(LPRINT_NORMAL, error);
-+      return;
-+    }
-   }
- 
++  if (cmdopts_parse(arg, &options))
+     return;
+-  }
+-
 -  // Check for an option parameter
 -  while (*paramlst) {
 -    if (!strcmp(*paramlst, "-q")) {
@@ -1341,10 +1499,11 @@
 -  fjid = *paramlst;
 -  msg = *(paramlst+1);
 -
-+  if (options.opts[0].value.swc)
-+    msg_type = LM_MESSAGE_SUB_TYPE_NORMAL;
-+  else if (options.opts[1].value.swc)
-+    msg_type = LM_MESSAGE_SUB_TYPE_HEADLINE;
++
++  if (options.opts[0].value.swc) // n
++    msg_type = msgtype_normal;
++  else if (options.opts[1].value.swc) // h
++    msg_type = msgtype_headline;
 +
 +  fjid = options.args[0].value.arg;
 +  msg  = options.args[1].value.arg;
@@ -1438,10 +1597,377 @@
  }
  
  //  buffer_updown(updown, nblines)
+@@ -1775,27 +1888,10 @@
+     scr_buffer_scroll_up_down(updown, nblines);
+ }
+ 
+-static void buffer_search(int direction, char *arg)
+-{
+-  if (!arg || !*arg) {
+-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+-    return;
+-  }
+-
+-  scr_buffer_search(direction, arg);
+-}
+-
+ static void buffer_date(char *date)
+ {
+   time_t t;
+ 
+-  if (!date || !*date) {
+-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+-    return;
+-  }
+-
+-  strip_arg_special_chars(date);
+-
+   t = from_iso8601(date, 0);
+   if (t)
+     scr_buffer_date(t);
+@@ -1804,98 +1900,156 @@
+                  "not correctly formatted or invalid.");
+ }
+ 
+-static void buffer_percent(char *arg1, char *arg2)
++// XXX % command before was able to handle %50
++static void do_buffer(char *arg)
+ {
+-  // Basically, user has typed "%arg1 arg2"
+-  // "%50"  -> arg1 = 50, arg2 null pointer
+-  // "% 50" -> arg1 = \0, arg2 = 50
+-
+-  if (!*arg1 && (!arg2 || !*arg2)) { // No value
+-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
++  enum buffer_scmd_t {
++    buffer_scmd_close, buffer_scmd_close_all,
++    buffer_scmd_clear, buffer_scmd_purge,
++    buffer_scmd_list,
++    buffer_scmd_top, buffer_scmd_bottom, buffer_scmd_up, buffer_scmd_down,
++    buffer_scmd_date, buffer_scmd_percent, buffer_scmd_readmark,
++    buffer_scmd_search_backward, buffer_scmd_search_forward,
++    buffer_scmd_scroll_lock, buffer_scmd_scroll_unlock,
++    buffer_scmd_scroll_toggle,
++    buffer_scmd_save,
++  } subcmd;
++  cmdopts_t options = {
++    "buffer",
++    NULL,
++    (cmdarg_t[1]){
++      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
++    },
++    (cmdopts_t[18]){
++      { "close",
++        (cmdopt_t[1]){
++          { CMDOPT_SWITCH | CMDOPT_LAST, 'a', "all", { .swc = 0 } }
++        },
++        (cmdarg_t[1]){
++          { CMDOPT_LAST, { .cmd = NULL } } // jid
++        },
++        NULL, (gpointer)buffer_scmd_close, 0 },
++      { "close_all", NULL, NULL, NULL, (gpointer)buffer_scmd_close_all, 0 },
++      { "clear", NULL, NULL, NULL, (gpointer)buffer_scmd_clear, 0 },
++      { "purge", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_LAST, { .arg = NULL } }, // jid
++        },
++        NULL, (gpointer)buffer_scmd_purge, 0 },
++      { "list", NULL, NULL, NULL, (gpointer)buffer_scmd_list, 0 },
++      { "top", NULL, NULL, NULL, (gpointer)buffer_scmd_top, 0 },
++      { "bottom", NULL, NULL, NULL, (gpointer)buffer_scmd_bottom, 0 },
++      { "up", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_LAST, { .arg = "1" } }, // lines
++        },
++        NULL, (gpointer)buffer_scmd_up, 0 },
++      { "down", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_LAST, { .arg = "1" } }, // lines
++        },
++        NULL, (gpointer)buffer_scmd_down, 0 },
++      { "date", NULL,
++        (cmdarg_t[1]){
++          // date
++          { CMDOPT_CATCHALL | CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = "1" } },
++        },
++        NULL, (gpointer)buffer_scmd_date, 0 },
++      { "%", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = "100" } }, // percent
++        },
++        NULL, (gpointer)buffer_scmd_percent, 0 },
++      { "readmark", NULL, NULL, NULL, (gpointer)buffer_scmd_readmark, 0 },
++      { "search_backward", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_LAST,
++            { .arg = NULL } },
++        },
++        NULL, (gpointer)buffer_scmd_search_backward, 0 },
++      { "search_forward", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_LAST,
++            { .arg = NULL } },
++        },
++        NULL, (gpointer)buffer_scmd_search_forward, 0 },
++      { "scroll_lock", NULL, NULL, NULL,
++        (gpointer)buffer_scmd_scroll_lock, 0 },
++      { "scroll_unlock", NULL, NULL, NULL,
++        (gpointer)buffer_scmd_scroll_unlock, 0 },
++      { "scroll_toggle", NULL, NULL, NULL,
++        (gpointer)buffer_scmd_scroll_toggle, 0 },
++      { "save", NULL,
++        (cmdarg_t[1]){
++          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
++            { .arg = NULL } },
++        },
++        NULL, (gpointer)buffer_scmd_save, CMDOPT_LAST },
++    },
++  };
++
++  if (!current_buddy)
++    return;
++
++  if (cmdopts_parse(arg, &options))
++    return;
++
++  subcmd = (enum buffer_scmd_t) options.args[0].value.cmd -> userdata;
++
++  if (subcmd == buffer_scmd_close && options.cmds[0].opts[0].value.swc)
++    subcmd = buffer_scmd_close_all;
++
++  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP &&
++      subcmd != buffer_scmd_close_all) {
++    scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer.");
++    cmdopts_free(&options);
+     return;
+   }
+ 
+-  if (*arg1 && arg2 && *arg2) {     // Two values
+-    scr_LogPrint(LPRINT_NORMAL, "Wrong parameters.");
+-    return;
++  if (subcmd == buffer_scmd_top) {
++    scr_buffer_top_bottom(-1);
++  } else if (subcmd == buffer_scmd_bottom) {
++    scr_buffer_top_bottom(1);
++  } else if (subcmd == buffer_scmd_clear) {
++    scr_buffer_clear();
++  } else if (subcmd == buffer_scmd_close) {
++    scr_buffer_purge(1, options.cmds[0].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_close_all) {
++    scr_buffer_purge_all(1);
++  } else if (subcmd == buffer_scmd_purge) {
++    scr_buffer_purge(0, options.cmds[3].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_scroll_lock) {
++    scr_buffer_scroll_lock(1);
++  } else if (subcmd == buffer_scmd_scroll_unlock) {
++    scr_buffer_scroll_lock(0);
++  } else if (subcmd == buffer_scmd_scroll_toggle) {
++    scr_buffer_scroll_lock(-1);
++  } else if (subcmd == buffer_scmd_up) {
++    buffer_updown(-1, options.cmds[6].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_down) {
++    buffer_updown(1, options.cmds[7].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_search_backward) {
++    scr_buffer_search(-1, options.cmds[11].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_search_forward) {
++    scr_buffer_search(1, options.cmds[12].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_date) {
++    buffer_date(options.cmds[8].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_percent) {
++    scr_buffer_percent(atoi(options.cmds[9].args[0].value.arg));
++  } else if (subcmd == buffer_scmd_save) {
++    scr_buffer_dump(options.cmds[17].args[0].value.arg);
++  } else if (subcmd == buffer_scmd_list) {
++    scr_buffer_list();
++  } else { // buffer_scmd_readmark
++    scr_buffer_jump_readmark();
+   }
+ 
+-  scr_buffer_percent(atoi((*arg1 ? arg1 : arg2)));
+-}
+-
+-static void do_buffer(char *arg)
+-{
+-  char **paramlst;
+-  char *subcmd;
+-
+-  if (!current_buddy)
+-    return;
+-
+-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
+-  subcmd = *paramlst;
+-  arg = *(paramlst+1);
+-
+-  if (!subcmd || !*subcmd) {
+-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
+-    free_arg_lst(paramlst);
+-    return;
+-  }
+-
+-  if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP &&
+-      strcasecmp(subcmd, "close_all")) {
+-    scr_LogPrint(LPRINT_NORMAL, "Groups have no buffer.");
+-    free_arg_lst(paramlst);
+-    return;
+-  }
+-
+-  if (!strcasecmp(subcmd, "top")) {
+-    scr_buffer_top_bottom(-1);
+-  } else if (!strcasecmp(subcmd, "bottom")) {
+-    scr_buffer_top_bottom(1);
+-  } else if (!strcasecmp(subcmd, "clear")) {
+-    scr_buffer_clear();
+-  } else if (!strcasecmp(subcmd, "close")) {
+-    scr_buffer_purge(1, arg);
+-  } else if (!strcasecmp(subcmd, "close_all")) {
+-    scr_buffer_purge_all(1);
+-  } else if (!strcasecmp(subcmd, "purge")) {
+-    scr_buffer_purge(0, arg);
+-  } else if (!strcasecmp(subcmd, "scroll_lock")) {
+-    scr_buffer_scroll_lock(1);
+-  } else if (!strcasecmp(subcmd, "scroll_unlock")) {
+-    scr_buffer_scroll_lock(0);
+-  } else if (!strcasecmp(subcmd, "scroll_toggle")) {
+-    scr_buffer_scroll_lock(-1);
+-  } else if (!strcasecmp(subcmd, "up")) {
+-    buffer_updown(-1, arg);
+-  } else if (!strcasecmp(subcmd, "down")) {
+-    buffer_updown(1, arg);
+-  } else if (!strcasecmp(subcmd, "search_backward")) {
+-    strip_arg_special_chars(arg);
+-    buffer_search(-1, arg);
+-  } else if (!strcasecmp(subcmd, "search_forward")) {
+-    strip_arg_special_chars(arg);
+-    buffer_search(1, arg);
+-  } else if (!strcasecmp(subcmd, "date")) {
+-    buffer_date(arg);
+-  } else if (*subcmd == '%') {
+-    buffer_percent(subcmd+1, arg);
+-  } else if (!strcasecmp(subcmd, "save")) {
+-    scr_buffer_dump(arg);
+-  } else if (!strcasecmp(subcmd, "list")) {
+-    scr_buffer_list();
+-  } else if (!strcasecmp(subcmd, "readmark")) {
+-    scr_buffer_jump_readmark();
+-  } else {
+-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+-  }
+-
+-  free_arg_lst(paramlst);
++  cmdopts_free(&options);
+ }
+ 
+ static void do_clear(char *arg)    // Alias for "buffer clear"
+ {
+-  do_buffer("clear");
++  scr_buffer_clear();
+ }
+ 
+ static void do_info(char *arg)
+@@ -2033,29 +2187,20 @@
+   }
+ }
+ 
++enum room_names_style_t {
++  room_names_style_normal = 0,
++  room_names_style_detail,
++  room_names_style_short,
++  room_names_style_quiet,
++  room_names_style_compact,
++};
++
+ // room_names() is a variation of do_info(), for chatrooms only
+-static void room_names(gpointer bud, char *arg)
++static void room_names(gpointer bud, enum room_names_style_t style)
+ {
+   const char *bjid;
+   char *buffer;
+   GSList *resources, *p_res;
+-  enum { style_normal = 0, style_detail, style_short,
+-         style_quiet, style_compact } style = 0;
+-
+-  if (*arg) {
+-    if (!strcasecmp(arg, "--short"))
+-      style = style_short;
+-    else if (!strcasecmp(arg, "--quiet"))
+-      style = style_quiet;
+-    else if (!strcasecmp(arg, "--detail"))
+-      style = style_detail;
+-    else if (!strcasecmp(arg, "--compact"))
+-      style = style_compact;
+-    else {
+-      scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
+-      return;
+-    }
+-  }
+ 
+   // Enter chat mode
+   scr_set_chatmode(TRUE);
+@@ -2075,12 +2220,12 @@
+     rstatus = buddy_getstatus(bud, p_res->data);
+     rst_msg = buddy_getstatusmsg(bud, p_res->data);
+ 
+-    if (style == style_short) {
++    if (style == room_names_style_short) {
+       snprintf(buffer, 4095, "[%c] %s%s%s", imstatus2char[rstatus],
+                (char*)p_res->data,
+                rst_msg ? " -- " : "", rst_msg ? rst_msg : "");
+       scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+-    } else if (style == style_compact) {
++    } else if (style == room_names_style_compact) {
+         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 +2241,12 @@
+       snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
+                (char*)p_res->data);
+       scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
+-      if (rst_msg && style != style_quiet) {
++      if (rst_msg && style != room_names_style_quiet) {
+         snprintf(buffer, 4095, "Status message: %s", rst_msg);
+         scr_WriteIncomingMessage(bjid, buffer,
+                                  0, HBB_PREFIX_INFO | HBB_PREFIX_CONT, 0);
+       }
+-      if (style == style_detail) {
++      if (style == room_names_style_detail) {
+         enum imrole role = buddy_getrole(bud, p_res->data);
+         enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
+ 
+@@ -2874,7 +3019,7 @@
+   fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
+   g_free (nick_utf8);
+   msg = to_utf8(arg);
+-  send_message_to(fjid_utf8, msg, NULL, LM_MESSAGE_SUB_TYPE_NOT_SET, FALSE);
++  send_message_to(fjid_utf8, msg, NULL, msgtype_not_set, FALSE);
+   g_free(fjid_utf8);
+   g_free(msg);
+   free_arg_lst(paramlst);
+@@ -3347,7 +3492,7 @@
+       cmd_room_leave(bud, arg);
+   } else if (!strcasecmp(subcmd, "names"))  {
+     if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+-      room_names(bud, arg);
++      room_names(bud, room_names_style_normal); // FIXME
+   } else if (!strcasecmp(subcmd, "nick"))  {
+     if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
+       room_nick(bud, arg);
 diff -r 92fa48ef53c9 mcabber/mcabber/commands.h
 --- a/mcabber/mcabber/commands.h	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/commands.h	Tue Feb 26 01:21:26 2013 +0200
-@@ -29,8 +29,9 @@
++++ b/mcabber/mcabber/commands.h	Tue Feb 26 23:50:10 2013 +0200
+@@ -14,6 +14,12 @@
+   gpointer userdata;
+ } cmd;
+ 
++typedef enum {
++  msgtype_not_set,
++  msgtype_normal,
++  msgtype_headline,
++} msgtype_t;
++
+ void cmd_init(void);
+ cmd *cmd_get(const char *command);
+ int  process_line(const char *line);
+@@ -29,8 +35,9 @@
  
  void cmd_room_whois(gpointer bud, const char *nick, guint interactive);
  void cmd_room_leave(gpointer bud, char *arg);
@@ -1449,13 +1975,13 @@
 -void say_cmd(char *arg, int parse_flags);
 +void cmd_setstatus(const char *recipient,
 +                   const char *status, const char *message);
-+void say_cmd(char *arg, LmMessageSubType msg_type);
++void say_cmd(char *arg, msgtype_t msgtype);
  
  #endif /* __MCABBER_COMMANDS_H__ */
  
 diff -r 92fa48ef53c9 mcabber/mcabber/roster.c
 --- a/mcabber/mcabber/roster.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/roster.c	Tue Feb 26 01:21:26 2013 +0200
++++ b/mcabber/mcabber/roster.c	Tue Feb 26 23:50:10 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,
@@ -1496,8 +2022,8 @@
      }
 diff -r 92fa48ef53c9 mcabber/mcabber/utils.c
 --- a/mcabber/mcabber/utils.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.c	Tue Feb 26 01:21:26 2013 +0200
-@@ -555,6 +555,317 @@
++++ b/mcabber/mcabber/utils.c	Tue Feb 26 23:50:10 2013 +0200
+@@ -555,6 +555,318 @@
      *str = tolower(*str);
  }
  
@@ -1689,7 +2215,7 @@
 +        } else { // command argument
 +          if (argument -> flags & CMDOPT_SUBCOMMAND) {
 +            gboolean found = FALSE;
-+            subcmd_t *subcommand = options -> cmds;
++            cmdopts_t *subcommand = options -> cmds;
 +            if (subcommand) {
 +              do {
 +                if (!g_strcmp0(s, subcommand -> name)) {
@@ -1700,7 +2226,7 @@
 +            }
 +            if (found) {
 +              argument -> value.cmd = subcommand;
-+              error = cmdopts_parse_internal(p, e, &(subcommand -> options));
++              error = cmdopts_parse_internal(p, e, subcommand);
 +              break;
 +            } else {
 +              error = "Unknown subcommand";
@@ -1786,6 +2312,7 @@
 +  options -> freeme = utf8;
 +  error = cmdopts_parse_internal(utf8, e, options);
 +  if (error) {
++    scr_log_print(LPRINT_NORMAL, "%s: %s", options -> name, error);
 +    cmdopts_free(options);
 +  }
 +  return error;
@@ -1794,7 +2321,7 @@
 +void cmdopts_free(cmdopts_t *options)
 +{
 +  cmdopt_t *option = options -> opts;
-+  subcmd_t *subcommand = options -> cmds;
++  cmdopts_t *subcommand = options -> cmds;
 +  if (option) {
 +    do {
 +      if ((option -> flags & (CMDOPT_CATCHALL|CMDOPT_SWITCH)) == CMDOPT_CATCHALL) {
@@ -1805,7 +2332,7 @@
 +  }
 +  if (subcommand) {
 +    do {
-+      cmdopts_free(&(subcommand -> options));
++      cmdopts_free(subcommand);
 +    } while (!(subcommand++ -> flags & CMDOPT_LAST));
 +  }
 +  g_free(options -> freeme);
@@ -1817,8 +2344,8 @@
  // Only quotes need a backslash
 diff -r 92fa48ef53c9 mcabber/mcabber/utils.h
 --- a/mcabber/mcabber/utils.h	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/utils.h	Tue Feb 26 01:21:26 2013 +0200
-@@ -43,6 +43,101 @@
++++ b/mcabber/mcabber/utils.h	Tue Feb 26 23:50:10 2013 +0200
+@@ -43,6 +43,97 @@
  char **split_arg(const char *arg, unsigned int n, int dontstriplast);
  void free_arg_lst(char **arglst);
  
@@ -1881,7 +2408,6 @@
 +// - we call callback
 +// - we free resources
 +typedef struct cmdopts_struct cmdopts_t;
-+typedef struct subcmd_struct subcmd_t;
 +typedef union {
 +  GSList *multiopt;
 +  gchar  *opt;
@@ -1894,24 +2420,21 @@
 +  cmdopt_value_t value;
 +} cmdopt_t;
 +typedef union {
-+  gchar    *arg;
-+  subcmd_t *cmd;
++  gchar     *arg;
++  cmdopts_t *cmd;
 +} cmdarg_value_t;
 +typedef struct {
 +  guint          flags;
 +  cmdarg_value_t value;
 +} cmdarg_t;
 +struct cmdopts_struct {
-+  cmdopt_t *opts;
-+  cmdarg_t *args;
-+  subcmd_t *cmds;
-+  gchar    *freeme; // private
-+};
-+struct subcmd_struct {
-+  guint      flags;
 +  const char *name;
-+  cmdopts_t  options;
-+  gpointer   userdata; // unused, for user convenience
++  cmdopt_t   *opts;
++  cmdarg_t   *args;
++  cmdopts_t  *cmds;
++  gpointer   userdata; // unused, for user's convenience
++  guint      flags;    // only meaningful for subcommands
++  gchar      *freeme;  // private
 +};
 +
 +const char *cmdopts_parse (const char *arg, cmdopts_t *options);
@@ -1922,7 +2445,7 @@
  char *ut_unescape_tabs_cr(const char *text);
 diff -r 92fa48ef53c9 mcabber/mcabber/xmpp_iq.c
 --- a/mcabber/mcabber/xmpp_iq.c	Sun Jan 27 00:40:37 2013 +0200
-+++ b/mcabber/mcabber/xmpp_iq.c	Tue Feb 26 01:21:26 2013 +0200
++++ b/mcabber/mcabber/xmpp_iq.c	Tue Feb 26 23:50:10 2013 +0200
 @@ -289,10 +289,7 @@
        if (value) {
          for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);