Forms in modular way
authorMyhailo Danylenko <isbear@ukrpost.net>
Sun, 22 Mar 2009 04:14:36 +0200
changeset 49 95f3bf77c598
parent 48 d31ae73038f7
child 50 12d8dd774fcc
Forms in modular way
examples/iq.lua
examples/iq_register.lua
examples/mcabberrc.lua
examples/remote.lua
examples/x_data.lua
--- a/examples/iq.lua	Sat Mar 21 16:47:56 2009 +0200
+++ b/examples/iq.lua	Sun Mar 22 04:14:36 2009 +0200
@@ -18,9 +18,9 @@
 			if smtype == 'result' then
 				success ( mess )
 			elseif smtype == 'error' then
-				fail ( mess:child( 'error' ):children():name () ) -- FIXME
+				fail ( mess:child( 'error' ):children():name (), mess ) -- FIXME
 			else
-				fail ( mess:xml () )
+				fail ( mess:xml (), mess )
 				return false
 			end
 			return true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/iq_register.lua	Sun Mar 22 04:14:36 2009 +0200
@@ -0,0 +1,197 @@
+
+-- IN-BAND REGISTRATION (XEP-0077)
+
+-- FIXME this is not yet finished, as format of form is undecided yet
+
+-- library
+
+require 'lm'
+require 'iq'
+require 'x_data'
+
+-- public
+
+iq_register = { }
+
+function iq_register.parse ( query )
+	local form  = { xmlns = 'jabber:iq:register', type = 'form' }
+
+	local instructions = query:child ( 'instructions' )
+	if instructions then
+		form.instructions = instructions:value ()
+	end
+	-- XXX how it can be mapped to common form?
+	--     and needs it be supplied?
+	if query:child ( 'registered' ) then
+		form.registered = true
+	end
+
+	local x = query:child ( 'x' )
+	if x:attribute ( 'xmlns' ) == 'jabber:x:data' then
+		form = form.parse ( x )
+		local format = form.format
+		form.format =
+			function ( form, root )
+					root.query = format ( form, { xmlns = 'jabber:iq:register' } )
+					return root
+			end
+		return form
+	end
+
+	local field = query:children ()
+	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 '' } } )
+		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 )
+	iq.send ( conn, to, 'get',
+		{
+			query = { xmlns = 'jabber:iq:register' },
+		},
+		function ( mess )
+			local query = mess:child ( 'query' )
+			if query and query:attribute ( 'xmlns' ) == 'jabber:iq:register' then
+				success ( iq_register.parse ( query ),
+					function ( form, success, fail )
+						form.type = 'submit' -- XXX
+						iq.send ( conn, to, 'set', form.format ( form, { } ), success , fail )
+					end,
+					function ( form, success, fail )
+						success ()
+					end )
+			end
+		end, fail )
+end
+
+function iq_register.unregister ( conn, to, success, fail )
+	iq.send ( conn, to, 'set',
+		{
+			query = { xmlns = 'jabber:iq:register',
+				remove = { },
+			},
+		},
+		function ( mess )
+			success ()
+		end,
+		function ( mesg, mess )
+			local query = mess:child ( 'query' )
+			if query and query:attribute ( 'xmlns' ) == 'jabber:iq:register' then
+				success ( iq_register.parse ( query ),
+					function ( form, success, fail )
+						form.type = 'submit' -- XXX
+						iq.send ( conn, to, 'set', form.format ( form, { } ), success, fail )
+					end,
+					function ( form, success, fail )
+						success ()
+					end )
+			else
+				fail ( mesg )
+			end
+		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."
+
+-- vim: se ts=4: --
--- a/examples/mcabberrc.lua	Sat Mar 21 16:47:56 2009 +0200
+++ b/examples/mcabberrc.lua	Sun Mar 22 04:14:36 2009 +0200
@@ -251,7 +251,7 @@
 
 -- DATA FORMS (XEP-0004)
 
-dopath 'xep0004'
+dopath 'x_data'
 
 -- SERVICE DISCOVERY (XEP-0030)
 
@@ -271,7 +271,7 @@
 
 -- IN-BAND REGISTRATION (XEP-0077)
 
-dopath 'xep0077'
+dopath 'iq_register'
 
 -- REMOTE CONTROLLING CLIENTS (XEP-0146)
 
--- a/examples/remote.lua	Sat Mar 21 16:47:56 2009 +0200
+++ b/examples/remote.lua	Sun Mar 22 04:14:36 2009 +0200
@@ -5,7 +5,7 @@
 
 require 'lm'
 require 'iq'
--- forms
+require 'x_data'
 require 'disco'
 
 -- public
@@ -24,42 +24,38 @@
 		function ( mess )
 			local c = mess:child ( 'command' )
 			if c then
-				local x = c:child ( 'x' )
-				if x then
-					local sid = c:attribute ( 'sessionid' )
-					local id  = parse_form ( x ) -- FIXME
-					forms[id].send =
-						function ( form )
-							iq.send ( conn, to, 'set',
-								{
-									command = { xmlns = 'http://jabber.org/protocol/commands', node = command, sessionid = sid,
-										x = { xmlns = 'jabber:x:data', type = 'form',
-											field = form.val,
-										},
+				local status = c:attribute ( 'status' )
+				if status == 'completed' then
+					success ()
+				else
+					local x = c:child ( 'x' )
+					if x then
+						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 } )
 									},
-								},
-								function ( mess )
-									local c = mess:child ( 'command' )
-									if c and c:attribute ( 'status' ) == 'completed' then
-										success ()
-										main.print_info ( to, 'Now you can run /form del ' .. id .. ' to delete form from list' )
-										form.status = 'acquired'
-									else
-										print ( 'D: FIXME: nonsuccessful remote command: ' .. mess:xml () )
-									end
-								end,
-								function ( mesg )
-									main.print_info ( to, 'Got non-successful response to form: ' .. mesg )
-									form.status = 'rejected'
-								end )
-							form.status = 'sent'
-						end
-					forms[id].status = 'filling'
-					main.print_info ( to, 'You have new form. To fill it, use /form ' .. id .. ' fieldname value' )
+									function ( mess )
+										local c = mess:child ( 'command' )
+										if c and c:attribute ( 'status' ) == 'completed' then
+											success ()
+										else
+											fail ( mess:xml () ) -- XXX more forms?
+										end
+									end, fail )
+							end,
+							function ( form, success, fail )
+								success ()
+							end )
+					else
+						fail ( mess:xml () ) -- XXX
+					end
 				end
 			end
