Arguments parsing
authorMyhailo Danylenko <isbear@ukrpost.net>
Sun, 15 Mar 2009 20:45:24 +0200
changeset 24 25552b21d3fb
parent 23 e441162b1386
child 25 38c68c285e41
Arguments parsing
TODO
examples/jobs.lua
examples/mcabberrc.lua
examples/xep0004.lua
examples/xep0030.lua
examples/xep0047.lua
examples/xep0060.lua
examples/xep0146.lua
examples/xep0163.lua
main.c
--- a/TODO	Sun Mar 15 15:03:51 2009 +0200
+++ b/TODO	Sun Mar 15 20:45:24 2009 +0200
@@ -10,5 +10,7 @@
 well, so, mcabber will pass all arguments to hooks in utf. but do we really need to convert names to utf?
 use glib filename charset conversion functions?
 toggle routine should handle multiple status toggles.
-dig, why mpd does not sends empty publish request on start
+dig, why mpd does not sends empty publish request on start (?)
+clarify situation with disco, maybe it is due to this we are unable to receive pep events
+improve pubsub interface
 
--- a/examples/jobs.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/jobs.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -31,28 +31,33 @@
 		local who
 		if args.t then
 			who = args.t
-			args.t = nil
 		else
 			who = main.current_buddy ()
 		end
 		local stat = args[1]
-		args[1] = nil
+		local mess = args[2]
 		delayed_jobs[who] = { }
-		delayed_jobs[who][stat] = 'say_to -q ' .. who .. ' ' .. rebuild_args_string ( args )
+		delayed_jobs[who][stat] = 'say_to -q ' .. who .. ' ' .. mess
 	end )
 
 main.command ( 'job',
 	function ( args )
-		local action, jid, stat = args:match ( "(%w+)%s+(%w+)%s+(%w)" )
+		args = parse_args ( args )
+		local action, jid, stat = args[1], args[2], args[3]
 		if action == 'del' then
 			delayed_jobs[jid][stat] = nil
 		else
-			print ( 'List of jobs:' )
+			local text = ''
 			for jid, jobs in pairs ( delayed_jobs ) do
 				for status, job in pairs ( jobs ) do
-					print ( ' - ' .. jid .. ' -> ' .. status .. ': ' .. job )
+					text = text .. '\n - ' .. jid .. ' -> ' .. status .. ': ' .. job
 				end
 			end
+			if text ~= '' then
+				print ( 'List of jobs:' .. text )
+			else
+				print ( 'No jobs' )
+			end
 		end
 	end, { "del" } )
 
--- a/examples/mcabberrc.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/mcabberrc.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -72,8 +72,6 @@
   reset bindings
 - you can omit unregistering of commands and xmpp features - module will unregister
   them automatically.
-- for now you can use parse_args and rebuild_args_string to deal with argument string,
-  but this is likely to be replaced with something more perfect.
 
 --]]
 
@@ -140,57 +138,25 @@
 	end
 end
 
--- FIXME: eats spaces
 function parse_args ( args )
-	local ret = {}
+	local ret        = {}
 	local still_opts = true
-	local optname
-	local option = false
-	for word in args:gmatch ( "%S+" ) do
+	local option     = nil
+	local arglist    = main.parse_args ( args )
+	for k, word in pairs ( arglist ) do
 		if still_opts and not option and word:sub ( 1, 1 ) == '-' then
-			option = true
-			optname = word:sub ( 2 )
+			option      = word:sub ( 2 )
 		elseif option then
-			ret[optname] = word
-			option = false
+			ret[option] = word
+			option      = nil
 		else
-			still_opts = false
+			still_opts  = false
 			table.insert ( ret, word )
 		end
 	end
 	return ret
 end
 
--- FIXME: get rid of this
-function rebuild_args_string ( tab )
-	local flags = nil
-	local args = nil
-	for k, v in pairs (tab) do
-		if type (k) == 'number' then
-			if args then
-				args = args .. ' ' .. v
-			else
-				args = v
-			end
-		else
-			if flags then
-				flags = flags .. ' ' .. '-' .. k .. ' ' .. v
-			else
-				flags = '-' .. k .. ' ' .. v
-			end
-		end
-	end
-	if flags and args then
-		return flags .. ' ' .. args
-	elseif flags then
-		return flags
-	elseif args then
-		return args
-	else
-		return ''
-	end
-end
-
 -- COMMANDS
 
 -- Help strings should not contain command, only arguments. This is necessary to support soft aliases.
--- a/examples/xep0004.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0004.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -1,6 +1,6 @@
 
 forms = { }
