[cmdopts] Dynamically-allocated values
authorMyhailo Danylenko <isbear@ukrpost.net>
Mon, 18 Mar 2013 02:26:50 +0200
changeset 79 07e696e91b6f
parent 78 ee30584c654b
child 80 93088d0c8140
[cmdopts] Dynamically-allocated values
cmdopts.diff
--- a/cmdopts.diff	Wed Mar 13 17:54:09 2013 +0200
+++ b/cmdopts.diff	Mon Mar 18 02:26:50 2013 +0200
@@ -2,40 +2,44 @@
 # Parent 1b0b563a81e6425da07c1da7ea4f947c4f3326cb
 [work-in-progress] Unified command option parsing
 
-  * wrecking chaos all over the place
-  * started integration with cmd subsystem
-  * broke everything
+  * complete change of commands.h interface
+    - uses different symbols to make compatibility layer possible
+  * completion system is broken now
+  * usable commands:
+    - roster
+    - color
+    - status/status_to
+    - add/del
+    - group
+    - say
+  * it does compile, but have not tested at all
+  * privatized say_cmd()
+  * dropped cmd_setstatus()
+  * process_line() still expects line to be in local encoding,
+    while cmd_execute() expects utf8 and rw
+  * process_line() can return error values, different from 255
 
   ** PREVIOUS ACHIEVEMENTS **
 
-  * cmdopts_parse() & cmdopts_free() in utils.c/h
-  * /roster uses parser
+  * function interface changes:
     * 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
+    * say_cmd()'s second argument is now of new type msgtype_t
+    * scr_multi* now store multiline in utf8
+  * /del:
     * 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
+  * /rename:
     * -r(--reset) instead of '-'
     * -j(--jid), -g(--group), -n(--name)
-  * /move uses parser
+  * /move:
     * -j(--jid), -n(--name)
-  * /rawxml uses parser
+  * misc:
+    * fix help for /buffer date
 
 diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_buffer.txt
 --- a/mcabber/doc/help/cs/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Přesune se o [n] řádků nahoru (výchozí: polovina obrazovky).
  /buffer down [n]
@@ -47,7 +51,7 @@
   Přesune se na procentuální pozici n%.
 diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_del.txt
 --- a/mcabber/doc/help/cs/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -56,7 +60,7 @@
  Smaže aktuální kontakt ze seznamu kontaktů (rosteru) a zruší povolení oznamování o stavu daného kontaktu (autorizaci) na obou stranách.
 diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_move.txt
 --- a/mcabber/doc/help/cs/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [skupina]
@@ -67,7 +71,7 @@
  Tip: V módu rozhovoru lze použít "/roster alternate" pro skok na přesunutý kontakt.
 diff -r 1b0b563a81e6 mcabber/doc/help/cs/hlp_rename.txt
 --- a/mcabber/doc/help/cs/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/cs/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/cs/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME jméno
@@ -79,7 +83,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_buffer.txt
 --- a/mcabber/doc/help/de/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/de/hlp_buffer.txt	Mon Mar 18 02:16:22 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]
@@ -91,7 +95,7 @@
   Springe zur Position "n" im Chatpuffer
 diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_del.txt
 --- a/mcabber/doc/help/de/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/de/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -100,7 +104,7 @@
  Löscht den gerade ausgewählten Buddy vom Roster. Außerdem werden die automatischen Presence Benachrichtigungen vom/zum Buddy gestoppt.
 diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_move.txt
 --- a/mcabber/doc/help/de/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/de/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,6 +1,7 @@
  
 - /MOVE [groupname]
@@ -112,7 +116,7 @@
  Tipp: Wenn der Chatmodus aktiviert ist, kannst du "/roster alternate" benutzen um zu dem gerade bewegten Buddy zu springen.
 diff -r 1b0b563a81e6 mcabber/doc/help/de/hlp_rename.txt
 --- a/mcabber/doc/help/de/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/de/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/de/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -124,7 +128,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_buffer.txt
 --- a/mcabber/doc/help/en/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/en/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Scroll the buffer up [n] lines (default: half a screen)
  /buffer down [n]
@@ -136,7 +140,7 @@
   Jump to position %n of the buddy chat buffer
 diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_del.txt
 --- a/mcabber/doc/help/en/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/en/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -146,7 +150,7 @@
 +Delete the current buddy or one, specified with [jid] from our roster, unsubscribe from its presence notification and unsubscribe it from ours.
 diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_move.txt
 --- a/mcabber/doc/help/en/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/en/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groupname]
@@ -157,7 +161,7 @@
  Tip: if the chatmode is enabled, you can use "/roster alternate" to jump to the moved buddy.
 diff -r 1b0b563a81e6 mcabber/doc/help/en/hlp_rename.txt
 --- a/mcabber/doc/help/en/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/en/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/en/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -169,7 +173,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_buffer.txt
 --- a/mcabber/doc/help/fr/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Défile vers le haut de [n] lignes (par défaut un demi écran)
  /buffer down [n]
@@ -181,7 +185,7 @@
   Va à la position n% du tampon
 diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_del.txt
 --- a/mcabber/doc/help/fr/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -190,7 +194,7 @@
  Supprime le contact sélectionné du roster, supprime notre abonnement à ses notifications de présence et supprime son abonnement aux nôtres.
 diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_move.txt
 --- a/mcabber/doc/help/fr/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groupname]
@@ -201,7 +205,7 @@
  Astuce : si le mode discussion (chatmode) est activé, vous pouvez utiliser "/roster alternate" pour vous positionner sur le contact que vous venez de déplacer.
 diff -r 1b0b563a81e6 mcabber/doc/help/fr/hlp_rename.txt
 --- a/mcabber/doc/help/fr/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/fr/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/fr/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nom
@@ -213,7 +217,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_buffer.txt
 --- a/mcabber/doc/help/it/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/it/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Fa scorrere indietro il buffer di [n] linee (default: metà schermo)
  /buffer down [n]
@@ -225,7 +229,7 @@
   Salta alla posizione %n del buffer di chat corrente
 diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_del.txt
 --- a/mcabber/doc/help/it/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/it/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -234,7 +238,7 @@
  Elimina il contatto corrente dal roster, cancellando la sottoscrizione alle reciproche notifiche della propria presenza.
 diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_move.txt
 --- a/mcabber/doc/help/it/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/it/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [gruppo]
@@ -245,7 +249,7 @@
  Trucco: se la modalità chat è abilitata, puoi usare "/roster alternate" per spostarti sul contatto appena mosso.
 diff -r 1b0b563a81e6 mcabber/doc/help/it/hlp_rename.txt
 --- a/mcabber/doc/help/it/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/it/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/it/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nome
@@ -257,7 +261,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_buffer.txt
 --- a/mcabber/doc/help/nl/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Scroll de buffer [n] regels omhoog (standaard: een half scherm)
  /buffer down [n]
@@ -269,7 +273,7 @@
   Spring naar positie %n in de buddy chat buffer
 diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_del.txt
 --- a/mcabber/doc/help/nl/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -278,7 +282,7 @@
  Verwijder de actieve buddy uit ons roster, en zet het wederzijds toezenden van status veranderingen stop.
 diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_move.txt
 --- a/mcabber/doc/help/nl/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [groepsnaam]
@@ -289,7 +293,7 @@
  Tip: indien chatmode actief is, kun je "/roster alternate" gebruiken om direct naar de verplaatste buddy te springen.
 diff -r 1b0b563a81e6 mcabber/doc/help/nl/hlp_rename.txt
 --- a/mcabber/doc/help/nl/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/nl/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/nl/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME naam
@@ -301,7 +305,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_del.txt
 --- a/mcabber/doc/help/pl/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -310,7 +314,7 @@
  Usuwa aktualnie zaznaczoną osobę z rostera, usuwa subskrypcję powiadomienia dostępności u danej osoby oraz u nas.
 diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_move.txt
 --- a/mcabber/doc/help/pl/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [nazwa grupy]
@@ -321,7 +325,7 @@
  Podpowiedź: jeśli jest włączony tryb czatu, możesz użyć "/roster alternate" aby skoczyć do przeniesionej osoby.
 diff -r 1b0b563a81e6 mcabber/doc/help/pl/hlp_rename.txt
 --- a/mcabber/doc/help/pl/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/pl/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/pl/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME nazwa
@@ -333,7 +337,7 @@
 +Optionally you can use one of --jid, --group or --name to select object, different from current.
 diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_buffer.txt
 --- a/mcabber/doc/help/ru/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Перемещает на [n] строк вверх в буфере (истории переписки) (по умолчанию: половина экрана)
  /buffer down [n]
@@ -345,7 +349,7 @@
   Перемещает на позицию %n в текущем буфере (истории переписки)
 diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_del.txt
 --- a/mcabber/doc/help/ru/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -355,7 +359,7 @@
 +Удаляет текущего пользователя (или указанного с помощью jid) из списка контактов, отключает уведомления о его статусе и отключает уведомление пользователя о вашем статусе.
 diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_move.txt
 --- a/mcabber/doc/help/ru/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,6 +1,7 @@
  
 - /MOVE [groupname]
@@ -367,7 +371,7 @@
  
 diff -r 1b0b563a81e6 mcabber/doc/help/ru/hlp_rename.txt
 --- a/mcabber/doc/help/ru/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/ru/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/ru/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME name
@@ -379,7 +383,7 @@
 +Для указания обьекта, отличного от текущего, можно использовать опции --jid, --group и --name.
 diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_buffer.txt
 --- a/mcabber/doc/help/uk/hlp_buffer.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_buffer.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_buffer.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -25,7 +25,7 @@
   Посунути буфер вверх на n рядків (якщо не вказано - пів екрану).
  /buffer down [n]
@@ -391,7 +395,7 @@
   Перейти до вказаної у процентах позиції.
 diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_del.txt
 --- a/mcabber/doc/help/uk/hlp_del.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_del.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_del.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,4 @@
  
 - /DEL
@@ -401,7 +405,7 @@
 +Потерти поточний контакт (або контакт, що має вказаний jid) зі списку. Також відписатися від його сповіщень про статус і відписати його від ваших.
 diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_move.txt
 --- a/mcabber/doc/help/uk/hlp_move.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_move.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_move.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,5 +1,6 @@
  
 - /MOVE [група]
@@ -413,7 +417,7 @@
  Примітка: в режимі розмови можна використати "/roster alternate", щоб перейти до нового місця контакту контакту.
 diff -r 1b0b563a81e6 mcabber/doc/help/uk/hlp_rename.txt
 --- a/mcabber/doc/help/uk/hlp_rename.txt	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/doc/help/uk/hlp_rename.txt	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/doc/help/uk/hlp_rename.txt	Mon Mar 18 02:16:22 2013 +0200
 @@ -1,4 +1,6 @@
  
 - /RENAME ім'я
@@ -424,7 +428,7 @@
 +Опції --jid, --group та --name дозволяють перейменовувати об’єкти, відмінні від поточного.
 diff -r 1b0b563a81e6 mcabber/mcabber/commands.c
 --- a/mcabber/mcabber/commands.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/commands.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/commands.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -19,7 +19,7 @@
   * USA
   */
@@ -434,7 +438,7 @@
  #include <stdlib.h>
  #include <sys/types.h>
  #include <sys/stat.h>
-@@ -43,512 +43,631 @@
+@@ -43,512 +43,671 @@
  #include "xmpp.h"
  #include "main.h"
  
@@ -457,21 +461,27 @@
 +  scmd_group_fold   = 1,
 +  scmd_group_toggle = -1,
 +} scmd_group_t;
++typedef enum {
++  msgtype_not_set,
++  msgtype_normal,
++  msgtype_headline,
++} msgtype_t;
 +
 +static void group_cmd (gpointer group, scmd_group_t action);
