cmdopts.diff
author Myhailo Danylenko <isbear@ukrpost.net>
Wed, 27 Feb 2013 23:35:53 +0200
changeset 73 feae79fcc408
parent 72 99d64d6ebe89
child 74 a879ea179877
permissions -rw-r--r--
[cmdopts] Convert /rawxml, fix /buffer arguments

# HG changeset patch
# Parent 92fa48ef53c909928706ab4c51518953339a38e4
Unified command option parsing

  * cmdopts_parse() & cmdopts_free() in utils.c/h
  * /roster uses parser
    * buddy_search() now expects argument in utf8
  * /say_to uses parser
  * /color uses parser
  * /status and /status_to use parser
    * cmd_setstatus() now expects separate status and message arguments
  * /add uses parser
  * /del uses parser
    * allows specifying jid, as /add does
    * -n(--dryrun) switch for debugging purposes
  * /group uses parser
  * /say uses parser
    * 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
  * /rename uses parser
    * -r(--reset) instead of '-'
    * -j(--jid), -g(--group), -n(--name)
  * /move uses parser
    * -j(--jid), -n(--name)
  * /rawxml uses parser

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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/cs/hlp_move.txt
--- a/mcabber/doc/help/cs/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/cs/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [skupina]
+ /MOVE [-j|--jid jid] [-n|--name name] [skupina]
 
 Přesune aktuální kontakt do požadované skupiny. Není-li skupina zadána, přesune se kontakt do výchozí skupiny. Pokud skupina neexistuje, automaticky se založí.
+You can select other buddy that current using options --jid and --name.
 Tip: V módu rozhovoru lze použít "/roster alternate" pro skok na přesunutý kontakt.
diff -r 92fa48ef53c9 mcabber/doc/help/cs/hlp_rename.txt
--- a/mcabber/doc/help/cs/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/cs/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME jméno
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] jméno
 
-Přejmenuje aktuálního uživatele nebo skupinu na 'jméno'. Je-li 'jméno' znak '-', dané jméno se odstraní ze seznamu kontaktů (rosteru) a zobrazí se 'jid'.
+Přejmenuje aktuálního uživatele nebo skupinu na 'jméno'.
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/de/hlp_move.txt
--- a/mcabber/doc/help/de/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/de/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,6 +1,7 @@
 
- /MOVE [groupname]
+ /MOVE [-j|--jid jid] [-n|--name name] [groupname]
 
 Verschiebt den ausgewählten Buddy zu der Gruppe "groupname". Wenn keine Gruppe angegeben wurde, wird der Buddy zur Standardgruppe verschoben. Wenn die Gruppe "groupname" nicht existiert, wird sie erst erschaffen.
+You can select other buddy that current using options --jid and --name.
 
 Tipp: Wenn der Chatmodus aktiviert ist, kannst du "/roster alternate" benutzen um zu dem gerade bewegten Buddy zu springen.
diff -r 92fa48ef53c9 mcabber/doc/help/de/hlp_rename.txt
--- a/mcabber/doc/help/de/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/de/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME name
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] newname
 
-Setzt den Namen des gerade ausgewählten Buddys bzw. der ausgewählten Gruppe auf "name". Wenn "name" - ist, so wird der Name vom Roster entfernt (mcabber wird in diesem Falle die JID bzw. den username anzeigen)
+Setzt den Namen des gerade ausgewählten Buddys bzw. der ausgewählten Gruppe auf "name".
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/en/hlp_move.txt
--- a/mcabber/doc/help/en/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/en/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [groupname]
+ /MOVE [-j|--jid jid] [-n|--name name] [groupname]
 
 Move the current buddy to the requested group.  If no group is specified, then the buddy is moved to the default group.  If the group "groupname" doesn't exist, it is created.
+You can select other buddy that current using options --jid and --name.
 Tip: if the chatmode is enabled, you can use "/roster alternate" to jump to the moved buddy.
diff -r 92fa48ef53c9 mcabber/doc/help/en/hlp_rename.txt
--- a/mcabber/doc/help/en/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/en/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME name
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] newname
 
-Rename the current buddy or group to the given "name".  If "name" is -, the name is removed from the roster (and mcabber will display the JID or username).
+Rename the current buddy or group to the given "newname".
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/fr/hlp_move.txt
--- a/mcabber/doc/help/fr/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/fr/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [groupname]
+ /MOVE [-j|--jid jid] [-n|--name name] [groupname]
 
 Déplace le contact sélectionné vers le groupe spécifié. Si aucun groupe n'est donné, le contact est déplacé vers le groupe par défaut. Si le groupe "groupname" n'existe pas, il est créé.
+You can select other buddy that current using options --jid and --name.
 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 92fa48ef53c9 mcabber/doc/help/fr/hlp_rename.txt
--- a/mcabber/doc/help/fr/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/fr/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME nom
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] nom
 
-Renomme le contact/groupe sélectionné avec le "nom" spécifié. Si le nom est «-», le nom est supprimé du roster (et mcabber affichera le JID ou le nom d'utilisateur).
+Renomme le contact/groupe sélectionné avec le "nom" spécifié.
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/it/hlp_move.txt
--- a/mcabber/doc/help/it/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/it/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [gruppo]
+ /MOVE [-j|--jid jid] [-n|--name name] [grouppo]
 
 Muove il contatto corrente nel gruppo richiesto. Se non viene specificato alcun gruppo, il contatto viene spostato nel gruppo si default. Se il gruppo "gruppo" non esiste, viene creato.
+You can select other buddy that current using options --jid and --name.
 Trucco: se la modalità chat è abilitata, puoi usare "/roster alternate" per spostarti sul contatto appena mosso.
diff -r 92fa48ef53c9 mcabber/doc/help/it/hlp_rename.txt
--- a/mcabber/doc/help/it/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/it/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME nome
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] nome
 
-Rinomina il contatto od il gruppo correnti usando "nome". Se si specifica - come "nome", il nome viene rimosso dal roster, ed al suo posto verranno visulizzati il JID o lo username.
+Rinomina il contatto od il gruppo correnti usando "nome".
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 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/nl/hlp_move.txt
--- a/mcabber/doc/help/nl/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/nl/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [groepsnaam]
+ /MOVE [-j|--jid jid] [-n|--name name] [groepsnaam]
 
 Verplaats de actieve buddy naar de aangegeven groep.  Indien geen groep werd gespecificeerd wordt buddy verplaatst naar de standaard groep.  Indien de groep "groepsnaam" niet bestaat, wordt die eerst aangemaakt.
+You can select other buddy that current using options --jid and --name.
 Tip: indien chatmode actief is, kun je "/roster alternate" gebruiken om direct naar de verplaatste buddy te springen.
diff -r 92fa48ef53c9 mcabber/doc/help/nl/hlp_rename.txt
--- a/mcabber/doc/help/nl/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/nl/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME naam
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] naam
 
-Hernoem de actieve buddy of groep in de aangegeven "naam". Indien '-' wordt gebruikt als 'naam', wordt die uit het roster verwijderd (en mcabber zal vervolgens de JID of gebruikersnaam tonen).
+Hernoem de actieve buddy of groep in de aangegeven "naam".
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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/pl/hlp_move.txt
--- a/mcabber/doc/help/pl/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/pl/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [nazwa grupy]
+ /MOVE [-j|--jid jid] [-n|--name name] [nazwa grupy]
 
 Przenosi aktualną osobę do grupy "nazwa grupy".  Jeśli nie podano nazwy grupy, wtedy osoba jest przenoszona do grupy domyślnej.  Jeśli grupa "nazwa grupy" nie istnieje, zostaje utworzona.
+You can select other buddy that current using options --jid and --name.
 Podpowiedź: jeśli jest włączony tryb czatu, możesz użyć "/roster alternate" aby skoczyć do przeniesionej osoby.
diff -r 92fa48ef53c9 mcabber/doc/help/pl/hlp_rename.txt
--- a/mcabber/doc/help/pl/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/pl/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME nazwa
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] nazwa
 
-Zmienia nazwę aktualnej osoby lub grupy na "nazwa". Jeżeli jako argument polecenia zostanie podany "-" wcześniejsza nazwa zostanie usunięta z listy kontaktów (mcabber będzie wyświetlał JID lub nazwę użytkownika). 
+Zmienia nazwę aktualnej osoby lub grupy na "nazwa". 
+If --reset is specified, "newname" is ignored and name will be reset to default - jid or username.
+Optionally you can use one of --jid, --group or --name to select object, different from current.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,4 @@
 
- /DEL
+ /DEL [-n|--dryrun] [jid]
 
-Удаляет текущего пользователя из списка контактов, отключает уведомления о его статусе и отключает уведомления пользователя о вашем статусе.
+Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_move.txt
--- a/mcabber/doc/help/ru/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/ru/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,6 +1,7 @@
 
- /MOVE [groupname]
+ /MOVE [-j|--jid jid] [-n|--name name] [groupname]
 
 Перемещает пользователя в определенную группу. Если группа не указана, пользователь перещается в группу по умолчанию (default group). Если группа "groupname" не создана, она автоматически создается.
+С помощью параметров --jid и --name можно перемещать контакты, отличные от текущего.
 Полезно: Если включен режим чата (chatmode), Вы можете использовать "/roster alternate" для перехода к перемещенному пользователю.
 
diff -r 92fa48ef53c9 mcabber/doc/help/ru/hlp_rename.txt
--- a/mcabber/doc/help/ru/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/ru/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME name
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group name] [-n|--name name] newname
 
