cmdopts.diff
author Myhailo Danylenko <isbear@ukrpost.net>
Mon, 11 Mar 2013 01:33:26 +0200
changeset 75 17cd00b2e722
parent 74 a879ea179877
child 76 3c8b784f92c7
permissions -rw-r--r--
[cmdopts] Struggling in chaos

# HG changeset patch
# Parent 92fa48ef53c909928706ab4c51518953339a38e4
[work-in-progress] Unified command option parsing

  * wrecking chaos all over the place
  * started integration with cmd subsystem
  * broke everything

  ** PREVIOUS ACHIEVEMENTS **

  * 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 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	Mon Mar 11 01:32:27 2013 +0200
@@ -19,7 +19,7 @@
  * USA
  */
 
-#include <string.h>
+#include <string.h> // g_memmove
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -43,512 +43,663 @@
 #include "xmpp.h"
 #include "main.h"
 
-#define IMSTATUS_AWAY           "away"
-#define IMSTATUS_ONLINE         "online"
-#define IMSTATUS_OFFLINE        "offline"
-#define IMSTATUS_FREE4CHAT      "free"
-#define IMSTATUS_AVAILABLE      "avail"
-#define IMSTATUS_NOTAVAILABLE   "notavail"
-#define IMSTATUS_DONOTDISTURB   "dnd"
-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
-# define IMSTATUS_INVISIBLE      "invisible"
+//
+//  Pre-declarations
+//
+
+// for process_line()
+typedef enum {
+  scmd_group_unfold = 0,
+  scmd_group_fold   = 1,
+  scmd_group_toggle = -1,
+} group_scmd_t;
+
+static void group_cmd (gpointer group, group_scmd_t action);
+
+//static void room_bookmark(gpointer bud, char *arg);
+
+#define BUILTIN_COUNT 3
+static cmdopts_t def_roster,
+                 def_color,
+                 def_status,
+                 def_status_to,
+#if 0
+                 def_add,
+                 def_del,
+                 def_group,
+                 def_say,
+                 def_msay,
+                 def_say_to,
+                 def_buffer,
+                 def_clear,
+                 def_info,
+                 def_rename,
+                 def_move,
+                 def_set,
+                 def_alias,
+                 def_bind,
+                 def_connect,
+                 def_disconnect,
+                 def_rawxml,
+                 def_room,
+                 def_authorization,
+                 def_version,
+                 def_request,
+                 def_event,
+                 def_help,
+                 def_pgp,
+                 def_iline,
+                 def_screen_refresh,
+                 def_chat_disable,
+                 def_source,
+                 def_otr,
+                 def_otrpolicy,
+                 def_echo,
+                 def_module,
+                 def_exit
 #endif
-
-// Return value container for the following functions
-static int retval_for_cmds;
-
-// Commands callbacks
-static void do_roster(char *arg);
-static void do_status(char *arg);
-static void do_status_to(char *arg);
-static void do_add(char *arg);
-static void do_del(char *arg);
-static void do_group(char *arg);
-static void do_say(char *arg);
-static void do_msay(char *arg);
-static void do_say_to(char *arg);
-static void do_buffer(char *arg);
-static void do_clear(char *arg);
-static void do_info(char *arg);
-static void do_rename(char *arg);
-static void do_move(char *arg);
-static void do_set(char *arg);
-static void do_alias(char *arg);
-static void do_bind(char *arg);
-static void do_connect(char *arg);
-static void do_disconnect(char *arg);
-static void do_rawxml(char *arg);
-static void do_room(char *arg);
-static void do_authorization(char *arg);
-static void do_version(char *arg);
-static void do_request(char *arg);
-static void do_event(char *arg);
-static void do_help(char *arg);
-static void do_pgp(char *arg);
-static void do_iline(char *arg);
-static void do_screen_refresh(char *arg);
-static void do_chat_disable(char *arg);
-static void do_source(char *arg);
-static void do_color(char *arg);
-static void do_otr(char *arg);
-static void do_otrpolicy(char *arg);
-static void do_echo(char *arg);
-static void do_module(char *arg);
-static void do_exit(char *arg);
-
-static void room_bookmark(gpointer bud, char *arg);
-
-// Global variable for the commands list
-static GSList *Commands;
-static GSList *safe_commands;
-
-#ifdef MODULES_ENABLE
-#include "modules.h"
-
-gpointer cmd_del(gpointer id)
+                 ;
+
+static cmd_handler_t do_roster,
+                     do_color,
+                     do_status,
+                     do_status_to,
+#if 0
+                     do_add,
+                     do_del,
+                     do_group,
+                     do_say,
+                     do_msay,
+                     do_say_to,
+                     do_buffer,
+                     do_clear,
+                     do_info,
+                     do_rename,
+                     do_move,
+                     do_set,
+                     do_alias,
+                     do_bind,
+                     do_connect,
+                     do_disconnect,
+                     do_rawxml,
+                     do_room,
+                     do_authorization,
+                     do_version,
+                     do_request,
+                     do_event,
+                     do_help,
+                     do_pgp,
+                     do_iline,
+                     do_screen_refresh,
+                     do_chat_disable,
+                     do_source,
+                     do_otr,
+                     do_otrpolicy,
+                     do_echo,
+                     do_module,
+                     do_exit
+#endif
+                     ;
+
+//
+//  Main commands mechanics
+//
+// Functions to parse mcabber command line and execute commands accordingly.
+// Allows to define commands and remove definitions.
+
+cmdopts_t **cmd_list  = NULL; // command structs list
+gsize       cmd_count = 0;    // number of commands (one less than memory allocated)
+
+// private
+void cmd_init (void)
 {
-  GSList *sl_cmd;
-  if (!id) return NULL;
-  for (sl_cmd = Commands; sl_cmd; sl_cmd = sl_cmd->next)
-    if (sl_cmd -> data == id) {
-      cmd *command = (cmd *) sl_cmd->data;
-      gpointer userdata = command->userdata;
-      Commands = g_slist_delete_link(Commands, sl_cmd);
-      compl_del_category_word(COMPL_CMD, command->name);
-      g_slice_free(cmd, command);
-      return userdata;
+  cmd_list = g_new ((cmdopts_t *), BUILTIN_COUNT+1);
+  cmd_list[0]  = def_roster;
+  cmd_list[1]  = def_color;
+  cmd_list[2]  = def_status;
+  cmd_list[3]  = def_status_to;
+#if 0
+  cmd_list[4]  = def_add;
+  cmd_list[5]  = def_del;
+  cmd_list[6]  = def_group;
+  cmd_list[7]  = def_say;
+  cmd_list[8]  = def_msay;
+  cmd_list[9]  = def_say_to;
+  cmd_list[10] = def_buffer;
+  cmd_list[11] = def_clear;
+  cmd_list[12] = def_info;
+  cmd_list[13] = def_rename;
+  cmd_list[14] = def_move;
+  cmd_list[15] = def_set;
+  cmd_list[16] = def_alias;
+  cmd_list[17] = def_bind;
+  cmd_list[18] = def_connect;
+  cmd_list[19] = def_disconnect;
+  cmd_list[20] = def_rawxml;
+  cmd_list[21] = def_room;
+  cmd_list[22] = def_authorization;
+  cmd_list[23] = def_version;
+  cmd_list[24] = def_request;
+  cmd_list[25] = def_event;
+  cmd_list[26] = def_help;
+  cmd_list[27] = def_pgp;
+  cmd_list[28] = def_iline;
+  cmd_list[29] = def_screen_refresh;
+  cmd_list[30] = def_chat_disable;
+  cmd_list[31] = def_source;
+  cmd_list[32] = def_otr;
+  cmd_list[33] = def_otrpolicy;
+  cmd_list[34] = def_echo;
+  cmd_list[35] = def_module;
+  cmd_list[36] = def_exit;
+#endif
+  cmd_list[BUILTIN_COUNT] = NULL;
+  cmd_count = BUILTIN_COUNT;
+}
+
+// private
+void cmd_uninit (void)
+{
+  g_free (cmd_list);
+  cmd_list  = NULL;
+  cmd_count = 0;
+}
+
+void cmd_define (cmdopts_t *command)
+{
+  cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count+2);
+  g_memmove(cmd_list, cmd_list+1, sizeof(cmdopts_t *) * cmd_count+1)
+  cmd_list[0] = command;
+  cmd_count ++;
+}
+
+void cmd_undef (cmdopts_t *command)
+{
+  gsize num = 0;
+  while (num < cmd_count) {
+    if (cmd_list[num] == command) {
+      g_memmove (cmd_list+num, cmd_list+num+1, sizeof(cmdopts_t *) * (cmd_count-num));
+      cmd_list = g_renew ((cmdopts_t *), cmd_list, cmd_count);
+      cmd_count --;
+      return;
     }
-  return NULL;
+    num ++;
+  }
 }
-#endif
-
-//  cmd_add()
-// Adds a command to the commands list and to the CMD completion list
-gpointer cmd_add(const char *name, const char *help, guint flags_row1,
-                 guint flags_row2, void (*f)(char*), gpointer userdata)
+
+//  error cmdopts_parse_argument ( startptr, endptr, flags )
+// Parses next argument according to flags and finishes it with zero.
+// Updates current/end pointers. Parsed string MUST be writable.
+// String may shrink in size (quotation/escapes), thus endpointer is also
+// updated.
+const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags)
 {
-  cmd *n_cmd = g_slice_new0(cmd);
-  strncpy(n_cmd->name, name, 32-1);
-  n_cmd->help = help;
-  n_cmd->completion_flags[0] = flags_row1;
-  n_cmd->completion_flags[1] = flags_row2;
-  n_cmd->func = f;
-  n_cmd->userdata = userdata;
-  Commands = g_slist_prepend(Commands, n_cmd);
-  // Add to completion CMD category
-  compl_add_category_word(COMPL_CMD, name);
-  return n_cmd;
+  gchar *p = *pr;
+  gchar *e = *er;
+  const char *error = NULL;
+  gboolean   quotes = FALSE;
+
+  if ((flags & cmdarg_catchall) && (flags & cmdarg_plain)) {
+    *pr = e;
+    return NULL;
+  }
+
+  while (p <= e && error != NULL) {
+    if (p == e && quotes) { // end of line in quotes
+      error = "Unfinished quoted argument.";
+    } else if ((*p == ' ' && (!quotes) && !(flags & cmdarg_catchall)) || p == e) { // argument ended
+      if (*p != '\0') {
+        *p = '\0';
+        p ++;
+      }
+      break;
+    } else if (*p == '\\' && !(flags & cmdarg_plain)) { // next char escape
+      g_memmove(p, p+1, e-(p+1)+1);
+      e --;
+      if (p == e) {
+        error = "Escape at the end of line.";
+      } else {
+        p ++;
+      }
+    } else if (*p == '"' && !(flags & cmdarg_plain)) { // quotation start/end
+      g_memmove(p, p+1, e-(p+1)+1);
+      e --;
+      quotes = !quotes;
+    } else { // still argument
+      p ++;
+    }
+  }
+
+  *pr = p;
+  *er = e;
+  return error;
 }
 