-form_cid = 0
+form_cid = main.add_category { 'del', 'send' }
 
 function insert_form ( form )
 	table.insert ( forms, form )
@@ -105,37 +105,35 @@
 	end
 end
 
-form_cid = main.command ( 'form',
+main.command ( 'form',
 	function ( args )
 		args = parse_args ( args )
 		local action = args[1]
 		local id = tonumber (args[1])
 		if forms[id] then
-			if args[2] then
-				local field = args[2]
-				if args[3] == 'clear' then
-					form_set ( id, field, nil )
-				else
-					args[1] = nil
-					args[2] = nil
-					if args[3] == 'set' then
-						args[3] = nil
-					end
-					form_set ( id, field, rebuild_args_string ( args ) )
+			local field = args[2]
+			if field then
+				local value = args[3]
+				if value == 'clear' then
+					value = nil
+				elseif value == 'set' then
+					value = args[4]
 				end
+				form_set ( id, field, value )
 			else
 				print ( 'Form: ' .. ( forms[id].title or '' ) .. '\n' .. forms[id].exp )
-				print ( 'Fields:' )
+				local text = 'Fields:'
 				for index, field in ipairs ( forms[id].val ) do -- this should not be here, but setting up callback just for this...
 					if field.type == 'jid-multi' or field.type == 'list-multi' or field.type == 'text-multi' then
-						print ( ' - ' .. field.var .. ' [' .. field.type .. ']:' )
+						text = text .. '\n - ' .. field.var .. ' [' .. field.type .. ']:'
 						for vin, value in ipairs ( field.value ) do
-							print ( '    * ' .. value[1] )
+							text = text .. '\n    * ' .. value[1]
 						end
 					else
-						print ( ' - ' .. field.var .. ' [' .. field.type .. ']: ' .. field.value[1] )
+						text = text .. '\n - ' .. field.var .. ' [' .. field.type .. ']: ' .. field.value[1]
 					end
 				end
+				print ( text )
 			end
 		elseif action == 'del' then
 			forms[tonumber(args[2])] = nil
@@ -146,12 +144,17 @@
 				form.send ( form )
 			end
 		else
-			print ( 'Forms list:' )
-			for id, form in ipairs ( forms ) do
-				print ( ' - ' .. tostring(id) .. ' ' .. form.title .. ' [' .. ( form.status or 'unknown' ) .. ']' )
+			local text = ''
+			for id, form in pairs ( forms ) do
+				text = text .. '\n - ' .. id .. ' ' .. form.title .. ' [' .. ( form.status or 'unknown' ) .. ']'
+			end
+			if text ~= '' then
+				print ( 'Forms list:' .. text )
+			else
+				print ( 'No forms' )
 			end
 		end
-	end, { "del", "send" } )
+	end, 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."
 
--- a/examples/xep0030.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0030.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -59,16 +59,11 @@
 		local who
 		if args.t then
 			who = args.t
-			args.t = nil
 		else
 			who = main.full_jid ()
 		end
 		if args[1] == 'items' then
-			local node = nil
-			if args[2] then
-				args[1] = nil
-				node = rebuild_args_string ( args )
-			end
+			local node = args[2]
 			disco_items (
 				function ( items )
 					if type ( items ) == 'string' then
--- a/examples/xep0047.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0047.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -168,35 +168,37 @@
 main.command ( 'ibb',
 	function ( args )
 		args = parse_args ( args )
-		if args[1] == 'send' then
+		local action = args[1]
+		if action == 'send' then
 			local who
 			if args.t then
 				who = args.t
-				args.t = nil
 			else
 				who = main.full_jid ()
 			end
-			args[1] = nil
-			send_file ( who, rebuild_args_string ( args ) )
-		elseif args[1] == 'accept' then
+			send_file ( who, args[2] )
+		elseif action == 'accept' then
 			local id = args[2]
-			args[1] = nil
-			args[2] = nil
 			if receiving_files[id] then
-				receiving_files[id].accept ( rebuild_args_string ( args ) )
+				receiving_files[id].accept ( args[3] )
 			end
-		elseif args[1] == 'reject' then
+		elseif action == 'reject' then
 			local id = args[2]
 			if receiving_files[id] then
 				receiving_files[id].reject ()
 			end
-		elseif args[1] == 'del' then
+		elseif action == 'del' then
 			local id = args[2]
 			receiving_files[id] = nil
 		else
-			print ( 'List of incoming streams:' )
+			local text = ''
 			for sid, data in pairs ( receiving_files ) do
-				print ( sid .. ': ' .. ( data.name or '(not set)' ) .. ' [' .. data.status .. ']' )
+				text = text .. '\n' ..  sid .. ': ' .. ( data.name or '(not set)' ) .. ' [' .. data.status .. ']'
+			end
+			if text ~= '' then
+				print ( 'List of incoming streams:' .. text )
+			else
+				print ( 'No streams' )
 			end
 		end
 	end, { "send", "accept", "reject", "del" } )
--- a/examples/xep0060.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0060.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -165,27 +165,29 @@
 
 main.command ( 'subscribe',
 	function ( args )
-		pubsub_subscribe ( main.current_buddy (), args )
+		args = parse_args ( args )
+		pubsub_subscribe ( args.t or main.current_buddy (), args )
 	end )
 main.command ( 'unsubscribe',
 	function ( args )
-		pubsub_unsubscribe ( main.current_buddy (), args )
+		args = parse_args ( args )
+		pubsub_unsubscribe ( args.t or main.current_buddy (), args )
 	end )
 main.command ( 'configure_node',
 	function ( args )
-		pubsub_configure_node ( main.current_buddy (), args )
+		args = parse_args ( args )
+		pubsub_configure_node ( args.t or main.current_buddy (), args )
 	end )
 main.command ( 'subscriptions',
 	function ( args )
-		pubsub_list_subscriptions ( main.current_buddy (), args )
+		args = parse_args ( args )
+		pubsub_list_subscriptions ( args.t or main.current_buddy (), args )
 	end )
 main.command ( 'subscription',
 	function ( args )
-		local node, jid, state, id = args:match ( '(.-)%s+(.-)%s+(.-)%s+(.+)' )
-		if not node then
-			node, jid, state = args:match ( '(.-)%s+(.-)%s+(.+)' )
-		end
-		pubsub_modify_subscription ( main.current_buddy (), node, jid, state, id )
+		args = parse_args ( args )
+		local node, jid, state, id = args[1], args[2], args[3], args[4]
+		pubsub_modify_subscription ( args.t or main.current_buddy (), node, jid, state, id )
 	end )
 
 commands_help['subscribe']      = "node_name\n\nSends pubsub subscription request to specified node of current buddy."
--- a/examples/xep0146.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0146.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -44,27 +44,32 @@
 		local who
 		if args.t then
 			who = args.t
-			args.t = nil
 		else
 			who = main.full_jid ()
 		end
-		if args[1] == 'list' or not args[1] then
+		local action = args[1]
+		if not action then
 			disco_items (
 				function ( items )
 					if type ( items ) == 'string' then
 						main.print_info ( who, string.format ( "Service items discovery for %s (http://jabber.org/protocol/commands) failed: %s", who, items ) )
 					else
-						main.print_info ( who, 'Available commands:' )
+						local text = ''
 						for index, item in ipairs ( items ) do
-							main.print_info ( who, ' - ' .. item.node )
+							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
 				end, who, 'http://jabber.org/protocol/commands' )
-		elseif args[1] then
-			remote_command ( who, args[1] )
+		elseif action then
+			remote_command ( who, action )
 		end
 	end, 'jid' )
 
-commands_help['remote'] = "[-t target_jid] [list | command]\n\nPrints list of available remote command or requests execution of specified command."
+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/xep0163.lua	Sun Mar 15 15:03:51 2009 +0200
+++ b/examples/xep0163.lua	Sun Mar 15 20:45:24 2009 +0200
@@ -269,12 +269,11 @@
 	end, boolean_cid )
 main.command ( 'mood',
 	function ( args )
+		args = parse_args ( args )
 		local data = { }
-		local mood, text = args:match ( "(%S-)%s+(.+)" )
-		if mood then
+		local mood, text = args[1], args[2]
+		if text then
 			data.text  = { text }
-		else
-			mood = args
 		end
 		if mood ~= '' then
 			data[mood] = { }
@@ -283,14 +282,13 @@
 	end )
 main.command ( 'activity',
 	function ( args )
+		args = parse_args ( args )
 		local data = { }
-		local activity, text = args:match ( "(%S-)%s+(.+)" )
-		if activity then
+		local activity, text = args[1], args[2]
+		if text then
 			data.text = { text }
-		else
-			activity = args
 		end
-		local act, subact = activity:match ( ".-%-.+" )
+		local act, subact = activity:match ( "(.-)%-(.+)" )
 		if not act then
 			act = activity
 		end
@@ -304,11 +302,10 @@
 	end )
 main.command ( 'location',
 	function ( args )
+		args = parse_args ( args )
 		local data = { }
-		local key, val, remains = args:match ( '%s*(%S+)%s+(%S+)(.*)' )
-		while key do
+		for key, val in pairs ( args ) do
 			data[key] = { val }
-			key, val, remains = args:match ( '%s*(%S+)%s+(%S+)(.*)' )
 		end
 		pep_publish ( 'geoloc', data )
 	end )
