Fully object forms interface (untested)
authorMyhailo Danylenko <isbear@ukrpost.net>
Sat, 28 Mar 2009 19:43:12 +0200
changeset 67 d33ca5572e91
parent 66 542f61e113cb
child 68 742878c74b8e
Fully object forms interface (untested)
examples/form_field.lua
examples/iq_register.lua
examples/lua.rc
examples/mc_forms.lua
examples/mc_pubsub.lua
examples/mc_register.lua
examples/mc_remote.lua
examples/mc_tune.lua
examples/mc_vcard.lua
examples/mcabberrc.lua
examples/pubsub.lua
examples/remote.lua
examples/vcard.lua
examples/x_data.lua
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/form_field.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -0,0 +1,99 @@
+
+-- DATA FORM FIELD
+
+local F = { }
+local M = { }
+M.__index = M
+
+-- field.new      -- create new field
+-- field:index    -- get index
+-- field:type     -- get type
+-- field:name     -- get var
+-- field:desc     -- get label, desc
+-- field:value    -- get/set value
+-- field:values   -- get values
+-- field:clear    -- clear value
+-- field:options  -- get options
+-- field:required -- get required
+
+function F.new ( args )
+	local obj = {
+		t = args.type,
+		l = args.label,
+		d = args.desc,
+		o = args.options,  -- XXX
+		v = args.value,    -- XXX
+		r = args.required,
+		n = args.var,
+		i = args.index,    -- XXX
+	}
+	setmetatable ( obj, M )
+	return obj
+end
+
+function M.index ( obj )
+	return obj.i
+end
+
+function M.type ( obj )
+	return obj.t
+end
+
+function M.name ( obj )
+	return obj.n
+end
+
+function M.desc ( obj )
+	return obj.l, obj.d
+end
+
+function M.options ( obj )
+	return pairs ( obj.o )
+end
+
+function M.value ( obj, value )
+	local ftype = obj.t
+	if value == nil then
+		-- XXX
+		return obj.v
+	else
+		if ftype == 'jid-multi' or ftype == 'list-multi' or ftype == 'text-multi' then
+			table.insert ( obj.v, value )
+		else
+			obj.v = value
+		end
+	end
+end
+
+function M.values ( obj )
+	local ftype = obj.t
+	if ftype == 'jid-multi' or ftype == 'list-multi' or ftype == 'text-multi' then
+		return ipairs ( obj.v )
+	else
+		return
+			function ( arg )
+				if not arg then
+					return obj.v
+				else
+					return nil
+				end
+			end, nil
+	end
+end
+
+function M.clear ( obj )
+	local ftype = obj.t
+	if ftype == 'jid-multi' or ftype == 'list-multi' or ftype == 'text-multi' then
+		obj.v = { }
+	else
+		obj.v = ''
+	end
+end
+
+function M.required ( obj )
+	return obj.r
+end
+
+return F
+
+-- vim: se ts=4: --
--- a/examples/iq_register.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/iq_register.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -3,60 +3,127 @@
 
 -- library
 
-local lm     = require 'lm'
-local iq     = require 'iq'
---local x_data =
-require 'x_data'
+local iq         = require 'iq'
+local x_data     = require 'x_data'
+local form_field = require 'form_field'
+
+--
+
+local F = { }
+local M = { }
+M.__index = M
 
--- public
-
-iq_register = { }
+function F.new ( args )
+	local form = {
+		xmlns        = 'jabber:iq:register',
+		ftype        = args.type or 'form',
+		registered   = args.registered,
+		-- XXX title
+		instructions = args.instructions,
+		x_data       = args.x_data,
+		f            = { },
+	}
+	setmetatable ( form, M )
+	return form
+end
 
-function iq_register.parse ( query )
-	local form  = { xmlns = 'jabber:iq:register', type = 'form' }
-
+function F.parse ( query )
 	local instructions = query:child ( 'instructions' )
 	if instructions then
-		form.instructions = instructions:value ()
+		instructions = instructions:value ()
 	end
-	-- XXX how it can be mapped to common form?
-	--     and needs it be supplied?
+	
+	local registered
 	if query:child ( 'registered' ) then
-		form.registered = true
+		registered = true
 	end
 
 	local x = query:child ( 'x' )
 	if x and x:attribute ( 'xmlns' ) == 'jabber:x:data' then
-		form = x_data.parse ( x )
-		local format = form.format
-		form.format =
-			function ( form, root )
-				root.query = format ( form, { xmlns = 'jabber:iq:register' } )
-				return root
-			end
-		return form
+		return F.new { type = 'form', registered = registered, instructions = instructions, x_data = x_data.parse ( x ) }
 	end
 
+	local form = F.new { type = 'form', registered = registered, instructions = instructions }
+
 	local field = query:child ()
 	while field do
 		local name  = field:name ()
 		if name ~= 'instructions' and name ~= 'registered' then
-			table.insert ( form, { type = 'text-single', var = name, value = field:value () or '' } )
+			form:add ( name, { type = 'text-single', value = field:value () or '' } )
 		end
 		field = field:next ()
 	end
-	form.format =
-		function ( form, root )
-			root.query = { xmlns = 'jabber:iq:register' }
-			for index, field in ipairs ( form ) do
-				root.query[field.var] = field.value
-			end
-			return root
-		end
+
 	return form
 end
 