++static void say_cmd (char *arg, msgtype_t msgtype);
 +
 +//static void room_bookmark(gpointer bud, char *arg);
 +
-+#define BUILTIN_COUNT 4
++#define BUILTIN_COUNT 8
 +static cmdopts_t def_roster,
 +                 def_color,
 +                 def_status,
 +                 def_status_to,
 +                 def_add,
 +                 def_del,
-+                 def_group;
++                 def_group,
++                 def_say;
 +#if 0
-+                 def_say,
 +                 def_msay,
 +                 def_say_to,
 +                 def_buffer,
@@ -586,8 +596,8 @@
 +  cmd_list[4]  = &def_add;
 +  cmd_list[5]  = &def_del;
 +  cmd_list[6]  = &def_group;
++  cmd_list[7]  = &def_say;
 +#if 0
-+  cmd_list[7]  = &def_say;
 +  cmd_list[8]  = &def_msay;
 +  cmd_list[9]  = &def_say_to;
 +  cmd_list[10] = &def_buffer;
@@ -631,18 +641,51 @@
 +  cmd_count = 0;
 +}
 +
-+// XXX:
-+//  * if command is found, maybe still put it at the start of queue?
++static size_t cmdopts_count_values (cmdopts_t *command)
++{
++  size_t max = -1;
++  if (command -> opts) {
++    cmdopt_t *opt;
++    for (opt = command -> opts; opt -> shortopt != 0; opt ++)
++      if (opt -> arg.pos > max)
++        max = opt -> arg.pos;
++  }
++  if (command -> args) {
++    cmdarg_t *arg;
++    for (arg = command -> args; arg -> name != NULL; arg ++)
++      if (arg -> pos > max)
++        max = arg -> pos;
++  }
++  if (command -> cmds) {
++    cmdopts_t *scmd;
++    for (scmd = command -> cmds; scmd -> name != NULL; scmd ++) {
++      size_t cnt = cmdopts_count_values (scmd);
++      if (cnt > max)
++        max = cnt;
+     }
++  }
++  return max;
++}
++
 +void cmd_define (cmdopts_t *command)
 +{
-+  cmdopts_t **cmd;
-+  for (cmd = cmd_list; *cmd != NULL; cmd ++)
-+    if (*cmd == command)
++  size_t n;
++  // we update value count even for existing commands - user may
++  // have screwed something up in struct.
++  command -> valno = cmdopts_count_values (command) + 1;
++  // check if command already exists
++  for (n = cmd_count; n > 0; n --)
++    if (cmd_list[n-1] == command) {
++      // raise it's priority
++      g_memmove(cmd_list+n-1, cmd_list+n, sizeof(cmdopts_t *) * (cmd_count-n-1));
++      cmd_list[cmd_count-1] = command;
 +      return;
++    }
++  // register new command
 +  cmd_list = g_renew (cmdopts_t *, cmd_list, cmd_count+2);
-+  cmd_list[cmd_count] = command;
++  cmd_list[cmd_count]   = command;
++  cmd_list[cmd_count+1] = NULL;
 +  cmd_count ++;
-+  cmd_list[cmd_count] = NULL;
 +}
 +
 +void cmd_undef (cmdopts_t *command)
@@ -654,17 +697,10 @@
 +      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.
@@ -672,18 +708,7 @@
 +// 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;
@@ -723,28 +748,35 @@
 +  *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)
++}
++
++static cmdarg_value_t *cmdopts_allocate_values (cmdopts_t *command)
++{
++  cmdarg_value_t *values;
++  size_t         n = command -> valno;
++
++  if (n == 0)
++    return NULL;
++
++  values = g_slice_alloc (sizeof(cmdarg_value_t) * n);
++
++  while (n > 0) {
++    n --;
++    values[n].src       = NULL;
++    values[n].flags     = cmdval_default;
++    values[n].value.arg = NULL;
++  }
++
++  return values;
++}
++
 +//  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);
++static gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command, cmdarg_value_t *values)
++{
 +  enum {             // Parser state transitions:
 +    in_space,        // -> in_space, in_optstart, in_argstart
 +    in_optstart,     // -> in_shortoptend, in_longoptstart, in_argstart ("-")
@@ -761,43 +793,46 @@
 +  size_t   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].shortopt != 0; n ++) {
-+        cmdopt_t *opt = command -> opts + n;
-+        if (opt -> flags & cmdopt_switch) {
-+          opt -> arg.value.swc = 0;
-+        } else {
-+          opt -> arg.value.roarg = opt -> arg.defval;
++        cmdopt_t       *opt = command -> opts + n;
++        cmdarg_value_t *val = values + opt -> arg.pos;
++        // do not overwrite already specified values
++        if (val -> src == NULL || !(val -> flags & cmdval_visited)) {
++          if (opt -> arg.flags & cmdarg_switch) {
++            val -> value.swc = 0;
++          } else {
++            val -> value.roarg = opt -> arg.defval; // XXX ro
++          }
++          val -> src = &(opt -> arg);
 +        }
-+        opt -> arg.flags &= ~(cmdarg_ppclear);
-       }
-     }
--  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].name != NULL; n ++) {
-+        cmdarg_t *arg = command -> args + n; 
-+        arg -> value.roarg = arg -> defval;
-+        arg -> flags  &= ~(cmdarg_ppclear);
++        cmdarg_t       *arg = command -> args + n;
++        cmdarg_value_t *val = values + arg -> pos;
++        // do not overwrite already specified values
++        if (val -> src == NULL || !(val -> flags & cmdval_visited)) {
++          val -> value.roarg = arg -> defval; // XXX ro
++          val -> src         = arg;
++        }
 +      }
 +    }
 +  }
 +