-		end,
-		fail )
+		end, fail )
 end
 
 -- mcabber
@@ -76,8 +72,36 @@
 		local conn   = lm.connection.bless ( main.connection () )
 		if action then
 			remote.command ( conn, who, action,
-				function ()
-					main.print_info ( who, ('Command %s executed'):format ( 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 ) )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/x_data.lua	Sun Mar 22 04:14:36 2009 +0200
@@ -0,0 +1,211 @@
+
+-- DATA FORMS (XEP-0004)
+
+-- library
+
+require 'lm'
+
+-- 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)
+
+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
+end
+
+function x_data.parse ( x )
+	local f = { xmlns = 'jabber:x:data', type = x:attribute ( 'type' ) }
+
+	local title = x:child ( 'title' )
+	if title then
+		f.title = title:value ()
+	end
+	local instructions = x:child ( 'instructions' )
+	if instructions then
+		f.instructions = instructions:value ()
+	end
+
+	local field = x:children ()
+	while field do
+		if field:name () == 'field' then
+			local ftype = field:attribute ( 'type' ) or 'text-single'
+			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 ()
+			end
+			if field:child ( 'required' ) then
+				r.required = true
+			end
+
+			if ftype == 'jid-multi' or ftype == 'list-multi' or ftype == 'text-multi' or ftype == 'list-single' then
+				local values  = { }
+				local options = { }
+
+				local item = field:children ()
+				while item do
+					local name = item:name ()
+					if name == 'value' then
+						table.insert ( values, item:value () )
+					elseif name == 'option' then
+						table.insert ( options, item:child( 'value' ):value () ) -- FIXME labels
+					end
+					item = item:next ()
+				end
+
+				if ftype == 'list-single' then
+					local value = field:child ( 'value' )
+					if value then
+						r.value = value:value ()
+					end
+				else
+					r.value = values
+				end
+
+				if ftype == 'list-multi' or ftype == 'list-single' then
+					r.options = options
+				end
+			else
+				local value = field:child ( 'value' )
+				if value then
+					r.value = value:value ()
+				end
+			end
+
+			table.insert ( f, 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
+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
+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
+						end
+					else
+						if type(field) ~= 'function' then -- skip any methods
+							desc = desc .. '\n - ' .. index .. ': ' .. field
+						end
+					end
+				end
+				print ( desc .. '\n' .. fields )
+			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
+		end
+	end, true, form_cid )
+
+commands_help['form'] = "[del form_id | send form_id | form_id [field_name {clear | [set] value}]\n\nWith bare form id prints info on that form.\nWith field name sets or clears field value. Set subcommand is optional, just to allow values, starting with 'clear'.\nWithout arguments prints form list."
+
+-- vim: se ts=4: --