- Переименовывает текущего пользователя или группу в заданное имя "name". Если заданное имя имеет значение "-", то заданное ранее имя текущего пользователя или группы удаляется из списка контактов (mcabber будет отображать JID или имя пользователя по умолчанию).
+Переименовывает текущего пользователя или группу в заданное имя "newname".
+Если указан параметр --reset, "newname" игнорируется, а имя сбрасывается (mcabber будет отображать JID или имя пользователя по умолчанию).
+Для указания обьекта, отличного от текущего, можно использовать опции --jid, --group и --name.
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	Wed Feb 27 23:34:50 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	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,4 @@
 
- /DEL
+ /DEL [-n|--dryrun] [jid]
 
-Потерти поточний контакт зі списку. На додачу відписатися від його повідомлень про статус і відписати його від ваших.
+Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_move.txt
--- a/mcabber/doc/help/uk/hlp_move.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/uk/hlp_move.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,5 +1,6 @@
 
- /MOVE [група]
+ /MOVE [-j|--jid jid] [-n|--name ім’я] [група]
 
-Переносить поточний контакт до вказаної групи. Якщо групу не вказати контакт опиниться у головній групі. Якщо група не існує, її буде створено.
+Переносить поточний контакт до вказаної групи. Якщо групу не вказати контакт опиниться у головній групі. Якщо групи не існує, її буде створено.
+За допомогою опцій --jid та --name можна перемістити контакт, відмінний від поточного.
 Примітка: в режимі розмови можна використати "/roster alternate", щоб перейти до нового місця контакту контакту.
diff -r 92fa48ef53c9 mcabber/doc/help/uk/hlp_rename.txt
--- a/mcabber/doc/help/uk/hlp_rename.txt	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/doc/help/uk/hlp_rename.txt	Wed Feb 27 23:34:50 2013 +0200
@@ -1,4 +1,6 @@
 
- /RENAME ім'я
+ /RENAME [-r|--reset] [-j|--jid jid] [-g|--group ім’я] [-n|--name ім’я] нове ім’я
 
 Змінює прізвисько поточного контакту або назву групи.
+За допомогою параметра --reset можна повернути контакту типову назву. При цьому нове ім’я (якщо вказане) ігнорується.
+Опції --jid, --group та --name дозволяють перейменовувати об’єкти, відмінні від поточного.
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	Wed Feb 27 23:34:50 2013 +0200
@@ -502,7 +502,9 @@
   if (!iscmd && scr_get_multimode() == 2
       && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
     // It isn't an /msay command
-    scr_append_multiline(xpline);
+    gchar *utf8 = to_utf8(xpline);
+    scr_append_multiline(utf8);
+    g_free(utf8);
     g_free(xpline);
     return 0;
   }
@@ -568,10 +570,12 @@
 
   if (*line != COMMAND_CHAR) {
     // This isn't a command
+    gchar *utf8 = to_utf8(line);
     if (scr_get_multimode())
-      scr_append_multiline(line);
+      scr_append_multiline(utf8);
     else
-      say_cmd((char*)line, 0);
+      say_cmd(utf8, msgtype_not_set);
+    g_free(utf8);
     return 0;
   }
 
@@ -755,7 +759,7 @@
   g_slist_free(notes);
 }
 