++  // general environment checking
++  if (command -> check) {
++    if ((error = command -> check (command, values))) {
++      gchar *err = error;
++      error = g_strdup_printf("%s: %s", command -> name, err);
++      g_free (err);
++    }
++  }
++
 +  // we allow parser to do one extra run on final '\0'
 +  while (p <= e && error == NULL) {
 +    if (state == in_space) { // space between args/options
@@ -836,8 +871,11 @@
 +          }
 +        }
 +        if (option != NULL) { // option is known
-+          if (option -> flags & cmdopt_switch) { // it is switch
-+            option -> arg.value.swc ++;
++          if (option -> arg.flags & cmdarg_switch) { // it is switch
++            cmdarg_value_t *val = values + option -> arg.pos;
++            val -> value.swc ++;
++            val -> src    = &(option -> arg);
++            val -> flags |= cmdval_visited;
 +            option = NULL;
 +          } else if (p == e) {
 +            error = g_strdup_printf ("%s: Option -%c needs an argument.", command -> name, option -> shortopt);
@@ -878,8 +916,11 @@
 +          }
 +        }
 +        if (option != NULL) { // option is known
-+          if (option -> flags & cmdopt_switch) { // it is switch
-+            option -> arg.value.swc ++;
++          if (option -> arg.flags & cmdarg_switch) { // it is switch
++            cmdarg_value_t *val = values + option -> arg.pos;
++            val -> value.swc ++;
++            val -> src    = &(option -> arg);
++            val -> flags |= cmdval_visited;
 +            option = NULL;
 +          } else if (p == e) {
 +            error = g_strdup_printf ("%s: Option --%s needs an argument.", command -> name, option -> longopt);
@@ -904,32 +945,30 @@
 +        arg = command -> args + argno;
 +      }
 +
-+      if ((err = cmdopts_parse_argument(&p, &e, arg -> flags))) { // get argument value
++      if ((err = cmdopts_parse_argument (&p, &e, arg -> flags))) { // get argument value
 +        if (!option) {
 +          error = g_strdup_printf ("%s: Can't parse argument %s (%lu): %s", command -> name, arg -> name, argno + 1, err);
-+        } else if (option -> shortopt) {
++        } else if (option -> longopt) {
++          error = g_strdup_printf ("%s: Can't parse argument for option --%s: %s", command -> name, option -> longopt, err);
++        } else {
 +          error = g_strdup_printf ("%s: Can't parse argument for option -%c: %s", command -> name, option -> shortopt, err);
-+        } else {
-+          error = g_strdup_printf ("%s: Can't parse argument for option --%s: %s", command -> name, option -> longopt, err);
 +        }
 +      } else {
-+        arg -> value.arg = s;
-+        arg -> flags |= cmdarg_visited;
++        cmdarg_value_t *val = values + arg -> pos;
++        val -> value.arg = s;
++        val -> src       = arg;
++        val -> flags    |= cmdval_visited;
 +        if (option) { // option argument
 +          option = NULL;
 +        } else { // normal argument
 +          if (arg -> flags & cmdarg_subcmd) { // subcommand
 +            cmdopts_t *subcmd;
-+            gsize     n; // XXX put command list into chkdata
-+            for (n = 0; command -> cmds[n].name != NULL; n ++) {
-+              if (!strcmp (s, command -> cmds[n].name)) {
-+                subcmd = command -> cmds + n;
++            for (subcmd = command -> cmds; subcmd -> name != NULL; subcmd ++)
++              if (!strcmp (s, subcmd -> name))
 +                break;
-+              }
-+            }
-+            if (subcmd != NULL) { // found subcommand
-+              arg -> value.cmd = subcmd;
-+              if ((error = cmdopts_parse_internal (&p, &e, subcmd))) {
++            if (subcmd -> name != NULL) { // found subcommand
++              val -> value.cmd = subcmd;
++              if ((error = cmdopts_parse_internal (&p, &e, subcmd, values))) {
 +                gchar *err = error;
 +                error = g_strdup_printf("%s %s", command -> name, err);
 +                g_free (err);
@@ -948,86 +987,232 @@
 +  *pr = p;
 +  *er = e;
 +
-+  // perform option argument checking
-+  if (error == NULL && command -> opts) {
-+    gsize n;
-+    for (n = 0; command -> opts[n].shortopt != 0; 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 -> longopt) {
-+                  error = g_strdup_printf ("%s: Error in argument for option --%s: %s", command -> name, opt -> longopt, err);
-+                } else {
-+                  error = g_strdup_printf ("%s: Error in argument for option -%c: %s", command -> name, opt -> shortopt, err);
-+                }
-+                g_free (err);
-+                break;
-+              } else {
-+                if (opt -> longopt) {
-+                  scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument for option --%s: %s", command -> name, opt -> longopt, err);
-+                } else {
-+                  scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument for option -%c: %s", command -> name, opt -> shortopt, err);
-+                }
-+                g_free (err);
-+              }
-+            }
++  return error;
++}
++
++// value types:      type               check                 required
++// - switch          no effect          toboolean()           no effect?
++// - option          check              call checker          fatal check error
++// - argument        check              call checker          fatal check error
++// - subcommand      no effect          check for required    fail if missing
++static gchar *cmdopts_check_values (cmdopts_t *command, cmdarg_value_t *values)
++{
++  size_t n = command -> valno;
++
++  if (n == 0)
++    return NULL;
++
++  // XXX reverse order?
++  while (n > 0) {
++    cmdarg_value_t *val = values + n - 1;
++    n --;
++    if ((val -> flags & cmdval_visited) || ((val -> src != NULL) && (val -> src -> flags & cmdarg_check))) {
++      if (val -> src -> flags & cmdarg_subcmd) { // subcommand - fail if required and missing
++        if ((val -> src -> flags & cmdarg_required) && (val -> value.cmd == NULL))
++          return g_strdup_printf ("%s: Not specified %s.", command -> name, val -> src -> name);
++      } else if (val -> src -> flags & cmdarg_switch) { // switch - toboolean if check
++        if (val -> src -> flags & cmdarg_check)
++          val -> value.swc %= 2;
++      } else if (val -> src -> type && val -> src -> type -> check) { // typed argument
++        gchar *err;
++        if ((err = val -> src -> type -> check (val))) {
++          // FIXME option name
++          if (val -> src -> flags & cmdarg_required) {
++            gchar *error = g_strdup_printf ("Error in argument value \"%s\": %s", val -> src -> name, err);
++            g_free (err);
++            return error;
++          } else {
++            scr_log_print (LPRINT_NORMAL, "Warning: %s: Error in argument value \"%s\": %s", command -> name, val -> src -> name, err);
++            g_free (err);
 +          }
 +        }
 +      }
 +    }
 +  }
 +
-+  // perform positional argument checking
-+  if (error == NULL && command -> args) {
-+    size_t n;
-+    for (n = 0; command -> args[n].name != NULL; 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 (%lu): %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 (%lu): %s", command -> name, arg -> name, n, err);
-+                g_free (err);
-+              }
-+            }
-+          }
-+        }
-+      }
+   return NULL;
+ }
+-#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)
++
++//  cmdopts_free ( commanddef )
++// Free various parser data, used in parsing process
++static void cmdopts_free_values (cmdopts_t *command, cmdarg_value_t *values)
+ {
+-  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;
++  size_t n = command -> valno;
++
++  if (n == 0)
++    return;
++
++  while (n > 0) {
++    cmdarg_value_t *val = values + n - 1;
++    n --;
++    if ((val -> flags & cmdval_freeme) &&
++        val -> src && val -> src -> type &&
++        val -> src -> type -> free) {
++      val -> src -> type -> free (val);
 +    }
 +  }
 +
-+  return error;
++  g_slice_free1 (sizeof(cmdarg_value_t) * n, values);
+ }
+ 
+-//  cmd_set_safe(name, safe)
+-// Sets if command can be used in startup configuration file.
+-gboolean cmd_set_safe(const gchar *name, gboolean safe)
++cmd_result_t cmd_execute (gchar *commandline, cmdexe_flags_t flags)
+ {
+-  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);
+-      }
++  gchar *s = commandline;
++  gchar *p, *e;
++  gchar *freeme = NULL;
++  const char *err;
++  gchar      *error;
++  cmdopts_t  *command = NULL;
++  const char *alias   = NULL;
++  cmdarg_value_t *values;
++  size_t n;
++
++  // skip command indicator and spaces at the beginning
++  while (*s == COMMAND_CHAR || *s == ' ')
++    s ++;
++  p = s;
++  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;
+     }
+-  if (safe)
+-    safe_commands = g_slist_append(safe_commands, g_strdup(name));
+-  else
+-    return FALSE;
+-  return TRUE;
++  }
++
++  // 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 (reverse - see cmd_define())
++  for (n = cmd_count; n > 0; n --) {
++    if (!strcmp (s, cmd_list[n-1] -> name)) {
++      command = cmd_list[n-1];
++      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;
++  }
++
++  // allocate dynamic storage for arguments
++  values = cmdopts_allocate_values (command);
++
++  // parse command line
++  if ((error = cmdopts_parse_internal (&p, &e, command, values))) {
++    scr_log_print (LPRINT_NORMAL, "%s", error);
++    g_free (error);
++    cmdopts_free_values (command, values);
++    g_free (freeme);
++    return cmd_result_syntax_error;
++  }
++
++  // do type checking on arguments
++  if ((error = cmdopts_check_values (command, values))) {
++    scr_log_print (LPRINT_NORMAL, "%s: %s", command -> name, error);
++    g_free (error);
++    cmdopts_free_values (command, values);
++    g_free (freeme);
++    return cmd_result_value_error;
++  }
++
++  // execute command handler
++  if (command -> handle) {
++    if ((error = command -> handle (command, values))) {
++      scr_log_print (LPRINT_NORMAL, "%s: %s", command -> name, error);
++      g_free (error);
++      cmdopts_free_values (command, values);
++      g_free (freeme);
++      return cmd_result_runtime_error;
++    }
++  }
++
++  // free resources
++  cmdopts_free_values (command, values);
++  g_free (freeme);
++  return cmd_result_ok;
  }
  
 -//  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)
++//  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)
  {
 -  GSList *sel;
 -  if (!name)
@@ -1036,40 +1221,8 @@
 -    if (!strcmp((const char *)sel->data, name))
 -      return TRUE;
 -  return FALSE;
-+  gsize n;
-+  if (command -> opts) {
-+    for (n = 0; command -> opts[n].shortopt != 0; 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].name != NULL; 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
 -// !!!
@@ -1077,8 +1230,7 @@
 -// ones in init_bindings()!
 -//
 -void cmd_init(void)
-+cmd_result_t cmd_execute (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",
@@ -1287,117 +1439,15 @@
 -  compl_add_category_word(COMPL_MODULE, "load");
 -  compl_add_category_word(COMPL_MODULE, "unload");
 -#endif
-+  gchar *s = commandline;
-+  gchar *p, *e;
-+  gchar *freeme = NULL;
-+  const char *err;
-+  gchar      *error;
-+  cmdopts_t  *command = NULL;
-+  const char *alias   = NULL;
-+  size_t n;
-+
-+  // skip command indicator and spaces at the beginning
-+  while (*s == COMMAND_CHAR || *s == ' ')
-+    s ++;
-+  p = s;
-+  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 (reverse - see cmd_define())
-+  for (n = cmd_count - 1; n >= 0; 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 -> handle) {
-+    if ((error = command -> handle (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;
@@ -1552,7 +1602,7 @@
    if (!*line) { // User only pressed enter
      if (scr_get_multimode()) {
        scr_append_multiline("");
-@@ -556,141 +675,584 @@
+@@ -556,141 +715,585 @@
      }
      if (current_buddy) {
        if (buddy_gettype(BUDDATA(current_buddy)) & ROSTER_TYPE_GROUP)
@@ -1663,7 +1713,7 @@
 +// * cmdarg_type_roster_group    - in roster, on '.' select group of current buddy -> bud
 +// + cmdarg_type_bjid         - any bjid -> bjid
 +// + cmdarg_type_fjid         - any fjid -> fjid
-+// + cmdarg_type_statusmask   - string -> string
++// + cmdarg_type_charset      - string -> string
 +// + cmdarg_type_uint         - string -> uint
 +// + cmdarg_type_nonspace     - strip, space only -> null
 +// * cmdarg_type_bjidmask
@@ -1675,7 +1725,7 @@
 +//  generic destructors
 +//
 +
-+void cmdarg_free_gfree (cmdarg_t *arg)
++void cmdarg_free_gfree (cmdarg_value_t *arg)
 +{
 +  g_free (arg -> value.arg);
 +}
@@ -1684,7 +1734,7 @@
 +//  command environment checkers
 +//
 +
-+gchar *cmd_check_online (cmdopts_t *command)
++gchar *cmd_check_online (cmdopts_t *command, cmdarg_value_t *values)
 +{
 +  if (!xmpp_is_online())
 +    return g_strdup ("You are not connected!");
@@ -1699,7 +1749,7 @@
 +// Replaces value.arg.
 +// Does not need freeing.
 +// No trailing spaces in defvalue - needs RW access for that.
-+gchar *cmdarg_check_nonspace (cmdarg_t *arg)
++gchar *cmdarg_check_nonspace (cmdarg_value_t *arg)
 +{
 +  gchar *val = arg -> value.arg;
 +
@@ -1743,28 +1793,28 @@
 +//
 +
 +// Uses chkdata as guint with allowed ROSTER_TYPE_*.
-+// Returns buddy roster entry in value.bud.
++// Returns buddy roster entry in value.rjid.bud.
 +// Recognizes as current ".", but not "" or NULL - use defvalue.
 +// Does not require freeing.
-+gchar *cmdarg_check_roster_bjid (cmdarg_t *arg)
++gchar *cmdarg_check_roster_bjid (cmdarg_value_t *arg)
 +{
 +  gchar *error = NULL;
 +
 +  if (!(error = cmdarg_check_nonspace(arg))) {
 +    const char *bjid = arg -> value.arg;
-+    guint      types = (guint) arg -> chkdata;
++    guint      types = (guint) arg -> src -> 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);
++        arg -> value.rjid.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;
++        arg -> value.rjid.bud = found->data;
 +      else
 +        error = g_strdup_printf("Jid <%s> is not in the roster.", bjid);
 +    } else { // jid is invalid
@@ -1772,8 +1822,9 @@
 +    }
 +  }
 +
++  arg -> value.rjid.resource = NULL;
 +  if (error)
-+    arg -> value.bud = NULL;
++    arg -> value.rjid.bud = NULL;
 +  return error;
 +}
 +
@@ -1794,9 +1845,8 @@
 +// 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
 +//  * merge with roster_bjid and use own flags in chkdata to signify types and resource allowed/required conditions
-+gchar *cmdarg_check_roster_resource (cmdarg_t *arg)
++gchar *cmdarg_check_roster_resource (cmdarg_value_t *arg)
 +{
 +  gchar    *error    = NULL;
 +  gpointer bud       = NULL;
@@ -1804,7 +1854,7 @@
 +
 +  if (!(error = cmdarg_check_nonspace(arg))) {
 +    char  *fjid = arg -> value.arg;
-+    guint types = (guint) arg -> chkdata;
++    guint types = (guint) arg -> src -> chkdata;
 +
 +    if (fjid[0] == '.' && fjid[1] == JID_RESOURCE_SEPARATOR) {
 +      // current buddy
@@ -1831,7 +1881,7 @@
 +        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.");
++          error = g_strdup("Currently selected buddy is of wrong type.");
 +      }
 +      if (bud) {
 +        GSList *resources, *p_res;
@@ -1850,8 +1900,8 @@
 +  }
 +
 +  if (error) {
-+    arg -> userdata  = NULL;
-+    arg -> value.arg = NULL;
++    arg -> value.rjid.bud      = NULL;
++    arg -> value.rjid.resource = NULL;
    } else {
 -    char *tmp;
 -    if (!check_jid_syntax(jidres) &&
@@ -1862,8 +1912,8 @@
 -      GSList *roster_elt;
 -      roster_elt = roster_find(jidres, jidsearch,
 -          ROSTER_TYPE_USER|ROSTER_TYPE_AGENT);
-+    arg -> userdata  = bud;
-+    arg -> value.arg = resource;
++    arg -> value.rjid.bud      = bud;
++    arg -> value.rjid.resource = resource;
 +  }
 +  return error;
 +}
@@ -1883,7 +1933,9 @@
 +// Does not require freeing.
 +// XXX:
 +//  * group, named "."?
-+gchar *cmdarg_check_roster_group (cmdarg_t *arg)
++//  * group, named " "? is it even possible?
++//  * check only that it is not NULL, and use "" as current?
++gchar *cmdarg_check_roster_group (cmdarg_value_t *arg)
 +{
 +  gchar    *error;
 +  gpointer group  = NULL;
@@ -1922,7 +1974,7 @@
 -        if (!g_strcmp0((char*)p_res->data, resource))
 -          found = TRUE;
 -        g_free(p_res->data);
-+  arg -> value.bud = group;
++  arg -> value.rjid.bud = group;
 +  return error;
 +}
 +
@@ -1941,7 +1993,7 @@
 +// Requires freeing.
 +// XXX:
 +//  * g_strdup jid?
-+gchar *cmdarg_check_fjid (cmdarg_t *arg)
++gchar *cmdarg_check_fjid (cmdarg_value_t *arg)
 +{
 +  gchar *error = NULL;
 +
@@ -1950,15 +2002,15 @@
 +
 +    if (fjid[0] == '.' && (fjid[1] == JID_RESOURCE_SEPARATOR || fjid[1] == '\0')) {
 +      const char *jid;
-+      if (!current_buddy)
++      if (!current_buddy) {
 +        error = g_strdup_printf ("No buddy selected.");
-+      else if (!(jid = buddy_getjid(BUDDATA(current_buddy)))) {
++      } else if (!(jid = buddy_getjid(BUDDATA(current_buddy)))) {
 +        error = g_strdup_printf ("Current buddy have no jid.");
 +      } else if (fjid[1] == '\0') {
 +        arg -> value.roarg = jid;
 +      } else {
 +        arg -> value.arg = g_strdup_printf ("%s%c%s", jid, JID_RESOURCE_SEPARATOR, fjid + 2);
-+        arg -> flags    |= cmdarg_freeme;
++        arg -> flags    |= cmdval_freeme;
        }
 -      g_slist_free(resources);
 -      if (!found) {
@@ -1989,7 +2041,7 @@
 +// Recognizes as current "." and "./res" (but still removes resource).
 +// Needs RW access to trim the resource - no resources in default values!
 +// Requires freeing (as fjid).
-+gchar *cmdarg_check_bjid (cmdarg_t *arg)
++gchar *cmdarg_check_bjid (cmdarg_value_t *arg)
 +{
 +  gchar *error = NULL;
 +
@@ -2020,7 +2072,7 @@
 +// XXX:
 +//  * use gulong? (strtoul allows to check conversion errors, while atoi - not)
 +//  * use flags in chkdata to specify signedness - it only affects two checks
-+gchar *cmdarg_check_uint (cmdarg_t *arg)
++gchar *cmdarg_check_uint (cmdarg_value_t *arg)
 +{
 +  gchar *error;
 +
@@ -2050,11 +2102,12 @@
 +};
 +
 +//
-+//  string -> statusmask
++//  string -> set of valid chars
 +//
 +
-+// Strips/checks for any non-valid status chars in mask.
-+// Returns mask in value.arg.
++// Strips/checks for any non-valid chars in argument.
++// Gets set of valid chars from chkdata.
++// Returns filtered string in value.arg.
 +// Recognizes "*" as glob.
 +// Does not require freeing.
 +// No errors in default vaules - needs RW for that.
@@ -2063,17 +2116,15 @@
 +//    * string2flags?
 +//  * canonicize?
 +//    * string2enum?
-+//  * common strchr callback with valid chars in chkdata?
-+//    * Then argument name should go into cmdarg struct - message would be too generic
-+gchar *cmdarg_check_statusmask (cmdarg_t *arg)
++//  * g_strdup (valid)?
++gchar *cmdarg_check_charset (cmdarg_value_t *arg)
 +{
 +  gchar *error;
 +
 +  if (!(error = cmdarg_check_nonspace(arg)) && arg -> value.arg) {
-+    const char *valid = "foand_?";
++    const char *valid = arg -> src -> chkdata;
 +    if (!strcmp(arg -> value.arg, "*")) {
-+      arg -> value.arg = g_strdup (valid);
-+      arg -> flags    |= cmdarg_freeme;
++      arg -> value.roarg = valid;
 +    } else {
 +      gchar *p = arg -> value.arg;
 +      gchar *e = p + strlen (p);
@@ -2082,9 +2133,9 @@
 +          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);
++          return g_strdup_printf ("Character '%c' not in set [%s].", *p, valid);
 +        } else {
-+          scr_log_print (LPRINT_NORMAL, "Warning: Wrong %s character [%c]", arg -> name, *p);
++          scr_log_print (LPRINT_NORMAL, "Warning: Wrong %s character [%c]", arg -> src -> name, *p);
 +          g_memmove (p, p+1, e-p-1);
 +          e --;
 +        }
@@ -2101,9 +2152,9 @@
 +  return error;
 +}
 +
-+const cmdarg_type_t cmdarg_type_statusmask = {
-+  cmdarg_check_statusmask,
-+  cmdarg_free_gfree,
++const cmdarg_type_t cmdarg_type_charset = {
++  cmdarg_check_charset,
++  NULL,
 +  NULL,
 +};
 +
@@ -2116,21 +2167,21 @@
 +// Returns 0 if not recognized and not required.
 +// Does not require freeing.
 +// XXX:
++//  * default value on error?
 +//  * also, print list of possible values on error?
-+//  * default value on error?
-+gchar *cmdarg_check_string2enum (cmdarg_t *arg)
++gchar *cmdarg_check_string2enum (cmdarg_value_t *arg)
 +{
 +  gchar *error;
 +
 +  if (!(error = cmdarg_check_nonspace(arg))) {
 +    const string2enum_t *list;
-+    for (list = arg -> chkdata; list -> name != NULL; list ++)
++    for (list = arg -> src -> chkdata; list -> name != NULL; list ++)
 +      if (!strcmp(list -> name, arg -> value.arg)) { // found
 +        arg -> value.uint = list -> value;
 +        return NULL;
 +      }
 +    // not found, error
-+    error = g_strdup_printf ("Value \"%s\" is invalid for %s.", arg -> value.arg, arg -> name);
++    error = g_strdup_printf ("Value \"%s\" is invalid.", arg -> value.arg);
 +  }
 +
 +  if (error)
@@ -2169,7 +2220,7 @@
 +//    * 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 *cmdarg_check_color (cmdarg_value_t *arg)
 +{
 +  gchar *error;
 +
@@ -2241,7 +2292,7 @@
  static void display_and_free_note(struct annotation *note, const char *winId)
  {
    gchar tbuf[128];
-@@ -755,41 +1317,15 @@
+@@ -755,41 +1358,15 @@
    g_slist_free(notes);
  }
  
@@ -2291,14 +2342,14 @@
      struct annotation *note = xmpp_get_storage_rosternotes(bjid, FALSE);
      if (note) {
        display_and_free_note(note, bjid);
-@@ -800,484 +1336,568 @@
+@@ -800,484 +1377,618 @@
    }
  }
  
 -//  roster_updown(updown, nitems)
 -// updown: -1=up, +1=down
 -inline static void roster_updown(int updown, char *nitems)
-+static gchar *do_roster (cmdopts_t *command);
++static gchar *do_roster (cmdopts_t *command, cmdarg_value_t *values);
 +
 +typedef enum {
 +  scmd_roster_bottom, scmd_roster_top, scmd_roster_up, scmd_roster_down,
@@ -2314,6 +2365,19 @@
 +  scmd_roster_hide, scmd_roster_show, scmd_roster_toggle,
 +} scmd_roster_t;
 +
++typedef enum {
++  pos_roster_scmd         = 0,
++  pos_roster_up_n         = 1,
++  pos_roster_down_n       = 1,
++  pos_roster_search_name  = 1,
++  pos_roster_display_mask = 1,
++  pos_roster_itemlock_jid = 1,
++  pos_roster_note_jid     = 1,
++  pos_roster_note_rst     = 2,
++  pos_roster_note_text    = 3,
++  pos_roster_reslock_jid  = 1,
++} pos_roster_t;
++
 +#define SCMD_ROSTER(NAME, ARGS...) \
 +    { #NAME, cmd_default, NULL, NULL, NULL, ARGS, NULL, (gpointer)scmd_roster_##NAME }
 +static cmdopts_t def_roster = {
@@ -2322,40 +2386,40 @@
 +  NULL,
 +  do_roster,
 +  NULL,
-+  (cmdarg_t[2]){{"subcommand", cmdarg_subcmd | cmdarg_check, NULL, NULL},{NULL}},
++  (cmdarg_t[2]){{"subcommand", pos_roster_scmd, cmdarg_subcmd | cmdarg_check, NULL, NULL},{NULL}},
 +  (cmdopts_t[25]){
 +    SCMD_ROSTER(bottom, NULL),
 +    SCMD_ROSTER(top,    NULL),
-+    SCMD_ROSTER(up,   (cmdarg_t[2]){{"n", cmdarg_chreq, "1", &cmdarg_type_uint},{NULL}}),
-+    SCMD_ROSTER(down, (cmdarg_t[2]){{"n", cmdarg_chreq, "1", &cmdarg_type_uint},{NULL}}),
++    SCMD_ROSTER(up,   (cmdarg_t[2]){{"n", pos_roster_up_n, cmdarg_chreq, "1", &cmdarg_type_uint},{NULL}}),
++    SCMD_ROSTER(down, (cmdarg_t[2]){{"n", pos_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, (cmdarg_t[2]){{"name", cmdarg_eol|cmdarg_required, NULL, &cmdarg_type_nonspace},{NULL}}),
-+    SCMD_ROSTER(display, (cmdarg_t[2]){{"statusmask", cmdarg_check, NULL, &cmdarg_type_statusmask},{NULL}}),
++    SCMD_ROSTER(search, (cmdarg_t[2]){{"name", pos_roster_search_name, cmdarg_eol|cmdarg_required, NULL, &cmdarg_type_nonspace},{NULL}}),
++    SCMD_ROSTER(display, (cmdarg_t[2]){{"statusmask", pos_roster_display_mask, cmdarg_check, NULL, &cmdarg_type_charset, (gpointer)"ofdna_?"},{NULL}}),
 +    SCMD_ROSTER(hide_offline,   NULL),
 +    SCMD_ROSTER(show_offline,   NULL),
 +    SCMD_ROSTER(toggle_offline, NULL),
-+    SCMD_ROSTER(item_lock,        (cmdarg_t[2]){{"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
-+    SCMD_ROSTER(item_unlock,      (cmdarg_t[2]){{"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
-+    SCMD_ROSTER(item_toggle_lock, (cmdarg_t[2]){{"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
++    SCMD_ROSTER(item_lock,        (cmdarg_t[2]){{"jid", pos_roster_itemlock_jid, cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
++    SCMD_ROSTER(item_unlock,      (cmdarg_t[2]){{"jid", pos_roster_itemlock_jid, cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
++    SCMD_ROSTER(item_toggle_lock, (cmdarg_t[2]){{"jid", pos_roster_itemlock_jid, cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM)},{NULL}}),
 +    { "note", cmd_default, NULL, NULL,
 +      (cmdopt_t[3]){
-+        {'r', "reset", cmdopt_switch, {"reset", cmdarg_default, NULL, NULL, NULL}},
-+        {'j', "jid",  cmdopt_default, {"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT)}},
++        {'r', "reset", {"reset", pos_roster_note_rst, cmdarg_switch, NULL, NULL, NULL}},
++        {'j', "jid",   {"jid",   pos_roster_note_jid, cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_ROOM|ROSTER_TYPE_AGENT)}},
 +        {0}
 +      },
 +      (cmdarg_t[2]){
-+        {"text", cmdarg_eol, NULL, &cmdarg_type_nonspace},
++        {"text", pos_roster_note_text, cmdarg_eol, NULL, &cmdarg_type_nonspace},
 +        {NULL}
 +      },
 +      NULL, (gpointer)scmd_roster_note
 +    },
 +    SCMD_ROSTER(notes, NULL),
-+    SCMD_ROSTER(resource_lock,   (cmdarg_t[2]){{"resource|fjid", cmdarg_chreq, NULL, &cmdarg_type_roster_resource, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)},{NULL}}),
-+    SCMD_ROSTER(resource_unlock, (cmdarg_t[2]){{"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)},{NULL}}),
++    SCMD_ROSTER(resource_lock,   (cmdarg_t[2]){{"resource|fjid", pos_roster_reslock_jid, cmdarg_chreq, NULL, &cmdarg_type_roster_resource, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT)},{NULL}}),
++    SCMD_ROSTER(resource_unlock, (cmdarg_t[2]){{"jid", pos_roster_reslock_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),
@@ -2363,7 +2427,7 @@
 +  },
 +};
 +
-+static gchar *do_roster(cmdopts_t *options)
++static gchar *do_roster(cmdopts_t *options, cmdarg_value_t *values)
  {
 -  int nbitems;
 -
@@ -2396,11 +2460,7 @@
 -  }
 -
 -  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[0].name)
-+    arg = options -> args[0].value.cmd -> args;
++  scmd_roster_t subcmd = (scmd_roster_t) (values[pos_roster_scmd].value.cmd -> userdata);
 +
 +  if (subcmd == scmd_roster_bottom) {
 +    scr_roster_bottom();
@@ -2411,9 +2471,9 @@
 -  } else if (!strcasecmp(subcmd, "bottom")) {
 -    scr_roster_bottom();
 +  } else if (subcmd == scmd_roster_up) {
-+    scr_roster_up_down(-1, arg -> value.uint);
++    scr_roster_up_down(-1, values[pos_roster_up_n].value.uint);
 +  } else if (subcmd == scmd_roster_down) {
-+    scr_roster_up_down(1, arg -> value.uint);
++    scr_roster_up_down(1, values[pos_roster_down_n].value.uint);
 +  } else if (subcmd == scmd_roster_group_prev) {
 +    scr_roster_prev_group();
 +  } else if (subcmd == scmd_roster_group_next) {
@@ -2425,7 +2485,7 @@
 +  } else if (subcmd == scmd_roster_unread_next) {
 +    scr_roster_unread_message(1);
 +  } else if (subcmd == scmd_roster_search) {
-+    scr_roster_search(arg -> value.arg);
++    scr_roster_search(values[pos_roster_search_name].value.arg);
      update_roster = TRUE;
 -  } else if (!strcasecmp(subcmd, "hide")) {
 -    scr_roster_visibility(0);
@@ -2435,7 +2495,7 @@
 -    scr_roster_visibility(-1);
 -  } else if (!strcasecmp(subcmd, "hide_offline")) {
 +  } else if (subcmd == scmd_roster_display) {
-+    scr_roster_display(arg -> value.arg);
++    scr_roster_display(values[pos_roster_display_mask].value.arg);
 +  } else if (subcmd == scmd_roster_hide_offline) {
      buddylist_set_hide_offline_buddies(TRUE);
 -    if (current_buddy)
@@ -2493,22 +2553,23 @@
 -    scr_LogPrint(LPRINT_NORMAL, "Unrecognized parameter!");
 -  free_arg_lst(paramlst);
 +  } else if (subcmd == scmd_roster_item_lock) {
-+    roster_buddylock(arg -> value.bud, 1);
++    roster_buddylock(values[pos_roster_itemlock_jid].value.rjid.bud, 1);
 +  } else if (subcmd == scmd_roster_item_unlock) {
-+    roster_buddylock(arg -> value.bud, 0);
++    roster_buddylock(values[pos_roster_itemlock_jid].value.rjid.bud, 0);
 +  } else if (subcmd == scmd_roster_item_toggle_lock) {
-+    roster_buddylock(arg -> value.bud, -1);
++    roster_buddylock(values[pos_roster_itemlock_jid].value.rjid.bud, -1);
 +  } else if (subcmd == scmd_roster_note) {
-+    roster_note(options -> cmds[18].opts[1].arg.value.bud,
-+                options -> cmds[18].opts[0].arg.value.swc,
-+                arg -> value.arg);
++    roster_note(values[pos_roster_note_jid].value.rjid.bud,
++                values[pos_roster_note_rst].value.swc,
++                values[pos_roster_note_text].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);
++    buddy_setactiveresource(values[pos_roster_reslock_jid].value.rjid.bud,
++                            values[pos_roster_reslock_jid].value.rjid.resource);
 +    scr_update_chat_status(TRUE);
 +  } else if (subcmd == scmd_roster_resource_unlock) {
-+    buddy_setactiveresource(arg -> value.bud, NULL);
++    buddy_setactiveresource(values[pos_roster_reslock_jid].value.rjid.bud, NULL);
 +    scr_update_chat_status(TRUE);
 +  } else if (subcmd == scmd_roster_hide) {
 +    scr_roster_visibility(0);
@@ -2529,7 +2590,8 @@
 +// custom argument types
 +
 +// statusmask + "clear"
-+static gchar *cmdarg_check_color_statusmask (cmdarg_t *arg)
++// Needs status char set in chkdata.
++static gchar *cmdarg_check_color_statusmask (cmdarg_value_t *arg)
  {
 -  char **paramlst;
 -  char *subcmd;
@@ -2545,7 +2607,7 @@
 +  if (!g_strcmp0 (arg -> value.arg, "clear"))
 +    return NULL;
 +  else
-+    return cmdarg_check_statusmask (arg);
++    return cmdarg_check_charset (arg);
 +}
 +
 +static const cmdarg_type_t cmdarg_type_color_statusmask = {
@@ -2556,16 +2618,18 @@
 +
 +// bjid + "*"
 +// Returns string, not buddy.
-+static gchar *cmdarg_check_color_roomjid (cmdarg_t *arg)
++// XXX:
++//  * strdup jid?
++//  * requires type in chkdata
++static gchar *cmdarg_check_color_roomjid (cmdarg_value_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?
++    arg -> value.roarg = buddy_getjid (arg -> value.rjid.bud);
    }
  
 -  if (!strcasecmp(subcmd, "roster")) {
@@ -2604,7 +2668,7 @@
 +
 +// command
 +
-+static gchar *do_color (cmdopts_t *command);
++static gchar *do_color (cmdopts_t *command, cmdarg_value_t *values);
 +
 +typedef enum {
 +  scmd_color_roster,
@@ -2612,6 +2676,17 @@
 +  scmd_color_muc,
 +} scmd_color_t;
 +
++typedef enum {
++  pos_color_scmd          = 0,
++  pos_color_roster_status = 1,
++  pos_color_roster_jid    = 2,
++  pos_color_roster_color  = 3,
++  pos_color_muc_room      = 1,
++  pos_color_muc_mode      = 2,
++  pos_color_nick_nick     = 1,
++  pos_color_nick_color    = 2,
++} pos_color_t;
++
 +static const string2enum_t s2e_color_muc[] = {
 +  { "on",     MC_ALL    },
 +  { "off",    MC_OFF    },
@@ -2626,36 +2701,36 @@
 +  NULL,
 +  do_color,
 +  NULL,
-+  (cmdarg_t[2]){{ "subcommand", cmdarg_subcmd | cmdarg_check, NULL, NULL },{NULL}},
++  (cmdarg_t[2]){{ "subcommand", pos_color_scmd, cmdarg_subcmd | cmdarg_check, NULL, NULL },{NULL}},
 +  (cmdopts_t[4]){
 +    {"roster", cmd_default, NULL, NULL, NULL, (cmdarg_t[4]){
-+        { "statusmask|clear", cmdarg_chreq,   NULL, &cmdarg_type_color_statusmask },
-+        { "jidmask",          cmdarg_default, NULL, &cmdarg_type_bjidmask         },
-+        { "color|-",          cmdarg_default, NULL, &cmdarg_type_color            },
++        { "statusmask|clear", pos_color_roster_status, cmdarg_chreq,   NULL, &cmdarg_type_color_statusmask, (gpointer)"ofdna_?" },
++        { "jidmask",          pos_color_roster_jid,    cmdarg_default, NULL, &cmdarg_type_bjidmask },
++        { "color|-",          pos_color_roster_color,  cmdarg_default, NULL, &cmdarg_type_color    },
 +        {NULL}
 +      }, NULL, (gpointer)scmd_color_roster},
 +    {"muc", cmd_default, NULL, NULL, NULL, (cmdarg_t[3]){
-+        { "roomjid",         cmdarg_chreq, NULL, &cmdarg_type_color_roomjid },
-+        { "on|off|preset|-", cmdarg_chreq, "on", &cmdarg_type_string2enum, (gpointer)s2e_color_muc},
++        { "roomjid",         pos_color_muc_room, cmdarg_chreq, NULL, &cmdarg_type_color_roomjid, (gpointer)ROSTER_TYPE_ROOM },
++        { "on|off|preset|-", pos_color_muc_mode, cmdarg_chreq, "on", &cmdarg_type_string2enum,   (gpointer)s2e_color_muc },
 +        {NULL}
 +      }, NULL, (gpointer)scmd_color_muc},
 +    {"mucnick", cmd_default, NULL, NULL, NULL, (cmdarg_t[3]){
-+        { "nick",    cmdarg_chreq, NULL, &cmdarg_type_nick  },
-+        { "color|-", cmdarg_chreq, NULL, &cmdarg_type_color },
++        { "nick",    pos_color_nick_nick,  cmdarg_chreq, NULL, &cmdarg_type_nick  },
++        { "color|-", pos_color_nick_color, cmdarg_chreq, NULL, &cmdarg_type_color },
 +        {NULL}
 +      }, NULL, (gpointer)scmd_color_mucnick},
 +    {NULL}
 +  },
 +};
 +
-+static gchar *do_color (cmdopts_t *options)
++static gchar *do_color (cmdopts_t *options, cmdarg_value_t *values)
 +{
-+  scmd_color_t subcmd = (scmd_color_t) options -> args[0].value.cmd -> userdata;
++  scmd_color_t subcmd = (scmd_color_t) (values[pos_color_scmd].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;
++    const gchar *status   = values[pos_color_roster_status].value.arg;
++    const gchar *wildcard = values[pos_color_roster_jid].value.arg;
++    const gchar *color    = values[pos_color_roster_color].value.arg;
 +    if (!strcmp(status, "clear")) { // Not a color command, clear all
        scr_roster_clear_color();
        update_roster = TRUE;
@@ -2713,11 +2788,11 @@
 -    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 );
++    scr_muc_color ( values[pos_color_muc_room].value.arg,
++                    values[pos_color_muc_mode].value.uint );
 +  } else { // scmd_color_mucnick
-+    scr_muc_nick_color( options -> cmds[2].args[0].value.arg,
-+                        options -> cmds[2].args[1].value.arg );
++    scr_muc_nick_color( values[pos_color_nick_nick].value.arg,
++                        values[pos_color_nick_color].value.arg );
 +  }
 +
 +  return NULL;
@@ -2735,7 +2810,7 @@
 +// custom type
 +
 +// needs corresponding s2e in chkdata
-+static gchar *cmdarg_check_status_status (cmdarg_t *arg)
++static gchar *cmdarg_check_status_status (cmdarg_value_t *arg)
  {
 -  char **paramlst;
 -  char *status;
@@ -2779,7 +2854,12 @@
 +
 +// command
 +
-+static gchar *do_status (cmdopts_t *command);
++static gchar *do_status (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_status_status  = 0,
++  pos_status_message = 1,
++} pos_status_t;
 +
 +static const string2enum_t s2e_status2[] = {
 +  { "away",      away        },
@@ -2837,17 +2917,17 @@
 +  do_status,
 +  NULL,
 +  (cmdarg_t[3]){
-+    {"status",  cmdarg_chreq, "show", &cmdarg_type_status_status, (gpointer)s2e_status2},
-+    {"message", cmdarg_eol,   NULL,   &cmdarg_type_nonspace},
++    {"status",  pos_status_status,  cmdarg_chreq, "show", &cmdarg_type_status_status, (gpointer)s2e_status2},
++    {"message", pos_status_message, cmdarg_eol,   NULL,   &cmdarg_type_nonspace},
 +    {NULL}
 +  },
 +  NULL,
 +};
 +
-+static gchar *do_status (cmdopts_t *options)
++static gchar *do_status (cmdopts_t *options, cmdarg_value_t *values)
  {
 -  if (!*arg) {
-+  if (options -> args[0].value.uint == imstatus_size) {
++  if (values[pos_status_status].value.uint == imstatus_size) {
      const char *sm = xmpp_getstatusmsg();
      scr_LogPrint(LPRINT_NORMAL, "Your status is: [%c] %s",
                   imstatus2char[xmpp_getstatus()],
@@ -2857,8 +2937,8 @@
 +    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);
++    xmpp_setstatus(values[pos_status_status].value.uint, NULL,
++                   values[pos_status_message].value.arg, FALSE);
    }
 -  arg = to_utf8(arg);
 -  cmd_setstatus(NULL, arg);
@@ -2871,7 +2951,13 @@
 +//  /status_to
 +//
 +
-+static gchar *do_status_to (cmdopts_t *command);
++static gchar *do_status_to (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_statusto_jid     = 0,
++  pos_statusto_status  = 1,
++  pos_statusto_message = 2,
++} pos_statusto_t;
 +
 +// no "show" here
 +static const string2enum_t s2e_status[] = {
@@ -2895,15 +2981,15 @@
 +  do_status_to,
 +  NULL,
 +  (cmdarg_t[4]){
-+    {"jid",     cmdarg_chreq, NULL, &cmdarg_type_fjid},
-+    {"status",  cmdarg_chreq, NULL, &cmdarg_type_status_status, (gpointer)s2e_status},
-+    {"message", cmdarg_eol,   NULL, &cmdarg_type_nonspace},
++    {"jid",     pos_statusto_jid,     cmdarg_chreq, NULL, &cmdarg_type_fjid},
++    {"status",  pos_statusto_status,  cmdarg_chreq, NULL, &cmdarg_type_status_status, (gpointer)s2e_status},
++    {"message", pos_statusto_message, cmdarg_eol,   NULL, &cmdarg_type_nonspace},
 +    {NULL}
 +  },
 +  NULL,
 +};
 +
-+static gchar *do_status_to (cmdopts_t *options)
++static gchar *do_status_to (cmdopts_t *options, cmdarg_value_t *values)
  {
 -  char **paramlst;
 -  char *fjid, *st, *msg;
@@ -2922,9 +3008,9 @@
 +  const char *fjid, *stname, *msg;
 +  enum imstatus st;
 +
-+  fjid = options -> args[0].value.arg;
-+  st   = options -> args[1].value.uint;
-+  msg  = options -> args[2].value.arg;
++  fjid = values[pos_statusto_jid].value.arg;
++  st   = values[pos_statusto_status].value.uint;
++  msg  = values[pos_statusto_message].value.arg;
 +
 +  { // get status name
 +    const string2enum_t *list;
@@ -2974,7 +3060,12 @@
 +//  /add
 +//
 +
-+static gchar *do_add (cmdopts_t *command);
++static gchar *do_add (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_add_jid  = 0,
++  pos_add_name = 1,
++} pos_add_t;
 +
 +static cmdopts_t def_add = {
 +  "add",
@@ -2983,21 +3074,21 @@
 +  do_add,
 +  NULL,
 +  (cmdarg_t[3]){
-+    {"jid",  cmdarg_chreq,   ".",  &cmdarg_type_bjid},
-+    {"name", cmdarg_default, NULL, &cmdarg_type_nonspace},
++    {"jid",  pos_add_jid,  cmdarg_chreq,   ".",  &cmdarg_type_bjid},
++    {"name", pos_add_name, cmdarg_default, NULL, &cmdarg_type_nonspace},
 +    {NULL}
 +  },
 +  NULL,
 +};
 +
-+static gchar *do_add (cmdopts_t *options)
++static gchar *do_add (cmdopts_t *options, cmdarg_value_t *values)
 +{
-+  gchar *jid = options -> args[0].value.arg;
++  gchar *jid = values[pos_add_jid].value.arg;
 +
 +  // XXX
 +  //mc_strtolower(jid);
 +
-+  xmpp_addbuddy(jid, options -> args[1].value.arg, NULL);
++  xmpp_addbuddy(jid, values[pos_add_name].value.arg, NULL);
 +  scr_LogPrint(LPRINT_LOGNORM, "Sent presence notification request to <%s>.",
 +               jid);
 +
@@ -3008,7 +3099,12 @@
 +//  /del
 +//
 +
-+static gchar *do_del (cmdopts_t *command);
++static gchar *do_del (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_del_jid    = 0,
++  pos_del_dryrun = 1,
++} pos_del_t;
 +
 +static cmdopts_t def_del = {
 +  "del",
@@ -3016,19 +3112,19 @@
 +  cmd_check_online,
 +  do_del,
 +  (cmdopt_t[2]){
-+    {'n', "dryrun", cmdopt_switch, {"dryrun", cmdarg_default, NULL, NULL}},
++    {'n', "dryrun", {"dryrun", pos_del_dryrun, cmdarg_switch|cmdarg_check, NULL, NULL}},
 +    {0}
 +  },
 +  (cmdarg_t[2]){
-+    {"jid", cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_GROUP)},
++    {"jid", pos_del_jid, cmdarg_chreq, ".", &cmdarg_type_roster_bjid, (gpointer)(ROSTER_TYPE_USER|ROSTER_TYPE_AGENT|ROSTER_TYPE_GROUP)},
 +    {NULL}
 +  },
 +  NULL,
 +};
 +
-+static gchar *do_del (cmdopts_t *options)
++static gchar *do_del (cmdopts_t *options, cmdarg_value_t *values)
 +{
-+  gpointer   buddy = options -> args[0].value.bud;
++  gpointer   buddy = values[pos_del_jid].value.rjid.bud;
 +  const char *jid  = buddy_getjid (buddy);
 +
 +  if (buddy_gettype(buddy) & ROSTER_TYPE_ROOM)
@@ -3038,7 +3134,7 @@
 +
 +  scr_LogPrint(LPRINT_LOGNORM, "Removing <%s>...", jid);
 +
-+  if (!(options -> opts[0].arg.value.swc)) {
++  if (!(values[pos_del_dryrun].value.swc)) {
 +    // Close the buffer
 +    scr_buffer_purge(1, jid);
 +
@@ -3229,7 +3325,12 @@
 -  free_arg_lst(paramlst);
  }
  
-+static gchar *do_group (cmdopts_t *command);
++static gchar *do_group (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_group_group  = 0,
++  pos_group_action = 1,
++} pos_group_t;
 +
 +static const string2enum_t s2e_group_scmd[] = {
 +  {"expand", scmd_group_unfold},
@@ -3250,21 +3351,21 @@
 +  do_group,
 +  NULL,
 +  (cmdarg_t[3]){
-+    {"subcommand", cmdarg_chreq,            NULL, &cmdarg_type_string2enum,
++    {"subcommand", pos_group_action, cmdarg_chreq, NULL, &cmdarg_type_string2enum,
 +                                                  (gpointer)s2e_group_scmd},
-+    {"group",      cmdarg_chreq|cmdarg_eol, ".",  &cmdarg_type_roster_group},
++    {"group", pos_group_group, cmdarg_chreq|cmdarg_eol, ".",  &cmdarg_type_roster_group},
 +    {NULL}
 +  },
 +  NULL,
 +};
 +
-+static gchar *do_group(cmdopts_t *options)
++static gchar *do_group(cmdopts_t *options, cmdarg_value_t *values)
 +{
 +  //if (!current_buddy) // XXX do we need this still?
 +  //  return g_strdup("Command needs selected buddy.");
 +
-+  group_cmd (options -> args[0].value.bud,
-+             options -> args[1].value.uint);
++  group_cmd (values[pos_group_group].value.rjid.bud,
++             values[pos_group_action].value.uint);
 +
 +  return NULL;
 +}
@@ -3279,7 +3380,7 @@
  {
    char *bare_jid, *rp;
    char *hmsg;
-@@ -1285,6 +1905,7 @@
+@@ -1285,6 +1996,7 @@
    gint retval = 0;
    int isroom;
    gpointer xep184 = NULL;
@@ -3287,7 +3388,7 @@
  
    if (!xmpp_is_online()) {
      scr_LogPrint(LPRINT_NORMAL, "You are not connected.");
-@@ -1299,11 +1920,15 @@
+@@ -1299,11 +2011,15 @@
      return 1;
    }
    if (check_jid_syntax((char*)fjid)) {
@@ -3305,7 +3406,7 @@
    // We must use the bare jid in hk_message_out()
    rp = strchr(fjid, JID_RESOURCE_SEPARATOR);
    if (rp)
-@@ -1354,8 +1979,7 @@
+@@ -1354,8 +2070,7 @@
  //  send_message(msg, subj, type_overwrite)
  // Write the message in the buddy's window and send the message on
  // the network.
@@ -3315,7 +3416,7 @@
  {
    const char *bjid;
    char *jid;
-@@ -1378,34 +2002,13 @@
+@@ -1378,34 +2093,13 @@
    else
      jid = g_strdup(bjid);
  
@@ -3345,14 +3446,14 @@
 -}
 -
 -void say_cmd(char *arg, int parse_flags)
-+void say_cmd(char *arg, msgtype_t msgtype)
++static 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 +2027,133 @@
+@@ -1424,80 +2118,137 @@
    }
  
    buddy_setflags(bud, ROSTER_FLAG_LOCK, TRUE);
@@ -3363,37 +3464,42 @@
 -  g_free(arg);
  }
  
-+#if 0
-+
- static void do_say(char *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 gchar *do_say (cmdopts_t *command, cmdarg_value_t *values);
++
++typedef enum {
++  pos_say_msg     = 0,
++  pos_say_msgtype = 1,
++} pos_say_t;
++
++static cmdopts_t def_say = {
++  "say",
++  cmd_default,
++  NULL,
++  do_say,
++  (cmdopt_t[4]){
++    {'n', "normal",   {"normal",   pos_say_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_normal}},
++    {'h', "headline", {"headline", pos_say_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_headline}},
++    {'d', "default",  {"default",  pos_say_msgtype, cmdarg_switch, NULL, NULL, NULL, (gpointer)msgtype_not_set}},
++    {0}
++  },
++  (cmdarg_t[2]){
++    {"message", pos_say_msg, cmdarg_eol | cmdarg_chreq, NULL, &cmdarg_type_nonspace},
++    {NULL}
++  },
++  NULL,
++};
++
++static gchar *do_say (cmdopts_t *options, cmdarg_value_t *values)
++{
++  say_cmd(values[pos_say_msg].value.arg,
++          (msgtype_t) (values[pos_say_msgtype].src -> userdata));
++  return NULL;
  }
  
++#if 0
++
  static void do_msay(char *arg)
  {
 -  /* Parameters: begin verbatim abort send send_to */
@@ -3535,7 +3641,7 @@
  
    if (!scr_get_multimode()) {
      scr_LogPrint(LPRINT_NORMAL, "No message to send.  "
-@@ -1508,49 +2164,47 @@
+@@ -1508,49 +2259,47 @@
    scr_set_chatmode(TRUE);
    scr_show_buddy_window();
  
@@ -3621,7 +3727,7 @@
  }
  
  //  load_message_from_file(filename)
-@@ -1566,7 +2220,7 @@
+@@ -1566,7 +2315,7 @@
    char *next_utf8_char;
    size_t len;
  
@@ -3630,7 +3736,7 @@
  
    if (!fd || fstat(fileno(fd), &buf)) {
      scr_LogPrint(LPRINT_LOGNORM, "Cannot open message file (%s)", filename);
-@@ -1634,130 +2288,103 @@
+@@ -1634,130 +2383,103 @@
  
  static void do_say_to(char *arg)
  {
@@ -3817,7 +3923,7 @@
  }
  
  //  buffer_updown(updown, nblines)
-@@ -1775,27 +2402,10 @@
+@@ -1775,27 +2497,10 @@
      scr_buffer_scroll_up_down(updown, nblines);
  }
  
@@ -3845,7 +3951,7 @@
    t = from_iso8601(date, 0);
    if (t)
      scr_buffer_date(t);
-@@ -1804,98 +2414,156 @@
+@@ -1804,98 +2509,156 @@
                   "not correctly formatted or invalid.");
  }
  
@@ -4084,7 +4190,7 @@
  }
  
  static void do_info(char *arg)
-@@ -2033,29 +2701,20 @@
+@@ -2033,29 +2796,20 @@
    }
  }
  
@@ -4123,7 +4229,7 @@
  
    // Enter chat mode
    scr_set_chatmode(TRUE);
-@@ -2075,12 +2734,12 @@
+@@ -2075,12 +2829,12 @@
      rstatus = buddy_getstatus(bud, p_res->data);
      rst_msg = buddy_getstatusmsg(bud, p_res->data);
  
@@ -4138,7 +4244,7 @@
          enum imrole role = buddy_getrole(bud, p_res->data);
          enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
          bool showaffil = (affil != affil_none);
-@@ -2096,12 +2755,12 @@
+@@ -2096,12 +2850,12 @@
        snprintf(buffer, 4095, "[%c] %s", imstatus2char[rstatus],
                 (char*)p_res->data);
        scr_WriteIncomingMessage(bjid, buffer, 0, HBB_PREFIX_INFO, 0);
@@ -4153,7 +4259,7 @@
          enum imrole role = buddy_getrole(bud, p_res->data);
          enum imaffiliation affil = buddy_getaffil(bud, p_res->data);
  
-@@ -2145,16 +2804,69 @@
+@@ -2145,16 +2899,69 @@
  
  static void do_rename(char *arg)
  {
@@ -4228,7 +4334,7 @@
    bjid   = buddy_getjid(bud);
    group  = buddy_getgroupname(bud);
    type   = buddy_gettype(bud);
-@@ -2162,11 +2874,13 @@
+@@ -2162,11 +2969,13 @@
  
    if (type & ROSTER_TYPE_SPECIAL) {
      scr_LogPrint(LPRINT_NORMAL, "You can't rename this item.");
@@ -4243,7 +4349,7 @@
      return;
    }
  
-@@ -2181,90 +2895,117 @@
+@@ -2181,90 +2990,117 @@
    //  }
    //}
  
@@ -4392,7 +4498,7 @@
      } else {
        // This is a local item, we move it without adding to roster.
        guint msgflag;
-@@ -2276,7 +3017,7 @@
+@@ -2276,7 +3112,7 @@
        msgflag = buddy_getflags(bud) & ROSTER_FLAG_MSG;
        if (msgflag)
          roster_msg_setflag(bjid, FALSE, FALSE);
@@ -4401,7 +4507,7 @@
        if (msgflag)
          roster_msg_setflag(bjid, FALSE, TRUE);
        if ((type & ROSTER_TYPE_ROOM) && xmpp_is_bookmarked(bjid) &&
-@@ -2285,8 +3026,7 @@
+@@ -2285,8 +3121,7 @@
      }
    }
  
@@ -4411,7 +4517,7 @@
    update_roster = TRUE;
  }
  
-@@ -2468,50 +3208,33 @@
+@@ -2468,50 +3303,33 @@
  
  static void do_rawxml(char *arg)
  {
@@ -4482,7 +4588,7 @@
  }
  
  //  check_room_subcommand(arg, param_needed, buddy_must_be_a_room)
-@@ -2815,6 +3538,8 @@
+@@ -2815,6 +3633,8 @@
    free_arg_lst(paramlst);
  }
  
@@ -4491,7 +4597,7 @@
  void cmd_room_leave(gpointer bud, char *arg)
  {
    gchar *roomid, *desc;
-@@ -2833,6 +3558,8 @@
+@@ -2833,6 +3653,8 @@
    g_free(roomid);
  }
  
@@ -4500,7 +4606,7 @@
  static void room_nick(gpointer bud, char *arg)
  {
    if (!buddy_getinsideroom(bud)) {
-@@ -2874,7 +3601,7 @@
+@@ -2874,7 +3696,7 @@
    fjid_utf8 = g_strdup_printf("%s/%s", buddy_getjid(bud), nick_utf8);
    g_free (nick_utf8);
    msg = to_utf8(arg);
@@ -4509,7 +4615,7 @@
    g_free(fjid_utf8);
    g_free(msg);
    free_arg_lst(paramlst);
-@@ -3052,6 +3779,8 @@
+@@ -3052,6 +3874,8 @@
    free_arg_lst(paramlst);
  }
  
@@ -4518,7 +4624,7 @@
  //  cmd_room_whois(..)
  // If interactive is TRUE, chatmode can be enabled.
  // Please note that usernick is expected in UTF-8 locale iff interactive is
-@@ -3146,6 +3875,8 @@
+@@ -3146,6 +3970,8 @@
      free_arg_lst(paramlst);
  }
  
@@ -4527,7 +4633,7 @@
  static void room_bookmark(gpointer bud, char *arg)
  {
    const char *roomid;
-@@ -3290,6 +4021,207 @@
+@@ -3290,6 +4116,207 @@
  
  static void do_room(char *arg)
  {
@@ -4735,7 +4841,7 @@
    char **paramlst;
    char *subcmd;
    gpointer bud;
-@@ -3347,7 +4279,7 @@
+@@ -3347,7 +4374,7 @@
        cmd_room_leave(bud, arg);
    } else if (!strcasecmp(subcmd, "names"))  {
      if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
@@ -4744,7 +4850,7 @@
    } else if (!strcasecmp(subcmd, "nick"))  {
      if ((arg = check_room_subcommand(arg, FALSE, bud)) != NULL)
        room_nick(bud, arg);
-@@ -4162,5 +5094,6 @@
+@@ -4162,5 +5189,6 @@
    }
    mcabber_set_terminate_ui();
  }
@@ -4753,22 +4859,38 @@
  /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2:  For Vim users... */
 diff -r 1b0b563a81e6 mcabber/mcabber/commands.h
 --- a/mcabber/mcabber/commands.h	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/commands.h	Wed Mar 13 17:51:29 2013 +0200
-@@ -5,32 +5,246 @@
++++ b/mcabber/mcabber/commands.h	Mon Mar 18 02:16:22 2013 +0200
+@@ -5,32 +5,338 @@
  
  #include <mcabber/config.h>
  
 -// Command structure
 +//
-+//  TODO:
++//  DONE(?):
 +//
 +// * 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?
++//   - with separate values - just specify the same index
++//   - use the same flags on all of the clustered options, or result may be a bit unpredictable
++// * for now we'll do without multi-options.
++// * checks
++//   - argument type == public struct with name and callbacks, simple!
++//   - command checks should be just single callbacks
++//   - argument 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
++//
++//  TODO:
++//
++// * checkers can use visited flag to check if value is rw
++//   - but what if in-checker assigns ro value, like buddy_getjid()?
++//   - maybe need separate flag for that
++// * now, that we have rjid, merge bjid/fjid checkers into one
++// * Usable documentation in sources/howto
++// * Changelog entry
 +// * --help
 +//   - use argument type names and clarify, if needed in descriptions
 +//   - command/subcommand descriptions
@@ -4789,81 +4911,107 @@
 +//     - defined lists (restrictive)
 +//     - date (restrictive)
 +//     - percent (numerical, restrictive)
-+//   - number (result - int)
++//   - number (result - uint)
 +//   - 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?
++// * Usable subsystem for completion, based on user-supplied completors
++//
++//  XXX:
++//
++// * while escaped aliases should not be expanded, we should still unquote them for command search
++// * allow escaping in options?
++// * merge cmdopt_t and cmdarg_t?
++//   - improve the checker error messages
++//   - extra NULLs to specify in arg definition
++//   - inconvenient positioning of shortopt/longopt or argname in sequence
++//   - merge options/arguments?
++//   - describe values, not opt/args?
++// * command ref and name in special value 0 and indexing starts from 1?
++//   - requires allocation of values always
++//   - though there's not that many commands without arguments
++// * command checker is not quite effective
++//   - I'd like to run it before allocating anything
++//   - so far only one of them
++// * unable to put checker before allocation because in many cases it will be in subcommand.
++//   - separate command/subcommand values and use subcommand callbacks?
++//   - ignore command options and always go directly to subcommand parsing if present?
++//     (i.e. subcommand is always first argument and consumes the end of the line
++// * type name generator callback
++//   for generic types to construct something like "foo|bar|baz"
 +// * [+foo|-foo] support?
-+// * Usable subsystem for completion, based on user-supplied completors
-+
-+// * 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
++// * Subcommands with fallback to argument?
++// * Multivalue options
++//   - list
++//     - special checker
++//     - or run checker multiple times
++//   - or run command several times
++//     - what if there are several of such arguments?
++//
++
++//
++//  Type predeclarations
++//
 +
 +typedef struct cmdopts_struct      cmdopts_t;
 +typedef struct cmdopt_struct       cmdopt_t;
 +typedef struct cmdarg_struct       cmdarg_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 struct cmdarg_value_struct cmdarg_value_t;
++
++//
++//  Callback type definitions
++//
++
++// Command execution environment check function.
++// Note: This is called before options are parsed, but values are already allocated.
++typedef gchar *(*cmd_checker_t)(cmdopts_t *command, cmdarg_value_t *args);
++// Command function itself.
++// Command definition is provided for userdata access, should not be modified.
++typedef gchar *(*cmd_handler_t)(cmdopts_t *command, cmdarg_value_t *args);
++// Should check value -> value.arg and replace, if needed.
++// Can set cmdarg_freeme flag to request type destructor call.
++// Can access argument definition via value -> src (but not modify it).
++typedef gchar *(*cmdarg_checker_t)(cmdarg_value_t *value);
++// Free resources, used in value, if needed.
++typedef void (*cmdarg_destructor_t)(cmdarg_value_t *value);
++// TODO Return possible completions for given argument.
++typedef void (*cmdarg_completor_t)(void);
++
++//
++//  Data type definitions
++//
++
++// command description flags
 +typedef enum {
-+  cmd_default     = 0,     // no flags
-+  cmd_safe        = 1<<0,  // command is safe to use in mcabberrc
++  cmd_default     = 0x0000, // no flags
++  cmd_safe        = 0x0001, // 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;
++// argument description flags
 +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_default  = 0x0000, // no flags
++  cmdarg_catchall = 0x0001, // argument consumes the end of command line
++  cmdarg_plain    = 0x0002, // quotes and escapes are not processed
++  cmdarg_check    = 0x0004, // forse checker call on argument
++  cmdarg_required = 0x0008, // checker errors are fatal (checker may use as well)
++  cmdarg_subcmd   = 0x0010, // argument is subcommand
++  cmdarg_switch   = 0x0020, // option without argument, value is count
++  // shortcuts:
 +  cmdarg_eol      = 0x0003, // catchall + plain
-+  cmdarg_chreq    = 0x0018, // check + required
-+  cmdarg_ppclear  = 0x00E0, // pre-parse clear = visited + checked + freeme
++  cmdarg_chreq    = 0x000C, // check + required
++  cmdarg_special  = 0x0030, // subcmd + switch
 +} cmdarg_flags_t;
-+
++// argument value flags (internal)
++typedef enum {
++  cmdval_default  = 0x0000, // no flags
++  cmdval_visited  = 0x0100, // marks initialized arguments
++  cmdval_freeme   = 0x0200, // marks argument, that needs freeing
++} cmdval_flags_t;
++
++// command description
 +struct cmdopts_struct {
 +  const char    *name;     // [user,req] command name (error messages, help, subcommands)
 +  cmd_flags_t   flags;     // [user,req] safe
@@ -4873,30 +5021,56 @@
 +  cmdarg_t      *args;     // [user,req] arguments
 +  cmdopts_t     *cmds;     // [user,req] subcommands
 +  gpointer      userdata;  // [user]
++  size_t        valno;     // internal, number of values to allocate
 +};
++// positional/option argument description
 +struct cmdarg_struct {
-+  const char     *name;    // [user,req] argument name - errors, help
-+  cmdarg_flags_t flags;    // [user,req] catchall, plain, required, subcommand
-+  const char     *defval;  // [user,req] default value
-+  const 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
++  const char           *name;    // [user,req] argument name - errors, help (unused for switches, but must be initialized)
++  const guint          pos;      // [user,req] value positional number
++  const cmdarg_flags_t flags;    // [user,req] catchall, plain, check, required, subcommand, switch
++  const char           *defval;  // [user,req] default value (unused for switches)
++  const cmdarg_type_t  *type;    // [user,req] type cbs - checker and completor (unused for switches and subcommands)
++  gconstpointer        chkdata;  // [user] instance data for type checker - eg string2enum list (unused for switches and subcommands)
++  gconstpointer        userdata; // [user]
 +};
++// option description
 +struct cmdopt_struct {
-+  char           shortopt; // [user,req]
-+  const char     *longopt; // [user,req]
-+  cmdopt_flags_t flags;    // [user,req] switch
-+  cmdarg_t       arg;      // [user,req]
++  const char           shortopt; // [user,req]
++  const char           *longopt; // [user,req]
++  cmdarg_t             arg;      // [user,req]
 +};
-+
++// argument type description
 +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]
++  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]
 +};
-+
++// argument value
++struct cmdarg_value_struct {
++  cmdarg_t       *src;           // source of value
++  cmdval_flags_t flags;          // visited, freeme
++  union {                        // value:
++    guint        uint;           // - unsigned integer
++    gint         sint;           // - signed integer
++    guint        swc;            // - switch count
++    const gchar  *roarg;         // - XXX default value
++    gchar        *arg;           // - string argument
++    cmdopts_t    *cmd;           // - subcommand
++    struct {                     // - roster jid:
++      gpointer   bud;            //   - buddy struct
++      gchar      *resource;      //   - resource (optional)
++    } rjid;                      // 
++    gpointer     ptr;            // - anything else
++  } value;                       //
++};
++
++//
++//  Public functions
++//
++
++// add command definition
 +void cmd_define (cmdopts_t *command);
++// remove command definition
 +void cmd_undef (cmdopts_t *command);
 +
 +//  error cmdopts_parse_argument ( startptr, endptr, flags )
@@ -4906,14 +5080,17 @@
 +// updated.
 +const char *cmdopts_parse_argument(gchar **pr, gchar **er, cmdarg_flags_t flags);
 +
++// flags for cmd_execute()
 +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;
++// return codes for cmd_execute() and process_line()
 +typedef enum {
 +  cmd_result_ok   = 0,       // all is ok
-+  cmd_result_syntax_error,   // syntax, environment or argument check error
++  cmd_result_syntax_error,   // syntax or environment check error
++  cmd_result_value_error,    // argument value missing or type 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"
@@ -4931,13 +5108,14 @@
 +
 +//  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.
++// and it is sent to the currently selected buddy. The line is converted from
++// local encoding to utf8.
 +// 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);
 +
 +//
-+//  Standard types
++//  Standard argument types
 +//
 +
  typedef struct {
@@ -4961,18 +5139,31 @@
 -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);
-+void cmdarg_free_gfree (cmdarg_t *arg);
-+
-+gchar *cmdarg_check_nonspace        (cmdarg_t *arg);
-+gchar *cmdarg_check_roster_bjid     (cmdarg_t *arg);
-+gchar *cmdarg_check_roster_resource (cmdarg_t *arg);
-+gchar *cmdarg_check_roster_group    (cmdarg_t *arg);
-+gchar *cmdarg_check_fjid            (cmdarg_t *arg);
-+gchar *cmdarg_check_bjid            (cmdarg_t *arg);
-+gchar *cmdarg_check_uint            (cmdarg_t *arg);
-+gchar *cmdarg_check_statusmask      (cmdarg_t *arg);
-+gchar *cmdarg_check_string2enum     (cmdarg_t *arg);
-+
++// destructor, that g_free()s value.arg
++void cmdarg_free_gfree (cmdarg_value_t *arg);
++
++// strips space at the start/end and ensures, that string have non-zero length
++gchar *cmdarg_check_nonspace        (cmdarg_value_t *arg);
++// checks, that jid is in roster and returns buddy
++gchar *cmdarg_check_roster_bjid     (cmdarg_value_t *arg);
++// checks, that jid is in roster and have specified resource, returns buddy and resource
++gchar *cmdarg_check_roster_resource (cmdarg_value_t *arg);
++// checks for group with given name and returns buddy
++gchar *cmdarg_check_roster_group    (cmdarg_value_t *arg);
++// checks correctness of jid syntax
++gchar *cmdarg_check_fjid            (cmdarg_value_t *arg);
++// checks correctness of jid syntax, strips resource
++gchar *cmdarg_check_bjid            (cmdarg_value_t *arg);
++// transforms to guint
++gchar *cmdarg_check_uint            (cmdarg_value_t *arg);
++// checks, that string consists of characters from a given set
++gchar *cmdarg_check_charset         (cmdarg_value_t *arg);
++// checks, that string is from the given list, and returns corresponding guint value
++gchar *cmdarg_check_string2enum     (cmdarg_value_t *arg);
++// checks mcabber color name syntax
++gchar *cmdarg_check_color           (cmdarg_value_t *arg);
++
++// ready for use standard type descriptions
 +const cmdarg_type_t cmdarg_type_nonspace;
 +const cmdarg_type_t cmdarg_type_roster_bjid;
 +const cmdarg_type_t cmdarg_type_roster_resource;
@@ -4980,7 +5171,7 @@
 +const cmdarg_type_t cmdarg_type_fjid;
 +const cmdarg_type_t cmdarg_type_bjid;
 +const cmdarg_type_t cmdarg_type_uint;
-+const cmdarg_type_t cmdarg_type_statusmask;
++const cmdarg_type_t cmdarg_type_charset;
 +const cmdarg_type_t cmdarg_type_string2enum;
 +const cmdarg_type_t cmdarg_type_color;
 +
@@ -4992,38 +5183,45 @@
 +void cmd_uninit (void);
 +
 +#if 0
-+//  error cmdopts_parse_internal ( startptr, endptr, commanddef )
++// return highest index for value, used in command
++size_t cmdopts_count_values (cmdopts_t *command);
++
++// allocate values array to store arguments
++cmdarg_value_t *cmdopts_allocate_values (cmdopts_t *command);
++
++//  error cmdopts_parse_internal ( startptr, endptr, commanddef, values )
 +// 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);
++gchar *cmdopts_parse_internal(gchar **pr, gchar **er, cmdopts_t *command, cmdarg_value_t *values);
++
++// perform type checking/conversion on parsed values
++gchar *cmdopts_check_values (cmdopts_t *command, cmdarg_value_t *values);
++
++//  cmdopts_free_values ( commanddef, values )
++// Free allocated by checkers resources and values array.
++void cmdopts_free_values(cmdopts_t *command, cmdarg_value_t *values);
 +
 +typedef enum {
 +  msgtype_not_set,
 +  msgtype_normal,
 +  msgtype_headline,
 +} msgtype_t;
++
++void say_cmd(char *arg, msgtype_t msgtype);
+ #endif
+-gboolean cmd_is_safe(const gchar *name);
  
  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 1b0b563a81e6 mcabber/mcabber/hooks.c
 --- a/mcabber/mcabber/hooks.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/hooks.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/hooks.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -638,10 +638,9 @@
  
    scr_LogPrint(LPRINT_LOGNORM, "Running hook-post-connect...");
@@ -5052,7 +5250,7 @@
  
 diff -r 1b0b563a81e6 mcabber/mcabber/roster.c
 --- a/mcabber/mcabber/roster.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/roster.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/roster.c	Mon Mar 18 02:16:22 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,
@@ -5093,7 +5291,7 @@
      }
 diff -r 1b0b563a81e6 mcabber/mcabber/screen.c
 --- a/mcabber/mcabber/screen.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/screen.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/screen.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -3630,7 +3630,7 @@
  {
    scr_check_auto_away(TRUE);
@@ -5163,7 +5361,7 @@
    }
 diff -r 1b0b563a81e6 mcabber/mcabber/settings.c
 --- a/mcabber/mcabber/settings.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/settings.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/settings.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -183,28 +183,12 @@
      if ((*line == '\n') || (*line == '\0') || (*line == '#'))
        continue;
@@ -5200,7 +5398,7 @@
    fclose(fp);
 diff -r 1b0b563a81e6 mcabber/mcabber/xmpp_iq.c
 --- a/mcabber/mcabber/xmpp_iq.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/mcabber/xmpp_iq.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/mcabber/xmpp_iq.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -71,20 +71,20 @@
  struct adhoc_status {
    char *name;   // the name used by adhoc
@@ -5249,12 +5447,12 @@
                                                "Status has been changed");
 diff -r 1b0b563a81e6 mcabber/modules/beep/beep.c
 --- a/mcabber/modules/beep/beep.c	Wed Mar 13 16:11:16 2013 +0200
-+++ b/mcabber/modules/beep/beep.c	Wed Mar 13 17:51:29 2013 +0200
++++ b/mcabber/modules/beep/beep.c	Mon Mar 18 02:16:22 2013 +0200
 @@ -31,6 +31,7 @@
  
  static void beep_init   (void);
  static void beep_uninit (void);
-+static gchar *do_beep (cmdopts_t *command);
++static gchar *do_beep (cmdopts_t *command, cmdarg_value_t *values);
  
  /* Module description */
  module_info_t info_beep = {
@@ -5269,7 +5467,7 @@
  
  /* Event handler */
  static guint beep_hh(const gchar *hookname, hk_arg_t *args, gpointer userdata)
-@@ -61,20 +60,38 @@
+@@ -61,20 +60,42 @@
    return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
  }
  
@@ -5284,6 +5482,10 @@
 +  { NULL,      0  },
 +};
 +
++typedef enum {
++  pos_beep_enable = 0,
++} pos_beep_t;
++
 +static cmdopts_t def_beep = {
 +  "beep",
 +  cmd_default,
@@ -5291,7 +5493,7 @@
 +  do_beep,
 +  NULL,
 +  (cmdarg_t[2]){
-+    {"on|off", cmdarg_chreq, "show", &cmdarg_type_string2enum, (gpointer)s2e_onoff},
++    {"on|off", pos_beep_enable, cmdarg_chreq, "show", &cmdarg_type_string2enum, (gpointer)s2e_onoff},
 +    {NULL}
 +  },
 +  NULL
@@ -5299,7 +5501,7 @@
 +
  /* beep command handler */
 -static void do_beep(char *args)
-+static gchar *do_beep(cmdopts_t *command)
++static gchar *do_beep(cmdopts_t *command, cmdarg_value_t *values)
  {
    /* Check arguments, and if recognized,
     * set mcabber option accordingly */
@@ -5307,17 +5509,17 @@
 -      !strcmp(args, "on") ||
 -      !strcmp(args, "yes") ||
 -      !strcmp(args, "1"))
-+  if (command -> args[0].value.uint == 1)
++  if (values[pos_beep_enable].value.uint == 1)
      settings_set(SETTINGS_TYPE_OPTION, "beep_enable", "1");
 -  else if (!strcmp(args, "disable") ||
 -           !strcmp(args, "off") ||
 -           !strcmp(args, "no") ||
 -           !strcmp(args, "0"))
-+  else if (command -> args[0].value.uint == 0)
++  else if (values[pos_beep_enable].value.uint == 0)
      settings_set(SETTINGS_TYPE_OPTION, "beep_enable", "0");
  
    /* Output current state, either if state is
-@@ -83,19 +100,14 @@
+@@ -83,19 +104,14 @@
      scr_log_print(LPRINT_NORMAL, "Beep on messages is enabled");
    else
      scr_log_print(LPRINT_NORMAL, "Beep on messages is disabled");
@@ -5339,7 +5541,7 @@
    /* Add handler
     * We are only interested in incoming message events
     */
-@@ -109,11 +121,7 @@
+@@ -109,11 +125,7 @@
    /* Unregister event handler */
    hk_del_handler(HOOK_POST_MESSAGE_IN, beep_hid);
    /* Unregister command */