Merge

Tue, 27 Oct 2015 23:14:19 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Tue, 27 Oct 2015 23:14:19 +0000
changeset 46
d711bf1e9979
parent 45
744c81c124b6 (diff)
parent 38
68458d0c50a0 (current diff)
child 47
1cbc0d9d132d

Merge

main.lua file | annotate | diff | comparison | revisions
scansion/objects/client.lua file | annotate | diff | comparison | revisions
--- a/main.lua	Wed Sep 23 01:03:56 2015 +0200
+++ b/main.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -55,6 +55,9 @@
 			local object = script.objects[action.object_name];
 			local handler = object.handler;
 			assert(handler[action.action], "Objects of type '"..object.type.."' do not support action '"..action.action.."'");
+			if action.annotation then
+				print(action.annotation);
+			end
 			print(object.name, action.action.."...");
 			local ok, err = pcall(handler[action.action], object, action.extra);
 			if not ok then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scansion/generator.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -0,0 +1,54 @@
+local it = require "scansion.iterators";
+
+local script_methods = {};
+
+local excluded_fields = { name = true, defined_line = true, type = true, annotation = true };
+function script_methods:client(data)
+	self:_append("["..data.name.."]");
+	for k, v in pairs(data) do
+		if not excluded_fields[k] then
+			self:_append("", k..": "..v);
+		end
+	end
+	self:_append("");
+end
+
+function script_methods:action(name, verb, data)
+	if self.in_header then
+		self.in_header = false;
+		self:_append("----------");
+		self:_append("");
+	end
+	
+	self:_append(name.." "..verb..(data and ":" or ""));
+	if data then
+		for s in tostring(data):gmatch("[^\n]+") do
+			self:_append("", s);
+		end
+	end
+	self:_append("");
+end
+
+function script_methods:_append(...)
+	local fields = { ... };
+	for i = 1, select("#", ...) do
+		fields[i] = tostring(fields[i]);
+	end
+	table.insert(self.lines, table.concat(fields, "\t"));
+end
+
+function script_methods:lines()
+	return it.values(self.lines);
+end
+
+function script_methods:render()
+	return table.concat(self.lines, "\n");
+end
+
+local script_mt = { __index = script_methods };
+
+return {
+	new_script = function ()
+		return setmetatable({ in_header = true, n_clients = 0, clients = {} }, script_mt);
+	end;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scansion/iterators.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -0,0 +1,201 @@
+-- Prosody IM
+-- Copyright (C) 2008-2010 Matthew Wild
+-- Copyright (C) 2008-2010 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+--[[ Iterators ]]--
+
+local it = {};
+
+local t_insert = table.insert;
+local select, unpack, next = select, unpack, next;
+local function pack(...) return { n = select("#", ...), ... }; end
+
+-- Reverse an iterator
+function it.reverse(f, s, var)
+	local results = {};
+
+	-- First call the normal iterator
+	while true do
+		local ret = { f(s, var) };
+		var = ret[1];
+	        if var == nil then break; end
+		t_insert(results, 1, ret);
+	end
+
+	-- Then return our reverse one
+	local i,max = 0, #results;
+	return function (results)
+			if i<max then
+				i = i + 1;
+				return unpack(results[i]);
+			end
+		end, results;
+end
+
+-- Iterate only over keys in a table
+local function _keys_it(t, key)
+	return (next(t, key));
+end
+function it.keys(t)
+	return _keys_it, t;
+end
+
+-- Iterate only over values in a table
+function it.values(t)
+	local key, val;
+	return function (t)
+		key, val = next(t, key);
+		return val;
+	end, t;
+end
+
+-- Given an iterator, iterate only over unique items
+function it.unique(f, s, var)
+	local set = {};
+
+	return function ()
+		while true do
+			local ret = pack(f(s, var));
+			var = ret[1];
+		        if var == nil then break; end
+		        if not set[var] then
+				set[var] = true;
+				return unpack(ret, 1, ret.n);
+			end
+		end
+	end;
+end
+
+--[[ Return the number of items an iterator returns ]]--
+function it.count(f, s, var)
+	local x = 0;
+
+	while true do
+		var = f(s, var);
+	        if var == nil then break; end
+		x = x + 1;
+	end
+
+	return x;
+end
+
+-- Return the first n items an iterator returns
+function it.head(n, f, s, var)
+	local c = 0;
+	return function (s, var)
+		if c >= n then
+			return nil;
+		end
+		c = c + 1;
+		return f(s, var);
+	end, s;
+end
+
+-- Skip the first n items an iterator returns
+function it.skip(n, f, s, var)
+	for i=1,n do
+		var = f(s, var);
+	end
+	return f, s, var;
+end
+
+-- Return the last n items an iterator returns
+function it.tail(n, f, s, var)
+	local results, count = {}, 0;
+	while true do
+		local ret = pack(f(s, var));
+		var = ret[1];
+	        if var == nil then break; end
+		results[(count%n)+1] = ret;
+		count = count + 1;
+	end
+
+	if n > count then n = count; end
+
+	local pos = 0;
+	return function ()
+		pos = pos + 1;
+		if pos > n then return nil; end
+		local ret = results[((count-1+pos)%n)+1];
+		return unpack(ret, 1, ret.n);
+	end
+	--return reverse(head(n, reverse(f, s, var))); -- !
+end
+
+function it.filter(filter, f, s, var)
+	if type(filter) ~= "function" then
+		local filter_value = filter;
+		function filter(x) return x ~= filter_value; end
+	end
+	return function (s, var)
+		local ret;
+		repeat ret = pack(f(s, var));
+			var = ret[1];
+		until var == nil or filter(unpack(ret, 1, ret.n));
+		return unpack(ret, 1, ret.n);
+	end, s, var;
+end
+
+local function _ripairs_iter(t, key) if key > 1 then return key-1, t[key-1]; end end
+function it.ripairs(t)
+	return _ripairs_iter, t, #t+1;
+end
+
+local function _range_iter(max, curr) if curr < max then return curr + 1; end end
+function it.range(x, y)
+	if not y then x, y = 1, x; end -- Default to 1..x if y not given
+	return _range_iter, y, x-1;
+end
+
+-- Convert the values returned by an iterator to an array
+function it.to_array(f, s, var)
+	local t, var = {};
+	while true do
+		var = f(s, var);
+	        if var == nil then break; end
+		t_insert(t, var);
+	end
+	return t;
+end
+
+-- Treat the return of an iterator as key,value pairs,
+-- and build a table
+function it.to_table(f, s, var)
+	local t, var2 = {};
+	while true do
+		var, var2 = f(s, var);
+	        if var == nil then break; end
+		t[var] = var2;
+	end
+	return t;
+end
+
+function combinations(iterator, ...)
+	local n_inputs = select("#", ...);
+	local inputs = { ... };
+	local f, s, var = {}, {}, {};
+	for i = 1, n_inputs do
+		f[i], s[i], var[i] = iterator(inputs[i]);
+	end
+
+	local level = 1;
+	return function ()
+		while level > 0 do
+			var[level] = f[level](s[level], var[level]);
+			if level == n_inputs and var[level] ~= nil then
+				return unpack(var, 1, n_inputs);
+			elseif var[level] then
+				level = level + 1;
+				f[level], s[level], var[level] = iterator(inputs[level]);
+			else				
+				level = level - 1;
+			end
+		end
+	end
+end
+
+return it;
--- a/scansion/objects/client.lua	Wed Sep 23 01:03:56 2015 +0200
+++ b/scansion/objects/client.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -10,7 +10,7 @@
 local function filter_expression(script, s)
 	local expr = s:match("^%$%{(.+)%}$");
 	if not expr then return s end
-	local name, value_name = expr:match("^(.-)'s (.+)$");
+	local name, value_name = expr:match("^(.+)'s (.+)$");
 	assert(name, "Unable to parse expression: "..expr);
 	local key = value_name:lower():gsub(" ", "_");
 	assert(script.objects[name], "Unknown object called "..name);
--- a/scansion/parser.lua	Wed Sep 23 01:03:56 2015 +0200
+++ b/scansion/parser.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -6,8 +6,9 @@
 	
 	local line_number = 0;
 	local last_object;
+	local annotation;
 	
-	for line in data:gmatch("([^\r\n]+)\r?\n") do
+	for line in data:gmatch("([^\r\n]*)\r?\n") do
 		line_number = line_number + 1;
 		if line:sub(1,1) == "[" then
 			local obj_type, name, extra = line:match("^%[(%a+)%] (.+)$");
@@ -30,10 +31,21 @@
 		elseif #parsed.actions > 0 and line:sub(1,1) == "\t" then
 			table.insert(parsed.actions[#parsed.actions].extra, line:sub(2));
 		elseif line:match("^%s*$") or line:match("^#") or line:match("^([/-])%1") then
-			-- Blank line or comment
+			-- Blank line or comment, save as annotation
+			if #line == 0 then
+				if annotation then
+					annotation.closed = true;
+				end
+			else
+				if (not annotation) or annotation.closed then
+					annotation = { line };
+				else
+					table.insert(annotation, line);
+				end
+			end
 		else
 			last_object = nil;
-			local name, action, extra = line:match("^(%a+) (%a+):?%s?(.*)$");
+			local name, action, extra = line:match("^([^:]+) (%a+):?%s?(.*)$");
 			if not name then
 				return nil, "Unable to parse action on line "..line_number;
 			end
@@ -44,7 +56,9 @@
 				object_name = name;
 				action = action:lower();
 				extra = {#extra>0 and extra or nil};
+				annotation = annotation and table.concat(annotation, "\n") or nil;
 			});
+			annotation = nil;
 		end
 	end
 	return parsed;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scansion/queue.lua	Tue Oct 27 23:14:19 2015 +0000
@@ -0,0 +1,72 @@
+-- Prosody IM
+-- Copyright (C) 2008-2015 Matthew Wild
+-- Copyright (C) 2008-2015 Waqas Hussain
+--
+-- This project is MIT/X11 licensed. Please see the
+-- COPYING file in the source package for more information.
+--
+
+-- Small ringbuffer library (i.e. an efficient FIFO queue with a size limit)
+-- (because unbounded dynamically-growing queues are a bad thing...)
+
+local have_utable, utable = pcall(require, "util.table"); -- For pre-allocation of table
+
+local function new(size, allow_wrapping)
+	-- Head is next insert, tail is next read
+	local head, tail = 1, 1;
+	local items = 0; -- Number of stored items
+	local t = have_utable and utable.create(size, 0) or {}; -- Table to hold items
+
+	return {
+		_items = t;
+		size = size;
+		count = function (self) return items; end;
+		push = function (self, item)
+			if items >= size then
+				if allow_wrapping then
+					tail = (tail%size)+1; -- Advance to next oldest item
+					items = items - 1;
+				else
+					return nil, "queue full";
+				end
+			end
+			t[head] = item;
+			items = items + 1;
+			head = (head%size)+1;
+			return true;
+		end;
+		pop = function (self)
+			if items == 0 then
+				return nil;
+			end
+			local item;
+			item, t[tail] = t[tail], 0;
+			tail = (tail%size)+1;
+			items = items - 1;
+			return item;
+		end;
+		peek = function (self)
+			if items == 0 then
+				return nil;
+			end
+			return t[tail];
+		end;
+		items = function (self)
+			return function (t, pos)
+				if pos >= t:count() then
+					return nil;
+				end
+				local read_pos = tail + pos;
+				if read_pos > t.size then
+					read_pos = (read_pos%size);
+				end
+				return pos+1, t._items[read_pos];
+			end, self, 0;
+		end;
+	};
+end
+
+return {
+	new = new;
+};
+
--- a/scripts/basic_message.scs	Wed Sep 23 01:03:56 2015 +0200
+++ b/scripts/basic_message.scs	Tue Oct 27 23:14:19 2015 +0000
@@ -1,3 +1,5 @@
+# A script testing basic message routing and delivery
+
 [Client] Romeo
 	jid: user@localhost
 	password: password
@@ -6,18 +8,32 @@
 	jid: juliet@localhost
 	password: password
 
+[Client] Juliet's phone
+	jid: juliet@localhost
+	password: password
+	resource: mobile
+
 ---------
 
+# Act 1, scene 1
+# The clients connect
+
 Romeo connects
 
 Juliet connects
 
+Juliet's phone connects
+
+# Romeo publishes his presence. Juliet has not, and so does not receive presence.
+
 Romeo sends:
 	<presence/>
 
 Romeo receives:
 	<presence from="${Romeo's full JID}" />
 
+# Romeo sends a message to Juliet's full JID
+
 Romeo sends:
 	<message to="${Juliet's full JID}" type="chat">
 		<body>Hello Juliet!</body>
@@ -28,6 +44,115 @@
 		<body>Hello Juliet!</body>
 	</message>
 
+# Romeo sends a message to Juliet's phone
+
+Romeo sends:
+	<message to="${Juliet's phone's full JID}" type="chat">
+		<body>Hello Juliet, on your phone.</body>
+	</message>
+
+Juliet's phone receives:
+	<message to="${Juliet's phone's full JID}" from="${Romeo's full JID}" type="chat">
+		<body>Hello Juliet, on your phone.</body>
+	</message>
+
+# Scene 2
+# This requires the server to support offline messages (which is optional).
+
+# Romeo sends a message to Juliet's bare JID. This is not immediately delivered, as she
+# has not published presence on either of her resources.
+
+Romeo sends:
+	<message to="juliet@localhost" type="chat">
+		<body>Hello Juliet, are you there?</body>
+	</message>
+
+# Juliet sends presence on her phone, and should receive the message there
+
+Juliet's phone sends:
+	<presence/>
+
+Juliet's phone receives:
+	<presence/>
+
+Juliet's phone receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>Hello Juliet, are you there?</body>
+		<delay xmlns='urn:xmpp:delay' from='localhost' />
+	</message>	
+
+# Romeo sends another bare-JID message, it should be delivered
+# instantly to Juliet's phone
+
+Romeo sends:
+	<message to="juliet@localhost" type="chat">
+		<body>Oh, hi!</body>
+	</message>
+
+Juliet's phone receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>Oh, hi!</body>
+	</message>	
+
+# Juliet's laptop goes online, but with a negative priority
+
+Juliet sends:
+	<presence>
+		<priority>-1</priority>
+	</presence>
+
+# Again, Romeo sends a message to her bare JID, but it should
+# only get delivered to her phone:
+
+Romeo sends:
+	<message to="juliet@localhost" type="chat">
+		<body>How are you?</body>
+	</message>
+
+Juliet's phone receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>How are you?</body>
+	</message>	
+
+# Romeo sends direct to Juliet's full JID, and she should receive it
+
+Romeo sends:
+	<message to="${Juliet's full JID}" type="chat">
+		<body>Are you hiding?</body>
+	</message>
+
+Juliet receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>Are you hiding?</body>
+	</message>
+
+# Juliet publishes non-negative presence
+
+Juliet sends:
+	<presence/>
+
+# And now Romeo's bare JID messages get delivered to both resources
+# (server behaviour may vary here)
+
+Romeo sends:
+	<message to="juliet@localhost" type="chat">
+		<body>There!</body>
+	</message>
+
+Juliet receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>There!</body>
+	</message>
+
+Juliet's phone receives:
+	<message from="${Romeo's full JID}" type="chat">
+		<body>There!</body>
+	</message>
+
+# The End
+
 Romeo disconnects
 
 Juliet disconnects
+
+Juliet's phone disconnects

mercurial