@@ -316,7 +313,7 @@
 commands_help['tune']     = "[enable|disable|on|off|yes|no|true|false]\n\nEnables or disables publishing of notifications about playing music in your player (currently only mpd is supported)."
 commands_help['mood']     = "[mood [message]]\n\nPublishes your mood.\nNote, that for now it does not checks for mood validity, so, see xep0107 for valid moods."
 commands_help['activity'] = "[activity[-specific_activity] [text]]\n\nPublishes your activity.\nNote, that for now it does not checks for activity validity, so, see xep0108 for valid activity values."
-commands_help['location'] = "[key value [key value ...]]\n\nPublishes your current geolocation.\nValid keys are accuracy, alt, area, bearing, building, country, datum, description, error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp and uri, according to xep0080."
+commands_help['location'] = "[-key value [-key value ...]]\n\nPublishes your current geolocation.\nValid keys are accuracy, alt, area, bearing, building, country, datum, description, error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp and uri, according to xep0080."
 
 pep_handler_registered = false
 
--- a/main.c	Sun Mar 15 15:03:51 2009 +0200
+++ b/main.c	Sun Mar 15 20:45:24 2009 +0200
@@ -534,7 +534,7 @@
 /// command function
 /// Function to handle newly registered command.
 /// A: string (arguments)