-static void roster_note(char *arg)
+static void roster_note(gchar *arg)
 {
   const char *bjid;
   guint type;
@@ -781,14 +785,9 @@
   }
 
   if (arg && *arg) {  // Set a note
-    gchar *msg, *notetxt;
-    msg = to_utf8(arg);
-    if (!strcmp(msg, "-"))
-      notetxt = NULL; // delete note
-    else
-      notetxt = msg;
-    xmpp_set_storage_rosternotes(bjid, notetxt);
-    g_free(msg);
+    if (!strcmp(arg, "-"))
+      arg = NULL; // delete note
+    xmpp_set_storage_rosternotes(bjid, arg);
   } else {      // Display a note
     struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
     if (note) {
@@ -819,175 +818,228 @@
 /* All these do_*() functions will be called with a "arg" parameter */
 /* (with arg not null)                                              */
 
-static void do_roster(char *arg)
+static void do_roster(char *args)
 {
-  char **paramlst;
-  char *subcmd;
-
-  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);
+  enum roster_scmd_t {
+    roster_scmd_bottom, roster_scmd_top, roster_scmd_up, roster_scmd_down,
+    roster_scmd_group_prev, roster_scmd_group_next,
+    roster_scmd_alternate,
+    roster_scmd_unread_first, roster_scmd_unread_next,
+    roster_scmd_search,
+    roster_scmd_display,
+    roster_scmd_hide_offline, roster_scmd_show_offline, roster_scmd_toggle_offline,
+    roster_scmd_item_lock, roster_scmd_item_unlock, roster_scmd_item_toggle_lock,
+    roster_scmd_note,
+    roster_scmd_resource_lock, roster_scmd_resource_unlock,
+    roster_scmd_hide, roster_scmd_show, roster_scmd_toggle,
+  } subcmd;
+#define ROSTER_SCMD_NOARG(NAME) \
+      { #NAME, NULL, NULL, NULL, (gpointer)roster_scmd_##NAME, 0 }
+// all of them have at most one argument
+#define ROSTER_SCMD(NAME, FLAGS, VALUE) \
+      { #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 } }
+    },
+    (cmdopts_t[23]){
+      ROSTER_SCMD_NOARG(bottom),
+      ROSTER_SCMD_NOARG(top),
+      ROSTER_SCMD(up,               0, "1"), // num lines
+      ROSTER_SCMD(down,             0, "1"), // num lines
+      ROSTER_SCMD_NOARG(group_prev),
+      ROSTER_SCMD_NOARG(group_next),
+      ROSTER_SCMD_NOARG(alternate),
+      ROSTER_SCMD_NOARG(unread_first),
+      ROSTER_SCMD_NOARG(unread_next),
+      ROSTER_SCMD(search, CMDOPT_REQUIRED | CMDOPT_CATCHALL, NULL), // to find
+      ROSTER_SCMD(display,          0, NULL), // status mask
+      ROSTER_SCMD_NOARG(hide_offline),
+      ROSTER_SCMD_NOARG(show_offline),
+      ROSTER_SCMD_NOARG(toggle_offline),
+      ROSTER_SCMD(item_lock,        0, "."), // jid
+      ROSTER_SCMD(item_unlock,      0, "."), // jid
+      ROSTER_SCMD(item_toggle_lock, 0, "."), // jid
+      ROSTER_SCMD(note, CMDOPT_CATCHALL, NULL), // note
+      ROSTER_SCMD(resource_lock,    0, NULL), // resource/jid
+      ROSTER_SCMD(resource_unlock,  0, NULL), // resource/jid
+      ROSTER_SCMD_NOARG(hide),
+      ROSTER_SCMD_NOARG(show),
+      { "toggle", NULL, NULL, NULL, (gpointer)roster_scmd_toggle,
+        CMDOPT_LAST },
+    },
+    NULL,
+  };
+  gchar *arg;
+
+  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")) {
+  if (subcmd == roster_scmd_bottom) {
+    scr_roster_bottom();
+    update_roster = TRUE;
+  } else if (subcmd == roster_scmd_top) {
     scr_roster_top();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "bottom")) {
-    scr_roster_bottom();
+  } else if (subcmd == roster_scmd_up) {
+    roster_updown(-1, arg);
+  } else if (subcmd == roster_scmd_down) {
+    roster_updown(1, arg);
+  } else if (subcmd == roster_scmd_group_prev) {
+    scr_roster_prev_group();
+  } else if (subcmd == roster_scmd_group_next) {
+    scr_roster_next_group();
+  } else if (subcmd == roster_scmd_alternate) {
+    scr_roster_jump_alternate();
+  } else if (subcmd == roster_scmd_unread_first) {
+    scr_roster_unread_message(0);
+  } else if (subcmd == roster_scmd_unread_next) {
+    scr_roster_unread_message(1);
+  } else if (subcmd == roster_scmd_search) {
+    scr_roster_search(arg);
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "hide")) {
-    scr_roster_visibility(0);
-  } else if (!strcasecmp(subcmd, "show")) {
-    scr_roster_visibility(1);
-  } else if (!strcasecmp(subcmd, "toggle")) {
-    scr_roster_visibility(-1);
-  } else if (!strcasecmp(subcmd, "hide_offline")) {
+  } else if (subcmd == roster_scmd_display) {
+    scr_roster_display(arg);
+  } else if (subcmd == roster_scmd_hide_offline) {
     buddylist_set_hide_offline_buddies(TRUE);
-    if (current_buddy)
+    if (current_buddy) // XXX
       buddylist_build();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "show_offline")) {
+  } else if (subcmd == roster_scmd_show_offline) {
     buddylist_set_hide_offline_buddies(FALSE);
     buddylist_build();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "toggle_offline")) {
+  } else if (subcmd == roster_scmd_toggle_offline) {
     buddylist_set_hide_offline_buddies(-1);
     buddylist_build();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "display")) {
-    scr_roster_display(arg);
-  } else if (!strcasecmp(subcmd, "item_lock")) {
+  } else if (subcmd == roster_scmd_item_lock) {
     roster_buddylock(arg, 1);
-  } else if (!strcasecmp(subcmd, "item_unlock")) {
+  } else if (subcmd == roster_scmd_item_unlock) {
     roster_buddylock(arg, 0);
-  } else if (!strcasecmp(subcmd, "item_toggle_lock")) {
+  } else if (subcmd == roster_scmd_item_toggle_lock) {
     roster_buddylock(arg, -1);
-  } else if (!strcasecmp(subcmd, "unread_first")) {
-    scr_roster_unread_message(0);
-  } else if (!strcasecmp(subcmd, "unread_next")) {
-    scr_roster_unread_message(1);
-  } else if (!strcasecmp(subcmd, "alternate")) {
-    scr_roster_jump_alternate();
-  } else if (!strncasecmp(subcmd, "search", 6)) {
-    strip_arg_special_chars(arg);
-    if (!arg || !*arg) {
-      scr_LogPrint(LPRINT_NORMAL, "What name or JID are you looking for?");
-      free_arg_lst(paramlst);
-      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")) {
+  } else if (subcmd == roster_scmd_note) {
     roster_note(arg);
-  } else if (!strcasecmp(subcmd, "resource_lock")) {
+  } else if (subcmd == roster_scmd_resource_lock) {
     roster_resourcelock(arg, TRUE);
-  } else if (!strcasecmp(subcmd, "resource_unlock")) {
+  } else if (subcmd == roster_scmd_resource_unlock) {
     roster_resourcelock(arg, FALSE);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  free_arg_lst(paramlst);
+  } else if (subcmd == roster_scmd_hide) {
+    scr_roster_visibility(0);
+  } else if (subcmd == roster_scmd_show) {
+    scr_roster_visibility(1);
+  } else { // roster_scmd_toggle
+    scr_roster_visibility(-1);
+  }
+
+  cmdopts_free(&options);
 }
 
 void do_color(char *arg)
 {
-  char **paramlst;
-  char *subcmd;
-
-  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);
+  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;
-    char **arglist = split_arg(arg, 3, 0);
-
-    status = *arglist;
-    wildcard = to_utf8(arglist[1]);
-    color = arglist[2];
-
-    if (status && !strcmp(status, "clear")) { // Not a color command, clear all
+  
+  subcmd = (enum color_scmd_t) options.args[0].value.cmd -> userdata;
+
+  if (subcmd == color_scmd_roster) {
+    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;
     } else {
-      if (!status || !*status || !wildcard || !*wildcard || !color || !*color) {
-        scr_LogPrint(LPRINT_NORMAL, "Missing argument");
+      if (!*status || !wildcard || !*wildcard || !color || !*color) {
+        scr_log_print(LPRINT_NORMAL, "Missing argument");
       } else {
         update_roster = scr_roster_color(status, wildcard, color) ||
                         update_roster;
       }
     }
-    free_arg_lst(arglist);
-    g_free(wildcard);
-  } else if (!strcasecmp(subcmd, "muc")) {
-    char **arglist = split_arg(arg, 2, 0);
-    char *free_muc = to_utf8(*arglist);
-    const char *muc = free_muc, *mode = arglist[1];
-    if (!muc || !*muc)
-      scr_LogPrint(LPRINT_NORMAL, "What MUC?");
-    else {
-      if (!strcmp(muc, "."))
-        if (!(muc = CURRENT_JID))
-          scr_LogPrint(LPRINT_NORMAL, "No JID selected");
-      if (muc) {
-        if (check_jid_syntax(muc) && strcmp(muc, "*"))
-          scr_LogPrint(LPRINT_NORMAL, "Not a JID");
-        else {
-          if (!mode || !*mode || !strcasecmp(mode, "on"))
-            scr_muc_color(muc, MC_ALL);
-          else if (!strcasecmp(mode, "preset"))
-            scr_muc_color(muc, MC_PRESET);
-          else if (!strcasecmp(mode, "off"))
-            scr_muc_color(muc, MC_OFF);
-          else if (!strcmp(mode, "-"))
-            scr_muc_color(muc, MC_REMOVE);
-          else
-            scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
-        }
+  } else if (subcmd == color_scmd_muc) {
+    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");
+    if (muc) {
+      if (check_jid_syntax(muc) && strcmp(muc, "*"))
+        scr_LogPrint(LPRINT_NORMAL, "Not a JID");
+      else {
+        if (!strcasecmp(mode, "on"))
+          scr_muc_color(muc, MC_ALL);
+        else if (!strcasecmp(mode, "preset"))
+          scr_muc_color(muc, MC_PRESET);
+        else if (!strcasecmp(mode, "off"))
+          scr_muc_color(muc, MC_OFF);
+        else if (!strcmp(mode, "-"))
+          scr_muc_color(muc, MC_REMOVE);
+        else
+          scr_LogPrint(LPRINT_NORMAL, "Unknown coloring mode");
       }
     }
-    free_arg_lst(arglist);
-    g_free(free_muc);
-  } else if (!strcasecmp(subcmd, "mucnick")) {
-    char **arglist = split_arg(arg, 2, 0);
-    const char *nick = *arglist, *color = arglist[1];
-    if (!nick || !*nick || !color || !*color)
-      scr_LogPrint(LPRINT_NORMAL, "Missing argument");
-    else
-      scr_muc_nick_color(nick, color);
-    free_arg_lst(arglist);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  free_arg_lst(paramlst);
+  } else { // color_scmd_mucnick
+    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);
+  }
+
+  cmdopts_free(&options);
 }
 
-//  cmd_setstatus(recipient, arg)
+//  cmd_setstatus(recipient, status, message)
 // Set your Jabber status.
-// - if recipient is not NULL, the status is sent to this contact only
-// - arg must be "status message" (message is optional)
-void cmd_setstatus(const char *recipient, const char *arg)
+// If recipient is not NULL, the status is sent to this contact only.
+void cmd_setstatus(const char *recipient, const char *status, const char *msg)
 {
-  char **paramlst;
-  char *status;
-  char *msg;
   enum imstatus st;
 
   if (!xmpp_is_online())
@@ -1000,15 +1052,15 @@
   if (!recipient)
     scr_check_auto_away(TRUE);
 
-  paramlst = split_arg(arg, 2, 1); // status, message
-  status = *paramlst;
-  msg = *(paramlst+1);
-
   if (!status) {
-    free_arg_lst(paramlst);
     return;
   }
 
+  // Use provided message
+  if (msg && !*msg) {
+    msg = NULL;
+  }
+
   if      (!strcasecmp(status, IMSTATUS_OFFLINE))       st = offline;
   else if (!strcasecmp(status, IMSTATUS_ONLINE))        st = available;
   else if (!strcasecmp(status, IMSTATUS_AVAILABLE))     st = available;
@@ -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")) {
-    if (!msg || !*msg) {
+    if (!msg) {
       // We want a message.  If there's none, we give up.
       scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-      free_arg_lst(paramlst);
       return;
     }
     st = xmpp_getstatus();  // Preserve current status
   } else {
     scr_LogPrint(LPRINT_NORMAL, "Unrecognized status!");
-    free_arg_lst(paramlst);
     return;
   }
 
-  // Use provided message
-  if (msg && !*msg) {
-    msg = NULL;
-  }
-
   // If a recipient is specified, let's don't use default status messages
   if (recipient && !msg)
     msg = "";
 
   xmpp_setstatus(st, recipient, msg, FALSE);
-
-  free_arg_lst(paramlst);
 }
 
 static void do_status(char *arg)
 {
-  if (!*arg) {
+  cmdopts_t options = {
+    "status",
+    NULL,
+    (cmdarg_t[2]){
+      // status
+      { 0,                                            { .arg = NULL } },
+      // message
+      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+    },
+    NULL,
+  };
+
+  if (cmdopts_parse(arg, &options))
+    return;
+
+  if (options.args[0].value.arg == NULL) {
     const char *sm = xmpp_getstatusmsg();
     scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
                  imstatus2char[xmpp_getstatus()],
                  (sm ? sm : ""));
-    return;
+  } else {
+    cmd_setstatus(NULL, options.args[0].value.arg, options.args[1].value.arg);
   }
-  arg = to_utf8(arg);
-  cmd_setstatus(NULL, arg);
-  g_free(arg);
+  
+  cmdopts_free(&options);
 }
 
 static void do_status_to(char *arg)
 {
-  char **paramlst;
+  cmdopts_t options = {
+    "status_to",
+    NULL,
+    (cmdarg_t[3]){
+      // jid
+      { CMDOPT_REQUIRED,                              { .arg = NULL } },
+      // status
+      { CMDOPT_REQUIRED,                              { .arg = NULL } },
+      // message
+      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = ""   } },
+    },
+    NULL,
+  };
   char *fjid, *st, *msg;