-//  cmd_set_safe(name, safe)
-// Sets if command can be used in startup configuration file.
-gboolean cmd_set_safe(const gchar *name, gboolean safe)
+//  error cmdopts_parse_internal ( startptr, endptr, commanddef )
+// Parses command arguments according to command definition.
+// Parsed string MUST be writable. Regardless of success or error, input
+// string should be considered corrupted by parsing process.
+// Even in case of error, commanddef should be passed to cmdopts_free().
+static gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command)
 {
-  GSList *sel;
-  if (!name)
-    return FALSE;
-  for (sel = safe_commands; sel; sel = sel->next)
-    if (!strcmp((const char *)sel->data, name)) {
-      if (safe)
-        return FALSE;
-      else {
-        g_free(sel->data);
-        safe_commands = g_slist_delete_link(safe_commands, sel);
+  enum {             // Parser state transitions:
+    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_space, error
+  } state = in_argstart;     // current parser state
+  gchar    *p        = *pr;  // current pointer
+  gchar    *e        = *er;  // end of line pointer
+  gchar    *s;               // start of current object pointer
+  gboolean opts_ended = FALSE; // don't allow options any more
+  cmdopt_t *option   = NULL; // option, for which argument is currently parsed
+  gsize    argno     = 0;    // number of current positional argument
+  gchar    *error    = NULL; // error message to return
+
+  // general environment checking
+  if (command -> check) {
+    if ((error = command -> check (command))) {
+      gchar *err = error;
+      error = g_strdup_printf("%s: %s", command -> name, err);
+      g_free (err);
+    }
+  }
+
+  // prepare: set default values for arguments and unset some fields
+  if (error == NULL) {
+    gsize n;
+    if (command -> opts) {
+      for (n = 0; command -> opts[n] != NULL; n ++) {
+        cmdopt_t *opt = command -> opts[n];
+        opt -> arg.val.arg = opt -> arg.defval;
+        opt -> cnt = 0;
       }
     }
-  if (safe)
-    safe_commands = g_slist_append(safe_commands, g_strdup(name));
-  else
-    return FALSE;
-  return TRUE;
+    if (command -> args) {
+      for (n = 0; command -> args[n] != NULL; n ++) {
+        cmdarg_t *arg = command -> args[n]; 
+        arg -> value.roarg = arg -> defval;
+        arg -> flags &= ~(cmdarg_visited|cmdarg_checked);
+      }
+    }
+  }
+
+  // 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 == e) { // 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 == e) { // argument '-'
+        opts_ended = TRUE;
+        s = p = 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 == e) { // option really ended
+        gsize n;
+        for (n = 0; command -> opts[n] != NULL; n ++) {
+          if (command -> opts[n] -> shortopt == *s) {
+            option = command -> opts[n];
+            break;
+          }
+        }
+        if (option != NULL) { // option is known
+          if (option -> flags & cmdopt_switch) { // it is switch
+            option -> arg.value.swc ++;
+            option = NULL;
+          } else if (p == e) {
+            error = g_strdup_printf ("%s: Option -%c needs an argument.", command -> name, option -> shortopt);
+          }
+          state = in_space;
+          p ++;
+        } else { // option is unknown
+          error = g_strdup_printf ("%s: Unknown option -%c.", command -> name, *s);
+        }
+      } else { // short option not ended
+        // consider it argument (to allow -autojoin)
+        opts_ended = TRUE;
+        s = p = p - 2;
+        state = in_argstart;
+      }
+    } else if (state == in_longoptstart) { // long option initialization
+      if (*p == ' ' || p == e) { // end of options '--'
+        opts_ended = TRUE;
+        state = in_space;
+        p ++;
+      } else if (*p == '-') { // argument, starting with '---'
+        opts_ended = TRUE;
+        s = p = 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 == e) { // long option ended
+        gsize n;
+        *p = '\0';
+        for (n = 0; command -> opts[n] != NULL; n ++) {
+          if (!g_strcmp0(command -> opts[n] -> longopt, s)) {
+            option = command -> opts[n];
+            break;
+          }
+        }
+        if (option != NULL) { // option is known
+          if (option -> flags & cmdopt_switch) { // it is switch
+            option -> arg.value.swc ++;
+            option = NULL;
+          } else if (p == e) {
+            error = g_strdup_printf ("%s: Option --%s needs an argument.", command -> name, option -> longopt);
+          }
+          state = in_space;
+          p ++;
+        } else { // option is unknown
+          error = g_strdup_printf ("%s: Unknown option --%s.", command -> name, option -> longopt);
+        }
+      } else { // still long option
+        p ++;
+      }
+    } else if (state == in_argstart) { // option/command argument initialization
+      cmdarg_flags_t flags;
+      const char     *err;
+      cmdarg_t       *arg;
+
+      if (option) { // option argument
+        arg = &(option -> arg);
+      } else if (!command -> args || !command -> args[argno]) { // no need to parse arguments any further
+        break;
+      } else { // normal aurgument
+        arg = command -> args[argno];
+      }
+
+      if ((err = cmdopt_parse_argument(&p, &e, arg -> flags))) { // get argument value
+        if (!option) {
+          error = g_strdup_printf ("%s: Can't parse argument %s (%u): %s", command -> name, arg -> name, argno + 1, err);
+        else if (option -> shortopt) {
+          error = g_strdup_printf ("%s: Can't parse argument for option -%c: %s", command -> name, option -> shortopt, err);
+        } else {
+          error = g_strdup_printt ("%s: Can't parse argument for option --%s: %s", command -> name, option -> longopt, err);
+        }
+      } else {
+        arg -> value.arg = s;
+        arg -> flags  |= cmdarg_visited;
+        if (option) { // option argument
+          option = NULL;
+        } else { // normal argument
+          if (argument -> flags & cmdarg_subcmd) { // subcommand
+            cmdopts_t *subcmd;
+            gsize     n; // XXX put command list into chkdata
+            for (n = 0; command -> cmds[n]; n ++) {
+              if (!strcmp (s, command -> cmds[n] -> name)) {
+                subcmd = command -> cmds[n];
+                break;
+              }
+            }
+            if (subcmd != NULL) { // found subcommand
+              arg -> value.cmd = subcmd;
+              if ((error = cmdopts_parse_internal (&p, &e, subcmd))) {
+                gchar *err = error;
+                error = g_strdup_printf("%s %s", command -> name, err);
+                g_free (err);
+              }
+            } else { // wrong subcommand
+              error = g_strdup_printf("%s: Unable to find subcommand \"%s\".", command -> name, s);
+            }
+          }
+          argno ++;
+        }
+      }
+      state = in_space;
+    }
+  }
+
+  *pr = p;
+  *er = e;
+
+  // perform option argument checking
+  if (error == NULL && command -> opts) {
+    gsize n;
+    for (n = 0; command -> opts[n]; n ++) {
+      cmdopt_t *opt = command -> opts[n];
+      if (!(opt -> flags & cmdopt_switch)) { // not a switch
+        // needs checking and not checked already
+        if ((opt -> arg.flags & (cmdarg_check | cmdarg_visited)) && !(opt -> arg.flags & cmdarg_checked)) {
+          if (opt -> arg.type && opt -> arg.type -> check) { // checker is present
+            gchar *err;
+            opt -> arg.flags |= cmdarg_checked;
+            if ((err = opt -> arg.type -> check (&(opt -> arg)))) {
+              if (opt -> arg.flags & cmdarg_required) {
+                if (opt -> shortopt) {
+                  error = g_strdup_printf ("%s: Error in argument for option -%c: %s", command -> name, opt -> shortopt, err);
+                } else {
+                  error = g_strdup_printt ("%s: Error in argument for option --%s: %s", command -> name, opt -> longopt, err);
+                }
+                g_free (err);
+                break;
+              } else {
+                if (opt -> shortopt) {
+                  scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument for option -%c: %s", command -> name, opt -> shortopt, err);
+                } else {
+                  scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument for option --%s: %s", command -> name, opt -> longopt, err);
+                }
+                g_free (err);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // perform positional argument checking
+  if (error == NULL && command -> args) {
+    gsize n;
+    for (n = 0; command -> args[n]; n ++) {
+      cmdarg_t *arg = command -> args[n];
+      // needs checking and not checked already
+      if ((arg -> flags & (cmdarg_check | cmdarg_visited)) && !(arg -> flags & cmdarg_checked)) {
+        if (arg -> flags & cmdarg_subcmd) { // subcommand
+          if (!arg -> value.cmd) {
+            if (arg -> flags & cmdarg_required) {
+              error = g_strdup_printf ("%s: No %s specified.", command -> name, arg -> name);
+              break;
+            } else { // XXX more prefixes
+              scr_log_print (LPRINT_NORMAL, "Warning: %s: No %s specified.", command -> name, arg -> name);
+            }
+          }
+        } else { // normal argument
+          if (arg -> type && arg -> type -> check) {
+            gchar *err;
+            arg -> flags |= cmdarg_checked;
+            if ((err = arg -> type -> check (arg))) {
+              if (arg -> flags & cmdarg_required) {
+                error = g_strdup_printf ("%s: Error in argument %s (%u): %s", command -> name, arg -> name, n, err);
+                g_free (err);
+                break;
+              } else { // XXX more prefixes
+                scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument %s (%u): %s", command -> name, arg -> name, n, err);
+                g_free (err);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return error;
 }
 
-//  cmd_is_safe(name)
-// Returns if command is safe or not
-gboolean cmd_is_safe(const gchar *name)
+//  cmdopts_free ( commanddef )
+// Free various parser data, used in parsing process
+static void cmdopts_free(cmdopts_t *command)
 {
-  GSList *sel;
-  if (!name)
-    return FALSE;
-  for (sel = safe_commands; sel; sel = sel->next)
-    if (!strcmp((const char *)sel->data, name))
-      return TRUE;
-  return FALSE;
+  gsize n;
+  if (command -> opts) {
+    for (n = 0; command -> opts[n]; n ++) {
+      cmdopt_t *opt = command -> opts[n];
+      if (!(opt -> flags & cmdopt_switch)) { // not switch
+        if (opt -> arg.flags & cmdarg_freeme) { // can free something
+          opt -> arg.flags &= ~cmdarg_freeme;
+          if (opt -> arg.type && opt -> arg.type -> free) { // need to free something
+            opt -> arg.type -> free (&(opt -> arg));
+          }
+        }
+      }
+    }
+  }
+  if (command -> args) {
+    for (n = 0; command -> args[n]; n ++) {
+      cmdarg_t *arg = command -> args[n];
+      if (arg -> flags & cmdarg_subcmd) { // subcommand
+        if (arg -> value.cmd) {
+          cmdopts_free (arg -> value.cmd);
+          arg -> value.cmd = NULL;
+        }
+      } else { // normal argument
+        if (arg -> flags & cmdarg_freeme) { // can free something
+          arg -> flags &= ~cmdarg_freeme;
+          if (arg -> type && arg -> type -> free) { // need to free something
+            arg -> type.free (arg);
+          }
+        }
+      }
+    }
+  }
 }
 
-//  cmd_init()
-// Commands table initialization
-// !!!
-// After changing commands names and it arguments names here, you must change
-// ones in init_bindings()!
-//
-void cmd_init(void)
+cmd_result_t cmd_execute_internal (gchar *commandline, cmdexe_flags_t flags)
 {
-  cmd_add("add", "Add a jabber user", COMPL_JID, 0, &do_add, NULL);
-  cmd_add("alias", "Add an alias", 0, 0, &do_alias, NULL);
-  cmd_add("authorization", "Manage subscription authorizations",
-          COMPL_AUTH, COMPL_JID, &do_authorization, NULL);
-  cmd_add("bind", "Add an key binding", 0, 0, &do_bind, NULL);
-  cmd_add("buffer", "Manipulate current buddy's buffer (chat window)",
-          COMPL_BUFFER, 0, &do_buffer, NULL);
-  cmd_add("chat_disable", "Disable chat mode", 0, 0, &do_chat_disable, NULL);
-  cmd_add("clear", "Clear the dialog window", 0, 0, &do_clear, NULL);
-  cmd_add("color", "Set coloring options", COMPL_COLOR, 0, &do_color, NULL);
-  cmd_add("connect", "Connect to the server", 0, 0, &do_connect, NULL);
-  cmd_add("del", "Delete the current buddy", 0, 0, &do_del, NULL);
-  cmd_add("disconnect", "Disconnect from server", 0, 0, &do_disconnect, NULL);
-  cmd_add("echo", "Display a string in the log window", 0, 0, &do_echo, NULL);
-  cmd_add("event", "Process an event", COMPL_EVENTSID, COMPL_EVENTS, &do_event,
-          NULL);
-  cmd_add("exit",  "Exit mcabber", 0, 0, &do_exit, NULL);
-  cmd_add("group", "Change group display settings",
-          COMPL_GROUP, COMPL_GROUPNAME, &do_group, NULL);
-  cmd_add("help", "Display some help", COMPL_CMD, 0, &do_help, NULL);
-  cmd_add("iline", "Manipulate input buffer", 0, 0, &do_iline, NULL);
-  cmd_add("info", "Show basic info on current buddy", 0, 0, &do_info, NULL);
-  cmd_add("module", "Manipulations with modules", COMPL_MODULE, 0, &do_module,
-          NULL);
-  cmd_add("move", "Move the current buddy to another group", COMPL_GROUPNAME,
-          0, &do_move, NULL);
-  cmd_add("msay", "Send a multi-lines message to the selected buddy",
-          COMPL_MULTILINE, 0, &do_msay, NULL);
-  cmd_add("otr", "Manage OTR settings", COMPL_OTR, COMPL_JID, &do_otr, NULL);
-  cmd_add("otrpolicy", "Manage OTR policies", COMPL_JID, COMPL_OTRPOLICY,
-          &do_otrpolicy, NULL);
-  cmd_add("pgp", "Manage PGP settings", COMPL_PGP, COMPL_JID, &do_pgp, NULL);
-  cmd_add("quit", "Exit the software", 0, 0, NULL, NULL);
-  cmd_add("rawxml", "Send a raw XML string", 0, 0, &do_rawxml, NULL);
-  cmd_add("rename", "Rename the current buddy", 0, 0, &do_rename, NULL);
-  cmd_add("request", "Send a Jabber IQ request", COMPL_REQUEST, COMPL_JID,
-          &do_request, NULL);
-  cmd_add("room", "MUC actions command", COMPL_ROOM, 0, &do_room, NULL);
-  cmd_add("roster", "Manipulate the roster/buddylist", COMPL_ROSTER, 0,
-          &do_roster, NULL);
-  cmd_add("say", "Say something to the selected buddy", 0, 0, &do_say, NULL);
-  cmd_add("say_to", "Say something to a specific buddy", COMPL_JID, 0,
-          &do_say_to, NULL);
-  cmd_add("screen_refresh", "Redraw mcabber screen", 0, 0, &do_screen_refresh,
-          NULL);
-  cmd_add("set", "Set/query an option value", 0, 0, &do_set, NULL);
-  cmd_add("source", "Read a configuration file", 0, 0, &do_source, NULL);
-  cmd_add("status", "Show or set your status", COMPL_STATUS, 0, &do_status,
-          NULL);
-  cmd_add("status_to", "Show or set your status for one recipient",
-          COMPL_JID, COMPL_STATUS, &do_status_to, NULL);
-  cmd_add("version", "Show mcabber version", 0, 0, &do_version, NULL);
-
-  cmd_set_safe("set", TRUE);
-  cmd_set_safe("bind", TRUE);
-  cmd_set_safe("alias", TRUE);
-  cmd_set_safe("pgp", TRUE);
-  cmd_set_safe("source", TRUE);
-  cmd_set_safe("status", TRUE);
-  cmd_set_safe("color", TRUE);
-  cmd_set_safe("otrpolicy", TRUE);
-  cmd_set_safe("module", TRUE);
-
-  // Status category
-  compl_add_category_word(COMPL_STATUS, "online");
-  compl_add_category_word(COMPL_STATUS, "avail");
-#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
-  compl_add_category_word(COMPL_STATUS, "invisible");
-#endif
-  compl_add_category_word(COMPL_STATUS, "free");
-  compl_add_category_word(COMPL_STATUS, "dnd");
-  compl_add_category_word(COMPL_STATUS, "notavail");
-  compl_add_category_word(COMPL_STATUS, "away");
-  compl_add_category_word(COMPL_STATUS, "offline");
-  compl_add_category_word(COMPL_STATUS, "message");
-
-  // Roster category
-  compl_add_category_word(COMPL_ROSTER, "bottom");
-  compl_add_category_word(COMPL_ROSTER, "top");
-  compl_add_category_word(COMPL_ROSTER, "up");
-  compl_add_category_word(COMPL_ROSTER, "down");
-  compl_add_category_word(COMPL_ROSTER, "group_prev");
-  compl_add_category_word(COMPL_ROSTER, "group_next");
-  compl_add_category_word(COMPL_ROSTER, "hide");
-  compl_add_category_word(COMPL_ROSTER, "show");
-  compl_add_category_word(COMPL_ROSTER, "toggle");
-  compl_add_category_word(COMPL_ROSTER, "display");
-  compl_add_category_word(COMPL_ROSTER, "hide_offline");
-  compl_add_category_word(COMPL_ROSTER, "show_offline");
-  compl_add_category_word(COMPL_ROSTER, "toggle_offline");
-  compl_add_category_word(COMPL_ROSTER, "item_lock");
-  compl_add_category_word(COMPL_ROSTER, "item_unlock");
-  compl_add_category_word(COMPL_ROSTER, "item_toggle_lock");
-  compl_add_category_word(COMPL_ROSTER, "alternate");
-  compl_add_category_word(COMPL_ROSTER, "search");
-  compl_add_category_word(COMPL_ROSTER, "unread_first");
-  compl_add_category_word(COMPL_ROSTER, "unread_next");
-  compl_add_category_word(COMPL_ROSTER, "note");
-  compl_add_category_word(COMPL_ROSTER, "resource_lock");
-  compl_add_category_word(COMPL_ROSTER, "resource_unlock");
-
-  // Buffer category
-  compl_add_category_word(COMPL_BUFFER, "clear");
-  compl_add_category_word(COMPL_BUFFER, "bottom");
-  compl_add_category_word(COMPL_BUFFER, "top");
-  compl_add_category_word(COMPL_BUFFER, "up");
-  compl_add_category_word(COMPL_BUFFER, "down");
-  compl_add_category_word(COMPL_BUFFER, "search_backward");
-  compl_add_category_word(COMPL_BUFFER, "search_forward");
-  compl_add_category_word(COMPL_BUFFER, "readmark");
-  compl_add_category_word(COMPL_BUFFER, "date");
-  compl_add_category_word(COMPL_BUFFER, "%");
-  compl_add_category_word(COMPL_BUFFER, "purge");
-  compl_add_category_word(COMPL_BUFFER, "close");
-  compl_add_category_word(COMPL_BUFFER, "close_all");
-  compl_add_category_word(COMPL_BUFFER, "scroll_lock");
-  compl_add_category_word(COMPL_BUFFER, "scroll_unlock");
-  compl_add_category_word(COMPL_BUFFER, "scroll_toggle");
-  compl_add_category_word(COMPL_BUFFER, "list");
-  compl_add_category_word(COMPL_BUFFER, "save");
-
-  // Group category
-  compl_add_category_word(COMPL_GROUP, "fold");
-  compl_add_category_word(COMPL_GROUP, "unfold");
-  compl_add_category_word(COMPL_GROUP, "toggle");
-
-  // Multi-line (msay) category
-  compl_add_category_word(COMPL_MULTILINE, "abort");
-  compl_add_category_word(COMPL_MULTILINE, "begin");
-  compl_add_category_word(COMPL_MULTILINE, "send");
-  compl_add_category_word(COMPL_MULTILINE, "send_to");
-  compl_add_category_word(COMPL_MULTILINE, "toggle");
-  compl_add_category_word(COMPL_MULTILINE, "toggle_verbatim");
-  compl_add_category_word(COMPL_MULTILINE, "verbatim");
-
-  // Room category
-  compl_add_category_word(COMPL_ROOM, "affil");
-  compl_add_category_word(COMPL_ROOM, "ban");
-  compl_add_category_word(COMPL_ROOM, "bookmark");
-  compl_add_category_word(COMPL_ROOM, "destroy");
-  compl_add_category_word(COMPL_ROOM, "invite");
-  compl_add_category_word(COMPL_ROOM, "join");
-  compl_add_category_word(COMPL_ROOM, "kick");
-  compl_add_category_word(COMPL_ROOM, "leave");
-  compl_add_category_word(COMPL_ROOM, "names");
-  compl_add_category_word(COMPL_ROOM, "nick");
-  compl_add_category_word(COMPL_ROOM, "privmsg");
-  compl_add_category_word(COMPL_ROOM, "remove");
-  compl_add_category_word(COMPL_ROOM, "role");
-  compl_add_category_word(COMPL_ROOM, "setopt");
-  compl_add_category_word(COMPL_ROOM, "topic");
-  compl_add_category_word(COMPL_ROOM, "unban");
-  compl_add_category_word(COMPL_ROOM, "unlock");
-  compl_add_category_word(COMPL_ROOM, "whois");
-
-  // Authorization category
-  compl_add_category_word(COMPL_AUTH, "allow");
-  compl_add_category_word(COMPL_AUTH, "cancel");
-  compl_add_category_word(COMPL_AUTH, "request");
-  compl_add_category_word(COMPL_AUTH, "request_unsubscribe");
-
-  // Request (query) category
-  compl_add_category_word(COMPL_REQUEST, "last");
-  compl_add_category_word(COMPL_REQUEST, "ping");
-  compl_add_category_word(COMPL_REQUEST, "time");
-  compl_add_category_word(COMPL_REQUEST, "vcard");
-  compl_add_category_word(COMPL_REQUEST, "version");
-
-  // Events category
-  compl_add_category_word(COMPL_EVENTS, "accept");
-  compl_add_category_word(COMPL_EVENTS, "ignore");
-  compl_add_category_word(COMPL_EVENTS, "reject");
-
-  // PGP category
-  compl_add_category_word(COMPL_PGP, "disable");
-  compl_add_category_word(COMPL_PGP, "enable");
-  compl_add_category_word(COMPL_PGP, "force");
-  compl_add_category_word(COMPL_PGP, "info");
-  compl_add_category_word(COMPL_PGP, "setkey");
-
-  // OTR category
-  compl_add_category_word(COMPL_OTR, "start");
-  compl_add_category_word(COMPL_OTR, "stop");
-  compl_add_category_word(COMPL_OTR, "fingerprint");
-  compl_add_category_word(COMPL_OTR, "smpq");
-  compl_add_category_word(COMPL_OTR, "smpr");
-  compl_add_category_word(COMPL_OTR, "smpa");
-  compl_add_category_word(COMPL_OTR, "info");
-  compl_add_category_word(COMPL_OTR, "key");
-
-  // OTR Policy category
-  compl_add_category_word(COMPL_OTRPOLICY, "plain");
-  compl_add_category_word(COMPL_OTRPOLICY, "manual");
-  compl_add_category_word(COMPL_OTRPOLICY, "opportunistic");
-  compl_add_category_word(COMPL_OTRPOLICY, "always");
-
-  // Color category
-  compl_add_category_word(COMPL_COLOR, "roster");
-  compl_add_category_word(COMPL_COLOR, "muc");
-  compl_add_category_word(COMPL_COLOR, "mucnick");
-
-#ifdef MODULES_ENABLE
-  // Module category
-  compl_add_category_word(COMPL_MODULE, "info");
-  compl_add_category_word(COMPL_MODULE, "list");
-  compl_add_category_word(COMPL_MODULE, "load");
-  compl_add_category_word(COMPL_MODULE, "unload");
-#endif
+  gchar *s = commandline;
+  gchar *p;
+  gchar *e;
+  gchar *freeme = NULL;
+  const char *err;
+  gchar *error;
+  cmdopts_t *command = NULL;
+  const char *alias = NULL;
+  gsize n;
+
+  // skip command indicator and spaces at the beginning
+  while (*s == COMMAND_CHAR || *s == ' ')
+    s ++;
+  gchar *p = s;
+  gchar *e = s + strlen (s);
+
+  // separate first word - command name
+  if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
+    scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
+    return cmd_result_syntax_error;
+  }
+
+  // check for quit command
+  if (!strcmp (s, "quit")) {
+    return cmd_result_quit;
+  }
+
+  // check if we're in verbatim mode
+  if ((flags & cmdexe_check_verbatim) && scr_get_multimode() == 2) {
+    if (strcmp(s, "msay")) { // it is not msay
+      return cmd_result_verbatim;
+    }
+  }
+
+  // check and expand alias
+  if ((alias = settings_get(SETTINGS_TYPE_ALIAS, s))) {
+    freeme = s = g_strdup_printf ("%s %s", alias, p);
+    p = s;
+    e = s + strlen (s);
+
+    if ((err = cmdopts_parse_argument(&p, &e, cmdarg_default))) {
+      scr_log_print(LPRINT_NORMAL, "error: Can't comprehend command name: %s.", err);
+      g_free (freeme);
+      return cmd_result_syntax_error;
+    }
+
+    // check for quit command again
+    if (!strcmp (s, "quit")) {
+      g_free (freeme);
+      return cmd_result_quit;
+    }
+  }
+
+  // find command with this name
+  for (n = 0; n < cmd_count; n ++) {
+    if (!strcmp(s, cmd_list[n] -> name)) {
+      command = cmd_list[n];
+      break;
+    }
+  }
+  if (command == NULL) {
+    scr_log_print(LPRINT_NORMAL, "error: Unable to find command \"%s\".", s);
+    g_free(freeme);
+    return cmd_result_not_found;
+  }
+
+  // check safety
+  if ((flags & cmdexe_check_safe) && !(command -> flags & cmd_safe)) {
+    scr_log_print(LPRINT_NORMAL, "error: Command \"%s\" is not safe to execute here.", command -> name);
+    g_free(freeme);
+    return cmd_result_not_found;
+  }
+
+  // parse command line
+  if ((error = cmdopts_parse_internal (&p, &e, command))) {
+    scr_log_print(LPRINT_NORMAL, "%s", error);
+    g_free(error);
+    cmdopts_free (command);
+    g_free(freeme);
+    return cmd_result_syntax_error;
+  }
+
+  // execute command handler
+  if (command -> handler) {
+    if ((error = command -> handler(command))) {
+      scr_log_print(LPRINT_NORMAL, "%s: %s", command -> name, error);
+      g_free(error);
+      cmdopts_free (command);
+      g_free(freeme);
+      return cmd_result_runtime_error;
+    }
+  }
+
+  // free resources
+  cmdopts_free(command);
+  g_free(freeme);
+  return cmd_result_ok;
 }
 
-//  expandalias(line)
-// If there is one, expand the alias in line and returns a new allocated line
-// If no alias is found, returns line
-// Note: if the returned pointer is different from line, the caller should
-//       g_free() the pointer after use
-char *expandalias(const char *line)
+//  process_line(line)
+// Process a command/message line. If this isn't a command, this is a message
+// and it is sent to the currently selected buddy.
+// Returns 255 if the line is the /quit command, 0 on success and some other
+// error codes.
+cmd_result_t process_line(const char *line)
 {
-  const char *p1, *p2;
-  char *word;
-  const gchar *value;
-  char *newline = (char*)line;
-
-  // Ignore leading COMMAND_CHAR
-  for (p1 = line ; *p1 == COMMAND_CHAR ; p1++)
-    ;
-  // Locate the end of the word
-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
-    ;
-  // Extract the word and look for an alias in the list
-  word = g_strndup(p1, p2-p1);
-  value = settings_get(SETTINGS_TYPE_ALIAS, (const char*)word);
-  g_free(word);
-
-  if (value)
-    newline = g_strdup_printf("%c%s%s", COMMAND_CHAR, value, p2);
-
-  return newline;
-}
-
-//  cmd_get
-// Finds command in the command list structure.
-// Returns a pointer to the cmd entry, or NULL if command not found.
-cmd *cmd_get(const char *command)
-{
-  const char *p1, *p2;
-  char *com;
-  GSList *sl_com;
-
-  // Ignore leading COMMAND_CHAR
-  for (p1 = command ; *p1 == COMMAND_CHAR ; p1++)
-    ;
-  // Locate the end of the command
-  for (p2 = p1 ; *p2 && (*p2 != ' ') ; p2++)
-    ;
-  // Copy the clean command
-  com = g_strndup(p1, p2-p1);
-
-  // Look for command in the list
-  for (sl_com=Commands; sl_com; sl_com = g_slist_next(sl_com)) {
-    if (!strcasecmp(com, ((cmd*)sl_com->data)->name))
-      break;
-  }
-  g_free(com);
-
-  if (sl_com)       // Command has been found.
-    return (cmd*)sl_com->data;
-  return NULL;
-}
-
-//  process_command(line, iscmd)
-// Process a command line.
-// If iscmd is TRUE, process the command even if verbatim mmode is set;
-// it is intended to be used for key bindings.
-// Return 255 if this is the /quit command, and 0 for the other commands.
-int process_command(const char *line, guint iscmd)
-{
-  char *p;
-  char *xpline;
-  cmd *curcmd;
-
-  if (!line)
-    return 0;
-
-  // We do alias expansion here
-  if (iscmd || scr_get_multimode() != 2)
-    xpline = expandalias(line);
-  else
-    xpline = (char*)line; // No expansion in verbatim multi-line mode
-
-  // We want to use a copy
-  if (xpline == line)
-    xpline = g_strdup(line);
-
-  // Remove trailing spaces:
-  for (p=xpline ; *p ; p++)
-    ;
-  for (p-- ; p>xpline && (*p == ' ') ; p--)
-    *p = 0;
-
-  // Command "quit"?
-  if ((iscmd || scr_get_multimode() != 2)
-      && (!strncasecmp(xpline, mkcmdstr("quit"), strlen(mkcmdstr("quit"))))) {
-    if (!xpline[5] || xpline[5] == ' ') {
-      g_free(xpline);
-      return 255;
-    }
-  } else if (iscmd && !strncasecmp(xpline, "quit", 4) &&
-             (!xpline[4] || xpline[4] == ' ')) {
-    // If iscmd is true we can have the command without the command prefix
-    // character (usually '/').
-    g_free(xpline);
-    return 255;
-  }
-
-  // If verbatim multi-line mode, we check if another /msay command is typed
-  if (!iscmd && scr_get_multimode() == 2
-      && (strncasecmp(xpline, mkcmdstr("msay "), strlen(mkcmdstr("msay "))))) {
-    // It isn't an /msay command
-    scr_append_multiline(xpline);
-    g_free(xpline);
-    return 0;
-  }
-
-  // Commands handling
-  curcmd = cmd_get(xpline);
-
-  if (!curcmd) {
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized command.  "
-                 "Please see the manual for a list of known commands.");
-    g_free(xpline);
-    return 0;
-  }
-  if (!curcmd->func) {
-    scr_LogPrint(LPRINT_NORMAL,
-                 "This functionality is not yet implemented, sorry.");
-    g_free(xpline);
-    return 0;
-  }
-  // Lets go to the command parameters
-  for (p = xpline+1; *p && (*p != ' ') ; p++)
-    ;
-  // Skip spaces
-  while (*p && (*p == ' '))
-    p++;
-  // Call command-specific function
-  retval_for_cmds = 0;
-#ifdef MODULES_ENABLE
-  if (curcmd->userdata)
-    (*(void (*)(char *p, gpointer u))curcmd->func)(p, curcmd->userdata);
-  else
-    (*curcmd->func)(p);
-#else
-  (*curcmd->func)(p);
-#endif
-  g_free(xpline);
-  return retval_for_cmds;
-}
-
-//  process_line(line)
-// Process a command/message line.
-// If this isn't a command, this is a message and it is sent to the
-// currently selected buddy.
-// Return 255 if the line is the /quit command, or 0.
-int process_line(const char *line)
-{
+  gchar *utf8;
+  int retval;
+
   if (!*line) { // User only pressed enter
     if (scr_get_multimode()) {
       scr_append_multiline("");
@@ -556,141 +707,509 @@
     }
     if (current_buddy) {
       if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
-        do_group("toggle");
+        group_cmd (BUDDATA(current_buddy), scmd_group_toggle);
       else {
         // Enter chat mode
         scr_set_chatmode(TRUE);
         scr_show_buddy_window();
       }
     }
-    return 0;
+    return cmd_result_input;
   }
 
-  if (*line != COMMAND_CHAR) {
-    // This isn't a command
+  utf8 = to_utf8(line);
+  if (*line != COMMAND_CHAR) { // input line
     if (scr_get_multimode())
-      scr_append_multiline(line);
+      scr_append_multiline(utf8);
     else
-      say_cmd((char*)line, 0);
-    return 0;
-  }
-
-  /* It is _probably_ a command -- except for verbatim multi-line mode */
-  return process_command(line, FALSE);
-}
-
-// Helper routine for buffer item_{lock,unlock,toggle_lock}
-// "lock" values: 1=lock 0=unlock -1=invert
-static void roster_buddylock(char *bjid, int lock)
-{
-  gpointer bud = NULL;
-  bool may_need_refresh = FALSE;
-
-  // Allow special jid "" or "." (current buddy)
-  if (bjid && (!*bjid || !strcmp(bjid, ".")))
-    bjid = NULL;
-
-  if (bjid) {
-    // The JID has been specified.  Quick check...
-    if (check_jid_syntax(bjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", bjid);
-    } else {
-      // Find the buddy
-      GSList *roster_elt;
-      roster_elt = roster_find(bjid, jidsearch,
-                               ROSTER_TYPE_USER|ROSTER_TYPE_ROOM);
-      if (roster_elt)
-        bud = roster_elt->data;
-      else
-        scr_LogPrint(LPRINT_NORMAL, "This jid isn't in the roster.");
-      may_need_refresh = TRUE;
-    }
-  } else {
-    // Use the current buddy
-    if (current_buddy)
-      bud = BUDDATA(current_buddy);
-  }
-
-  // Update the ROSTER_FLAG_USRLOCK flag
-  if (bud) {
-    if (lock == -1)
-      lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
-    buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
-    if (may_need_refresh) {
-      buddylist_build();
-      update_roster = TRUE;
+      say_cmd(utf8, msgtype_not_set);
+    retval = 0;
+  } else { // command or verbatim multiline message
+    retval = cmd_execute_internal (utf8, cmdexe_check_verbatim);
+    if (retval == cmd_result_verbatim) {
+      g_free(utf8); // buffer has been corrupted by parser
+      utf8 = to_utf8(line);
+      scr_append_multiline(utf8);
+      retval = cmd_result_input;
     }
   }
+  g_free(utf8);
+
+  return retval;
 }
 
-static void roster_resourcelock(char *jidres, gboolean lock) {
-  gpointer bud = NULL;
-  char *resource = NULL;
-
-  if (!jidres) {
-    if (lock) return;
-    jidres = ".";
-  }
-
-  if (jidres[0] == '.' &&
-      (jidres[1] == '\0' || jidres[1] == JID_RESOURCE_SEPARATOR)) {
-    //Special jid: . or ./resource
-    switch (jidres[1]) {
-      case JID_RESOURCE_SEPARATOR:
-        resource = jidres+2;
-      case '\0':
-        if (current_buddy)
-          bud = BUDDATA(current_buddy);
-    }
-  } else {
-    char *tmp;
-    if (!check_jid_syntax(jidres) &&
-        (tmp = strchr(jidres, JID_RESOURCE_SEPARATOR))) {
-      //Any other valid full jid
-      *tmp = '\0'; // for roster search by bare jid;
-      resource = tmp+1;
-      GSList *roster_elt;
-      roster_elt = roster_find(jidres, jidsearch,
-          ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
-      if (roster_elt)
-        bud = roster_elt->data;
-      *tmp = JID_RESOURCE_SEPARATOR;
-    }
-    if (!bud) {
-      //Resource for current buddy
-      if (current_buddy)
-        bud = BUDDATA(current_buddy);
-      resource = jidres;
+//
+//  Standard types
+//
+// This section contains standard argument type definitions, used in built-in
+// mcabber commands.
+//
+
+// TODO: (and variations with 'required')
+// + cmdarg_type_roster_bjid     - in roster, with specified types -> bud
+// + cmdarg_type_roster_resource - in roster, with specified types, have resource -> bud + resource
+// * cmdarg_type_roster_fjid     - in roster, with specified types, might have non-existing resource -> bud + resource
+// * cmdarg_type_roster_jid      - in roster, with specified types, might have or not have resource -> bud + (resource)
+// * cmdarg_type_bjid         - any bjid -> bjid
+// + cmdarg_type_fjid         - any fjid -> fjid
+// + cmdarg_type_statusmask   - string -> string
+// + cmdarg_type_uint         - string -> uint
+// + cmdarg_type_nonspace     - strip, space only -> null
+// * cmdarg_type_bjidmask
+// + cmdarg_type_color
+// + cmdarg_type_string2enum
+// * cmdarg_type_nick        - provide completions first from current room, then from all other, nonspace, do not restrict
+
+//
+//  common methods
+//
+
+static void cmdarg_free_gfree (optarg_t *arg)
+{
+  g_free (arg -> value.arg);
+}
+
+//
+//  string -> stripspace string
+//
+
+// Strips leading and trailing spaces, checks if anything left.
+// Replaces value.arg.
+// Does not need freeing.
+// No trailing spaces in defvalue - needs RW access for that.
+static gchar *cmdarg_check_nospace (optarg_t *arg)
+{
+  gchar *val = arg -> value.arg;
+  if (val) {
+    while (isspace(*val))
+      val ++;
+    if (*val) { // valid arg
+      arg -> value.arg = val;
+      // XXX requires RW access. default values must not contain trailing space
+      while (*val)
+        val ++;
+      while (isspace(*val))
+        val --;
+      val ++;
+      if (*val)
+        *val = '\0';
+      return NULL;
     }
   }
-  
-  if (bud && buddy_gettype(bud) & (ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)) {
-    if (lock) {
-      GSList *resources, *p_res;
-      gboolean found = FALSE;
-      resources = buddy_getresources(bud);
-      for (p_res = resources ; p_res ; p_res = g_slist_next(p_res)) {
-        if (!g_strcmp0((char*)p_res->data, resource))
-          found = TRUE;
-        g_free(p_res->data);
+  // error
+  arg -> value.arg = NULL;
+  return g_strdup("Non-space value required.");
+}
+
+cmdarg_type_t cmdarg_type_nospace = {
+  cmdarg_check_nospace,
+  NULL,
+  NULL,
+};
+
+//
+//  bjid -> bud
+//
+
+// Uses chkdata as guint with allowed ROSTER_TYPE_*.
+// Returns buddy roster entry in value.bud.
+// Recognizes as current ".", but not "" or NULL - use defvalue.
+// Does not require freeing.
+static gchar *cmdarg_check_roster_bjid (optarg_t *arg)
+{
+  gchar *error = NULL;
+
+  if (!(error = cmdarg_check_nospace_required(arg))) {
+    const char *bjid = arg -> value.arg;
+    guint      types = (guint) arg -> chkdata;
+
+    if (!strcmp(bjid, ".")) { // current buddy
+      if (!current_buddy)
+        error = g_strdup_printf("No buddy selected.");
+      else if (buddy_gettype(BUDDATA(current_buddy)) & types)
+        arg -> value.bud = BUDDATA(current_buddy);
+      else // TODO: improve message
+        error = g_strdup_printf("Currently selected buddy is of wrong type.");
+    } else if (!check_jid_syntax(bjid)) { // valid jid specified
+      GSList *found = roster_find(bjid, jidsearch, types);
+      if (found)
+        arg -> value.bud = found->data;
+      else
+        error = g_strdup_printf("Jid <%s> is not in the roster.", bjid);
+    } else { // jid is invalid
+      error =  g_strdup_printf("<%s> is not a valid Jabber ID.", bjid);
+    }
+  }
+
+  if (error)
+    arg -> value.bud = NULL;
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_bjid = {
+  cmdarg_check_roster_bjid,
+  NULL,
+  NULL,
+};
+
+//
+//  fjid -> bud + resource
+//
+
+// Uses chkdata as guint with allowed ROSTER_TYPE_*.
+// Returns buddy roster entry in userdata.
+// Returns resource string in value.arg.
+// Recognizes as current "./res" and "res".
+// Does not require freeing.
+// No full "jid/resource" syntax in defvalue - needs rw for that.
+// XXX:
+//  * make return value a custom struct { .bud, .res } instead of using userdata
+//  * allow bjids and '.'
+//  * merge with roster_bjid and use own flags in chkdata to signify types and resource allowed/required conditions
+//  * it is rather check_roster_resource
+static gchar *cmdarg_check_roster_resource(optarg_t *arg)
+{
+  gchar    *error    = NULL;
+  gpointer bud       = NULL;
+  char     *resource = NULL;
+
+  if (!(error = cmdarg_check_nospace(arg))) {
+    const char *fjid     = arg -> value.arg;
+    guint      types     = (guint) arg -> chkdata;
+
+    if (fjid[0] == '.' && fjid[1] == JID_RESOURCE_SEPARATOR) {
+      // current buddy
+      resource = fjid+2;
+    } else if (!check_jid_syntax(fjid) && (resource = strchr(fjid, JID_RESOURCE_SEPARATOR))) {
+      // valid jid
+      GSList *found;
+      *resource = '\0'; // XXX needs rw
+      found = roster_find(fjid, jidsearch, types);
+      if (found) {
+        bud = found->data;
+        resource ++;
+      } else
+        error = g_strdup_printf("Jid <%s> is not in the roster.", bjid);
+    } else {
+      // jid is invalid - let's consider it resource (XXX)
+      resource = fjid;
+    }
+    // resource for current buddy
+    if (error == NULL && resource) {
+      if (bud == NULL) {
+        if (!current_buddy)
+          error = g_strdup_printf("No buddy selected.");
+        else if (buddy_gettype(BUDDATA(current_buddy)) & types)
+          bud = BUDDATA(current_buddy);
+        else // TODO: improve message
+          error = g_strdup_printf("Currently selected buddy is of wrong type.");
       }
-      g_slist_free(resources);
-      if (!found) {
-        scr_LogPrint(LPRINT_NORMAL, "No such resource <%s>...", jidres);
-        return;
+      if (bud) {
+        GSList *resources, *p_res;
+        gboolean found = FALSE;
+        resources = buddy_getresources(bud);
+        for (p_res = resources; p_res; p_res = g_slist_next(p_res)) {
+          if (!g_strcmp0((char*)p_res->data, resource))
+            found = TRUE;
+          g_free(p_res->data);
+        }
+        g_slist_free(resources);
+        if (!found)
+          error = g_strdup_printf("No such resource <%s%c%s>...", buddy_getjid(bud), JID_RESOURCE_SEPARATOR, resource);
       }
+    }
+  }
+
+  if (error) {
+    arg -> userdata  = NULL;
+    arg -> value.arg = NULL;
+  } else {
+    arg -> userdata  = bud;
+    arg -> value.arg = resource;
+  }
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_roster_resource = {
+  cmdarg_check_roster_resource,
+  NULL,
+  NULL,
+};
+
+//
+//  fjid -> fjid
+//
+
+// Returns corrected fjid in value.arg.
+// Recognizes as current "." and "./res".
+// Requires freeing.
+// XXX:
+//  * destructor is rather generic g_freer, publish?
+static gchar *cmdarg_check_fjid(optarg_t *arg)
+{
+  gchar *error = NULL;
+
+  if (!(error = cmdarg_check_nospace(arg))) {
+    const char *fjid = arg -> value.arg;
+
+    if (fjid[0] == '.' && (fjid[1] == JID_RESOURCE_SEPARATOR || fjid[1] == '\0')) {
+      const char *jid;
+      if (!current_buddy)
+        error = g_strdup_printf ("No buddy selected.");
+      else if (!(jid = buddy_getjid(BUDDATA(current_buddy)))) {
+        error = g_strdup_printf ("Current buddy have no jid.");
+      } else if (fjid[1] == '\0') {
+        arg -> value.arg = jid;
+      } else {
+        arg -> value.arg = g_strdup_printf ("%s%c%s", jid, JID_RESOURCE_SEPARATOR, fjid + 2);
+        arg -> flags |= cmdarg_freeme;
+      }
+    } else if (check_jid_syntax(fjid)) {
+      error = g_strdup_printf ("Jid <%s> is invalid.", fjid);
+    }
+  }
+
+  if (error)
+    arg -> value.arg = NULL;
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_fjid = {
+  cmdarg_check_fjid,
+  cmdarg_free_gfree,
+  NULL,
+};
+
+//
+//  string -> uint
+//
+
+// Returns unsigned integer in value.uint.
+// Does not require freeing.
+// XXX:
+//  * use gulong? (strtoul allows to check conversion errors, while atoi - not)
+//  * use flags in chkdata to specify signedness - it only affects two checks
+static gchar *cmdarg_check_uint (cmdarg_t *arg)
+{
+  gchar *error;
+
+  if (!(error = cmdarg_check_nospace(arg))) {
+    const char *e = NULL;
+    const char *s = arg -> value.arg;
+    long n = strtol(s, &e, 0);
+    if (*e != '\0')
+      error = g_strdup_printf ("Invalid number \"%s\".", s);
+    else if (n < 0)
+      error = g_strdup ("Value must be greater than zero.");
+    else if (n > G_MAXUINT)
+      error = g_strdup ("Value %d is too big.", n);
+    else
+      arg -> value.uint = (guint) n;
+  }
+
+  if (error)
+    arg -> value.uint = 0;
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_uint = {
+  cmdarg_check_uint,
+  NULL,
+  NULL,
+};
+
+//
+//  string -> statusmask
+//
+
+// Strips/checks for any non-valid status chars in mask.
+// Returns mask in value.arg.
+// Recognizes "*" as glob.
+// Does not require freeing.
+// No errors in default vaules - needs RW for that.
+// XXX:
+//  * check duplicates?
+//    * string2flags?
+//  * canonicize?
+//    * string2enum?
+//  * common strchr callback with valid chars in chkdata?
+//    * Then argument name should go into cmdarg struct - message would be too generic
+static gchar *cmdarg_check_statusmask (cmdarg_t *arg)
+{
+  gchar *error;
+
+  if (!(error = cmdarg_check_nospace(arg)) && arg -> value.arg) {
+    const char *valid = "foand_?";
+    if (!strcmp(arg -> value.arg, "*")) {
+      arg -> value.arg = g_strdup (valid);
+      arg -> flags    |= cmdarg_freeme;
     } else {
-      resource = NULL;
+      gchar *p = arg -> value.arg;
+      gchar *e = p + strlen (p);
+      while (p < e) {
+        if (strchr(valid, *p)) {
+          p ++;
+        } else if (arg -> flags & cmdarg_required) {
+          // this is valid use of flag in checker
+          return g_strdup_printf ("%s can only contain characters [%s].", arg -> name, valid);
+        } else {
+          scr_log_print (LPRINT_NORMAL, "Warning: Wrong %s character [%c]", arg -> name, *p);
+          memmove (p, p+1, e-p-1);
+          e --;
+        }
+      }
+      if (arg -> value.arg == e) // arg is not required and we deleted all string
+        arg -> value.arg = NULL;
     }
-    buddy_setactiveresource(bud, resource);
-    scr_update_chat_status(TRUE);
+  }
+
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_statusmask = {
+  cmdarg_check_statusmask,
+  cmdarg_free_gfree,
+  NULL,
+};
+
+//
+//  string -> enum
+//
+
+typedef struct {
+  const char *name;
+  guint      value;
+} string2enum_t;
+
+// Uses chkdata as a pointer to continuous array of string2enum_t structs.
+// Returns corresponding value in value.uint.
+// Returns 0 if not recognized and not required.
+// Does not require freeing.
+// XXX:
+//  * also, print list of possible values on error?
+static gchar *cmdarg_check_string2enum (cmdarg_t *arg)
+{
+  gchar *error;
+
+  if (!(error = cmdarg_check_nospace(arg))) {
+    string2enum_t *list = arg -> chkdata;
+    gsize i;
+    for (i = 0; list[i] -> name != NULL; i ++) {
+      if (!strcmp(list[i] -> name, arg -> value.arg)) { // found
+        arg -> value.uint = list[i] -> value;
+        return NULL;
+      }
+      list ++;
+    }
+    // not found, error
+    error = g_strdup_printf("Value \"%s\" is invalid for %s.", arg -> value.arg, arg -> name);
+  }
+
+  if (error)
+    arg -> value.uint = 0; // XXX default value?
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_string2enum = {
+  cmdarg_check_string2enum,
+  NULL,
+  NULL,
+};
+
+//
+//  string -> color name
+//
+
+static string2enum_t s2e_color[] = {
+  { "default", -1 },
+  { "black",   1  },
+  { "red",     1  },
+  { "green",   1  },
+  { "yellow",  1  },
+  { "blue",    1  },
+  { "magenta", 1  },
+  { "white",   1  },
+  { NULL,      0  },
+};
+
+// Recognizes "-" for reset, prefix "bright", standard names and numerical values.
+// Returns color name in value.arg.
+// Does not require freeing.
+// XXX
+//  * in fact, we can straight away do color parsing & allocate ccolor,
+//    but to not break too much things, for now we'll wait with that.
+//    * that needs access to ncurses internals, so, probably, this will
+//      be better done, when moving related command definitions to
+//      corresponding subsystems.
+static gchar *cmdarg_check_color (cmdarg_t *arg)
+{
+  gchar *error;
+
+  if (!(error = cmdarg_check_nospace(arg))) {
+    const char *color = arg -> value.arg;
+    gsize i;
+    // reset color
+    if (!strcmp(color, "-")) {
+      return NULL;
+    }
+    // allow "bright" prefix
+    if (!strncmp(color, "bright", 6))
+      color += 6;
+    // check names
+    for (i = 0; s2e_color[i] -> name != NULL; i ++) {
+      if (!strcmp (s2e_color[i] -> name, color)) {
+        return NULL;
+      }
+    }
+    { // not found, check for numerical value
+      const char *e = NULL;
+      long n = strtol(color, &e, 0);
+      if (*e != '\0' || n < 0 || n > 256)
+        error = g_strdup_printf ("Invalid color \"%s\".", arg -> value.arg);
+    }
+  }
+
+  if (error)
+    arg -> value.arg = NULL;
+  return error;
+}
+
+cmdarg_type_t cmdarg_type_color = {
+  cmdarg_check_color,
+  NULL,
+  NULL,
+};
+
+//
+//  Command definitions
+//
+// This section contains definitions for built-in mcabber commands.
+// If you add a definition here, that should be automatically loaded,
+// you also have to add it to cmd_init().
+//
+
+//
+//  /roster
+//
+
+// Helper routine for /roster item_{lock,unlock,toggle_lock}
+// "lock" values: 1=lock 0=unlock -1=invert
+static void roster_buddylock(gpointer bud, int lock)
+{
+  // Update the ROSTER_FLAG_USRLOCK flag
+  if (lock == -1)
+    lock = !(buddy_getflags(bud) & ROSTER_FLAG_USRLOCK);
+  buddy_setflags(bud, ROSTER_FLAG_USRLOCK, lock);
+  if ((!current_buddy) || bud == BUDDATA(current_buddy)) {
+    buddylist_build();
+    update_roster = TRUE;
   }
 }
+
 //  display_and_free_note(note, winId)
 // Display the note information in the winId buffer, and free note
 // (winId is a bare jid or NULL for the status window, in which case we
-// display the note jid too)
+// display the note jid too).
 static void display_and_free_note(struct annotation *note, const char *winId)
 {
   gchar tbuf[128];
@@ -755,41 +1274,15 @@
   g_slist_free(notes);
 }
 
-static void roster_note(char *arg)
+static void roster_note(gpointer bud, gboolean reset, gchar *note)
 {
-  const char *bjid;
-  guint type;
-
-  if (!current_buddy)
-    return;
-
-  bjid = buddy_getjid(BUDDATA(current_buddy));
-  type = buddy_gettype(BUDDATA(current_buddy));
-
-  if (!bjid && type == ROSTER_TYPE_SPECIAL && !arg) {
-    // We're in the status window (the only special buffer currently)
-    // Let's display all server notes
-    display_all_annotations();
-    return;
-  }
-
-  if (!bjid || (type != ROSTER_TYPE_USER &&
-               type != ROSTER_TYPE_ROOM &&
-               type != ROSTER_TYPE_AGENT)) {
-    scr_LogPrint(LPRINT_NORMAL, "This item can't have a note.");
-    return;
-  }
-
-  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);
-  } else {      // Display a note
+  const char *bjid = buddy_getjid(bud);
+
+  if (note) // set note
+    xmpp_set_storage_rosternotes(bjid, note);
+  else if (reset) // delete note
+    xmpp_set_storage_rosternotes(bjid, NULL);
+  else { // display a note
     struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
     if (note) {
       display_and_free_note(note, bjid);
@@ -800,194 +1293,273 @@
   }
 }
 
-//  roster_updown(updown, nitems)
-// updown: -1=up, +1=down
-inline static void roster_updown(int updown, char *nitems)
+typedef enum {
+  scmd_roster_bottom, scmd_roster_top, scmd_roster_up, scmd_roster_down,
+  scmd_roster_group_prev, scmd_roster_group_next,
+  scmd_roster_alternate,
+  scmd_roster_unread_first, scmd_roster_unread_next,
+  scmd_roster_search,
+  scmd_roster_display,
+  scmd_roster_hide_offline, scmd_roster_show_offline, scmd_roster_toggle_offline,
+  scmd_roster_item_lock, scmd_roster_item_unlock, scmd_roster_item_toggle_lock,
+  scmd_roster_note, scmd_roster_notes,
+  scmd_roster_resource_lock, scmd_roster_resource_unlock,
+  scmd_roster_hide, scmd_roster_show, scmd_roster_toggle,
+} scmd_roster_t;
+
+#define SCMD_ROSTER(NAME, ARGS) \
+    { #NAME, NULL, NULL, NULL, ARGS, NULL, (gpointer)scmd_roster_##NAME }
+static cmdopts_t def_roster = {
+  "roster",
+  NULL,
+  do_roster,
+  NULL,
+  {{"subcommand", cmdarg_subcmd | cmdarg_check, NULL, NULL}, NULL},
+  {
+    SCMD_ROSTER(bottom, NULL),
+    SCMD_ROSTER(top,    NULL),
+    SCMD_ROSTER(up,   {{"n", cmdarg_chreq, "1", cmdarg_type_uint}, NULL}),
+    SCMD_ROSTER(down, {{"n", cmdarg_chreq, "1", cmdarg_type_uint}, NULL}),
+    SCMD_ROSTER(group_prev, NULL),
+    SCMD_ROSTER(group_next, NULL),
+    SCMD_ROSTER(alternate, NULL),
+    SCMD_ROSTER(unread_first, NULL),
+    SCMD_ROSTER(unread_next,  NULL),
+    SCMD_ROSTER(search, {{"name", cmdarg_eol|cmdarg_required, NULL, cmdarg_type_nonspace}, NULL}),
+    SCMD_ROSTER(display, {{"statusmask", cmdarg_check, NULL, cmdarg_type_statusmask}, NULL}),
+    SCMD_ROSTER(hide_offline,   NULL),
+    SCMD_ROSTER(show_offline,   NULL),
+    SCMD_ROSTER(toggle_offline, NULL),
+    SCMD_ROSTER(item_lock,        {{"jid", cmdarg_chreq, ".", cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
+    SCMD_ROSTER(item_unlock,      {{"jid", cmdarg_chreq, ".", cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
+    SCMD_ROSTER(item_toggle_lock, {{"jid", cmdarg_chreq, ".", cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)}, NULL}),
+    { "note", NULL, NULL,
+      {
+        {cmdopt_switch,  'r', "reset", {"reset", cmdarg_default, NULL, NULL, NULL}},
+        {cmdopt_default, 'j', "jid",   {"jid", cmdarg_chreq, ".", cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT)}},
+        NULL,
+      },
+      {
+        {cmdarg_eol, NULL, cmdarg_type_nonspace},
+        NULL,
+      },
+      NULL, (gpointer)scmd_roster_note
+    },
+    SCMD_ROSTER(notes, NULL),
+    SCMD_ROSTER(resource_lock,   {{"resource|fjid", cmdarg_chreq, NULL, cmdarg_type_roster_resource, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)}, NULL}),
+    SCMD_ROSTER(resource_unlock, {{"jid", cmdarg_chreq, ".", cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)}, NULL}),
+    SCMD_ROSTER(hide,   NULL),
+    SCMD_ROSTER(show,   NULL),
+    SCMD_ROSTER(toggle, NULL),
+    NULL,
+  },
+};
+
+static gchar *do_roster(cmdopts_t *options)
 {
-  int nbitems;
-
-  if (!nitems || !*nitems)
-    nbitems = 1;
-  else
-    nbitems = strtol(nitems, NULL, 10);
-
-  if (nbitems > 0)
-    scr_roster_up_down(updown, nbitems);
-}
-
-/* Commands callback functions */
-/* All these do_*() functions will be called with a "arg" parameter */
-/* (with arg not null)                                              */
-
-static void do_roster(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);
-    return;
-  }
-
-  if (!strcasecmp(subcmd, "top")) {
+  scmd_roster_t subcmd = (scmd_roster_t) (options -> args[0] -> value.cmd -> userdata);
+  cmdarg_t      *arg   = NULL;
+
+  if (options -> args[0] -> value.cmd -> args)
+    arg = options -> args[0] -> value.cmd -> args[0];
+
+  if (subcmd == scmd_roster_bottom) {
+    scr_roster_bottom();
+    update_roster = TRUE;
+  } else if (subcmd == scmd_roster_top) {
     scr_roster_top();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "bottom")) {
-    scr_roster_bottom();
+  } else if (subcmd == scmd_roster_up) {
+    scr_roster_up_down(-1, arg -> value.uint);
+  } else if (subcmd == scmd_roster_down) {
+    scr_roster_up_down(1, arg -> value.uint);
+  } else if (subcmd == scmd_roster_group_prev) {
+    scr_roster_prev_group();
+  } else if (subcmd == scmd_roster_group_next) {
+    scr_roster_next_group();
+  } else if (subcmd == scmd_roster_alternate) {
+    scr_roster_jump_alternate();
+  } else if (subcmd == scmd_roster_unread_first) {
+    scr_roster_unread_message(0);
+  } else if (subcmd == scmd_roster_unread_next) {
+    scr_roster_unread_message(1);
+  } else if (subcmd == scmd_roster_search) {
+    scr_roster_search(arg -> value.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 == scmd_roster_display) {
+    scr_roster_display(arg -> value.arg);
+  } else if (subcmd == scmd_roster_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 == scmd_roster_show_offline) {
     buddylist_set_hide_offline_buddies(FALSE);
     buddylist_build();
     update_roster = TRUE;
-  } else if (!strcasecmp(subcmd, "toggle_offline")) {
+  } else if (subcmd == scmd_roster_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")) {
-    roster_buddylock(arg, 1);
-  } else if (!strcasecmp(subcmd, "item_unlock")) {
-    roster_buddylock(arg, 0);
-  } else if (!strcasecmp(subcmd, "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")) {
-    roster_note(arg);
-  } else if (!strcasecmp(subcmd, "resource_lock")) {
-    roster_resourcelock(arg, TRUE);
-  } else if (!strcasecmp(subcmd, "resource_unlock")) {
-    roster_resourcelock(arg, FALSE);
-  } else
-    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
-  free_arg_lst(paramlst);
+  } else if (subcmd == scmd_roster_item_lock) {
+    roster_buddylock(arg -> value.bud, 1);
+  } else if (subcmd == scmd_roster_item_unlock) {
+    roster_buddylock(arg -> value.bud, 0);
+  } else if (subcmd == scmd_roster_item_toggle_lock) {
+    roster_buddylock(arg -> value.bud, -1);
+  } else if (subcmd == scmd_roster_note) {
+    roster_note(options -> cmds[18] -> opts[1] -> value.bud,
+                options -> cmds[18] -> opts[0] -> value.swc,
+                arg -> value.arg);
+  } else if (subcmd == scmd_roster_notes) {
+    display_all_annotations();
+  } else if (subcmd == scmd_roster_resource_lock) {
+    buddy_setactiveresource(arg -> userdata, arg -> value.arg);
+    scr_update_chat_status(TRUE);
+  } else if (subcmd == scmd_roster_resource_unlock) {
+    buddy_setactiveresource(arg -> value.bud, NULL);
+    scr_update_chat_status(TRUE);
+  } else if (subcmd == scmd_roster_hide) {
+    scr_roster_visibility(0);
+  } else if (subcmd == scmd_roster_show) {
+    scr_roster_visibility(1);
+  } else { // scmd_roster_toggle
+    scr_roster_visibility(-1);
+  }
+
+  return NULL;
 }
 
-void do_color(char *arg)
+//
+//  /color
+//
+
+// custom argument types
+
+// statusmask + "clear"
+static gchar *cmdarg_check_color_statusmask (cmdarg_t *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);
-    return;
+  if (!g_strcmp0(arg -> value.arg, "clear"))
+    return NULL;
+  else
+    return cmdarg_check_statusmask (arg);
+}
+
+static cmdarg_type_t cmdarg_type_color_statusmask = {
+  cmdarg_check_color_statusmask,
+  NULL,
+  NULL,
+};
+
+// bjid + "*"
+// Returns string, not buddy.
+static gchar *cmdarg_check_color_roomjid (cmdarg_t *arg)
+{
+  gchar *error;
+
+  if (!g_strcmp0(arg -> value.arg, "*"))
+    return NULL;
+  
+  arg -> chkdata = (gpointer) ROSTER_TYPE_ROOM;
+  if (!(error = cmdarg_check_roster_bjid (arg))) {
+    arg -> value.roarg = buddy_getjid (arg -> value.bud); // XXX strdup?
   }
 
-  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
+  if (error)
+    arg -> value.arg = NULL;
+  return error;
+}
+
+static cmdarg_type_t cmdarg_type_color_roomjid = {
+  cmdarg_check_color_roomjid,
+  NULL,
+  NULL,
+};
+
+// command
+
+typedef enum {
+  scmd_color_roster,
+  scmd_color_mucnick,
+  scmd_color_muc,
+} scmd_color_t;
+
+static string2enum_t s2e_color_muc[] = {
+  { "on",     MC_ALL    },
+  { "off",    MC_OFF    },
+  { "preset", MC_PRESET },
+  { "-",      MC_REMOVE },
+  { NULL,     0         },
+};
+
+static cmdopts_t def_color = {
+  "color",
+  NULL,
+  do_color,
+  NULL,
+  {{ "subcommand", cmdarg_subcmd | cmdarg_check, NULL, NULL }, NULL},
+  {
+    {"roster", NULL, NULL, NULL, {
+        { "statusmask|clear", cmdarg_chreq, NULL, cmdarg_type_color_statusmask },
+        { "jidmask", cmdarg_default, NULL, cmdarg_type_bjidmask         },
+        { "color|-", cmdarg_default, NULL, cmdarg_type_color            },
+        NULL,
+      }, NULL, (gpointer)scmd_color_roster},
+    {"muc", NULL, NULL, NULL, {
+        { "roomjid", cmdarg_chreq, NULL, cmdarg_type_color_roomjid },
+        { "on|off|preset|-", cmdarg_chreq, "on", cmdarg_type_string2enum, (gpointer)s2e_color_muc},
+        NULL,
+      }, NULL, (gpointer)scmd_color_muc},
+    {"mucnick", NULL, NULL, NULL {
+        { "nick", cmdarg_chreq, NULL, cmdarg_type_nick  },
+        { "color|-", cmdarg_chreq, NULL, cmdarg_type_color },
+        NULL,
+      }, NULL, (gpointer)scmd_color_mucnick},
+    NULL,
+  },
+};
+
+static gchar *do_color(cmdopts_t *options)
+{
+  scmd_color_t subcmd = (scmd_color_t) options -> args[0] -> value.cmd -> userdata;
+
+  if (subcmd == scmd_color_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 (!wildcard || !*wildcard || !color || !*color) {
+        // freaking "clear" :(
+        return g_strdup ("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");
-        }
-      }
-    }
-    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 if (subcmd == scmd_color_muc) {
+    scr_muc_color ( options -> cmds[1] -> args[0] -> value.arg,
+                    options -> cmds[1] -> args[1] -> value.uint );
+  } else { // scmd_color_mucnick
+    scr_muc_nick_color( options -> cmds[2] -> args[0] -> value.arg,
+                        options -> cmds[2] -> args[1] -> value.arg );
+  }
+
+  return NULL;
 }
 
-//  cmd_setstatus(recipient, arg)
+//
+//  /status
+//
+
+// FIXME: this should go to xmpp_iq, that uses it
+//  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 +1572,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,229 +1592,347 @@
   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)
+// custom type
+
+// XXX status|message|show -> status
+static string2enum_t s2e_status2[] = {
+  { "away",      away        },
+  { "offline",   offline     },
+  { "online",    available   },
+  { "avail",     available   },
+  { "notavail",  notavail    },
+  { "dnd",       dontdisturb },
+  { "free",      freeforchat },
+#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+  { "invisible", invisible   },
+#endif
+  { "show",      imstatus_size},
+  { NULL,        0           },
+};
+
+// needs corresponding s2e in chkdata
+static gchar *cmdarg_check_status_status (cmdarg_t *arg)
 {
-  if (!*arg) {
+  gchar *error;
+
+  if (!g_strcmp0(arg, "message")) {
+    arg -> value.uint = xmpp_getstatus(); // Preserve current status
+    return NULL;
+  }
+
+  return cmdarg_check_string2enum (arg);
+}
+
+static cmdarg_type_t cmdarg_type_status_status = {
+  cmdarg_check_status_status,
+  NULL,
+  NULL,
+};
+
+// command
+
+cmdopts_t def_status = {
+  "status",
+  NULL,
+  do_status,
+  NULL,
+  {
+    {"status", cmdarg_default, NULL, cmdarg_type_status_status, (gpointer)s2e_status2},
+    {"message", cmdarg_eol, NULL, cmdarg_type_nonspace},
+    NULL,
+  },
+  NULL,
+};
+
+static gchar *do_status(cmdopts_t *options)
+{
+  if (options -> args[0] -> value.uint == imstatus_size) {
     const char *sm = xmpp_getstatusmsg();
     scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
                  imstatus2char[xmpp_getstatus()],
                  (sm ? sm : ""));
+  } else {
+    if (!xmpp_is_online())
+      scr_LogPrint(LPRINT_NORMAL, "You are currently not connected...");
+    scr_check_auto_away(TRUE);
+    xmpp_setstatus(options -> args[0] -> value.uint, NULL,
+                   options -> args[1] -> value.arg, FALSE);
+  }
+  return NULL;
+}
+
+//
+//  /status_to
+//
+
+// no "show" here
+static string2enum_t s2e_status[] = {
+  { "away",      away        },
+  { "offline",   offline     },
+  { "online",    available   },
+  { "avail",     available   },
+  { "notavail",  notavail    },
+  { "dnd",       dontdisturb },
+  { "free",      freeforchat },
+#ifdef WITH_DEPRECATED_STATUS_INVISIBLE
+  { "invisible", invisible   },
+#endif
+  { NULL,        0           },
+};
+
+cmdopts_t def_status_to = {
+  "status_to",
+  NULL,
+  do_status_to,
+  NULL,
+  {
+    {"jid", cmdarg_chreq, NULL, cmdarg_type_fjid},
+    {"status", cmdarg_chreq, NULL, cmdarg_type_status_status, (string2enum_t)s2e_status},
+    {"message", cmdarg_eol,   NULL, cmdarg_type_nonspace},
+    NULL,
+  },
+  NULL,
+};
+
+static gchar *do_status_to(cmdopts_t *options)
+{
+  const char *fjid, *stname, *msg;
+  enum imstatus st;
+  gsize i;
+
+  fjid = options -> args[0] -> value.arg;
+  st   = options -> args[1] -> value.uint;
+  msg  = options -> args[2] -> value.arg;
+
+  for (i = 0; s2e_status[i] -> name != NULL; i++) {
+    if (s2e_status[i] -> value == st) {
+      stname = s2e_status[i] -> name;
+      break;
+    }
+  }
+  msg = msg ? msg : "";
+
+  scr_LogPrint(LPRINT_LOGNORM, 
+               "Sending to <%s> /status %s %s", fjid, stname, msg);
+  if (!xmpp_is_online())
+    scr_LogPrint(LPRINT_NORMAL, "You are currently not connected...");
+  xmpp_setstatus(st, fjid, msg, FALSE);
+
+  return NULL
+}
+  
+#if 0
+static void do_add(char *arg)
+{
+  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;
   }
-  arg = to_utf8(arg);
-  cmd_setstatus(NULL, arg);
-  g_free(arg);
-}
-
-static void do_status_to(char *arg)
-{
-  char **paramlst;
-  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;
-  }
-
-  // Allow things like /status_to "" away
-  if (!*fjid || !strcmp(fjid, "."))
-    fjid = NULL;
-
-  if (fjid) {
+
+  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(fjid)) {
-      scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8,
-                   "<%s> is not a valid Jabber ID.", fjid);
-      fjid = NULL;
+    if (check_jid_syntax(jid)) {
+      scr_LogPrint(LPRINT_NORMAL, "<%s> is not a valid Jabber ID.", jid);
+      jid = 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);
+      mc_strtolower(jid);
     }
   } else {
     // Add the current buddy
     if (current_buddy)
-      fjid = (char*)buddy_getjid(BUDDATA(current_buddy));
-    if (!fjid)
+      jid = (char*)buddy_getjid(BUDDATA(current_buddy));
+    if (!jid)
       scr_LogPrint(LPRINT_NORMAL, "Please specify a Jabber ID.");
   }
 
-  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);
+  if (jid) {
+    // 2nd parameter = optional nickname
+    xmpp_addbuddy(jid, nick, NULL);
+    scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
+                 jid);
   }
-  free_arg_lst(paramlst);
+
+  cmdopts_free(&options);
 }
 
-static void do_add(char *arg)
+static void do_del(char *arg)
 {
-  char **paramlst;
-  char *id, *nick;
-  char *jid_utf8 = NULL;
+  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;
   }
 
-  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;
+
+  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);
+      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 {
-    // Add the current buddy
+    // Use 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.");
+    else
+      buddy = BUDDATA(current_buddy);
   }
 
-  if (nick)
-    nick = to_utf8(nick);
-
-  if (id) {
-    // 2nd parameter = optional nickname
-    xmpp_addbuddy(id, nick, NULL);
-    scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
-                 id);
+  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();
+    }
   }
 
-  g_free(jid_utf8);
-  g_free(nick);
-  free_arg_lst(paramlst);
+  cmdopts_free(&options);
 }
 
-static void do_del(char *arg)
+#endif
+
+static void group_cmd (gpointer group, group_scmd_t action) 
 {
-  const char *bjid;
-
-  if (*arg) {
-    scr_LogPrint(LPRINT_NORMAL, "This action does not require a parameter; "
-                 "the currently-selected buddy will be deleted.");
-    return;
-  }
+  // We'll have to redraw the chat window if we're not currently on the group
+  // entry itself, because it means we'll have to leave the current buddy
+  // chat window.
+  if (action != scmd_group_unfold &&
+      ((!current_buddy) ||
+       (group != BUDDATA(current_buddy) &&
+        group == buddy_getgroup(BUDDATA(current_buddy)))))
+    scr_roster_prev_group();
+
+  buddy_hide_group(group, action);
+
+  buddylist_build();
+  update_roster = TRUE;
+}
+
+#if 0
+
+static void do_group(char *arg)
+{
+  scmd_group_t 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 {
@@ -1253,31 +1943,19 @@
     goto do_group_return;
   }
 
-  // We'll have to redraw the chat window if we're not currently on the group
-  // entry itself, because it means we'll have to leave the current buddy
-  // chat window.
-  leave_buddywindow = (group != BUDDATA(current_buddy) &&
-                       group == buddy_getgroup(BUDDATA(current_buddy)));
-
   if (!(buddy_gettype(group) & ROSTER_TYPE_GROUP)) {
     scr_LogPrint(LPRINT_NORMAL, "You need to select a group.");
     goto do_group_return;
   }
 
-  if (group_state != group_unfold && leave_buddywindow)
-    scr_roster_prev_group();
-
-  buddy_hide_group(group, group_state);
-
-  buddylist_build();
-  update_roster = TRUE;
+  group_cmd (group, subcmd);
 
 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 +1963,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 +1978,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 +2037,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 +2060,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 +2085,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 +2220,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 +2276,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 +2344,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 +2458,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 +2470,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 +2757,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 +2790,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 +2811,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 +2860,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 +2930,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 +2951,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 +3073,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 +3082,7 @@
     }
   }
 
-  g_free(group_utf8);
-  g_free(newgroupname);
+  cmdopts_free(&options);
   update_roster = TRUE;
 }
 
@@ -2468,50 +3264,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 +3653,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);
@@ -3290,6 +4069,207 @@
 
 static void do_room(char *arg)
 {
+  enum room_scmd_t {
+    room_scmd_join, room_scmd_leave,
+    room_scmd_names,
+    room_scmd_nick,
+    room_scmd_privmsg,
+    room_scmd_remove,
+    room_scmd_topic,
+    room_scmd_unlock,
+    room_scmd_destroy,
+    room_scmd_whois,
+    room_scmd_ban, room_scmd_unban,
+    room_scmd_invite, room_scmd_kick,
+    room_scmd_role, room_scmd_affil,
+    room_scmd_setopt, room_scmd_bookmark,
+  } subcmd;
+  cmdopts_t options = {
+    "room",
+    (cmdopt_t[1]){
+      { CMDOPT_LAST, 'j', "jid", { .opt = NULL } },
+    },
+    (cmdarg_t[1]){
+      { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+    },
+    (cmdopts_t[18]){
+      { "join", NULL,
+        (cmdarg_t[3]){
+          { 0,           { .arg = "." } },  // room jid
+          { 0,           { .arg = NULL } }, // nick
+          { CMDOPT_LAST, { .arg = NULL } }, // password
+        },
+        NULL, (gpointer)room_scmd_join, 0 },
+      { "leave", NULL,
+        (cmdarg_t[1]){
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_join, 0 },
+      { "names",
+        (cmdopt_t[4]){
+          { CMDOPT_SWITCH,               'd', "detail",  { .swc = 0 } },
+          { CMDOPT_SWITCH,               's', "short",   { .swc = 0 } },
+          { CMDOPT_SWITCH,               'q', "quiet",   { .swc = 0 } },
+          { CMDOPT_SWITCH | CMDOPT_LAST, 'c', "compact", { .swc = 0 } },
+        },
+        NULL, NULL, (gpointe)room_scmd_names, 0 },
+      { "nick", NULL,
+        (cmdarg_t[1]){
+          // nick
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_nick, 0 },
+      { "privmsg", NULL,
+        (cmdarg_t[2]) {
+          // nick
+          { CMDOPT_REQUIRED, { .arg = NULL } },
+          // message
+          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
+            { .arg = NULL } },
+        }
+        NULL, (gpointer)room_scmd_privmsg, 0 },
+      { "remove", NULL, NULL, NULL, (gpointer)room_scmd_remove, 0 },
+      { "topic", NULL,
+        (cmdarg_t[1]){
+          // topic
+          { CMDOPT_REQUIRED | CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST,
+            { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_topic, 0 },
+      { "unlock", NULL, NULL, NULL, (gpointer)room_scmd_unlock, 0 },
+      { "destroy", NULL,
+        (cmdarg_t[1]){
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_destroy, 0 },
+      { "whois", NULL,
+        (cmdarg_t[1]){
+          // nick
+          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
+        }, NULL, (gpointer)room_scmd_whois, 0 },
+      { "ban", NULL,
+        (cmdarg_t[2]){
+          // nick
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        }, NULL, (gpointer)room_scmd_ban, 0 },
+      { "unban", NULL,
+        (cmdarg_t[1]){
+          // nick
+          { CMDOPT_REQUIRED | CMDOPT_LAST, { .arg = NULL } },
+        }, NULL, (gpointer)room_scmd_unban, 0 },
+      { "invite", NULL, // XXX [-d|direct] ?
+        (cmdarg_t[2]){
+          // jid
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_invite, 0 },
+      { "kick", NULL,
+        (cmdarg_t[2]){
+          // jid
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        },
+        NULL, (gpointer)room_scmd_kick, 0 },
+      { "role", NULL,
+        (cmdarg_t[3]){
+          // jid
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // role
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        }, NULL, (gpointer)room_scmd_role, 0 },
+      { "affil", NULL,
+        (cmdarg_t[3]){
+          // jid
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // affiliation
+          { CMDOPT_REQUIRED,                              { .arg = NULL } },
+          // reason
+          { CMDOPT_CATCHALL | CMDOPT_PLAIN | CMDOPT_LAST, { .arg = NULL } },
+        }, NULL, (gpointer)room_scmd_affil, 0 },
+      // XXX below goes some crazy subcommands stuff, that should be rather static lists
+      { "setopt", NULL,
+        (cmdarg_t[1]){
+          { CMDOPT_REQUIRED | CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+        },
+        (cmdopts_t[3]){
+          { "print_status", NULL,
+            (cmdarg_t[1]){
+              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+            },
+            (cmdopts_t[4]){
+              { "default",    NULL, NULL, NULL, NULL, 0 },
+              { "none",       NULL, NULL, NULL, NULL, 0 },
+              { "in_and_out", NULL, NULL, NULL, NULL, 0 },
+              { "all",        NULL, NULL, NULL, NULL, CMDOPT_LAST },
+            }, NULL, 0 },
+          { "auto_whois", NULL,
+            (cmdarg_t[1]){
+              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+            },
+            (cmdopts_t[3]){
+              { "default", NULL, NULL, NULL, NULL, 0 },
+              { "off",     NULL, NULL, NULL, NULL, 0 },
+              { "on",      NULL, NULL, NULL, NULL, CMDOPT_LAST },
+            }, NULL, 0 },
+          { "flag_joins", NULL,
+            (cmdarg_t[1]){
+              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+            },
+            (cmdopts_t[4]){
+              { "default", NULL, NULL, NULL, NULL, 0 },
+              { "none",    NULL, NULL, NULL, NULL, 0 },
+              { "joins",   NULL, NULL, NULL, NULL, 0 },
+              { "all",     NULL, NULL, NULL, NULL, CMDOPT_LAST },
+            }, NULL, CMDOPT_LAST },
+        }, (gpointer)room_scmd_setopt, 0 },
+      { "bookmark", NULL,
+        (cmdarg_t[1]){
+          { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+        },
+        (cmdopts_t[2]){
+          { "add", NULL,
+            (cmdarg_t[1]){
+              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+            },
+            (cmdopts_t[2]){
+              { "+autojoin", NULL,
+                (cmdarg_t[1]){
+                  // nick
+                  { CMDARG_LAST, { .arg = NULL } },
+                }, NULL, NULL, 0 },
+              { "-autojoin", NULL,
+                (cmdarg_t[1]){
+                  // nick
+                  { CMDARG_LAST, { .arg = NULL } },
+                }, NULL, NULL, CMDOPT_LAST },
+            }, NULL, 0 },
+          { "del", NULL,
+            (cmdarg_t[1]){
+              { CMDOPT_SUBCOMMAND | CMDOPT_LAST, { .cmd = NULL } },
+            },
+            (cmdopts_t[2]){
+              { "+autojoin", NULL,
+                (cmdarg_t[1]){
+                  // nick
+                  { CMDARG_LAST, { .arg = NULL } },
+                }, NULL, NULL, 0 },
+              { "-autojoin", NULL,
+                (cmdarg_t[1]){
+                  // nick
+                  { CMDARG_LAST, { .arg = NULL } },
+                }, NULL, NULL, CMDOPT_LAST },
+            }, NULL, 0 },
+        }, (gpointer)room_scmd_bookmark, CMDOPT_LAST },
+    },
+  };
   char **paramlst;
   char *subcmd;
   gpointer bud;
@@ -3347,7 +4327,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);
@@ -4162,5 +5142,6 @@
   }
   mcabber_set_terminate_ui();
 }
+#endif
 
 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
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	Mon Mar 11 01:32:27 2013 +0200
@@ -5,32 +5,214 @@
 
 #include <mcabber/config.h>
 
-// Command structure
-typedef struct {
-  char name[32];
-  const char *help;
-  guint completion_flags[2];
-  void (*func)(char *);
-  gpointer userdata;
-} cmd;
+//
+//  TODO:
+//
+// * Clustering of options/switches
+// * Multivalue options
+//   - list
+//     - special checker
+//     - or run checker multiple times
+//   - or run command several times
+//     - what if there are several of such arguments?
+// * --help
+//   - use argument type names and clarify, if needed in descriptions
+//   - command/subcommand descriptions
+//   - 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, check symbols)
+//     - jidmask (non-restrictive, compl: inform what jids match)
+//     - search string (non-restrictive, compl: inform what matches, if possible)
+//     - rosternote (non-restrictive)
+//     - 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 (compl: nicks in rooms, dictionary?)
+// * Non-argument checks for commands/subcommands:
+//   - xmpp_is_online()
+//   - current_buddy
+// * Subcommands with fallback to argument?
+// * [+foo|-foo] support?
+// * Usable subsystem for completion, based on user-supplied completors
 
-void cmd_init(void);
-cmd *cmd_get(const char *command);
-int  process_line(const char *line);
-int  process_command(const char *line, guint iscmd);
-char *expandalias(const char *line);
-#ifdef MODULES_ENABLE
-gpointer cmd_del(gpointer id);
-gpointer cmd_add(const char *name, const char *help, guint flags1, guint flags2,
-                 void (*f)(char*), gpointer userdata);
-gboolean cmd_set_safe(const gchar *name, gboolean safe);
+// * for now we'll do without multi-options.
+// * and without clustering.
+// * checks
+//   - argument type == public struct with name and callbacks, simple!
+//   - command checks should be just single callbacks
+// * now, what checker needs?
+//   - [in] value string (current or default)
+//   - [in] additional input data for some generic checkers (like string2enum wordlist)
+//   - [out] replacement value (not necessary string)
+//     - replacement value may need freeing
+//   - [out] error string
+//     - that may need freeing
+
+typedef struct cmdopts_struct      cmdopts_t;
+typedef struct cmdopt_struct       cmdopt_t;
+typedef struct cmdarg_struct       cmdarg_t;
+typedef struct cmdarg_value_struct cmdarg_value_t;
+typedef struct cmdarg_type_struct  cmdarg_type_t;
+
+// note, this is called before options are parsed!
+typedef gchar *(*cmd_checker_t)(cmdopts_t *opts);
+// command function itself
+typedef gchar *(*cmd_handler_t)(cmdopts_t *opts);
+// should check arg -> val -> val.arg and replace, if needed
+typedef gchar *(*cmdarg_checker_t)(cmdarg_t *arg);
+// free resources, used in arg -> val -> val.arg, if needed
+typedef void (*cmdarg_destructor_t)(cmdarg_t *arg);
+// todo
+typedef void (*cmdarg_completor_t)(void); // FIXME
+
+typedef union {
+  guint       uint;        // unsigned integer
+  gint        sint;        // signed integer
+  guint       swc;         // switch count
+  const gchar *roarg;      // default value
+  gchar       *arg;        // string argument
+  cmdopts_t   *cmd;        // subcommand
+  gpointer    bud;         // buddy data (roster entry)
+  gpointer    ptr;         // anything else
+} cmdarg_value_t;
+
+typedef enum {
+  cmd_default     = 0,     // no flags
+  cmd_safe        = 1<<0,  // command is safe to use in mcabberrc
+} cmd_flags_t;
+typedef enum {
+  cmdopt_default  = 0,     // no flags
+  cmdopt_switch   = 1<<0,  // option have no argument
+} cmdopt_flags_t;
+typedef enum {
+  cmdarg_default  = 0x0000, // no flags ['u' - user, 'p' - parser, 'c' - checker]
+  cmdarg_catchall = 0x0001, // u2p, argument consumes the end of command line
+  cmdarg_plain    = 0x0002, // u2p, quotes and escapes are not processed
+  cmdarg_subcmd   = 0x0004, // u2p, argument is subcommand
+  cmdarg_check    = 0x0008, // u2p, force checker call on argument
+  cmdarg_required = 0x0010, // u2p[c], treat checker errors as errors or warnings (may be used by checker too)
+  cmdarg_visited  = 0x0020, // p2p, marks initialized arguments
+  cmdarg_checked  = 0x0040, // p2p, marks checked argument
+  cmdarg_freeme   = 0x0080, // c2p, marks argument, that needs freeing
+  // convenience shortcuts
+  cmdarg_eol      = 0x0003, // catchall + plain
+  cmdarg_chreq    = 0x0018, // check + required
+} cmdarg_flags_t;
+
+struct cmdopts_struct {
+  const char    *name;     // [user,req] command name (error messages, help, subcommands)
+  cmd_flags_t   flags;     // [user,req] safe
+  cmd_checker_t check;     // [user,req] checker routine
+  cmd_handler_t handle;    // [user,req] main command processing function
+  cmdopt_t      **opts;    // [user,req] options
+  cmdarg_t      **args;    // [user,req] arguments
+  cmdopts_t     **cmds;    // [user,req] subcommands
+  gpointer      userdata;  // [user]
+};
+struct cmdopt_struct {
+  cmdopt_flags_t flags;    // [user,req] switch
+  char           shortopt; // [user,req]
+  const char     *longopt; // [user,req]
+  cmdarg_t       arg;      // [user,req]
+};
+struct cmdarg_struct {
+  const char     *name;    // [user,req] argument name - errors, help
+  cmdarg_flags_t flags;    // [user,req] catchall, plain, required, subcommand
+  const char     *defval;  // [user,req] default value
+  cmdarg_type_t  *type;    // [user,req] type cbs - checker and completor
+  gpointer       chkdata;  // [user] instance data for type checker - eg string2enum list
+  gpointer       userdata; // [user]
+  cmdarg_value_t value;    // [parser,chk] current value
+};
+
+struct cmdarg_type_struct {
+  cmdarg_checker_t    check;    // [user,req] check string and set argument value
+  cmdarg_destructor_t free;     // [user,req] free argument value
+  cmdarg_completor_t  complete; // [user,req]
+};
+
+void cmd_define (cmdopts_t *command);
+void cmd_undef (cmdopts_t *command);
+
+//  error cmdopts_parse_argument ( startptr, endptr, flags )
+// Parses next argument according to flags and finishes it with zero.
+// Updates current/end pointers. Parsed string MUST be writable.
+// String may shrink in size (quotation/escapes), thus endpointer is also
+// updated.
+const char *cmdopts_parse_argument(gchar **pr, gchar *er, cmdarg_flags_t flags);
+
+typedef enum {
+  cmdexe_default        = 0,    // no flags
+  cmdexe_check_safe     = 1<<0, // we're parsing main config file
+  cmdexe_check_verbatim = 1<<1, // check for verbatim input mode
+} cmdexe_flags_t;
+typedef enum {
+  cmd_result_ok   = 0,       // all is ok
+  cmd_result_syntax_error,   // syntax, environment or argument check error
+  cmd_result_not_found,      // no such command
+  cmd_result_runtime_error,  // error while executing command
+  cmd_result_verbatim,       // we're in verbatim mode and this is not "msay"
+  cmd_result_input,          // [process_line only] line was a message
+  cmd_result_quit = 255,     // this is hard-coded "quit" command
+} cmd_result_t;
+
+//  error cmd_execute ( command, flags )
+// Checks for "quit", "msay" in verbatim mode, expands aliases, checks command
+// for safety of execution, parses command's arguments according to definition,
+// executes command function. Returns cmdexe_result_ok (0) if all was ok.
+// Non-zero values mean, that command was not executed for some reason.
+// One special value is cmd_result_quit (255) - if it is "quit" command.
+cmd_result_t cmd_execute (gchar *commandline, cmdexe_flags_t flags);
+
+//  process_line(line)
+// Process a command/message line. If this isn't a command, this is a message
+// and it is sent to the currently selected buddy.
+// Returns 255 if the line is the /quit command, 0 on success and some other
+// error codes.
+cmd_result_t process_line(const char *line);
+
+//
+//  Private
+//
+
+void cmd_init (void);
+void cmd_uninit (void);
+
+#if 0
+//  error cmdopts_parse_internal ( startptr, endptr, commanddef )
+// Parses command arguments according to command definition.
+// Parsed string MUST be writable. Regardless of success or error, input
+// string should be considered corrupted by parsing process.
+// Even in case of error, commanddef should be passed to cmdopts_free().
+gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command);
+
+//  cmdopts_free ( commanddef )
+// Free various parser data, used in parsing process
+void cmdopts_free(cmdopts_t *command);
 #endif
-gboolean cmd_is_safe(const gchar *name);
+
+typedef enum {
+  msgtype_not_set,
+  msgtype_normal,
+  msgtype_headline,
+} msgtype_t;
 
 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	Mon Mar 11 01:32:27 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/screen.c
--- a/mcabber/mcabber/screen.c	Sun Jan 27 00:40:37 2013 +0200
+++ b/mcabber/mcabber/screen.c	Mon Mar 11 01:32:27 2013 +0200
@@ -3626,7 +3626,7 @@
 {
   scr_check_auto_away(TRUE);
   last_activity_buddy = current_buddy;
-  if (process_line(inputLine))
+  if (process_line(inputLine) == cmd_result_quit)
     return 255;
   // Add line to history
   scr_cmdhisto_addline(inputLine);
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	Mon Mar 11 01:32:27 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");