changeset 3414:66bda434d476

mod_statistics: Update 'top' to (hopefully) work with Prosody 0.11/Lua 5.2
author Matthew Wild <mwild1@gmail.com>
date Sun, 23 Dec 2018 11:00:38 +0000
parents 6421c9f05e02
children 6c806a99f802
files mod_statistics/mod_statistics.lua mod_statistics/prosodytop.lib.lua mod_statistics/prosodytop.lua mod_statistics/top.lib.lua mod_statistics/top.lua
diffstat 5 files changed, 354 insertions(+), 356 deletions(-) [+]
line wrap: on
line diff
--- a/mod_statistics/mod_statistics.lua	Sat Dec 22 19:02:21 2018 +0100
+++ b/mod_statistics/mod_statistics.lua	Sun Dec 23 11:00:38 2018 +0000
@@ -131,7 +131,7 @@
 	if command == "top" then
 		local dir = module:get_directory();
 		package.path = dir.."/?.lua;"..dir.."/?.lib.lua;"..package.path;
-		local prosodytop = require "prosodytop";
+		local prosodytop = module:require "prosodytop";
 		prosodytop.run();
 	end
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_statistics/prosodytop.lib.lua	Sun Dec 23 11:00:38 2018 +0000
@@ -0,0 +1,138 @@
+local curses = require "curses";
+local server = require "net.server_select";
+local timer = require "util.timer";
+
+assert(curses.timeout, "Incorrect version of curses library. Try 'sudo luarocks install luaposix'");
+
+local top = module:require "top";
+
+function main()
+	local stdscr = curses.stdscr()  -- it's a userdatum
+	--stdscr:clear();
+	local view = top.new({
+		stdscr = stdscr;
+		prosody = { up_since = os.time() };
+		conn_list = {};
+	});
+
+	timer.add_task(0.01, function ()
+		local ch = stdscr:getch();
+		if ch then
+			if stdscr:getch() == 410 then
+				view:resized();
+			else
+				curses.ungetch(ch);
+			end
+		end
+		return 0.2;
+	end);
+
+	timer.add_task(0, function ()
+		view:draw();
+		return 1;
+	end);
+
+	--[[
+	posix.signal(28, function ()
+		table.insert(view.conn_list, { jid = "WINCH" });
+		--view:draw();
+	end);
+	]]
+
+	-- Fake socket object around stdin
+	local stdin = {
+        	getfd = function () return 0; end;
+        	dirty = function (self) return false; end;
+        	settimeout = function () end;
+        	send = function (_, d) return #d, 0; end;
+        	close = function () end;
+        	receive = function (_, patt)
+        		local ch = stdscr:getch();
+        		if ch >= 0 and ch <=255 then
+        			return string.char(ch);
+        		elseif ch == 410 then
+        			view:resized();
+        		else
+        			table.insert(view.conn_list, { jid = tostring(ch) }); --FIXME
+        		end
+        		return "";
+        	end
+	};
+	local function on_incoming(stdin, text)
+		-- TODO: Handle keypresses
+		if text:lower() == "q" then os.exit(); end
+	end
+	stdin = server.wrapclient(stdin, "stdin", 0, {
+		onincoming = on_incoming, ondisconnect = function () end
+	}, "*a");
+
+	local function handle_line(line)
+		local e = {
+			STAT = function (name) return function (value)
+				view:update_stat(name, value);
+			end end;
+			SESS = function (id) return function (jid) return function (stats)
+				view:update_session(id, jid, stats);
+			end end end;
+		};
+		local chunk = assert(loadstring(line));
+		setfenv(chunk, e);
+		chunk();
+	end
+
+	local stats_listener = {};
+
+	function stats_listener.onconnect(conn)
+		--stdscr:mvaddstr(6, 0, "CONNECTED");
+	end
+
+	local partial = "";
+	function stats_listener.onincoming(conn, data)
+		--print("DATA", data)
+		data = partial..data;
+		local lastpos = 1;
+		for line, pos in data:gmatch("([^\n]+)\n()") do
+			lastpos = pos;
+			handle_line(line);
+		end
+		partial = data:sub(lastpos);
+	end
+
+	function stats_listener.ondisconnect(conn, err)
+		stdscr:mvaddstr(6, 0, "DISCONNECTED: "..(err or "unknown"));
+	end
+
+	local conn = require "socket".tcp();
+	assert(conn:connect("localhost", 5782));
+	handler = server.wrapclient(conn, "localhost", 5782, stats_listener, "*a");
+end
+
+return {
+	run = function ()
+		--os.setlocale("UTF-8", "all")
+
+		curses.initscr()
+		curses.cbreak()
+		curses.echo(false)  -- not noecho !
+		curses.nl(false)    -- not nonl !
+		curses.timeout(0);
+
+		local ok, err = pcall(main);
+
+		--while true do stdscr:getch() end
+		--stdscr:endwin()
+
+		if ok then
+			ok, err = xpcall(server.loop, debug.traceback);
+		end
+
+		curses.endwin();
+
+		--stdscr:refresh();
+		if not ok then
+			print(err);
+		end
+
+		print"DONE"
+	end;
+};
--- a/mod_statistics/prosodytop.lua	Sat Dec 22 19:02:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,138 +0,0 @@
-local curses = require "curses";
-local server = require "net.server_select";
-local timer = require "util.timer";
-
-assert(curses.timeout, "Incorrect version of curses library. Try 'sudo luarocks install luaposix'");
-
-local top = require "top";
-
-function main()
-	local stdscr = curses.stdscr()  -- it's a userdatum
-	--stdscr:clear();
-	local view = top.new({
-		stdscr = stdscr;
-		prosody = { up_since = os.time() };
-		conn_list = {};
-	});
-
-	timer.add_task(0.01, function ()
-		local ch = stdscr:getch();
-		if ch then
-			if stdscr:getch() == 410 then
-				view:resized();
-			else
-				curses.ungetch(ch);
-			end
-		end
-		return 0.2;
-	end);
-
-	timer.add_task(0, function ()
-		view:draw();
-		return 1;
-	end);
-
-	--[[
-	posix.signal(28, function ()
-		table.insert(view.conn_list, { jid = "WINCH" });
-		--view:draw();
-	end);
-	]]
-
-	-- Fake socket object around stdin
-	local stdin = {
-        	getfd = function () return 0; end;
-        	dirty = function (self) return false; end;
-        	settimeout = function () end;
-        	send = function (_, d) return #d, 0; end;
-        	close = function () end;
-        	receive = function (_, patt)
-        		local ch = stdscr:getch();
-        		if ch >= 0 and ch <=255 then
-        			return string.char(ch);
-        		elseif ch == 410 then
-        			view:resized();
-        		else
-        			table.insert(view.conn_list, { jid = tostring(ch) }); --FIXME
-        		end
-        		return "";
-        	end
-	};
-	local function on_incoming(stdin, text)
-		-- TODO: Handle keypresses
-		if text:lower() == "q" then os.exit(); end
-	end
-	stdin = server.wrapclient(stdin, "stdin", 0, {
-		onincoming = on_incoming, ondisconnect = function () end
-	}, "*a");
-
-	local function handle_line(line)
-		local e = {
-			STAT = function (name) return function (value)
-				view:update_stat(name, value);
-			end end;
-			SESS = function (id) return function (jid) return function (stats)
-				view:update_session(id, jid, stats);
-			end end end;
-		};
-		local chunk = assert(loadstring(line));
-		setfenv(chunk, e);
-		chunk();
-	end
-
-	local stats_listener = {};
-
-	function stats_listener.onconnect(conn)
-		--stdscr:mvaddstr(6, 0, "CONNECTED");
-	end
-
-	local partial = "";
-	function stats_listener.onincoming(conn, data)
-		--print("DATA", data)
-		data = partial..data;
-		local lastpos = 1;
-		for line, pos in data:gmatch("([^\n]+)\n()") do
-			lastpos = pos;
-			handle_line(line);
-		end
-		partial = data:sub(lastpos);
-	end
-
-	function stats_listener.ondisconnect(conn, err)
-		stdscr:mvaddstr(6, 0, "DISCONNECTED: "..(err or "unknown"));
-	end
-
-	local conn = require "socket".tcp();
-	assert(conn:connect("localhost", 5782));
-	handler = server.wrapclient(conn, "localhost", 5782, stats_listener, "*a");
-end
-
-return {
-	run = function ()
-		--os.setlocale("UTF-8", "all")
-
-		curses.initscr()
-		curses.cbreak()
-		curses.echo(false)  -- not noecho !
-		curses.nl(false)    -- not nonl !
-		curses.timeout(0);
-
-		local ok, err = pcall(main);
-
-		--while true do stdscr:getch() end
-		--stdscr:endwin()
-
-		if ok then
-			ok, err = xpcall(server.loop, debug.traceback);
-		end
-
-		curses.endwin();
-
-		--stdscr:refresh();
-		if not ok then
-			print(err);
-		end
-
-		print"DONE"
-	end;
-};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_statistics/top.lib.lua	Sun Dec 23 11:00:38 2018 +0000
@@ -0,0 +1,215 @@
+local array = require "util.array";
+local it = require "util.iterators";
+local curses = require "curses";
+local stats = module:require "stats".stats;
+local time = require "socket".gettime;
+
+local sessions_idle_after = 60;
+local stanza_names = {"message", "presence", "iq"};
+
+local top = {};
+top.__index = top;
+
+local status_lines = {
+	"Prosody $version - $time up $up_since, $total_users users, $cpu busy";
+	"Connections: $total_c2s c2s, $total_s2sout s2sout, $total_s2sin s2sin, $total_component component";
+	"Memory: $memory_lua lua, $memory_allocated process ($memory_used in use)";
+	"Stanzas in: $message_in_per_second message/s, $presence_in_per_second presence/s, $iq_in_per_second iq/s";
+	"Stanzas out: $message_out_per_second message/s, $presence_out_per_second presence/s, $iq_out_per_second iq/s";
+};
+
+function top:draw()
+	self:draw_status();
+	self:draw_column_titles();
+	self:draw_conn_list();
+	self.statuswin:refresh();
+	self.listwin:refresh();
+	--self.infowin:refresh()
+	self.stdscr:move(#status_lines,0)
+end
+
+-- Width specified as cols or % of unused space, defaults to
+-- title width if not specified
+local conn_list_columns = {
+	{ title = "ID", key = "id", width = "8" };
+	{ title = "JID", key = "jid", width = "100%" };
+	{ title = "STANZAS IN>", key = "total_stanzas_in", align = "right" };
+	{ title = "MSG", key = "message_in", align = "right", width = "4" };
+	{ title = "PRES", key = "presence_in", align = "right", width = "4" };
+	{ title = "IQ", key = "iq_in", align = "right", width = "4" };
+	{ title = "STANZAS OUT>", key = "total_stanzas_out", align = "right" };
+	{ title = "MSG", key = "message_out", align = "right", width = "4" };
+	{ title = "PRES", key = "presence_out", align = "right", width = "4" };
+	{ title = "IQ", key = "iq_out", align = "right", width = "4" };
+	{ title = "BYTES IN", key = "bytes_in", align = "right" };
+	{ title = "BYTES OUT", key = "bytes_out", align = "right" };
+
+};
+
+function top:draw_status()
+	for row, line in ipairs(status_lines) do
+		self.statuswin:mvaddstr(row-1, 0, (line:gsub("%$([%w_]+)", self.data)));
+		self.statuswin:clrtoeol();
+	end
+	-- Clear stanza counts
+	for _, stanza_type in ipairs(stanza_names) do
+		self.prosody[stanza_type.."_in_per_second"] = 0;
+		self.prosody[stanza_type.."_out_per_second"] = 0;
+	end
+end
+
+local function padright(s, width)
+	return s..string.rep(" ", width-#s);
+end
+
+local function padleft(s, width)
+	return string.rep(" ", width-#s)..s;
+end
+
+function top:resized()
+	self:recalc_column_widths();
+	--self.stdscr:clear();
+	self:draw();
+end
+
+function top:recalc_column_widths()
+	local widths = {};
+	self.column_widths = widths;
+	local total_width = curses.cols()-4;
+	local free_width = total_width;
+	for i = 1, #conn_list_columns do
+		local width = conn_list_columns[i].width or "0";
+		if not(type(width) == "string" and width:sub(-1) == "%") then
+			width = math.max(tonumber(width), #conn_list_columns[i].title+1);
+			widths[i] = width;
+			free_width = free_width - width;
+		end
+	end
+	for i = 1, #conn_list_columns do
+		if not widths[i] then
+			local pc_width = tonumber((conn_list_columns[i].width:gsub("%%$", "")));
+			widths[i] = math.floor(free_width*(pc_width/100));
+		end
+	end
+	return widths;
+end
+
+function top:draw_column_titles()
+	local widths = self.column_widths;
+	self.listwin:attron(curses.A_REVERSE);
+	self.listwin:mvaddstr(0, 0, "  ");
+	for i, column in ipairs(conn_list_columns) do
+		self.listwin:addstr(padright(column.title, widths[i]));
+	end
+	self.listwin:addstr("  ");
+	self.listwin:attroff(curses.A_REVERSE);
+end
+
+local function session_compare(session1, session2)
+	local stats1, stats2 = session1.stats, session2.stats;
+	return (stats1.total_stanzas_in + stats1.total_stanzas_out) >
+		(stats2.total_stanzas_in + stats2.total_stanzas_out);
+end
+
+function top:draw_conn_list()
+	local rows = curses.lines()-(#status_lines+2)-5;
+	local cutoff_time = time() - sessions_idle_after;
+	local widths = self.column_widths;
+	local top_sessions = array.collect(it.values(self.active_sessions)):sort(session_compare);
+	for index = 1, rows do
+		local session = top_sessions[index];
+		if session then
+			if session.last_update < cutoff_time then
+				self.active_sessions[session.id] = nil;
+			else
+				local row = {};
+				for i, column in ipairs(conn_list_columns) do
+					local width = widths[i];
+					local v = tostring(session[column.key] or ""):sub(1, width);
+					if #v < width then
+						if column.align == "right" then
+							v = padleft(v, width-1).." ";
+						else
+							v = padright(v, width);
+						end
+					end
+					table.insert(row, v);
+				end
+				if session.updated then
+					self.listwin:attron(curses.A_BOLD);
+				end
+				self.listwin:mvaddstr(index, 0, "  "..table.concat(row));
+				if session.updated then
+					session.updated = false;
+					self.listwin:attroff(curses.A_BOLD);
+				end
+			end
+		else
+			-- FIXME: How to clear a line? It's 5am and I don't feel like reading docs.
+			self.listwin:move(index, 0);
+			self.listwin:clrtoeol();
+		end
+	end
+end
+
+function top:update_stat(name, value)
+	self.prosody[name] = value;
+end
+
+function top:update_session(id, jid, stats)
+	self.active_sessions[id] = stats;
+	stats.id, stats.jid, stats.stats = id, jid, stats;
+	stats.total_bytes = stats.bytes_in + stats.bytes_out;
+	for _, stanza_type in ipairs(stanza_names) do
+		self.prosody[stanza_type.."_in_per_second"] = (self.prosody[stanza_type.."_in_per_second"] or 0) + stats[stanza_type.."_in"];
+		self.prosody[stanza_type.."_out_per_second"] = (self.prosody[stanza_type.."_out_per_second"] or 0) + stats[stanza_type.."_out"];
+	end
+	stats.total_stanzas_in = stats.message_in + stats.presence_in + stats.iq_in;
+	stats.total_stanzas_out = stats.message_out + stats.presence_out + stats.iq_out;
+	stats.last_update = time();
+	stats.updated = true;
+end
+
+local function new(base)
+	setmetatable(base, top);
+	base.data = setmetatable({}, {
+		__index = function (t, k)
+			local stat = stats[k];
+			if stat and stat.tostring then
+				if type(stat.tostring) == "function" then
+					return stat.tostring(base.prosody[k]);
+				elseif type(stat.tostring) == "string" then
+					local v = base.prosody[k];
+					if v == nil then
+						return "?";
+					end
+					return (stat.tostring):format(v);
+				end
+			end
+			return base.prosody[k];
+		end;
+	});
+
+	base.active_sessions = {};
+
+	base.statuswin = curses.newwin(#status_lines, 0, 0, 0);
+
+	base.promptwin = curses.newwin(1, 0, #status_lines, 0);
+	base.promptwin:addstr("");
+	base.promptwin:refresh();
+
+	base.listwin = curses.newwin(curses.lines()-(#status_lines+2)-5, 0, #status_lines+1, 0);
+	base.listwin:syncok();
+
+	base.infowin = curses.newwin(5, 0, curses.lines()-5, 0);
+	base.infowin:mvaddstr(1, 1, "Hello world");
+	base.infowin:border(0,0,0,0);
+	base.infowin:syncok();
+	base.infowin:refresh();
+
+	base:resized();
+
+	return base;
+end
+
+return { new = new };
--- a/mod_statistics/top.lua	Sat Dec 22 19:02:21 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-module("prosodytop", package.seeall);
-
-local array = require "util.array";
-local it = require "util.iterators";
-local curses = require "curses";
-local stats = require "stats".stats;
-local time = require "socket".gettime;
-
-local sessions_idle_after = 60;
-local stanza_names = {"message", "presence", "iq"};
-
-local top = {};
-top.__index = top;
-
-local status_lines = {
-	"Prosody $version - $time up $up_since, $total_users users, $cpu busy";
-	"Connections: $total_c2s c2s, $total_s2sout s2sout, $total_s2sin s2sin, $total_component component";
-	"Memory: $memory_lua lua, $memory_allocated process ($memory_used in use)";
-	"Stanzas in: $message_in_per_second message/s, $presence_in_per_second presence/s, $iq_in_per_second iq/s";
-	"Stanzas out: $message_out_per_second message/s, $presence_out_per_second presence/s, $iq_out_per_second iq/s";
-};
-
-function top:draw()
-	self:draw_status();
-	self:draw_column_titles();
-	self:draw_conn_list();
-	self.statuswin:refresh();
-	self.listwin:refresh();
-	--self.infowin:refresh()
-	self.stdscr:move(#status_lines,0)
-end
-
--- Width specified as cols or % of unused space, defaults to
--- title width if not specified
-local conn_list_columns = {
-	{ title = "ID", key = "id", width = "8" };
-	{ title = "JID", key = "jid", width = "100%" };
-	{ title = "STANZAS IN>", key = "total_stanzas_in", align = "right" };
-	{ title = "MSG", key = "message_in", align = "right", width = "4" };
-	{ title = "PRES", key = "presence_in", align = "right", width = "4" };
-	{ title = "IQ", key = "iq_in", align = "right", width = "4" };
-	{ title = "STANZAS OUT>", key = "total_stanzas_out", align = "right" };
-	{ title = "MSG", key = "message_out", align = "right", width = "4" };
-	{ title = "PRES", key = "presence_out", align = "right", width = "4" };
-	{ title = "IQ", key = "iq_out", align = "right", width = "4" };
-	{ title = "BYTES IN", key = "bytes_in", align = "right" };
-	{ title = "BYTES OUT", key = "bytes_out", align = "right" };
-
-};
-
-function top:draw_status()
-	for row, line in ipairs(status_lines) do
-		self.statuswin:mvaddstr(row-1, 0, (line:gsub("%$([%w_]+)", self.data)));
-		self.statuswin:clrtoeol();
-	end
-	-- Clear stanza counts
-	for _, stanza_type in ipairs(stanza_names) do
-		self.prosody[stanza_type.."_in_per_second"] = 0;
-		self.prosody[stanza_type.."_out_per_second"] = 0;
-	end
-end
-
-local function padright(s, width)
-	return s..string.rep(" ", width-#s);
-end
-
-local function padleft(s, width)
-	return string.rep(" ", width-#s)..s;
-end
-
-function top:resized()
-	self:recalc_column_widths();
-	--self.stdscr:clear();
-	self:draw();
-end
-
-function top:recalc_column_widths()
-	local widths = {};
-	self.column_widths = widths;
-	local total_width = curses.cols()-4;
-	local free_width = total_width;
-	for i = 1, #conn_list_columns do
-		local width = conn_list_columns[i].width or "0";
-		if not(type(width) == "string" and width:sub(-1) == "%") then
-			width = math.max(tonumber(width), #conn_list_columns[i].title+1);
-			widths[i] = width;
-			free_width = free_width - width;
-		end
-	end
-	for i = 1, #conn_list_columns do
-		if not widths[i] then
-			local pc_width = tonumber((conn_list_columns[i].width:gsub("%%$", "")));
-			widths[i] = math.floor(free_width*(pc_width/100));
-		end
-	end
-	return widths;
-end
-
-function top:draw_column_titles()
-	local widths = self.column_widths;
-	self.listwin:attron(curses.A_REVERSE);
-	self.listwin:mvaddstr(0, 0, "  ");
-	for i, column in ipairs(conn_list_columns) do
-		self.listwin:addstr(padright(column.title, widths[i]));
-	end
-	self.listwin:addstr("  ");
-	self.listwin:attroff(curses.A_REVERSE);
-end
-
-local function session_compare(session1, session2)
-	local stats1, stats2 = session1.stats, session2.stats;
-	return (stats1.total_stanzas_in + stats1.total_stanzas_out) >
-		(stats2.total_stanzas_in + stats2.total_stanzas_out);
-end
-
-function top:draw_conn_list()
-	local rows = curses.lines()-(#status_lines+2)-5;
-	local cutoff_time = time() - sessions_idle_after;
-	local widths = self.column_widths;
-	local top_sessions = array.collect(it.values(self.active_sessions)):sort(session_compare);
-	for index = 1, rows do
-		session = top_sessions[index];
-		if session then
-			if session.last_update < cutoff_time then
-				self.active_sessions[session.id] = nil;
-			else
-				local row = {};
-				for i, column in ipairs(conn_list_columns) do
-					local width = widths[i];
-					local v = tostring(session[column.key] or ""):sub(1, width);
-					if #v < width then
-						if column.align == "right" then
-							v = padleft(v, width-1).." ";
-						else
-							v = padright(v, width);
-						end
-					end
-					table.insert(row, v);
-				end
-				if session.updated then
-					self.listwin:attron(curses.A_BOLD);
-				end
-				self.listwin:mvaddstr(index, 0, "  "..table.concat(row));
-				if session.updated then
-					session.updated = false;
-					self.listwin:attroff(curses.A_BOLD);
-				end
-			end
-		else
-			-- FIXME: How to clear a line? It's 5am and I don't feel like reading docs.
-			self.listwin:move(index, 0);
-			self.listwin:clrtoeol();
-		end
-	end
-end
-
-function top:update_stat(name, value)
-	self.prosody[name] = value;
-end
-
-function top:update_session(id, jid, stats)
-	self.active_sessions[id] = stats;
-	stats.id, stats.jid, stats.stats = id, jid, stats;
-	stats.total_bytes = stats.bytes_in + stats.bytes_out;
-	for _, stanza_type in ipairs(stanza_names) do
-		self.prosody[stanza_type.."_in_per_second"] = (self.prosody[stanza_type.."_in_per_second"] or 0) + stats[stanza_type.."_in"];
-		self.prosody[stanza_type.."_out_per_second"] = (self.prosody[stanza_type.."_out_per_second"] or 0) + stats[stanza_type.."_out"];
-	end
-	stats.total_stanzas_in = stats.message_in + stats.presence_in + stats.iq_in;
-	stats.total_stanzas_out = stats.message_out + stats.presence_out + stats.iq_out;
-	stats.last_update = time();
-	stats.updated = true;
-end
-
-function new(base)
-	setmetatable(base, top);
-	base.data = setmetatable({}, {
-		__index = function (t, k)
-			local stat = stats[k];
-			if stat and stat.tostring then
-				if type(stat.tostring) == "function" then
-					return stat.tostring(base.prosody[k]);
-				elseif type(stat.tostring) == "string" then
-					local v = base.prosody[k];
-					if v == nil then
-						return "?";
-					end
-					return (stat.tostring):format(v);
-				end
-			end
-			return base.prosody[k];
-		end;
-	});
-
-	base.active_sessions = {};
-
-	base.statuswin = curses.newwin(#status_lines, 0, 0, 0);
-
-	base.promptwin = curses.newwin(1, 0, #status_lines, 0);
-	base.promptwin:addstr("");
-	base.promptwin:refresh();
-
-	base.listwin = curses.newwin(curses.lines()-(#status_lines+2)-5, 0, #status_lines+1, 0);
-	base.listwin:syncok();
-
-	base.infowin = curses.newwin(5, 0, curses.lines()-5, 0);
-	base.infowin:mvaddstr(1, 1, "Hello world");
-	base.infowin:border(0,0,0,0);
-	base.infowin:syncok();
-	base.infowin:refresh();
-
-	base:resized();
-
-	return base;
-end
-
-return _M;