-  char *jid_utf8 = NULL;
-
-  paramlst = split_arg(arg, 3, 1); // jid, status, [message]
-  fjid = *paramlst;
-  st = *(paramlst+1);
-  msg = *(paramlst+2);
-
-  if (!fjid || !st) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "Please specify both a Jabber ID and a status.");
-    free_arg_lst(paramlst);
+
+  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, "."))
@@ -1086,15 +1150,13 @@
   if (fjid) {
     // The JID has been specified.  Quick check...
     if (check_jid_syntax(fjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
+      scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber ID.", fjid);
       fjid = NULL;
     } else {
       // Convert jid to lowercase
       char *p = fjid;
       for ( ; *p && *p != JID_RESOURCE_SEPARATOR; p++)
         *p = tolower(*p);
-      fjid = jid_utf8 = to_utf8(fjid);
     }
   } else {
     // Add the current buddy
@@ -1105,144 +1167,190 @@
   }
 
   if (fjid) {
-    char *cmdline;
-    if (!msg)
-      msg = "";
-    msg = to_utf8(msg);
-    cmdline = g_strdup_printf("%s %s", st, msg);
-    scr_LogPrint(LPRINT_LOGNORM, "Sending to <%s> /status %s", fjid, cmdline);
-    cmd_setstatus(fjid, cmdline);
-    g_free(msg);
-    g_free(cmdline);
-    g_free(jid_utf8);
+    scr_LogPrint(LPRINT_LOGNORM, 
+                 "Sending to <%s> /status %s %s", fjid, st, msg);
+    cmd_setstatus(fjid, st, msg);
   }
-  free_arg_lst(paramlst);
+
+  cmdopts_free(&options);
 }
 
 static void do_add(char *arg)
 {
-  char **paramlst;
-  char *id, *nick;
-  char *jid_utf8 = NULL;
+  cmdopts_t options = {
+    "add",
+    NULL,
+    (cmdarg_t[2]){
+      // jid
+      { 0,                                            { .arg = "."  } },
+      // rostername
+      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+    },
+    NULL,
+  };
+  gchar *jid, *nick;
 
   if (!xmpp_is_online()) {
     scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
     return;
   }
 
-  paramlst = split_arg(arg, 2, 0); // jid, [nickname]
-  id = *paramlst;
-  nick = *(paramlst+1);
-
-  if (!id)
-    nick = NULL; // Allow things like: /add "" nick
-  else if (!*id || !strcmp(id, "."))
-    id = NULL;
-
-  if (id) {
+  if (cmdopts_parse(arg, &options))
+    return;
+
+  jid  = options.args[0].value.arg;
+  nick = options.args[1].value.arg;
+
+  if (jid && (!*jid || !strcmp(jid, ".")))
+    jid = NULL;
+
+  if (jid) {
     // The JID has been specified.  Quick check...
-    if (check_jid_syntax(id)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", id);
-      id = NULL;
+    if (check_jid_syntax(jid)) {
+      scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber ID.", jid);
+      jid = NULL;
     } else {
-      mc_strtolower(id);
-      id = jid_utf8 = to_utf8(id);
+      mc_strtolower(jid);
     }
   } else {
     // Add the current buddy
     if (current_buddy)
-      id = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!id)
+      jid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!jid)
       scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
   }
 
-  if (nick)
-    nick = to_utf8(nick);
-
-  if (id) {
+  if (jid) {
     // 2nd parameter = optional nickname
-    xmpp_addbuddy(id, nick, NULL);
+    xmpp_addbuddy(jid, nick, NULL);
     scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
-                 id);
+                 jid);
   }
 
-  g_free(jid_utf8);
-  g_free(nick);
-  free_arg_lst(paramlst);
+  cmdopts_free(&options);
 }
 
 static void do_del(char *arg)
 {
-  const char *bjid;
-
-  if (*arg) {
-    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 } },
+    },
+    (cmdarg_t[1]){
+      { CMDOPT_LAST, { .arg = "."  } }, // jid
+    },
+    NULL,
+  };
+  gchar *jid;
+  gpointer buddy;
+
+  if (!xmpp_is_online()) {
+    scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
     return;
   }
 
+  if (cmdopts_parse(arg, &options))
+    return;
+
+  jid = options.args[0].value.arg;
+
+  if (jid && (!*jid || !strcmp(jid, ".")))
+    jid = NULL;
+
+  if (jid) {
+    // The JID has been specified.  Quick check...
+    if (check_jid_syntax(jid)) {
+      scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber ID.", jid);
+      jid = NULL;
+    } else {
+      GSList *found;
+      mc_strtolower(jid);
+      found = roster_find(jid, jidsearch, ROSTER_TYPE_USER |
+                          ROSTER_TYPE_AGENT | ROSTER_TYPE_GROUP);
+      if (!found) {
+        scr_log_print(LPRINT_NORMAL, "Jabber ID <%s> is not in roster.", jid);
+        jid = NULL;
+      } else {
+        buddy = found -> data;
+      }
+    }
+  } else {
+    // Use current buddy
+    if (current_buddy)
+      jid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!jid)
+      scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
+    else
+      buddy = BUDDATA(current_buddy);
+  }
+
+  if (jid) {
+    if (buddy_gettype(buddy) & ROSTER_TYPE_ROOM) {
+      // This is a chatroom
+      if (buddy_getinsideroom(buddy)) {
+        scr_LogPrint(LPRINT_NORMAL, "You haven't left this room!");
+        cmdopts_free(&options);
+        return;
+      }
+    }
+
+    scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", jid);
+
+    if (!options.opts[0].value.swc) {
+      // Close the buffer
+      scr_buffer_purge(1, jid);
+
+      xmpp_delbuddy(jid);
+      scr_update_buddy_window();
+    }
+  }
+
+  cmdopts_free(&options);
+}
+
+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) \
+      { #NAME, NULL, (cmdarg_t[1]){ \
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } }, \
+        }, 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),
+    },
+  };
+  gchar *name;
+  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;
-
-  if (!*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    return;
-  }
-
-  if (!current_buddy)
-    return;
-
-  paramlst = split_arg(arg, 2, 0); // subcmd, [arg]
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd)
-    goto do_group_return;   // Should not happen
-
-  if (!strcasecmp(subcmd, "expand") || !strcasecmp(subcmd, "unfold"))
-    group_state = group_unfold;
-  else if (!strcasecmp(subcmd, "shrink") || !strcasecmp(subcmd, "fold"))
-    group_state = group_fold;
-  else if (!strcasecmp(subcmd, "toggle"))
-    group_state = group_toggle;
-  else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-    goto do_group_return;
-  }
-
-  if (arg && *arg) {
+  
+  subcmd = (enum group_scmd_t) options.args[0].value.cmd -> userdata;
+  name   = options.args[0].value.cmd -> args[0].value.arg;
+
+  if (name && *name) {
     GSList *roster_elt;
-    char *group_utf8 = to_utf8(arg);
-    roster_elt = roster_find(group_utf8, namesearch, ROSTER_TYPE_GROUP);
-    g_free(group_utf8);
+    roster_elt = roster_find(name, namesearch, ROSTER_TYPE_GROUP);
     if (roster_elt)
       group = buddy_getgroup(roster_elt->data);
   } else {
@@ -1264,20 +1372,20 @@
     goto do_group_return;
   }
 
-  if (group_state != group_unfold && leave_buddywindow)
+  if (subcmd != group_scmd_unfold && leave_buddywindow)
     scr_roster_prev_group();
 
-  buddy_hide_group(group, group_state);
+  buddy_hide_group(group, subcmd);
 
   buddylist_build();
   update_roster = TRUE;
 
 do_group_return:
-  free_arg_lst(paramlst);
+  cmdopts_free(&options);
 }
 
 static int send_message_to(const char *fjid, const char *msg, const char *subj,
-                           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)) {
-    scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                 "<%s> is not a valid Jabber ID.", fjid);
+    scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber ID.", fjid);
     return 1;
   }
 
+  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);
 }
 
-static LmMessageSubType scan_mtype(char **arg)
-{
-  // Try splitting it
-  char **parlist = split_arg(*arg, 2, 1);
-  LmMessageSubType result = LM_MESSAGE_SUB_TYPE_NOT_SET;
-  // Is it a good parameter?
-  if (parlist && *parlist) {
-    if (!strcmp("-n", *parlist)) {
-      result = LM_MESSAGE_SUB_TYPE_NORMAL;
-    } else if (!strcmp("-h", *parlist)) {
-      result = LM_MESSAGE_SUB_TYPE_HEADLINE;
-    }
-    if (result != LM_MESSAGE_SUB_TYPE_NOT_SET || (!strcmp("--", *parlist)))
-      *arg += strlen(*arg) - (parlist[1] ? strlen(parlist[1]) : 0);
-  }
-  // Anything found? -> skip it
-  free_arg_lst(parlist);
-  return result;
-}
-
-void say_cmd(char *arg, int parse_flags)
+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 +1515,131 @@
   }
 
   buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
-  if (parse_flags)
-    msgtype = scan_mtype(&arg);
-  arg = to_utf8(arg);
   send_message(arg, NULL, msgtype);
-  g_free(arg);
 }
 
 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 } },
+    },
+    (cmdarg_t[1]){
+      { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_REQUIRED | CMDOPT_LAST,
+        { .arg = NULL } },
+    },
+    NULL,
+  };
+  msgtype_t msgtype = msgtype_not_set;
+
+  if (cmdopts_parse(arg, &options))
+    return;
+
+  if (options.opts[0].value.swc)
+    msgtype = msgtype_normal;
+  else if (options.opts[1].value.swc)
+    msgtype = msgtype_headline;
+
+  say_cmd(options.args[0].value.arg, msgtype);
+
+  cmdopts_free(&options);
 }
 
 static void do_msay(char *arg)
 {
-  /* Parameters: begin verbatim abort send send_to */
-  char **paramlst;
-  char *subcmd;
-
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-    scr_LogPrint(LPRINT_NORMAL, "Please read the manual before using "
-                 "the /msay command.");
-    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) {
+    if (scr_get_multimode())
+      subcmd = msay_scmd_send;
+    else
+      subcmd = msay_scmd_begin;
+  } else if (subcmd == msay_scmd_toggle_verbatim) {
+    if (scr_get_multimode())
+      subcmd = msay_scmd_send;
+    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())
       scr_LogPrint(LPRINT_NORMAL, "Leaving multi-line message mode.");
     scr_set_multimode(FALSE, NULL);
     goto do_msay_return;