-void lua_main_command_handler (char *args, lua_command_callback_t *cb)
+static void lua_main_command_handler (char *args, lua_command_callback_t *cb)
 {
 	lua_rawgeti (cb->L, LUA_REGISTRYINDEX, cb->reference);
 	lua_pushstring (cb->L, args);
@@ -544,6 +544,84 @@
 	}
 }
 
+/// main.parse_args
+/// Function to parse command argument string. Returns table of strings.
+/// It can parse barewords (with escapes), double-quoted strings (with escapes) and single-quoted strings (without escapes).
+/// If args string ends with escape symbol, it is preserved.
+/// Arguments are separated only by whitespace, so, consequential quoted strings or barewords are one argument.
+/// Note: As for now, it does not preserve leading quote symbol for non-closed quoted strings. You should not rely on this behaviour.
+/// Examples:
+/// * ab\ cd\'e\\f\"
+/// * "ab cd'e\\f\""
+/// * 'ab cd'\''e\f'
+/// * ab" cd'"'e\f'
+/// A: string
+/// R: table
+static int lua_main_parse_args (lua_State *L)
+{
+	const char  *args = luaL_checkstring (L, 1);
+	const char  *p    = args;
+	luaL_Buffer  buf;
+
+	lua_newtable (L);
+	luaL_buffinit (L, &buf);
+	while (*p) {
+		if (*p == ' ') {
+			++p;
+			continue;
+		}
+		if (*p == '"') { // soft quote
+			const char *start = ++p;
+			while (*p) {
+				if (*p == '\\') { // escape symbol
+					luaL_addlstring (&buf, start, p - start);
+					start = ++p;
+					if (*p) // skip symbol
+						++p;
+					else // add last \ in line
+						luaL_addchar (&buf, '\\');
+				} else if (*p == '"') // quotation end
+					break;
+				else
+					++p;
+			}
+			luaL_addlstring (&buf, start, p - start);
+			if (*p)
+				++p;
+		} else if (*p == '\'') { // no-escape quote
+			const char *start = ++p;
+			while (*p && *p != '\'')
+				p++;
+			luaL_addlstring (&buf, start, p - start);
+			if (*p)
+				++p;
+		} else { // bareword
+			const char *start = p;
+			while (*p) {
+				if (*p == '\\') {
+					luaL_addlstring (&buf, start, p - start);
+					start = ++p;
+					if (*p) // skip symbol
+						++p;
+					else // add last \ in line
+						luaL_addchar (&buf, '\\');
+				} else if (*p == ' ' || *p == '\'' || *p == '"')
+					break;
+				else
+					++p;
+			}
+			luaL_addlstring (&buf, start, p - start);
+		}
+
+		if ((!*p) || *p == ' ') {
+			luaL_pushresult (&buf);
+			luaL_ref (L, -2);
+			luaL_buffinit (L, &buf);
+		}
+	}
+	return 1;
+}
+
 /// main.add_category
 /// Adds completion category.
 /// A: table (values are used as words for completion, optional)
@@ -894,6 +972,7 @@
 	reg ( binding )
 	reg ( add_feature )
 	reg ( del_feature )
+	reg ( parse_args )
 	reg ( add_category )
 	reg ( del_category )
 	reg ( add_completion )