-function iq_register.register ( conn, to, success, fail )
+function M.type ( form )
+	return form.ftype
+end
+
+function M.desc ( form )
+	if form.x_data then
+		return form.x_data:desc ()
+	else
+		return form.title, form.instructions
+	end
+end
+
+function M.format ( form, root, format_as )
+	local ft = format_as or form:type ()
+
+	if form.x_data then
+		root.query = form.x_data:format ( { xmlns = 'jabber:iq:register' }, ft )
+	else
+		root.query = { xmlns = 'jabber:iq:register' }
+		for index, field in form:fields () do
+			root.query[field:name ()] = field:value ()
+		end
+	end
+
+	if ft == 'form' then
+		local title, instructions = form:desc ()
+		if instructions then
+			root.query.instructions = { instructions }
+		end
+		if form.registered then
+			root.query.registered = { }
+		end
+	end
+
+	return root
+end
+
+function M.fields ( form )
+	if form.x_data then
+		return form.x_data:fields ()
+	else
+		return ipairs ( form.f )
+	end
+end
+
+function M.add ( form, name, fld )
+	if form.x_data then
+		return form.x_data:add ( name, fld )
+	else
+		fld.var   = name
+		fld.index = #form.f + 1
+		local obj = form_field.new ( fld )
+		table.insert ( form.f, obj )
+		form.f[name] = obj
+		return obj
+	end
+end
+
+function M.field ( form, name )
+	if form.x_data then
+		return form.x_data:field ( name )
+	else
+		return form.f[name]
+	end
+end
+
+function F.register ( conn, to, success, fail )
 	iq.send ( conn, to, 'get',
 		{
 			query = { xmlns = 'jabber:iq:register' },
@@ -64,10 +131,9 @@
 		function ( mess )
 			local query = mess:child ( 'query' )
 			if query and query:attribute ( 'xmlns' ) == 'jabber:iq:register' then
-				success ( iq_register.parse ( query ),
+				success ( F.parse ( query ),
 					function ( form, success, fail )
-						form.type = 'submit' -- XXX
-						iq.send ( conn, to, 'set', form.format ( form, { } ), success , fail )
+						iq.send ( conn, to, 'set', form:format ( form, { }, 'submit' ), success , fail )
 					end,
 					function ( form, success, fail )
 						success ()
@@ -76,7 +142,7 @@
 		end, fail )
 end
 
-function iq_register.unregister ( conn, to, success, fail )
+function F.unregister ( conn, to, success, fail )
 	iq.send ( conn, to, 'set',
 		{
 			query = { xmlns = 'jabber:iq:register',
@@ -89,10 +155,9 @@
 		function ( mesg, mess )
 			local query = mess:child ( 'query' )
 			if query and query:attribute ( 'xmlns' ) == 'jabber:iq:register' then
-				success ( iq_register.parse ( query ),
+				success ( F.parse ( query ),
 					function ( form, success, fail )
-						form.type = 'submit' -- XXX
-						iq.send ( conn, to, 'set', form.format ( form, { } ), success, fail )
+						iq.send ( conn, to, 'set', form:format ( form, { }, 'submit' ), success, fail )
 					end,
 					function ( form, success, fail )
 						success ()
@@ -103,94 +168,6 @@
 		end )
 end
 
--- mcabber
-
-main.command ( 'register',
-	function ( args )
-		local who
-		if args and args ~= '' then
-			who = args
-		else
-			who = main.full_jid ()
-		end
-		iq_register.register ( lm.connection.bless ( main.connection () ), who,
-			function ( form, submit, reject )
-				local id = #forms + 1
-				forms[id] = {
-					form   = form,
-					submit =
-						function ( form )
-							submit ( form,
-								function ()
-									main.print_info ( who, 'Successfully registered' )
-								end,
-								function ( mesg )
-									main.print_info ( who, 'Registration failed: ' .. mesg )
-								end )
-						end,
-					reject =
-						function ( form )
-							reject ( form,
-								function ()
-									main.print_info ( who, 'Registration cancelled' )
-								end,
-								function ( mesg )
-									main.print_info ( who, 'Registration cancellation failed: ' .. mesg )
-								end )
-						end,
-				}
-				print ( 'You have new form ' .. id )
-			end,
-			function ( mesg )
-				main.print_info ( who, 'Registration failed: ' .. mesg )
-			end )
-	end, false, 'jid' )
-main.command ( 'cancel',
-	function ( args )
-		local who
-		if args and args ~= '' then
-			who = args
-		else
-			who = main.full_jid ()
-		end
-		iq_register.unregister ( lm.connection.bless ( main.connection () ), who,
-			function ( form, submit, reject )
-				if not form then
-					main.print_info ( who, 'Successfully unregistered' )
-				else
-					local id = #forms + 1
-					forms[id] = {
-						form   = form,
-						submit =
-							function ( form )
-								submit ( form,
-									function ()
-										main.print_info ( who, 'Successfully unregistered' )
-									end,
-									function ( mesg )
-										main.print_info ( who, 'Unregistrering failed: ' .. mesg )
-									end )
-							end,
-						reject =
-							function ( form )
-								reject ( form,
-									function ()
-										main.print_info ( who, 'Unregistration cancelled' )
-									end,
-									function ( mesg )
-										main.print_info ( who, 'Unregistration cancellation failed: ' .. mesg )
-									end )
-							end,
-					}
-					print ( 'You have new form ' .. id )
-				end
-			end,
-			function ( mesg )
-				main.print_info ( who, 'Unregistering failed: ' .. mesg )
-			end )
-	end, false, 'jid' )
-
-commands_help['register'] = "[jid]\n\nSends registration request to jid (or current buddy). You, probably, then will need to fill and send some form."
-commands_help['cancel'] = "[jid]\n\nSends registration cancellation request to jid (or current buddy). May require a form filling."
+return F
 
 -- vim: se ts=4: --
--- a/examples/lua.rc	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/lua.rc	Sat Mar 28 19:43:12 2009 +0200
@@ -6,7 +6,7 @@
 set lua_hook_function    = hook_handler
 
 # enable lua-loudmouth debug output to be shown in log
-set lua_lm_debug         = 0
+set lua_lm_debug         = 1
 
 # show notifications for pep events
 set lua_pep_notification = enable
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/mc_forms.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -0,0 +1,114 @@
+
+local form_cid = main.add_category { 'del', 'send' }
+local forms = { }
+
+-- public
+function insert_form ( form, submit, reject )
+	table.insert ( forms, {
+			form   = form,
+			submit = submit,
+			reject = reject,
+		} )
+	main.add_completion ( form_cid, tostring(#forms) )
+	print ( "You have new form. Use /form " .. #forms .. " to fill and submit or cancel it." )
+	return #forms
+end
+
+main.command ( 'form',
+	function ( args )
+		local id, action = tonumber (args[1]), args[2]
+		if forms[id] then
+			local form = forms[id].form
+			if action == 'send' then
+				forms[id].submit ( form )
+			elseif action == 'reject' then
+				forms[id].reject ( form )
+			elseif action == 'del' then
+				main.del_completion ( form_cid, id )
+				forms[id] = nil
+			elseif action then
+				local fname, value
+				if action == 'set' then
+					fname = args[3]
+					value = args[4]
+				else
+					fname = action
+					value = args[3]
+				end
+				local field = form:field ( fname )
+				if field then
+					if value then
+						field:value ( value )
+					else
+						field:clear ()
+					end
+				else
+					-- XXX create?
+					print ( 'Field not found: ' .. fname )
+				end
+			else
+				local desc = 'Form ' .. id .. '\n - Type: ' .. form:type ()
+				local title, instructions = form:desc ()
+				if title then
+					desc = desc .. '\n - Title: ' .. title
+				end
+				if instructions then
+					desc = desc .. '\n - Instructions: ' .. instructions
+				end
+
+				local fields = 'Fields:'
+				for index, field in form:fields () do
+					local ftype = field:type ()
+
+					fields = fields .. '\n - '
+					
+					if field:required () then
+						fields = fields .. '* '
+					end
+
+					local label, descr = field:desc ()
+					if label then
+						fields = fields .. label .. ' (' .. ( field.var or '' ) .. ')'
+					else
+						fields = fields .. ( field:name () or '<no name>' )
+					end
+					fields = fields .. ' [' .. ftype .. ']'
+					if descr then
+						fields = fields .. ': ' .. descr
+					end
+
+					if ftype == 'list-single' or ftype == 'list-multi' then
+						fields = fields .. '\n   Options:'
+						for option, label in field:options () do
+							fields = fields .. '\n    * ' .. option .. ' - ' .. label
+						end
+					end
+					
+					if ftype == 'list-multi' or ftype == 'text-multi' or ftype == 'jid-multi' then
+						fields = fields .. '\n   Values: '
+						for vin, value in field:values () do
+							fields = fields .. '\n    * ' .. value
+						end
+					else
+						fields = fields .. '\n   Value: ' .. ( field:value () or '' )
+					end
+				end
+				print ( desc .. '\n' .. fields )
+			end
+		else
+			local text = ''
+			for id, form in pairs ( forms ) do
+				local title = form.form:desc ()
+				text = text .. '\n - ' .. id .. ' ' .. ( title or 'Untitled' ) .. ' [' .. form.form:type () .. ']'
+			end
+			if text ~= '' then
+				print ( 'Forms list:' .. text )
+			else
+				print ( 'No forms' )
+			end
+		end
+	end, true, form_cid )
+
+commands_help['form'] = "[form_id [send | reject | [set] fieldname [value]]]\n\nWithout arguments prints form list.\nWith bare form id prints info on that form.\nWhen setting multivalue field, new values are added, not replacing previous.\nWithout value unsets field, multivalue fields lose all their values."
+
+-- vim: se ts=4: --
--- a/examples/mc_pubsub.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/mc_pubsub.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -68,31 +68,25 @@
 		elseif action:sub ( 1, 4 ) == 'conf' then
 			pubsub.configure_node ( conn, who, node,
 				function ( form, submit, reject )
-					local id = #forms + 1
-					forms[id] = {
-						form = form,
-						submit =
-							function ( form )
-								submit ( form,
-									function ()
-										main.print_info ( who, 'Node configuration completed' )
-									end,
-									function ( mesg )
-										main.print_info ( who, 'Node configuration failed: ' .. mesg )
-									end )
-							end,
-						reject =
-							function ( form )
-								reject ( form,
-									function ()
-										main.print_info ( who, 'Node configuration cancelled' )
-									end,
-									function ( mesg )
-										main.print_info ( who, 'Node configuration cancellation failed: ' .. mesg )
-									end )
-							end,
-					}
-					print ( 'You have new form ' .. id )
+					insert_form ( form,
+						function ( form )
+							submit ( form,
+								function ()
+									main.print_info ( who, 'Node configuration completed' )
+								end,
+								function ( mesg )
+									main.print_info ( who, 'Node configuration failed: ' .. mesg )
+								end )
+						end,
+						function ( form )
+							reject ( form,
+								function ()
+									main.print_info ( who, 'Node configuration cancelled' )
+								end,
+								function ( mesg )
+									main.print_info ( who, 'Node configuration cancellation failed: ' .. mesg )
+								end )
+						end )
 				end,
 				function ( mesg )
 					main.print_info ( who, 'Node configuration failed: ' .. mesg )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/mc_register.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -0,0 +1,81 @@
+
+local lm          = require 'lm'
+local iq_register = require 'iq_register'
+
+main.command ( 'register',
+	function ( args )
+		local who
+		if args and args ~= '' then
+			who = args
+		else
+			who = main.full_jid ()
+		end
+		iq_register.register ( lm.connection.bless ( main.connection () ), who,
+			function ( form, submit, reject )
+				insert_form ( form,
+					function ( form )
+						submit ( form,
+							function ()
+								main.print_info ( who, 'Successfully registered' )
+							end,
+							function ( mesg )
+								main.print_info ( who, 'Registration failed: ' .. mesg )
+							end )
+					end,
+					function ( form )
+						reject ( form,
+							function ()
+								main.print_info ( who, 'Registration cancelled' )
+							end,
+							function ( mesg )
+								main.print_info ( who, 'Registration cancellation failed: ' .. mesg )
+							end )
+					end )
+			end,
+			function ( mesg )
+				main.print_info ( who, 'Registration failed: ' .. mesg )
+			end )
+	end, false, 'jid' )
+main.command ( 'cancel',
+	function ( args )
+		local who
+		if args and args ~= '' then
+			who = args
+		else
+			who = main.full_jid ()
+		end
+		iq_register.unregister ( lm.connection.bless ( main.connection () ), who,
+			function ( form, submit, reject )
+				if not form then
+					main.print_info ( who, 'Successfully unregistered' )
+				else
+					insert_form ( form,
+						function ( form )
+							submit ( form,
+								function ()
+									main.print_info ( who, 'Successfully unregistered' )
+								end,
+								function ( mesg )
+									main.print_info ( who, 'Unregistrering failed: ' .. mesg )
+								end )
+						end,
+						function ( form )
+							reject ( form,
+								function ()
+									main.print_info ( who, 'Unregistration cancelled' )
+								end,
+								function ( mesg )
+									main.print_info ( who, 'Unregistration cancellation failed: ' .. mesg )
+								end )
+						end )
+				end
+			end,
+			function ( mesg )
+				main.print_info ( who, 'Unregistering failed: ' .. mesg )
+			end )
+	end, false, 'jid' )
+
+commands_help['register'] = "[jid]\n\nSends registration request to jid (or current buddy). You, probably, then will need to fill and send some form."
+commands_help['cancel'] = "[jid]\n\nSends registration cancellation request to jid (or current buddy). May require a form filling."
+
+-- vim: se ts=4: --
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/mc_remote.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -0,0 +1,66 @@
+
+local lm     = require 'lm'
+local remote = require 'remote'
+
+main.command ( 'remote',
+	function ( args )
+		local who
+		if args.t then
+			who = args.t
+		else
+			who = main.full_jid ()
+		end
+		local action = args[1]
+		local conn   = lm.connection.bless ( main.connection () )
+		if action then
+			remote.command ( conn, who, action,
+				function ( form, submit, reject )
+					if not form then
+						main.print_info ( who, ('Command %s completed'):format ( action ) )
+					else
+						insert_form ( form, -- XXX
+							function ( form )
+								submit ( form,
+									function ()
+										main.print_info ( who, ('Command %s completed'):format ( action ) )
+									end,
+									function ( mesg )
+										main.print_info ( who, ('Command %s execution failed: %s'):format ( action, mesg ) )
+									end )
+							end,
+							function ( form )
+								reject ( form,
+									function ()
+										main.print_info ( who, ('Command %s execution cancelled'):format ( action ) )
+									end,
+									function ( mesg )
+										main.print_info ( who, ('Command %s execution cancellation failed: %s'):format ( action, mesg ) )
+									end )
+							end )
+					end
+				end,
+				function ( mesg )
+					main.print_info ( who, ('Command %s execution failed: %s'):format ( action, mesg ) )
+				end )
+		else
+			remote.list ( conn, who,
+				function ( items )
+					local text = ''
+					for index, item in ipairs ( items ) do
+						text = text .. '\n - ' .. item.node
+					end
+					if text ~= '' then
+						main.print_info ( who, 'Available commands:' .. text )
+					else
+						main.print_info ( who, 'No commands available.' )
+					end
+				end,
+				function ( mesg )
+					main.print_info ( who, ("Remote commands list for %s failed: %s"):format ( who, mesg ) )
+				end )
+		end
+	end, true, 'jid' )
+
+commands_help['remote'] = "[-t target_jid] [remote_command]\n\nPrints list of available remote command or requests execution of specified command."
+
+-- vim: se ts=4: --
--- a/examples/mc_tune.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/mc_tune.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -85,7 +85,7 @@
 local function mpd_callback ()
 	local sdata = mpd_getstatus ()
 	if sdata then
-		pep.publish ( lm.connection.bless ( main.connection () ), 'http://jabber.org/protocol/tune',
+		tune.publish ( lm.connection.bless ( main.connection () ),
 			function ()
 			end,
 			function ( mesg )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/mc_vcard.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -0,0 +1,34 @@
+
+local lm    = require 'lm'
+local vcard = require 'vcard'
+
+main.command ( 'vcard-temp',
+	function ( args )
+		vcard.retrieve ( lm.connection.bless ( main.connection () ), args[1],
+			function ( form, submit, reject )
+				insert_form ( form,
+					function ( form )
+						submit ( form,
+							function ()
+								print ( 'Vcard changed' )
+							end,
+							function ( mesg )
+								print ( 'Vcard changing error: ' .. mesg )
+							end )
+					end,
+					function ( form )
+						reject ( form,
+							function ()
+								print ( 'Vcard changing cancelled' )
+							end,
+							function ( mesg )
+								print ( 'Vcard changing cancellation error: ' .. mesg )
+							end )
+					end )
+			end,
+			function ( mesg )
+				print ( 'Vcard obtaining error: ' .. mesg )
+			end )
+	end, true, 'jid' )
+
+-- vim: se ts=4: --
--- a/examples/mcabberrc.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/mcabberrc.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -255,9 +255,11 @@
 
 require 'room_priv'
 
--- DATA FORMS (XEP-0004)
+-- FORMS
 
-require 'x_data'
+require 'mc_forms'
+
+-- DATA FORMS (XEP-0004)
 
 -- SERVICE DISCOVERY (XEP-0030)
 
@@ -269,7 +271,7 @@
 
 -- VCARD-TEMP (XEP-0054)
 
-require 'vcard'
+require 'mc_vcard'
 
 -- PUBLISH-SUBSRIBE (XEP-0060)
 
@@ -285,7 +287,7 @@
 
 -- IN-BAND REGISTRATION (XEP-0077)
 
-require 'iq_register'
+require 'mc_register'
 
 -- USER LOCATION (XEP-0080)
 
@@ -309,7 +311,7 @@
 
 -- REMOTE CONTROLLING CLIENTS (XEP-0146)
 
-require 'remote'
+require 'mc_remote'
 
 -- PERSONAL EVENTING PROTOCOL (XEP-0163)
 
--- a/examples/pubsub.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/pubsub.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -3,9 +3,8 @@
 
 -- library
 
-local lm = require 'lm'
-local iq = require 'iq'
-require 'x_data'
+local iq     = require 'iq'
+local x_data = require 'x_data'
 
 --
 
@@ -163,11 +162,10 @@
 			if x then
 				success ( x_data.parse ( x ),
 					function ( form, success, fail )
-						form.type = 'submit'
 						iq.send ( conn, to, 'set',
 							{
 								pubsub = { xmlns = 'http://jabber.org/protocol/pubsub#owner',
-									configure = form.format ( form, { node = node } ),
+									configure = form:format ( { node = node }, 'submit' ),
 								},
 							}, success, fail )
 					end,
@@ -175,9 +173,7 @@
 						iq.send ( conn, to, 'set',
 							{
 								pubsub = { xmlns = 'http://jabber.org/protocol/pubsub#owner',
-									configure = { node = node,
-										x = { xmlns = 'jabber:x:data', type = 'cancel' }, -- FIXME
-									},
+									configure = form:format ( { node = node }, 'cancel' ),
 								},
 							}, success, fail )
 					end )
--- a/examples/remote.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/remote.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -3,20 +3,19 @@
 
 -- library
 
-local lm    = require 'lm'
-local iq    = require 'iq'
-require 'x_data'
-local disco = require 'disco'
+local iq     = require 'iq'
+local x_data = require 'x_data'
+local disco  = require 'disco'
 
 -- public
 
-remote = { }
+local F = { }
 
-function remote.list ( conn, to, success, fail )
+function F.list ( conn, to, success, fail )
 	disco.items ( conn, to, success, fail, 'http://jabber.org/protocol/commands' )
 end
 
-function remote.command ( conn, to, command, success, fail )
+function F.command ( conn, to, command, success, fail )
 	iq.send ( conn, to, 'set',
 		{
 			command = { xmlns = 'http://jabber.org/protocol/commands', action = 'execute', node = command },
@@ -33,26 +32,23 @@
 						local sid = c:attribute ( 'sessionid' )
 						success ( x_data.parse ( x ),
 							function ( form, success, fail )
-								form.type = 'submit' -- XXX in standard there is 'form' :/
 								iq.send ( conn, to, 'set',
 									{
-										command = form.format ( form, { xmlns = 'http://jabber.org/protocol/commands', node = command, sessionid = sid } )
+										command = form:format ( { xmlns = 'http://jabber.org/protocol/commands', node = command, sessionid = sid }, 'submit' ),
 									},
 									function ( mess )
 										local c = mess:child ( 'command' )
 										if c and c:attribute ( 'status' ) == 'completed' then
 											success ()
 										else
-											fail ( mess:xml () ) -- XXX more forms?
+											fail ( mess:xml () ) -- XXX more forms? results?
 										end
 									end, fail )
 							end,
 							function ( form, success, fail )
 								iq.send ( conn, to, 'set',
 									{
-										command = { xmlns = 'http://jabber.org/protocol/commands', node = command, sessionid = sid,
-											x = { xmlns = 'jabber:x:data', type = 'cancel' }, -- FIXME
-										},
+										command = form:format ( { xmlns = 'http://jabber.org/protocol/commands', node = command, sessionid = sid }, 'cancel' ),
 									}, success, fail )
 							end )
 					else
@@ -63,73 +59,6 @@
 		end, fail )
 end
 
--- mcabber
-
-main.command ( 'remote',
-	function ( args )
-		local who
-		if args.t then
-			who = args.t
-		else
-			who = main.full_jid ()
-		end
-		local action = args[1]
-		local conn   = lm.connection.bless ( main.connection () )
-		if action then
-			remote.command ( conn, who, action,
-				function ( form, submit, reject )
-					if not form then
-						main.print_info ( who, ('Command %s completed'):format ( action ) )
-					else
-						local id = #forms + 1
-						forms[id] = {
-							form = form,
-							submit =
-								function ( form )
-									submit ( form,
-										function ()
-											main.print_info ( who, ('Command %s completed'):format ( action ) )
-										end,
-										function ( mesg )
-											main.print_info ( who, ('Command %s execution failed: %s'):format ( action, mesg ) )
-										end )
-								end,
-							reject =
-								function ( form )
-									reject ( form,
-										function ()
-											main.print_info ( who, ('Command %s execution cancelled'):format ( action ) )
-										end,
-										function ( mesg )
-											main.print_info ( who, ('Command %s execution cancellation failed: %s'):format ( action, mesg ) )
-										end )
-								end,
-						}
-						print ( 'You have new form ' .. id )
-					end
-				end,
-				function ( mesg )
-					main.print_info ( who, ('Command %s execution failed: %s'):format ( action, mesg ) )
-				end )
-		else
-			remote.list ( conn, who,
-				function ( items )
-					local text = ''
-					for index, item in ipairs ( items ) do
-						text = text .. '\n - ' .. item.node
-					end
-					if text ~= '' then
-						main.print_info ( who, 'Available commands:' .. text )
-					else
-						main.print_info ( who, 'No commands available.' )
-					end
-				end,
-				function ( mesg )
-					main.print_info ( who, ("Remote commands list for %s failed: %s"):format ( who, mesg ) )
-				end )
-		end
-	end, true, 'jid' )
-
-commands_help['remote'] = "[-t target_jid] [remote_command]\n\nPrints list of available remote command or requests execution of specified command."
+return F
 
 -- vim: se ts=4: --
--- a/examples/vcard.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/vcard.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -3,48 +3,86 @@
 
 -- library
 
-local lm = require 'lm'
-local iq = require 'iq'
-require 'x_data'
+local iq         = require 'iq'
+local form_field = require 'form_field'
+
+--
+
+local F = { }
+local M = { }
+M.__index = M
 
--- public
+function F.new ( args )
+	local form = {
+		xmlns        = 'vcard-temp',
+		ftype        = args.type or 'form',
+		title        = args.title,
+		instructions = args.instructions,
+		f            = { },
+	}
+	setmetatable ( form, M )
+	return form
+end
 
-vcard = { }
-
-local function vcard_parse ( node, path, ret )
+local function vcard_parse ( node, path, form )
 	local item = node:child ()
 	if item then
 		while item do
-			vcard_parse ( item, ( path and path .. '/' or '' ) .. item:name (), ret )
+			vcard_parse ( item, ( path and path .. '/' or '' ) .. item:name (), form )
 			item = item:next ()
 		end
 	elseif path then
-		table.insert ( ret, { var = path, type = 'text-single', value = node:value () } )
+		form:add ( path, { type = 'text-single', value = node:value () } )
 	end
 end
 
-function vcard.parse ( card )
-	local form = { xmlns = 'vcard-temp', type = 'form' }
+function F.parse ( card )
+	local form = F.new { type = 'form' }
 	vcard_parse ( card, nil, form )
-	form.format =
-		function ( form, root )
-			root.vCard = { xmlns = 'vcard-temp' }
-			for k, field in ipairs ( form ) do
-				local el = root.vCard
-				for k in field.var:gmatch ( '[^/]+' ) do
-					if not el[k] then
-						el[k] = { }
-					end
-					el = el[k]
-				end
-				el[1] = field.value
-			end
-			return root
-		end
 	return form
 end
 
-function vcard.get ( conn, from, success, fail )
+function M.type ( form )
+	return form.ftype
+end
+
+function M.desc ( form )
+	return form.title, form.instructions
+end
+
+function M.format ( form, root, format_as )
+	root.vCard = { xmlns = 'vcard-temp' }
+	for k, field in form:fields () do
+		local el = root.vCard
+		for k in field:name():gmatch ( '[^/]+' ) do
+			if not el[k] then
+				el[k] = { }
+			end
+			el = el[k]
+		end
+		el[1] = field:value ()
+	end
+	return root
+end
+
+function M.fields ( form )
+	return ipairs ( form.f )
+end
+
+function M.add ( form, name, fld )
+	fld.var   = name
+	fld.index = #form.f + 1
+	local obj = form_field.new ( fld )
+	table.insert ( form.f, obj )
+	form.f[name] = obj
+	return obj
+end
+
+function M.field ( form, name )
+	return form.f[name]
+end
+
+function F.retrieve ( conn, from, success, fail )
 	iq.send ( conn, from, 'get',
 		{
 			vCard = { xmlns = 'vcard-temp' },
@@ -52,10 +90,9 @@
 		function ( mess )
 			local card = mess:child ( 'vCard' )
 			if card and card:attribute ( 'xmlns' ) == 'vcard-temp' then
-				success ( vcard.parse ( mess:child ( 'vCard' ) ),
+				success ( F.parse ( mess:child ( 'vCard' ) ),
 					function ( form, success, fail )
-						form.type = 'submit' -- :)
-						iq.send ( conn, from, 'set', form.format ( form, { } ), success, fail )
+						iq.send ( conn, from, 'set', form:format ( form, { }, 'submit' ), success, fail )
 					end,
 					function ( form, success, fail )
 						success ()
@@ -66,41 +103,6 @@
 		end, fail )
 end
 
--- mcabber
-
-main.command ( 'vcard-temp',
-	function ( args )
-		vcard.get ( lm.connection.bless ( main.connection () ), args[1],
-			function ( form, submit, reject )
-				local id = #forms + 1
-				forms[id] = {
-					form = form,
-					submit =
-						function ( form )
-							submit ( form,
-								function ()
-									print ( 'Vcard changed' )
-								end,
-								function ( mesg )
-									print ( 'Vcard changing error: ' .. mesg )
-								end )
-						end,
-					reject =
-						function ( form )
-							reject ( form,
-								function ()
-									print ( 'Vcard changing cancelled' )
-								end,
-								function ( mesg )
-									print ( 'Vcard changing cancellation error: ' .. mesg )
-								end )
-						end,
-				}
-				print ( 'You have new form ' .. id )
-			end,
-			function ( mesg )
-				print ( 'Vcard obtaining error: ' .. mesg )
-			end )
-	end, true, 'jid' )
+return F
 
 -- vim: se ts=4: --
--- a/examples/x_data.lua	Fri Mar 27 12:06:19 2009 +0200
+++ b/examples/x_data.lua	Sat Mar 28 19:43:12 2009 +0200
@@ -3,64 +3,80 @@
 
 -- library
 
-local lm = require 'lm'
+local form_field = require 'form_field'
 
--- public
+--
 
-x_data = { }
-
--- iq_register should encapsulate x_data in results, so, looks like we need callbacks again?
--- though, it can redefine method... but it is not too pretty. for now it will be so.
-
--- result needs separate routine? (reported & item's)
+local F = { }
+local M = { }
+M.__index = M
 
-function x_data.format ( form, root )
-	root.x = { xmlns = 'jabber:x:data', type = form.type }
-	local fields = { }
-	for i, field in ipairs ( form ) do
-		if field.type ~= 'fixed' then
-			local value
-			if field.type == 'list-multi' or field.type == 'text-multi' or field.type == 'jid-multi' then
-				value = { }
-				for j, v in ipairs ( field.value ) do
-					table.insert ( value, { v } )
-				end
-			else
-				value = { field.value }
-			end
-			table.insert ( fields, { type = field.type, var = field.var, value = value } )
-		end
-	end
-	root.x.field = fields
-	return root
+-- form.new       -- new form
+-- form.parse     -- parse existing form -- according to form type!
+-- form:type      -- get type
+-- form:desc      -- get title, instructions
+-- form:format    -- format form -- according to form type!
+-- form:fields    -- iterator, returns field objects
+-- form:add       -- create field
+-- form:field     -- get field
+-- ?? form:del    -- delete field
+-- field.new      -- create field
+-- field:index    -- get index
+-- field:type     -- get type
+-- field:name     -- get var
+-- field:desc     -- get label, desc
+-- field:value    -- get/set value
+-- field:values   -- get values
+-- field:clear    -- clear value
+-- field:options  -- get options
+-- field:required -- get required
+
+-- form    may have 'fixed' fields and should be ordered
+-- submit  must not have 'fixed' fields and may be not ordered
+-- cancel  must not have anything
+-- result  should not have 'fixed', may be not ordered and can contain reported/item... multiple instances.
+
+function F.new ( args )
+	local form  = {
+		xmlns        = 'jabber:x:data',
+		ftype        = args.type or 'form',
+		title        = args.title,
+		instructions = args.instructions,
+		f            = { },
+	}
+	setmetatable ( form, M )
+	return form
 end
 
-function x_data.parse ( x )
-	local f = { xmlns = 'jabber:x:data', type = x:attribute ( 'type' ) }
-
+function F.parse ( x )
 	local title = x:child ( 'title' )
 	if title then
-		f.title = title:value ()
+		title = title:value ()
 	end
+
 	local instructions = x:child ( 'instructions' )
 	if instructions then
-		f.instructions = instructions:value ()
+		instructions = instructions:value ()
 	end
 
+	local f = F.new { type = x:attribute ( 'type' ), title = title, instructions = instructions }
+
 	local field = x:child ()
 	while field do
-		if field:name () == 'field' then
+		local name = field:name ()
+		if name == 'field' then
 			local ftype = field:attribute ( 'type' ) or 'text-single'
+			local var   = field:attribute ( 'var' )
 			local r = {
 				type  = ftype,
-				var   = field:attribute ( 'var' ), -- may be absent on 'fixed' fields
 				label = field:attribute ( 'label' ),
 			}
 
 			local desc = field:child ( 'desc' )
 			if desc then
-				r.decs = desc:value ()
+				r.desc = desc:value ()
 			end
+
 			if field:child ( 'required' ) then
 				r.required = true
 			end
@@ -75,7 +91,7 @@
 					if name == 'value' then
 						table.insert ( values, item:value () )
 					elseif name == 'option' then
-						table.insert ( options, item:child( 'value' ):value () ) -- FIXME labels
+						options[item:child( 'value' ):value ()] = item:attribute ( 'label' ) or ''
 					end
 					item = item:next ()
 				end
@@ -99,113 +115,112 @@
 				end
 			end
 
-			table.insert ( f, r )
+			f:add ( var, r )
 		end
 
 		field = field:next ()
 	end
 
-	f.format = x_data.format
-
 	return f
 end
 
-function x_data.set ( f, name, value )
-	for i, field in ipairs ( f ) do
-		if field.var == name then
-			if field.type == 'jid-multi' or field.type == 'list-multi' or field.type == 'text-multi' then
-				if value == nil then
-					field.value = { }
-				else
-					table.insert ( field.value, value )
-				end
-			else
-				field.value = value
-			end
-		end
-	end
+function M.type ( form )
+	return form.ftype
 end
 
--- mcabber
-
-local form_cid = main.add_category { 'del', 'send' }
-forms = { }
-
-function insert_form ( form )
-	table.insert ( forms, form )
-	main.add_completion ( form_cid, tostring(#forms) )
-	return #forms
+function M.desc ( form )
+	return form.title, form.instructions
 end
 
-main.command ( 'form',
-	function ( args )
-		local id, action = tonumber (args[1]), args[2]
-		if forms[id] then
-			local form = forms[id].form
-			if action == 'send' then
-				forms[id].submit ( form )
-			elseif action == 'reject' then
-				forms[id].reject ( form )
-			elseif action == 'del' then
-				main.del_completion ( form_cid, id )
-				forms[id] = nil
-			elseif action == 'set' then
-				x_data.set ( form, args[3], args[4] )
-			elseif action then
-				x_data.set ( form, action, args[3] )
-			else
-				local desc = 'Form ' .. id
-				local fields = 'Fields:'
-				for index, field in pairs ( form ) do
-					if type(index) == 'number' then
-						fields = fields .. '\n - '
-						if field.required then
-							fields = fields .. '* '
-						end
-						if field.label then
-							fields = fields .. field.label .. ' (' .. ( field.var or '' ) .. ')'
-						else
-							fields = fields .. ( field.var or '<no name>' )
-						end
-						fields = fields .. ' [' .. field.type .. ']'
-						if field.desc then
-							fields = fields .. ': ' .. desc
-						end
-						if field.options then
-							fields = fields .. '\n   Options:'
-							for i, opt in ipairs ( field.options ) do
-								fields = fields .. ' ' .. opt
-							end
-						end
-						if field.type == 'list-multi' or field.type == 'text-multi' or field.type == 'jid-multi' then
-							fields = fields .. '\n   Values: '
-							for vin, value in ipairs ( field.value ) do
-								fields = fields .. '\n    * ' .. value
-							end
-						else
-							fields = fields .. '\n   Value: ' .. ( field.value or '' )
-						end
-					else
-						if type(field) ~= 'function' then -- skip any methods
-							desc = desc .. '\n - ' .. index .. ': ' .. field
-						end
-					end
+function M.format ( form, root, format_as )
+	local ft = format_as or form:type ()
+
+	root.x = { xmlns = 'jabber:x:data', type = ft }
+
+	if ft == 'cancel' then
+		return root
+	end
+
+	if ft == 'form' then
+		local title, instructions = form:desc ()
+		if title then
+			root.x.title = { title }
+		end
+		if instructions then
+			root.x.instructions = { instructions }
+		end
+	end
+
+	local fields = { }
+	for i, field in form:fields () do
+		local ftype = field:type ()
+
+		local options, label, desc, required
+		if ft == 'form' then
+			label, desc = field:desc ()
+			if desc then
+				desc = { desc }
+			end
+
+			if field:required () then
+				required = { }
+			end
+		
+			for option, label in field:options () do
+				table.insert ( options, { label = label, value = { option } } )
+			end
+		end
+
+		local value
+		if ftype == 'list-multi' or ftype == 'text-multi' or ftype == 'jid-multi' then
+			if ft == 'form' then
+				for option, label in field:options () do
+					table.insert ( options, { label = label, value = { option } } )
 				end
-				print ( desc .. '\n' .. fields )
+			end
+
+			value = { }
+			for j, v in ipairs ( field.value ) do
+				table.insert ( value, { v } )
 			end
 		else
-			local text = ''
-			for id, form in pairs ( forms ) do
-				text = text .. '\n - ' .. id .. ' ' .. ( form.form.title or 'Untitled' ) .. ' [' .. form.form.xmlns .. ', ' .. form.form.type .. ']'
-			end
-			if text ~= '' then
-				print ( 'Forms list:' .. text )
-			else
-				print ( 'No forms' )
-			end
+			value = { field:value () }
+		end
+
+		if ftype ~= 'fixed' or ft == 'form' then
+			table.insert ( fields, {
+					type   = ftype,
+					var    = field:name (),
+					label  = label,
+					desc   = desc,
+					value  = value,
+					option = options
+				} )
 		end
-	end, true, form_cid )
+	end
+	root.x.field = fields
+
+	return root
+end
+
+function M.fields ( form )
+	return ipairs ( form.f )
+end
 
-commands_help['form'] = "[form_id [send | reject | [set] fieldname [value]]]\n\nWithout arguments prints form list.\nWith bare form id prints info on that form.\nWhen setting multivalue field, new values are added, not replacing previous.\nWithout value unsets field, multivalue fields lose all their values."
+function M.add ( form, var, fld )
+	fld.var   = var
+	fld.index = #form.f + 1 -- XXX
+	local obj = form_field.new ( fld )
+	table.insert ( form.f, obj )
+	form.f[var] = obj
+	return obj
+end
+
+function M.field ( form, var )
+	-- works well on indices
+	return form.f[var]
+end
+
+return F
 
 -- vim: se ts=4: --