-  } else if ((!strcasecmp(subcmd, "begin")) ||
-             (!strcasecmp(subcmd, "verbatim"))) {
-    bool verbat;
-    gchar *subj_utf8 = to_utf8(arg);
-    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) {
+      scr_set_multimode(2, subject);
     } else {
-      scr_set_multimode(1, subj_utf8);
-      verbat = FALSE;
+      scr_set_multimode(1, subject);
     }
 
     scr_LogPrint(LPRINT_NORMAL, "Entered %smulti-line message mode.",
-                 verbat ? "VERBATIM " : "");
+                 subcmd == msay_scmd_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)
       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;
   }
 
-  /* send/send_to command */
+  /* msay_scmd_send or msay_scmd_send_to */
 
   if (!scr_get_multimode()) {
     scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
@@ -1508,49 +1650,47 @@
   scr_set_chatmode(TRUE);
   scr_show_buddy_window();
 
-  if (!strcasecmp(subcmd, "send_to")) {
-    int err = FALSE;
-    gchar *msg_utf8;
-    LmMessageSubType msg_type = scan_mtype(&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).
-    arg = to_utf8(arg);
-    msg_utf8 = to_utf8(scr_get_multiline());
-    if (msg_utf8) {
-      err = send_message_to(arg, msg_utf8, scr_get_multimode_subj(), msg_type,
-                            FALSE);
-      g_free(msg_utf8);
-    }
-    g_free(arg);
-    if (err)
-      goto do_msay_return;
-  } else { // Send to currently selected buddy
-    gpointer bud;
-    gchar *msg_utf8;
-
-    if (!current_buddy) {
-      scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?");
-      goto do_msay_return;
-    }
-
-    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;
-    }
-
-    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
-    msg_utf8 = to_utf8(scr_get_multiline());
-    if (msg_utf8) {
-      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;
+
+      // 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;
+    } else { // Send to currently selected buddy
+      gpointer bud;
+
+      if (!current_buddy) {
+        scr_LogPrint(LPRINT_NORMAL, "Whom are you talking to?");
+        goto do_msay_return;
+      }
+
+      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;
+      }
+
+      buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
+      send_message(msg, scr_get_multimode_subj(), msg_type);
     }
   }
+
   scr_set_multimode(FALSE, NULL);
   scr_LogPrint(LPRINT_NORMAL, "You have left multi-line message mode.");
+
 do_msay_return:
-  free_arg_lst(paramlst);
+  cmdopts_free(&options);
 }
 
 //  load_message_from_file(filename)
@@ -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 *msg;
-  char *unescaped_msg = NULL;
-  char *uncompletedfjid = NULL;
-  char *file = NULL;
-  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 }    },
+      { CMDOPT_SWITCH, 'e', "escapes",  { .swc = 0 }    },
+      { CMDOPT_SWITCH, 'q', "quiet",    { .swc = 0 }    },
+      { CMDOPT_LAST,   'f', "file",     { .opt = NULL } },
+    },
+    (cmdarg_t[2]){
+      // jid
+      { CMDOPT_REQUIRED,                              { .arg = NULL } },
+      // message
+      { CMDOPT_LAST | CMDOPT_PLAIN | CMDOPT_CATCHALL, { .arg = 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.");
     return;
   }
 
-  msg_type = scan_mtype(&arg);
-  paramlst = split_arg(arg, 2, 1); // jid, message (or option, jid, message)
-
-  if (!*paramlst) {  // No parameter?
-    scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
-    free_arg_lst(paramlst);
+  if (cmdopts_parse(arg, &options))
     return;
-  }
-
-  // Check for an option parameter
-  while (*paramlst) {
-    if (!strcmp(*paramlst, "-q")) {
-      char **oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message
-      free_arg_lst(oldparamlst);
-      quiet = TRUE;
-    } else if (!strcmp(*paramlst, "-e")) {
-      char **oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, message
-      free_arg_lst(oldparamlst);
-      eval = TRUE;
-    } else if (!strcmp(*paramlst, "-f")) {
-      char **oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // filename, jid
-      free_arg_lst(oldparamlst);
-      if (!*paramlst) {
-        scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
-        free_arg_lst(paramlst);
-        return;
-      }
-      file = g_strdup(*paramlst);
-      // One more parameter shift...
-      oldparamlst = paramlst;
-      paramlst = split_arg(*(oldparamlst+1), 2, 1); // jid, nothing
-      free_arg_lst(oldparamlst);
-    } else
-      break;
-  }
-
-  if (!*paramlst) {
-    scr_LogPrint(LPRINT_NORMAL, "Wrong usage.");
-    free_arg_lst(paramlst);
-    return;
-  }
-
-  fjid = *paramlst;
-  msg = *(paramlst+1);
-
+
+  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;
+  file = options.opts[4].value.opt;
+
+  // ideally, this should go to commandline parsing subsystem
   if (fjid[0] == '.') {
     const gchar *cjid = (current_buddy ? CURRENT_JID : NULL);
     if (fjid[1] == '\0') {
-      fjid = g_strdup(cjid);
+      fjid = (char *)cjid; // FIXME
     } else if (fjid[1] == JID_RESOURCE_SEPARATOR) {
       char *res_utf8 = to_utf8(fjid+2);
-      fjid = g_strdup_printf("%s%c%s", cjid, JID_RESOURCE_SEPARATOR, res_utf8);
+      freeme = fjid = g_strdup_printf("%s%c%s", cjid, JID_RESOURCE_SEPARATOR, res_utf8);
       g_free(res_utf8);
-    } else
-      fjid = to_utf8(fjid);
-  } else
-    fjid = to_utf8(fjid);
-
+    }
+  }
+
+  // ditto
   if (!strchr(fjid, JID_DOMAIN_SEPARATOR)) {
     const gchar *append_server = settings_opt_get("default_server");
     if (append_server) {
       gchar *res = strchr(fjid, JID_RESOURCE_SEPARATOR);
-      uncompletedfjid = fjid;
       if (res) {
         *res++ = '\0';
         fjid = g_strdup_printf("%s%c%s%c%s", fjid, JID_DOMAIN_SEPARATOR, append_server,
                                JID_RESOURCE_SEPARATOR, res);
       } else
         fjid = g_strdup_printf("%s%c%s", fjid, JID_DOMAIN_SEPARATOR, append_server);
+      g_free(freeme);
+      freeme = fjid;
     }
   }
 
+  // as well
   if (check_jid_syntax(fjid)) {
     scr_LogPrint(LPRINT_NORMAL, "Please specify a valid Jabber ID.");
-    free_arg_lst(paramlst);
-    g_free(uncompletedfjid);
-    g_free(fjid);
+    g_free(freeme);
     return;
   }
 
-  if (!file) {
-    msg_utf8 = to_utf8(msg);
-    if (eval) {
-      unescaped_msg = ut_unescape_tabs_cr(msg_utf8);
+  if (file == NULL) {
+    if (options.opts[2].value.swc) {
+      freeme2 = ut_unescape_tabs_cr(msg);
       // We must not free() if the original string was returned
-      if (unescaped_msg == msg_utf8)
-        unescaped_msg = NULL;
+      if (freeme2 == msg)
+        freeme2 = NULL;
+      else
+        msg = freeme2;
     }
-    msg = (unescaped_msg ? unescaped_msg : msg_utf8);
   } else {
     char *filename_xp;
     if (msg)
       scr_LogPrint(LPRINT_NORMAL, "say_to: extra parameter ignored.");
     filename_xp = expand_filename(file);
-    msg = msg_utf8 = load_message_from_file(filename_xp);
+    freeme2 = msg = load_message_from_file(filename_xp);
     g_free(filename_xp);
-    g_free(file);
   }
 
-  send_message_to(fjid, msg, NULL, msg_type, quiet);
-
-  g_free(uncompletedfjid);
-  g_free(fjid);
-  g_free(msg_utf8);
-  g_free(unescaped_msg);
-  free_arg_lst(paramlst);
+  send_message_to(fjid, msg, NULL, msg_type, options.opts[3].value.swc);
+
+  cmdopts_free(&options);
+  g_free(freeme);
+  g_free(freeme2);
 }
 
 //  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 = NULL } }, // lines
+        },
+        NULL, (gpointer)buffer_scmd_up, 0 },
+      { "down", NULL,
+        (cmdarg_t[1]){
+          { CMDOPT_LAST, { .arg = NULL } }, // 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_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_clear) {
+    scr_buffer_clear();
+  } else if (subcmd == buffer_scmd_purge) {
+    scr_buffer_purge(0, options.cmds[3].args[0].value.arg);
+  } else if (subcmd == buffer_scmd_list) {
+    scr_buffer_list();
+  } else 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_up) {
+    buffer_updown(-1, options.cmds[7].args[0].value.arg);
+  } else if (subcmd == buffer_scmd_down) {
+    buffer_updown(1, options.cmds[8].args[0].value.arg);
+  } else if (subcmd == buffer_scmd_date) {
+    buffer_date(options.cmds[9].args[0].value.arg);
+  } else if (subcmd == buffer_scmd_percent) {
+    scr_buffer_percent(atoi(options.cmds[10].args[0].value.arg));
+  } else if (subcmd == buffer_scmd_readmark) {
+    scr_buffer_jump_readmark();
+  } else if (subcmd == buffer_scmd_search_backward) {
+    scr_buffer_search(-1, options.cmds[12].args[0].value.arg);
+  } else if (subcmd == buffer_scmd_search_forward) {
+    scr_buffer_search(1, options.cmds[13].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 { // buffer_scmd_save
+    scr_buffer_dump(options.cmds[17].args[0].value.arg);
   }
 
-  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);
 
@@ -2145,16 +2290,69 @@
 
 static void do_rename(char *arg)
 {
+  cmdopts_t options = {
+    "rename",
+    (cmdopt_t[4]){
+      { CMDOPT_SWITCH, 'r', "reset", { .swc = 0 } },
+      { 0,             'n', "name",  { .opt = NULL } },
+      { 0,             'g', "group", { .opt = NULL } },
+      { CMDOPT_LAST,   'j', "jid",   { .opt = "." } },
+    },
+    (cmdarg_t[1]){
+      { CMDOPT_CATCHALL | CMDOPT_LAST, { .arg = "" } }, // new name
+    },
+    NULL,
+  };
   gpointer bud;
   const char *bjid, *group;
   guint type, on_srv;
   char *newname, *p;
-  char *name_utf8;
-
-  if (!current_buddy)
+  gboolean reset;
+  enum findwhat srchwhat = jidsearch;
+  guint         srchtype = ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | ROSTER_TYPE_AGENT;
+  gchar        *srchterm;
+
+  if (cmdopts_parse(arg, &options))
     return;
-  bud = BUDDATA(current_buddy);
-
+
+  reset   = options.opts[0].value.swc;
+  newname = options.args[0].value.arg;
+
+  if (options.opts[1].value.opt) { // n
+    srchwhat = namesearch;
+    srchtype |= ROSTER_TYPE_GROUP;
+    srchterm = options.opts[1].value.opt;
+  } else if (options.opts[2].value.opt) { // g
+    srchwhat = namesearch;
+    srchtype = ROSTER_TYPE_GROUP;
+    srchterm = options.opts[2].value.opt;
+  } else
+    srchterm = options.opts[3].value.opt;
+
+  if (!*srchterm || !strcmp(srchterm, ".")) {
+    if (!current_buddy) {
+      cmdopts_free(&options);
+      return;
+    }
+    bud = BUDDATA(current_buddy);
+    if (srchtype == ROSTER_TYPE_GROUP)
+      bud = buddy_getgroup(bud);
+  } else {
+    GSList *found;
+    if (srchwhat == jidsearch && check_jid_syntax(srchterm)) {
+      scr_log_print(LPRINT_NORMAL, "You must specify a valid jid!");
+      cmdopts_free(&options);
+      return;
+    }
+    found = roster_find(srchterm, srchwhat, srchtype);
+    if (!found) {
+      scr_log_print(LPRINT_NORMAL, "Can't find <%s>!", srchterm);
+      cmdopts_free(&options);
+      return;
+    }
+    bud = found -> data;
+  }
+    
   bjid   = buddy_getjid(bud);
   group  = buddy_getgroupname(bud);
   type   = buddy_gettype(bud);
@@ -2162,11 +2360,13 @@
 
   if (type & ROSTER_TYPE_SPECIAL) {
     scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
+    cmdopts_free(&options);
     return;
   }
 
-  if (!*arg && !(type & ROSTER_TYPE_GROUP)) {
+  if (!*newname && !reset) {
     scr_LogPrint(LPRINT_NORMAL, "Please specify a new name.");
+    cmdopts_free(&options);
     return;
   }
 
@@ -2181,90 +2381,117 @@
   //  }
   //}
 
-  newname = g_strdup(arg);
   // Remove trailing space
   for (p = newname; *p; p++) ;
   while (p > newname && *p == ' ') *p = 0;
 
-  strip_arg_special_chars(newname);
-
-  name_utf8 = to_utf8(newname);
-
   if (type & ROSTER_TYPE_GROUP) {
     // Rename a whole group
-    foreach_group_member(bud, &move_group_member, name_utf8);
+    foreach_group_member(bud, &move_group_member, newname);
     // Let's jump to the previous buddy, because this group name should
     // disappear when we receive the server answer.
     scr_roster_up_down(-1, 1);
   } else {
     // Rename a single buddy
-    guint del_name = 0;
-    if (!*newname || !strcmp(arg, "-"))
-      del_name = TRUE;
     if (on_srv) {
-      /* We do not rename the buddy right now because the server could reject
-       * the request.  Let's wait for the server answer.
-       */
-      xmpp_updatebuddy(bjid, (del_name ? NULL : name_utf8),
+      // We do not rename the buddy right now because the server could reject
+      // the request.  Let's wait for the server answer.
+      xmpp_updatebuddy(bjid, (reset ? NULL : newname),
                        group && *group ? group : NULL);
     } else {
       // This is a local item, we rename it without adding to roster.
-      buddy_setname(bud, (del_name ? (char*)bjid : name_utf8));
+      buddy_setname(bud, (reset ? (char*)bjid : newname));
       if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
           settings_opt_get_int("muc_bookmark_autoupdate"))
         room_bookmark(bud, NULL);
     }
   }
 
-  g_free(name_utf8);
-  g_free(newname);
+  cmdopts_free(&options);
   update_roster = TRUE;
 }
 
 static void do_move(char *arg)
 {
+  cmdopts_t options = {
+    "move",
+    (cmdopt_t[4]){
+      { 0,           'n', "name", { .opt = NULL } },
+      { CMDOPT_LAST, 'j', "jid",  { .opt = "." } },
+    },
+    (cmdarg_t[1]){
+      { CMDOPT_CATCHALL | CMDOPT_LAST, { .arg = "" } }, // new group name
+    },
+    NULL,
+  };
   gpointer bud;
   const char *bjid, *name, *oldgroupname;
   guint type, on_srv;
   char *newgroupname, *p;
-  char *group_utf8;
-
-  if (!current_buddy)
+  enum findwhat srchwhat = jidsearch;
+  gchar        *srchterm;
+
+  if (cmdopts_parse(arg, &options))
     return;
-  bud = BUDDATA(current_buddy);
+
+  newgroupname = options.args[0].value.arg;
+
+  if (options.opts[0].value.opt) { // n
+    srchwhat = namesearch;
+    srchterm = options.opts[0].value.opt;
+  } else
+    srchterm = options.opts[1].value.opt;
+
+  if (!*srchterm || !strcmp(srchterm, ".")) {
+    if (!current_buddy) {
+      cmdopts_free(&options);
+      return;
+    }
+    bud = BUDDATA(current_buddy);
+  } else {
+    GSList *found;
+    if (srchwhat == jidsearch && check_jid_syntax(srchterm)) {
+      scr_log_print(LPRINT_NORMAL, "You must specify a valid jid!");
+      cmdopts_free(&options);
+      return;
+    }
+    found = roster_find(srchterm, srchwhat, ROSTER_TYPE_USER |
+                        ROSTER_TYPE_ROOM | ROSTER_TYPE_AGENT);
+    if (!found) {
+      scr_log_print(LPRINT_NORMAL, "Can't find <%s>!", srchterm);
+      cmdopts_free(&options);
+      return;
+    }
+    bud = found -> data;
+  }
 
   bjid = buddy_getjid(bud);
   name = buddy_getname(bud);
   type = buddy_gettype(bud);
   on_srv = buddy_getonserverflag(bud);
-
   oldgroupname = buddy_getgroupname(bud);
 
   if (type & ROSTER_TYPE_GROUP) {
     scr_LogPrint(LPRINT_NORMAL, "You can't move groups!");
+    cmdopts_free(&options);
     return;
   }
   if (type & ROSTER_TYPE_SPECIAL) {
     scr_LogPrint(LPRINT_NORMAL, "You can't move this item.");
+    cmdopts_free(&options);
     return;
   }
 
-  newgroupname = g_strdup(arg);
   // Remove trailing space
   for (p = newgroupname; *p; p++) ;
   while (p > newgroupname && *p == ' ') *p-- = 0;
 
-  strip_arg_special_chars(newgroupname);
-
-  group_utf8 = to_utf8(newgroupname);
-  if (strcmp(oldgroupname, group_utf8)) {
+  if (strcmp(oldgroupname, newgroupname)) {
     if (on_srv) {
-      xmpp_updatebuddy(bjid, name, *group_utf8 ? group_utf8 : NULL);
+      xmpp_updatebuddy(bjid, name, *newgroupname ? newgroupname : NULL);
       scr_roster_up_down(-1, 1);
-
-      /* We do not move the buddy right now because the server could reject
-       * the request.  Let's wait for the server answer.
-       */
+      // We do not move the buddy right now because the server could reject
+      // the request.  Let's wait for the server answer.
     } else {
       // This is a local item, we move it without adding to roster.
       guint msgflag;
@@ -2276,7 +2503,7 @@
       msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
       if (msgflag)
         roster_msg_setflag(bjid, FALSE, FALSE);
-      buddy_setgroup(bud, group_utf8);
+      buddy_setgroup(bud, newgroupname);
       if (msgflag)
         roster_msg_setflag(bjid, FALSE, TRUE);
       if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
@@ -2285,8 +2512,7 @@
     }
   }
 
-  g_free(group_utf8);
-  g_free(newgroupname);
+  cmdopts_free(&options);
   update_roster = TRUE;
 }
 
@@ -2468,50 +2694,33 @@
 
 static void do_rawxml(char *arg)
 {
-  char **paramlst;
-  char *subcmd;
+  cmdopts_t options = {
+    "rawxml",
+    NULL,
+    (cmdarg_t[1]){
+      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+    },
+    (cmdopts_t[1]){
+      { "send", NULL,
+        (cmdarg_t[1]){
+          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
+            { .arg = NULL } },
+        },
+        NULL },
+    },
+  };
 
   if (!xmpp_is_online()) {
     scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
     return;
   }
 
-  paramlst = split_arg(arg, 2, 1); // subcmd, arg
-  subcmd = *paramlst;
-  arg = *(paramlst+1);
-
-  if (!subcmd || !*subcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Please read the manual page"
-                 " before using /rawxml :-)");
-    free_arg_lst(paramlst);
+  if (cmdopts_parse(arg, &options))
     return;
-  }
-
-  if (!strcasecmp(subcmd, "send"))  {
-    gchar *buffer;
-
-    if (!subcmd || !*subcmd) {
-      scr_LogPrint(LPRINT_NORMAL, "Missing parameter.");
-      free_arg_lst(paramlst);
-      return;
-    }
-
-    // We don't strip_arg_special_chars() here, because it would be a pain for
-    // the user to escape quotes in a XML stream...
-
-    buffer = to_utf8(arg);
-    if (buffer) {
-      scr_LogPrint(LPRINT_NORMAL, "Sending XML string");
-      lm_connection_send_raw(lconnection, buffer, NULL);
-      g_free(buffer);
-    } else {
-      scr_LogPrint(LPRINT_NORMAL, "Conversion error in XML string.");
-    }
-  } else {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  }
-
-  free_arg_lst(paramlst);
+
+  scr_LogPrint(LPRINT_NORMAL, "Sending XML string");
+  lm_connection_send_raw(lconnection, options.cmds[0].args[0].value.arg, NULL);
+  cmdopts_free(&options);
 }
 
 //  check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
@@ -2874,7 +3083,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 +3556,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	Wed Feb 27 23:34:50 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);
-void cmd_setstatus(const char *recipient, const char *arg);
-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, 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	Wed Feb 27 23:34:50 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,
 // return NULL;
+// Note: before this function considered its argument to be in local encoding,
+//       now argument must be in utf8.
 GList *buddy_search(char *string)
 {
   GList *buddy = current_buddy;
   roster *roster_usr;
   if (!buddylist || !current_buddy) return NULL;
   for (;;) {
-    gchar *jid_locale, *name_locale;
     char *found = NULL;
 
     buddy = g_list_next(buddy);
@@ -1601,17 +1602,13 @@
 
     roster_usr = (roster*)buddy->data;
 
-    jid_locale = from_utf8(roster_usr->jid);
-    if (jid_locale) {
-      found = strcasestr(jid_locale, string);
-      g_free(jid_locale);
+    if (roster_usr->jid) {
+      found = strcasestr(roster_usr->jid, string);
       if (found)
         return buddy;
     }
-    name_locale = from_utf8(roster_usr->name);
-    if (name_locale) {
-      found = strcasestr(name_locale, string);
-      g_free(name_locale);
+    if (roster_usr->name) {
+      found = strcasestr(roster_usr->name, string);
       if (found)
         return buddy;
     }
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	Wed Feb 27 23:34:50 2013 +0200
@@ -555,6 +555,318 @@
     *str = tolower(*str);
 }
 
+// in_space        -> in_space, in_optstart, in_argstart
+// in_optstart     -> in_shortoptend, in_longoptstart, in_argstart ('-')
+// in_shortoptend  -> in_space, in_argstart, error
+// in_longoptstart -> in_longopt, in_space, in_argstart ('---')
+// in_longopt      -> in_longopt, in_space, error
+// in_argstart     -> in_arg, success
+// in_arg          -> in_arg, in_space, error
+
+// arguments: rw buffer in utf8, end of buffer pointer, options description struct
+static const char *cmdopts_parse_internal(gchar *arg, gchar *e, cmdopts_t *options)
+{
+  // parser state
+  enum {
+    in_space,
+    in_optstart,
+    in_shortoptstart,
+    in_shortoptend,
+    in_longoptstart,
+    in_longopt,
+    in_argstart,
+    in_arg,
+  } state = in_space;
+  // current pointer, start of object pointer
+  gchar *p, *s;
+  // inside of quotes in non-plain argument
+  gboolean quotes = FALSE;
+  // non-option argument or end-of-options marker encountered
+  gboolean opts_ended = FALSE;
+  // option, for which argument is currently parsed
+  cmdopt_t *option = NULL;
+  // argument, that we currently parse
+  cmdarg_t *argument = NULL;
+  // flags of option/argument
+  guint flags = 0;
+  // error message to return
+  const char *error = NULL;
+
+  p = arg;
+  // we allow parser to do one extra run on final '\0'
+  while (p <= e && error == NULL) {
+    if (state == in_space) { // space between args/options
+      if (*p == ' ' || *p == '\0') { // still space
+        p ++;
+      } else if (*p == '-' && !opts_ended) { // option
+        state = in_optstart;
+        p ++;
+      } else { // argument
+        if (!option) {
+          opts_ended = TRUE;
+        }
+        s = p;
+        state = in_argstart;
+      }
+    } else if (state == in_optstart) { // long/short option
+      if (*p == ' ' || *p == '\0') { // argument '-'
+        opts_ended = TRUE;
+        s = p - 1;
+        state = in_argstart;
+      } else if (*p == '-') { // long option
+        state = in_longoptstart;
+        p ++;
+      } else { // short option
+        s = p;
+        state = in_shortoptend;
+        p ++;
+      }
+    } else if (state == in_shortoptend) { // short option
+      if (*p == ' ' || *p == '\0') { // option really ended
+        gboolean found = FALSE;
+        option = options -> opts;
+        if (option) {
+          do {
+            if (option -> shortopt == *s) {
+              found = TRUE;
+              break;
+            }
+          } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
+        }
+        if (found) { // option is known
+          if (option -> flags & CMDOPT_SWITCH) { // it is switch
+            if (option -> flags & CMDOPT_CATCHALL) {
+              option -> value.swc ++;
+            } else {
+              option -> value.swc = !option -> value.swc;
+            }
+            option = NULL;
+          } else { // it is option
+            if (*p == '\0') {
+              error = "Short option argument not specified";
+            }
+          }
+          state = in_space;
+          p ++;
+        } else { // option is unknown
+          error = "Unknown short option";
+        }
+      } else { // short option not ended
+        // consider it argument (to allow -autojoin)
+        p --;
+        opts_ended = TRUE;
+        s = p - 1;
+        state = in_argstart;
+      }
+    } else if (state == in_longoptstart) { // long option initialization
+      if (*p == ' ' || *p == '\0') { // end of options '--'
+        opts_ended = TRUE;
+        state = in_space;
+        p ++;
+      } else if (*p == '-') { // argument, starting with '---'
+        opts_ended = TRUE;
+        s = p - 2;
+        state = in_argstart;
+      } else { // it is long option
+        s = p;
+        state = in_longopt;
+        p ++;
+      }
+    } else if (state == in_longopt) { // long option name
+      if (*p == ' ' || *p == '\0') { // long option ended
+        gboolean found = FALSE;
+        gboolean eof = *p == '\0';
+        *p = '\0';
+        option = options -> opts;
+        if (option) {
+          do {
+            if (!g_strcmp0 (option -> longopt, s)) {
+              found = TRUE;
+              break;
+            }
+          } while ((!(option++ -> flags & CMDOPT_LAST)) && !found);
+        }
+        if (found) { // option is known
+          if (option -> flags & CMDOPT_SWITCH) { // it is switch
+            if (option -> flags & CMDOPT_CATCHALL) {
+              option -> value.swc ++;
+            } else {
+              option -> value.swc = !option -> value.swc;
+            }
+            option = NULL;
+          } else { // it is option
+            if (eof) {
+              error = "Long option argument not specified";
+            }
+          }
+          state = in_space;
+          p ++;
+        } else { // option is unknown
+          error = "Unknown long option";
+        }
+      } else { // still long option
+        p ++;
+      }
+    } else if (state == in_argstart) { // option/command argument initialization
+      if (option) {
+        flags = option -> flags & ~CMDOPT_CATCHALL; // catchall in options indicates multi-options
+      } else {
+        if (!argument) {
+          argument = options -> args;
+        }
+        if (!argument) { // no need to parse arguments at all
+          break;
+        }
+        flags = argument -> flags;
+        if ((flags & CMDOPT_CATCHALL) && (flags & CMDOPT_PLAIN)) { // can finish right away
+          argument -> value.arg = s;
+          break;
+        }
+      }
+      quotes = FALSE;
+      state = in_arg;
+    } else if (state == in_arg) { // option/command argument value
+      if (*p == '\0' && quotes) { // end of line in quotes
+        error = "Unfinished quoted argument";
+      } else if ((*p == ' ' && (!quotes) && !(flags & CMDOPT_CATCHALL)) || *p == '\0') { // argument ended
+        if (*p != '\0') {
+          *p = '\0';
+          p ++;
+        } 
+        if (option) { // option argument
+          if (option -> flags & CMDOPT_CATCHALL) { // multi-value option
+            option -> value.multiopt = g_slist_append (option -> value.multiopt, s);
+          } else { // single-value option
+            option -> value.opt = s;
+          }
+          option = NULL;
+        } else { // command argument
+          if (argument -> flags & CMDOPT_SUBCOMMAND) {
+            gboolean found = FALSE;
+            cmdopts_t *subcommand = options -> cmds;
+            if (subcommand) {
+              do {
+                if (!g_strcmp0(s, subcommand -> name)) {
+                  found = TRUE;
+                  break;
+                }
+              } while (!(subcommand++ -> flags & CMDOPT_LAST));
+            }
+            if (found) {
+              argument -> value.cmd = subcommand;
+              error = cmdopts_parse_internal(p, e, subcommand);
+              break;
+            } else {
+              error = "Unknown subcommand";
+            }
+          } else {
+            argument -> value.arg = s;
+            if (argument -> flags & CMDOPT_LAST) { // last argument
+              break;
+            }
+            argument ++;
+          }
+        }
+        state = in_space;
+      } else if (*p == '\\' && !(flags & CMDOPT_PLAIN)) { // next char escape
+        memmove(p, p+1, e-(p+1)+1);
+        e --;
+        if (*p == '\0') {
+          error = "Escape at the end of line";
+        }
+        p ++;
+      } else if (*p == '"' && !(flags & CMDOPT_PLAIN)) { // quotation start/end
+        memmove(p, p+1, e-(p+1)+1);
+        e --;
+        quotes = !quotes;
+      } else { // still argument
+        p ++;
+      }
+    }
+  }
+
+  // check required flags on options
+  if (error == NULL && options -> opts) {
+    option = options -> opts;
+    do {
+      if (option -> flags & CMDOPT_REQUIRED) {
+        if (option -> flags & CMDOPT_SWITCH) {
+          // no way to check trigger switches, but no point in it as well
+          if (option -> flags & CMDOPT_CATCHALL && option -> value.swc == 0) {
+            error = "Required switch is not specified";
+            break;
+          }
+        } else {
+          if ((option -> flags & CMDOPT_CATCHALL && option -> value.multiopt == NULL) ||
+              ((!(option -> flags & CMDOPT_CATCHALL)) && option -> value.opt == NULL)) {
+            error = "Required option is not specified";
+            break;
+          }
+        }
+      }
+    } while (!(option++ -> flags & CMDOPT_LAST));
+  }
+
+  // check required flags on arguments
+  if (error == NULL && options -> args) {
+    argument = options -> args;
+    do {
+      if (argument -> flags & CMDOPT_REQUIRED) {
+        if (argument -> flags & CMDOPT_SUBCOMMAND) {
+          if (argument -> value.cmd == NULL) {
+            error = "Subcommand is not specified";
+            break;
+          }
+        } else {
+          if (argument -> value.arg == NULL) {
+            error = "Required argument is not specified";
+            break;
+          }
+        }
+      }
+    } while (!(argument++ -> flags & CMDOPT_LAST));
+  }
+
+  return error;
+}
+
+const char *cmdopts_parse(const char *arg, cmdopts_t *options)
+{
+  gchar *utf8 = to_utf8(arg);
+  gchar *e;
+  const char *error;
+
+  for (e = utf8; *e; e++);
+  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;
+}
+
+void cmdopts_free(cmdopts_t *options)
+{
+  cmdopt_t *option = options -> opts;
+  cmdopts_t *subcommand = options -> cmds;
+  if (option) {
+    do {
+      if ((option -> flags & (CMDOPT_CATCHALL|CMDOPT_SWITCH)) == CMDOPT_CATCHALL) {
+        g_slist_free(option -> value.multiopt);
+        option -> value.multiopt = NULL;
+      }
+    } while (!(option++ -> flags & CMDOPT_LAST));
+  }
+  if (subcommand) {
+    do {
+      cmdopts_free(subcommand);
+    } while (!(subcommand++ -> flags & CMDOPT_LAST));
+  }
+  g_free(options -> freeme);
+  options -> freeme = NULL;
+}
+
 //  strip_arg_special_chars(string)
 // Remove quotes and backslashes before an escaped quote
 // 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	Wed Feb 27 23:34:50 2013 +0200
@@ -43,6 +43,125 @@
 char **split_arg(const char *arg, unsigned int n, int dontstriplast);
 void free_arg_lst(char **arglst);
 
+//  error cmdopts_parse (argstring, optionlist)
+// Function parses command argument string according to provided list of
+// options and arguments. If in this process it encounters an error, it
+// returns error string (that should be displayed and g_free'd afterwards).
+// Note: For now returned error is constant string, that shouldn't be freed,
+// but we're getting there.
+// After processing you should free freeme and any GSList values of catchall
+// options (only lists itself, not values). For your convenience, there is
+// cmdopts_free(), that does exactly that.
+// The function recognizes four kinds of expressions:
+//  - Options with arguments in a form '-f bar' or '--foo bar'
+//  - Switches without arguments in a form '-f' or '--foo'
+//  - End-of-options marker '--'
+//  - Individual arguments ('-' and '---' are considered arguments too)
+// To define command line syntax, you pass cmdopts_t struct, that contains
+// two contiguous lists of cmdopt_t and cmdarg_t structs accordingly. The
+// last struct in list must have CMDOPT_LAST flag set.
+// You can specify your own default values, they will be replaced/appended
+// if needed.
+// You can omit specifying longopt or shortopt (put NULL or '\0' there).
+// Note: returned values and arguments are already converted to utf8.
+
+// Flags:
+// Only applies to options, defined if option does not have argument.
+#define CMDOPT_SWITCH     ( 1<<0 )
+// Don't process quotes and escapes in argument (applies to option arguments too).
+#define CMDOPT_PLAIN      ( 1<<1 )
+// For options   - put all encountered values into GSList value.multiopt
+//                 instead of overwriting value.opt.
+// For switches  - increment value.swc instead of logical flipping.
+// For arguments - grab the rest of the line without splitting on spaces.
+// Implicitly last argument.
+#define CMDOPT_CATCHALL   ( 1<<2 )
+// Option/argument must have value.
+#define CMDOPT_REQUIRED   ( 1<<3 )
+// Last entry in struct sequence.
+#define CMDOPT_LAST       ( 1<<4 )
+// Argument only, argument is the name for subcommand.
+// Implicitly last argument.
+#define CMDOPT_SUBCOMMAND ( 1<<5 )
+
+// TODO:
+// * Options/arguments value should become 'default' field, value field will go
+//   later, so that user will not need to initialize it.
+// * Clustering of options/switches
+// * --help
+//   * Add argument names for --help.
+//   * Argument or command/subcommand descriptions for --help?
+//   * Or maybe just do "/help foo"?
+// * Add argument type field and subsystem to check values. Uses so far:
+//   - fjid (handle . and ./resource, perform check_jid_syntax())
+//   - bjid (handle ., strip resource, perform check_jid_syntax())
+//   - bjid in roster/buddylist (result - buddy?)
+//   - name in roster/buddylist (result - buddy?)
+//   - group in roster (result - buddy?)
+//   - other non-space string (strip trailing/starting spaces)
+//     - statusmask (restrictive)
+//     - jidmask (non-restrictive)
+//     - search string
+//     - rosternote
+//     - color (restrictive, semi-defined list)
+//     - nick (non-restrictive)
+//     - defined lists (restrictive)
+//     - date (restrictive)
+//     - percent (numerical, restrictive)
+//   - number (result - int)
+//   - filename (expand_filename(), g_utf8_to_filename()?)
+//   - custom (maybe some helpers for constant stringlists)
+//   - text message (complete nicks in rooms)
+// * Add non-argument checks for commands/subcommands:
+//   - xmpp_is_online()
+//   - current_buddy
+// * --help generates error with short usage, based on info in options struct.
+// * Subcommands with fallback to argument?
+// * [+foo|-foo] support?
+// * Integrate with commands
+// * Integrate with completion
+// * Usable subsystem for completion, based on user-supplied argument types
+// * Update docs above
+
+// so, the process of command execution would look like:
+// - we walk through the options, set default values
+// - we parse argument string, populating options  - on this or on next step
+// - we check for required options availability    - we can call generic argument check routine, based on argument type
+// - we call callback
+// - we free resources
+typedef struct cmdopts_struct cmdopts_t;
+typedef union {
+  GSList *multiopt;
+  gchar  *opt;
+  guint  swc;
+} cmdopt_value_t;
+typedef struct {
+  guint          flags;
+  char           shortopt;
+  const char     *longopt;
+  cmdopt_value_t value;
+} cmdopt_t;
+typedef union {
+  gchar     *arg;
+  cmdopts_t *cmd;
+} cmdarg_value_t;
+typedef struct {
+  guint          flags;
+  cmdarg_value_t value;
+} cmdarg_t;
+struct cmdopts_struct {
+  const char *name;
+  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);
+void cmdopts_free(cmdopts_t *options);
+
 void replace_nl_with_dots(char *bufstr);
 char *ut_expand_tabs(const char *text);
 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	Wed Feb 27 23:34:50 2013 +0200
@@ -289,10 +289,7 @@
       if (value) {
         for (s = adhoc_status_list; !s->name || strcmp(s->name, value); s++);
         if (s->name) {
-          char *status = g_strdup_printf("%s %s", s->status,
-                                         message ? message : "");
-          cmd_setstatus(NULL, status);
-          g_free(status);
+          cmd_setstatus(NULL, s->status, message ? message : "");
           lm_message_node_set_attribute(command, "status", "completed");
           lm_message_node_add_dataform_result(command,
                                               "Status has been changed");