tek namespace and application restructured
authorTimm S. Mueller <tmueller@neoscientists.org>
Sun, 20 May 2007 18:28:42 +0200
changeset 18877f536d09f08
parent 187 f69f724fddd9
child 189 efb1bd425c56
tek namespace and application restructured
cgi-bin/Makefile
cgi-bin/loona.lua
cgi-bin/tek.lua
cgi-bin/tek/app/loona.lua
cgi-bin/tek/cgi.lua
cgi-bin/tek/cgi/document.lua
cgi-bin/tek/cgi/header.lua
cgi-bin/tek/cgi/header/cookies.lua
cgi-bin/tek/cgi/request.lua
cgi-bin/tek/cgi/request/args.lua
cgi-bin/tek/cgi/session.lua
cgi-bin/tek/class/http.lua
cgi-bin/tek/class/http/header.lua
cgi-bin/tek/class/http/request.lua
cgi-bin/tek/class/loona.lua
cgi-bin/tek/class/loona/buffer.lua
cgi-bin/tek/class/loona/markup.lua
cgi-bin/tek/class/loona/util.lua
cgi-bin/tek/lib.lua
cgi-bin/tek/lib/luahtml.c
cgi-bin/tek/os/posix.c
cgi-bin/tek/posix.c
cgi-bin/tek/util.lua
cgi-bin/tek/web.lua
cgi-bin/tek/web/include.c
cgi-bin/tek/web/markup.lua
extensions/checkaccept.lua
extensions/contactform.lua
extensions/login.lua
extensions/search.lua
     1.1 --- a/cgi-bin/Makefile	Sun May 20 18:22:42 2007 +0200
     1.2 +++ b/cgi-bin/Makefile	Sun May 20 18:28:42 2007 +0200
     1.3 @@ -21,34 +21,30 @@
     1.4  .c.o:
     1.5  	$(CC) $(INCL) $(WARN) $(DEBUG) $(OPT) -fPIC -DPIC -c $? -o $@ 
     1.6  
     1.7 -modules: tek/web/include.so tek/posix.so
     1.8 +modules: tek/lib/luahtml.so tek/os/posix.so
     1.9  
    1.10  all: modules
    1.11  
    1.12 -install: tek/web/include.so tek/posix.so
    1.13 -	-install -d $(INSTPATH)/tek/cgi/header $(INSTPATH)/tek/cgi/request $(INSTPATH)/tek/web
    1.14 -	-luac -s -o $(INSTPATH)/tek.lua tek.lua
    1.15 -	-luac -s -o $(INSTPATH)/tek/cgi.lua tek/cgi.lua
    1.16 -	-luac -s -o $(INSTPATH)/tek/util.lua tek/util.lua
    1.17 -	-luac -s -o $(INSTPATH)/tek/web.lua tek/web.lua
    1.18 -	-luac -s -o $(INSTPATH)/tek/cgi/document.lua tek/cgi/document.lua
    1.19 -	-luac -s -o $(INSTPATH)/tek/cgi/header.lua tek/cgi/header.lua
    1.20 -	-luac -s -o $(INSTPATH)/tek/cgi/request.lua tek/cgi/request.lua
    1.21 -	-luac -s -o $(INSTPATH)/tek/cgi/session.lua tek/cgi/session.lua
    1.22 -	-luac -s -o $(INSTPATH)/tek/web/markup.lua tek/web/markup.lua
    1.23 -	-luac -s -o $(INSTPATH)/tek/cgi/header/cookies.lua tek/cgi/header/cookies.lua
    1.24 -	-luac -s -o $(INSTPATH)/tek/cgi/request/args.lua tek/cgi/request/args.lua
    1.25 -	-luac -s -o $(INSTPATH)/tek/web/markup.lua tek/web/markup.lua
    1.26 -	-install -s tek/*.so $(INSTPATH)/tek
    1.27 -	-install -s tek/web/*.so $(INSTPATH)/tek/web
    1.28 +install: tek/lib/luahtml.so tek/os/posix.so
    1.29 +	-install -d $(INSTPATH)/tek/app $(INSTPATH)/tek/lib $(INSTPATH)/tek/os
    1.30 +	-install -d $(INSTPATH)/tek/class/http $(INSTPATH)/tek/class/loona
    1.31 +	-install -s tek/lib/*.so $(INSTPATH)/tek/lib
    1.32 +	-install -s tek/os/*.so $(INSTPATH)/tek/os
    1.33 +	-luac -s -o $(INSTPATH)/tek/lib.lua tek/lib.lua
    1.34 +	-luac -s -o $(INSTPATH)/tek/app/loona.lua tek/app/loona.lua
    1.35 +	-luac -s -o $(INSTPATH)/tek/class/http.lua tek/class/http.lua
    1.36 +	-luac -s -o $(INSTPATH)/tek/class/http/header.lua tek/class/http/header.lua
    1.37 +	-luac -s -o $(INSTPATH)/tek/class/http/request.lua tek/class/http/request.lua
    1.38 +	-luac -s -o $(INSTPATH)/tek/class/loona.lua tek/class/loona.lua
    1.39 +	-luac -s -o $(INSTPATH)/tek/class/loona/buffer.lua tek/class/loona/buffer.lua
    1.40 +	-luac -s -o $(INSTPATH)/tek/class/loona/markup.lua tek/class/loona/markup.lua
    1.41 +	-luac -s -o $(INSTPATH)/tek/class/loona/util.lua tek/class/loona/util.lua
    1.42  
    1.43 -
    1.44 -tek/web/include.so: tek/web/include.o
    1.45 +tek/lib/luahtml.so: tek/lib/luahtml.o
    1.46  	$(CC) $^ -shared -o $@ $(LIBS)
    1.47  
    1.48 -tek/posix.so: tek/posix.o
    1.49 +tek/os/posix.so: tek/os/posix.o
    1.50  	$(CC) $^ -shared -o $@ $(LIBS)
    1.51  
    1.52  clean:
    1.53 -	-rm tek/*.o tek/*.so
    1.54 -	-rm tek/web/*.o tek/web/*.so
    1.55 +	-rm tek/lib/*.so tek/lib/*.o tek/os/*.so tek/os/*.o
     2.1 --- a/cgi-bin/loona.lua	Sun May 20 18:22:42 2007 +0200
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,1672 +0,0 @@
     2.4 -
     2.5 ---
     2.6 ---	loona - tiny CMS
     2.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     2.8 ---	See copyright notice in COPYRIGHT
     2.9 ---
    2.10 -
    2.11 -local tek = require "tek"
    2.12 -local cgi = require "tek.cgi"
    2.13 -local posix = require "tek.posix"
    2.14 -require "tek.cgi.request"
    2.15 -require "tek.cgi.request.args"
    2.16 -require "tek.cgi.session"
    2.17 -require "tek.web"
    2.18 -require "tek.web.markup"
    2.19 -require "tek.util"
    2.20 -
    2.21 -
    2.22 -local boxed_G = { 
    2.23 -	string = string, table = table,
    2.24 -	assert = assert, collectgarbage = collectgarbage, dofile = dofile,
    2.25 -	error = error, getfenv = getfenv, getmetatable = getmetatable,
    2.26 -	ipairs = ipairs, load = load, loadfile = loadfile, loadstring = loadstring,
    2.27 -	next = next, pairs = pairs, pcall = pcall, print = print,
    2.28 -	rawequal = rawequal, rawget = rawget, rawset = rawset, require = require,
    2.29 -	select = select, setfenv = setfenv, setmetatable = setmetatable,
    2.30 -	tonumber = tonumber, tostring = tostring, type = type, unpack = unpack,
    2.31 -	xpcall = xpcall
    2.32 -}
    2.33 -
    2.34 -local table, string, assert, unpack, ipairs, pairs, type, require =
    2.35 -	table, string, assert, unpack, ipairs, pairs, type, require
    2.36 -local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv
    2.37 -local open, remove, rename, getenv, time, date =
    2.38 -	io.open, os.remove, os.rename, os.getenv, os.time, os.date
    2.39 -
    2.40 -
    2.41 -module "loona"
    2.42 -
    2.43 -
    2.44 -_VERSION = 3
    2.45 -_REVISION = 2
    2.46 -
    2.47 -
    2.48 -local function lookupname(tab, val)
    2.49 -	if tab then
    2.50 -		for i, v in ipairs(tab) do
    2.51 -			if v.name == val then
    2.52 -				return i
    2.53 -			end
    2.54 -		end
    2.55 -	end
    2.56 -end
    2.57 -
    2.58 -
    2.59 -local atom = { }
    2.60 -
    2.61 -
    2.62 -function atom:new(o)
    2.63 -	o = o or { }
    2.64 -	setmetatable(o, self)
    2.65 -	self.__index = self
    2.66 -	return o
    2.67 -end
    2.68 -
    2.69 -
    2.70 -local loona = atom:new(getfenv())
    2.71 -
    2.72 -
    2.73 -function loona:dbmsg(msg, detail)
    2.74 - 	return (msg and detail and self.authuser) and
    2.75 - 		("%s : %s"):format(msg, detail) or msg
    2.76 -end
    2.77 -
    2.78 -
    2.79 -function loona:checkprofilename(n)
    2.80 -	assert(n:match("^%w+$") and n ~= "current",
    2.81 -		self:dbmsg("Invalid profile name", n))
    2.82 -	return n
    2.83 -end
    2.84 -
    2.85 -
    2.86 -function loona:checklanguage(n)
    2.87 -	assert(n:match("^%l%l$"), self:dbmsg("Invalid language code", n))
    2.88 -	return n
    2.89 -end
    2.90 -
    2.91 -
    2.92 -function loona:checkbodyname(s)
    2.93 -	s = s or "main"
    2.94 -	assert(s:match("^[%w_]*%w+[%w_]*$"), self:dbmsg("Invalid body name", s))
    2.95 -	return s
    2.96 -end
    2.97 -
    2.98 -
    2.99 -function loona:deleteprofile(p, lang)
   2.100 -	p = self.config.contentdir .. "/" .. p .. "_" .. (lang or self.lang)
   2.101 -	for e in tek.util.readdir(p) do
   2.102 - 		local success, msg = remove(p .. "/" .. e)
   2.103 -		assert(success, self:dbmsg("Error removing entry in profile", msg))
   2.104 -	end
   2.105 -	return remove(p)
   2.106 -end
   2.107 -
   2.108 -
   2.109 -function loona:copyprofile(dstprof, srcprof, dstlang, srclang)
   2.110 -	local contentdir = self.config.contentdir
   2.111 -	local src = ("%s/%s_%s"):format(contentdir,
   2.112 -		srcprof or self.profile, srclang or self.lang)
   2.113 -	local dst = ("%s/%s_%s"):format(contentdir,
   2.114 -		dstprof or self.profile, dstlang or self.lang)
   2.115 -	assert(src ~= dst, self:dbmsg("Attempt to copy profile over itself"))
   2.116 -	assert(posix.stat(src, "mode") == "directory",
   2.117 -		self:dbmsg("Source profile not a directory", src))
   2.118 -	local success, msg = posix.mkdir(dst)
   2.119 -	assert(success, self:dbmsg("Error creating profile directory " .. dst, msg))
   2.120 -	for e in tek.util.readdir(src) do
   2.121 -		local ext = e:match("^[^.].*%.([^.]*)$")
   2.122 -		if ext ~= "LOCK" then
   2.123 -			local f = src .. "/" .. e
   2.124 -			if posix.stat(f, "mode") == "file" then
   2.125 -				success, msg = tek.copyfile(f, dst .. "/" .. e)
   2.126 -				assert(success, self:dbmsg("Error copying file", msg))
   2.127 -			end
   2.128 -		end
   2.129 -	end
   2.130 -	-- create "current" symlink if none exists for new profile/language
   2.131 -	if not posix.readlink(contentdir .. "/current_" .. dstlang) then
   2.132 -		self:makecurrent(dstprof, dstlang)
   2.133 -	end
   2.134 -end
   2.135 -
   2.136 -
   2.137 -function loona:makecurrent(prof, lang)
   2.138 -	prof = prof or self.profile
   2.139 -	lang = lang or self.lang
   2.140 -	local contentdir = self.config.contentdir
   2.141 -	local newpath = ("%s/current_%s"):format(contentdir, lang)
   2.142 -	local tmppath = newpath .. "." .. self.session.name
   2.143 -	local success, msg = posix.symlink(prof .. "_" .. lang, tmppath)
   2.144 -	assert(success, self:dbmsg("Cannot create symlink", msg))
   2.145 -	success, msg = rename(tmppath, newpath)
   2.146 -	assert(success, self:dbmsg("Cannot put symlink in place", msg))
   2.147 -	return true
   2.148 -end
   2.149 -
   2.150 -
   2.151 -function loona:publishprofile(profile, lang)
   2.152 -	lang = lang or self.lang
   2.153 -	local contentdir = self.config.contentdir
   2.154 -	
   2.155 -	-- Get languages for the current profile
   2.156 -	
   2.157 -	local plangs = { }
   2.158 -	local lmatch = "^" .. self.profile .. "_(%w+)$"
   2.159 -	for e in tek.util.readdir(self.config.contentdir) do
   2.160 -		local l = e:match(lmatch)
   2.161 -		if l then
   2.162 -			table.insert(plangs, l)
   2.163 -		end
   2.164 -	end
   2.165 -	
   2.166 -	-- For all languages, update "current" symlink
   2.167 -	
   2.168 -	for _, lang in ipairs(plangs) do
   2.169 -		self:makecurrent(profile, lang)
   2.170 -	end
   2.171 -	
   2.172 -	-- These arguments are overwritten globally and need to get restored
   2.173 -	
   2.174 -	local save_args = { self.args.lang, self.args.profile, self.args.session }
   2.175 -	
   2.176 -	-- For all languages, unroll site to static HTML
   2.177 -	
   2.178 -	for _, lang in ipairs(plangs) do
   2.179 -		local ext = (#plangs == 1 and ".html") or (".html." .. lang)
   2.180 -		self:recursesections(self.sections, function(self, s, e, path)
   2.181 -			path = path and path .. "/" .. e.name or e.name
   2.182 -			if not e.notvisible then
   2.183 -				loona:dumphtml { requestpath = path, requestlang = lang,
   2.184 -					htmlext = ext, insecure = true }
   2.185 -			end
   2.186 -			return path
   2.187 -		end)
   2.188 -	end
   2.189 -	
   2.190 -	-- Restore arguments
   2.191 -	
   2.192 -	self.args.lang, self.args.profile, self.args.session = unpack(save_args)
   2.193 -	
   2.194 -	-- Update file cache
   2.195 -
   2.196 -	local htdocs = self.config.htdocsdir
   2.197 -	local cache = self.config.htmlcachedir
   2.198 -
   2.199 -	for e in tek.util.readdir(cache) do
   2.200 -		local f = e:match("^.*%.html%.?(%w*)$")
   2.201 -		if f and f ~= "tmp" then
   2.202 -			local success, msg = remove(htdocs .. "/" .. e)
   2.203 -			success, msg = remove(cache .. "/" .. e)
   2.204 - 			assert(success,
   2.205 - 				self:dbmsg("Could not purge cached HTML file", msg))
   2.206 -		end
   2.207 -	end
   2.208 -	
   2.209 -	for e in tek.util.readdir(cache) do
   2.210 -		local f = e:match("^(.*%.html%.?%w*)%.tmp$")
   2.211 -		if f then
   2.212 -			local success, msg = rename(cache .. "/" .. e, cache .. "/" .. f)
   2.213 -			assert(success,
   2.214 -				self:dbmsg("Could not update cached HTML file", msg))
   2.215 -			success, msg = rename(htdocs .. "/" .. e, htdocs .. "/" .. f)
   2.216 -			assert(success,
   2.217 -				self:dbmsg("Could not update cached HTML file", msg))
   2.218 -		end
   2.219 -	end
   2.220 -end
   2.221 -
   2.222 -
   2.223 -function loona:recursesections(s, func, ...)
   2.224 -	for _, e in ipairs(s) do
   2.225 -		local udata = { func(self, s, e, unpack(arg)) }
   2.226 -		if e.subs then
   2.227 -			self:recursesections(e.subs, func, unpack(udata))
   2.228 -		end
   2.229 -	end
   2.230 -end
   2.231 -
   2.232 -
   2.233 -function loona:indexsections()
   2.234 -	self:recursesections(self.sections, function(self, s, e)
   2.235 -		e.notvalid = (not self.secure and e.secure) or 
   2.236 -			(not self.authuser and e.secret) or nil
   2.237 -		e.notvisible = e.notvalid or not self.authuser and e.hidden or nil
   2.238 -		s[e.name] = e
   2.239 -	end)
   2.240 -end
   2.241 -
   2.242 -
   2.243 ---	Decompose section path into a stack of sections, returning only up to
   2.244 ---	the last valid element in the path. additionally returns the table of
   2.245 ---	the last section path element (or the default section)
   2.246 -
   2.247 -function loona:getsection(path)
   2.248 -	local default = not self.authuser and self.config.defname
   2.249 -	local tab = { { entries = self.sections, name = default } }
   2.250 -	local ss = self.sections
   2.251 -	local sectionpath
   2.252 -	(path or ""):gsub("(%w+)/?", function(a)
   2.253 -		if ss then
   2.254 -			local s = ss[a]
   2.255 -			if s and not s.notvalid then
   2.256 -				sectionpath = s
   2.257 -				tab[#tab].name = a
   2.258 -				ss = s.subs
   2.259 -				if ss then
   2.260 -					table.insert(tab, { entries = ss })
   2.261 -				end
   2.262 -			else
   2.263 -				ss = nil -- stop.
   2.264 -			end
   2.265 -		end
   2.266 -	end)
   2.267 -	if not self.section and not sectionpath then
   2.268 -		sectionpath = self.sections[default]
   2.269 -		if sectionpath then
   2.270 -			table.insert(tab, { entries = sectionpath.subs })
   2.271 -		end
   2.272 -	end
   2.273 -	return tab, sectionpath
   2.274 -end
   2.275 -
   2.276 -
   2.277 -function loona:getpath(delimiter, maxdepth)
   2.278 -	local t = { }
   2.279 -	local d = 0
   2.280 -	maxdepth = maxdepth or #self.submenus
   2.281 -	for _, menu in ipairs(self.submenus) do
   2.282 -		if menu.name then
   2.283 -			table.insert(t, menu.name)
   2.284 -		end
   2.285 -		d = d + 1
   2.286 -		if d == maxdepth then
   2.287 -			break
   2.288 -		end
   2.289 -	end
   2.290 -	return table.concat(t, delimiter or "/")
   2.291 -end
   2.292 -
   2.293 -
   2.294 -function loona:deletesection(fname, all_bodies)
   2.295 -	local fullname = self.contentdir .. "/" .. fname
   2.296 -	local success, msg = remove(fullname)
   2.297 -	if all_bodies then
   2.298 -		local pat = "^" .. 
   2.299 -			fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
   2.300 -		for e in tek.util.readdir(self.contentdir) do
   2.301 -			if e:match(pat) then
   2.302 -				remove(self.contentdir .. "/" .. e)
   2.303 -			end
   2.304 -		end
   2.305 -	end
   2.306 -	return success, msg
   2.307 -end
   2.308 -
   2.309 -
   2.310 -function loona:addpath(path, e)
   2.311 -	local tab = self.sections
   2.312 -	path:gsub("(%w+)/?", function(a)
   2.313 -		if tab then
   2.314 -			local s = tab[a]
   2.315 -			if s then
   2.316 -				if not s.subs then
   2.317 -					s.subs = { }
   2.318 -				end
   2.319 -				tab = s.subs
   2.320 -			else
   2.321 -				table.insert(tab, e)
   2.322 -				tab[a] = e
   2.323 - 				tab = nil -- stop
   2.324 -			end
   2.325 -		end
   2.326 -	end)
   2.327 -	return e
   2.328 -end
   2.329 -
   2.330 -
   2.331 -function loona:rmpath(path)
   2.332 -	local parent
   2.333 -	local tab = self.sections
   2.334 -	path:gsub("(%w+)/?", function(a)
   2.335 -		if tab then
   2.336 -			local idx = lookupname(tab, a)
   2.337 -			if idx then
   2.338 -				if tab[idx].subs then
   2.339 -					parent = tab[idx]
   2.340 -					tab = tab[idx].subs
   2.341 -				else
   2.342 -					table.remove(tab, idx)
   2.343 -					tab[a] = nil
   2.344 -					if #tab == 0 and parent then
   2.345 -						parent.subs = nil
   2.346 -					end
   2.347 -					tab = nil
   2.348 -				end
   2.349 -			end
   2.350 -		end
   2.351 -	end)
   2.352 -end
   2.353 -
   2.354 -
   2.355 -function loona:checkpath(path)
   2.356 -	if path ~= "index" then -- "index" is reserved
   2.357 -		local res, idx
   2.358 -		local tab = self.sections
   2.359 -		path:gsub("(%w+)/?", function(a)
   2.360 -			if tab then
   2.361 -				local i = lookupname(tab, a)
   2.362 -				if i then
   2.363 -					res, idx = tab, i
   2.364 -					tab = tab[i].subs
   2.365 -				else
   2.366 -					res, idx = nil, nil
   2.367 -				end
   2.368 -			end
   2.369 -		end)
   2.370 -		return res, idx
   2.371 -	end
   2.372 -end
   2.373 -
   2.374 -
   2.375 -function loona:title()
   2.376 -	return self.section and (self.section.title or self.section.label or
   2.377 -		self.section.name) or ""
   2.378 -end
   2.379 -
   2.380 -
   2.381 ---	Run a site function snippet, with full error recovery
   2.382 ---	(also recovers from errors in error handling function)
   2.383 -
   2.384 -function loona:dosnippet(func, errfunc)
   2.385 -	local ret = { tek.catch(func) }
   2.386 -	if ret[1] == 0 or (errfunc and tek.catch(errfunc) == 0) then
   2.387 -		return unpack(ret)
   2.388 -	end
   2.389 -	self:out("<h2>Error</h2>")
   2.390 -	self:out("<h3>" .. self:encodeform(ret[2] or "") .. "</h3>")
   2.391 -	if self.authuser then
   2.392 -		if type(ret[3]) == "string" then
   2.393 -			self:out("<p>" .. self:encodeform(ret[3]) .. "</p>")
   2.394 -		end
   2.395 -		if ret[4] then
   2.396 -			self:out("<pre>" .. self:encodeform(ret[4]) .. "</pre>")
   2.397 -		end
   2.398 -	end
   2.399 -end	
   2.400 -
   2.401 -
   2.402 -function loona:lockfile(file)
   2.403 -	return not self.session and true or 
   2.404 -		posix.symlink(self.session.filename, file .. ".LOCK")
   2.405 -end
   2.406 -
   2.407 -
   2.408 -function loona:unlockfile(file)
   2.409 -	return not self.session and true or remove(file .. ".LOCK")
   2.410 -end
   2.411 -
   2.412 -
   2.413 -function loona:saveindex()
   2.414 -	local tempname = self.indexfname .. "." .. self.session.name
   2.415 -	local f, msg = open(tempname, "wb")
   2.416 -	assert(f, self:dbmsg("Error opening section file for writing", msg))
   2.417 -	tek.dump(self.sections, function(...)
   2.418 -		f:write(unpack(arg))
   2.419 -	end)
   2.420 -	f:close()
   2.421 -	local success, msg = rename(tempname, self.indexfname)
   2.422 -	assert(success, self:dbmsg("Error renaming section file", msg))
   2.423 -end
   2.424 -
   2.425 -
   2.426 -function loona:savebody(fname, content)
   2.427 -	fname = self.contentdir .. "/" .. fname
   2.428 -	local f, msg = open(fname, "wb")
   2.429 -	assert(f, self:dbmsg("Could not open file for writing", msg))
   2.430 -	f:write(content or "")
   2.431 -	f:close()
   2.432 -end
   2.433 -
   2.434 -
   2.435 -function loona:runboxed(func, envitems, ...)
   2.436 -	local fenv = {
   2.437 - 		arg = arg,
   2.438 - 		loona = self
   2.439 - 	}
   2.440 - 	if envitems then
   2.441 -	 	for k, v in pairs(envitems) do
   2.442 - 			fenv[k] = v
   2.443 - 		end
   2.444 - 	end
   2.445 - 	setmetatable(fenv, { __index = boxed_G }) -- boxed global environment
   2.446 -	setfenv(func, fenv)
   2.447 -	return func()
   2.448 -end
   2.449 -
   2.450 -
   2.451 -function loona:include(fname, ...)
   2.452 -	assert(not fname:match("%W"), self:dbmsg("Invalid include name", fname))
   2.453 -	local fname2 = ("%s/%s.lua"):format(self.config.extdir, fname)
   2.454 -	local f, msg = open(fname2)
   2.455 -	assert(f, self:dbmsg("Cannot open file", msg))
   2.456 -	local parsed, msg = self:loadhtml(f, "loona:out", fname2)
   2.457 -	assert(parsed, self:dbmsg("Syntax error", msg))
   2.458 -	return self:runboxed(parsed, nil, unpack(arg))
   2.459 -end
   2.460 -
   2.461 -
   2.462 ---	produce link target (simple)
   2.463 -
   2.464 -function loona:shref(section, arg)
   2.465 -	local args2 = { } -- propagated or new arguments
   2.466 -	for _, a in ipairs(arg) do
   2.467 -		local key, val = a:match("^(%w+)=(.*)$")
   2.468 -		if key and val then -- "arg=val" sets/overrides argument
   2.469 -			table.insert(args2, { name = key, value = val })
   2.470 -		elseif self.args[a] then -- just "arg" propagates argument
   2.471 -			table.insert(args2, { name = a, value = self.args[a] })
   2.472 -		end
   2.473 -	end
   2.474 -	local doc = self:getdocname(section, #args2 > 0)
   2.475 -	local url, anch = doc:match("^(.+)(#.+)$")
   2.476 -	local notfirst = doc:match("%?")
   2.477 -	local href = { anch and url or doc }
   2.478 -	for i, arg in ipairs(args2) do
   2.479 -		if i > 1 or notfirst then
   2.480 -			table.insert(href, "&amp;")
   2.481 -		else
   2.482 -			table.insert(href, "?")
   2.483 -		end
   2.484 -		table.insert(href, arg.name .. "=" .. cgi.encodeurl(arg.value))
   2.485 -	end
   2.486 -	if anch then
   2.487 -		insert(href, anch)
   2.488 -	end
   2.489 -	return table.concat(href)
   2.490 -end
   2.491 -
   2.492 -
   2.493 ---	produce link target, implicit propagation of lang, profile, session
   2.494 -
   2.495 -function loona:href(section, ...)
   2.496 -	if self.session then
   2.497 -		table.insert(arg, 1, "profile")
   2.498 -		table.insert(arg, 1, "session")
   2.499 -	end
   2.500 -	if self.explicitlang then
   2.501 -		table.insert(arg, 1, "lang")
   2.502 -	end
   2.503 -	return self:shref(section, arg)
   2.504 -end
   2.505 -
   2.506 -
   2.507 -function loona:ilink(target, text, extra)
   2.508 -	return ('<a href="%s"%s>%s</a>'):format(target, extra or "", text)
   2.509 -end
   2.510 -
   2.511 -
   2.512 ---	internal link, implicit propagation of lang, profile, session
   2.513 -
   2.514 -function loona:link(section, text, ...)
   2.515 -	return self:ilink(self:href(section, unpack(arg)), text or section,
   2.516 -	' class="intlink"')
   2.517 -end
   2.518 -
   2.519 -
   2.520 ---	external link (opens in a new window), no argument propagation
   2.521 -
   2.522 -function loona:elink(target, text)
   2.523 -	return self:ilink(target, text or target, self.config.extlinkextra)
   2.524 -end
   2.525 -
   2.526 -
   2.527 ---	plain link, no implicit argument propagation
   2.528 -
   2.529 -function loona:plink(section, text, ...)
   2.530 -	return self:ilink(self:shref(section, arg), text or section)
   2.531 -end
   2.532 -
   2.533 -
   2.534 ---	user interface link, implicit propagation of lang, profile, session
   2.535 -
   2.536 -function loona:uilink(section, text, ...)
   2.537 -	return self:ilink(self:href(section, unpack(arg)), text or section,
   2.538 -	' class="uilink"')
   2.539 -end
   2.540 -
   2.541 -
   2.542 ---	produce a hidden input value in forms
   2.543 -
   2.544 -function loona:hidden(name, value)
   2.545 -	return not value and "" or
   2.546 -		('<input type="hidden" name="%s" value="%s" />'):format(name, value)
   2.547 -end
   2.548 -
   2.549 -
   2.550 -function loona:scanprofiles(func)
   2.551 -	local tab = { }
   2.552 -	local dir = self.config.contentdir
   2.553 -	for f in tek.util.readdir(dir) do
   2.554 -		if posix.lstat(dir .. "/" .. f, "mode") == "directory" then
   2.555 -			f = func(f)
   2.556 -			if f then
   2.557 -				table.insert(tab, f)
   2.558 -			end
   2.559 -		end
   2.560 -	end
   2.561 -	table.sort(tab)
   2.562 -	for _, v in ipairs(tab) do
   2.563 -		tab[v] = v	
   2.564 -	end
   2.565 -	return tab
   2.566 -end
   2.567 -
   2.568 -
   2.569 -function loona:getprofiles(lang)
   2.570 -	lang = lang or self.lang
   2.571 -	return self:scanprofiles(function(f)
   2.572 -		return f:match("^(%w+)_" .. lang .. "$")
   2.573 -	end)
   2.574 -end
   2.575 -
   2.576 -
   2.577 -function loona:getlanguages(prof)
   2.578 -	prof = prof or self.profile
   2.579 -	return self:scanprofiles(function(f)
   2.580 -		return f:match("^" .. prof .. "_(%l%l)$")
   2.581 -	end)
   2.582 -end
   2.583 -
   2.584 -
   2.585 ---	Functions to produce a navigation menu
   2.586 -
   2.587 -local newent = { name = "new", label = "[+]", action="actionnew=true" }
   2.588 -
   2.589 -function loona:rmenu(level, render, path, addnew, recurse)
   2.590 -	local sub = (addnew and level == #self.submenus + 1) and
   2.591 -		{ name = "new", entries = { }} or self.submenus[level]
   2.592 - 	if sub and sub.entries then
   2.593 -		local visible = { }
   2.594 -		for _, e in ipairs(sub.entries) do
   2.595 -			if not e.notvisible then
   2.596 -				table.insert(visible, e)
   2.597 -			end
   2.598 -		end
   2.599 -		if addnew then
   2.600 -			table.insert(visible, newent)
   2.601 -		end
   2.602 -		local numvis = #visible
   2.603 -		if numvis > 0 then
   2.604 -			render.listbegin(self, level, numvis, path)
   2.605 -			for idx, e in ipairs(visible) do
   2.606 -				local label = self:encodeform(e.label or e.name)
   2.607 -				local newpath = path and path .. "/" .. e.name or e.name
   2.608 -				local active = (e.name == sub.name)
   2.609 -				render.itembegin(self, level, idx)
   2.610 -				render.link(self, level, newpath, label, active, e.action)
   2.611 -				if recurse and active then
   2.612 -					self:rmenu(level + 1, render, newpath, addnew, recurse)
   2.613 -				end
   2.614 -				render.itemend(self)
   2.615 -			end
   2.616 -			render.listend(self)
   2.617 -		end
   2.618 -	end
   2.619 -end
   2.620 -
   2.621 -
   2.622 -function loona:menu(level, recurse, render)
   2.623 -	level = level or 1
   2.624 -	render = render or { }
   2.625 -	render.link = render.link or 
   2.626 -		function(self, level, path, label, active, ...)
   2.627 -			self:out(('<a %shref="%s">%s</a>\n'):format(active and 
   2.628 -				'class="active" ' or "", self:href(path, unpack(arg)), label))
   2.629 -		end
   2.630 -	render.listbegin = render.listbegin or
   2.631 -		function(self, level) -- , numvis, path
   2.632 -			self:out('<ul id="menulevel' .. level .. '">\n')
   2.633 -		end
   2.634 -	render.listend = render.listend or
   2.635 -		function(self)
   2.636 -			self:out('</ul>\n')
   2.637 -		end
   2.638 -	render.itembegin = render.itembegin or
   2.639 -		function(self) -- , level, idx
   2.640 -			self:out('<li>\n')
   2.641 -		end
   2.642 -	render.itemend = render.itemend or
   2.643 -		function(self)
   2.644 -			self:out('</li>\n')
   2.645 -		end
   2.646 -	recurse = recurse == nil and true or recurse
   2.647 -	local path = level > 1 and self:getpath("/", level - 1) or nil
   2.648 -	local addnew = self.authuser and not self.ispubprofile
   2.649 -	self:rmenu(level, render, path, addnew, recurse)
   2.650 -end
   2.651 -
   2.652 -
   2.653 -function loona:loadcontent(fname)
   2.654 -	if fname then
   2.655 -		local f = open(self.contentdir .. "/" .. fname)
   2.656 -		local c = f:read("*a")
   2.657 -		f:close()
   2.658 -		return c
   2.659 -	end
   2.660 -	return ""
   2.661 -end
   2.662 -
   2.663 -
   2.664 -function loona:loadmarkup(fname)
   2.665 -	return (fname and fname ~= "") and
   2.666 -		self:domarkup(self:loadcontent(fname)) or ""
   2.667 -end
   2.668 -
   2.669 -
   2.670 -function loona:editable(editkey, fname, savename)
   2.671 -	
   2.672 -	local contentdir = self.contentdir
   2.673 -	local edit, show, hidden, extramsg, changed
   2.674 -	
   2.675 -	if self.authuser then
   2.676 -		
   2.677 -		local hiddenvars = table.concat( {
   2.678 -			self:hidden("lang", self.args.lang),
   2.679 -			self:hidden("profile", self.profile),
   2.680 -			self:hidden("session", self.session.id),
   2.681 -			self:hidden("editkey", editkey) }, " ")
   2.682 -	
   2.683 -		local lockfname = fname and (contentdir .. "/" .. fname)
   2.684 -		
   2.685 -		if self.useralert and editkey == self.args.editkey then
   2.686 -			
   2.687 -			--	display user alert/request/confirmation
   2.688 -			
   2.689 -			hidden = true
   2.690 -			self:out([[
   2.691 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.692 -				<fieldset>
   2.693 -					<legend>]] .. self.useralert.text ..[[</legend>
   2.694 -					]] .. (self.useralert.confirm or "") .. [[
   2.695 -					]] .. (self.useralert.returnto or "") .. [[
   2.696 -					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL  ..[[" />
   2.697 -					]] .. hiddenvars .. [[
   2.698 -				</fieldset>
   2.699 -			</form>
   2.700 -			]])
   2.701 -			
   2.702 -		elseif self.args.actionnew and editkey == "main" then
   2.703 -			
   2.704 -			--	form for creating a new section
   2.705 -			
   2.706 -			hidden = true
   2.707 -			if self.ispubprofile then
   2.708 -				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
   2.709 -			end
   2.710 -			self:out([[
   2.711 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.712 -				<fieldset>
   2.713 -					<legend>
   2.714 -						]] .. self.locale.CREATE_NEW_SECTION_UNDER .. " " .. self.sectionpath .. [[
   2.715 -					</legend>
   2.716 -					<table>
   2.717 -						<tr>
   2.718 -							<td align="right">
   2.719 -								]] .. self.locale.PATHNAME .. [[
   2.720 -							</td>
   2.721 -							<td>
   2.722 -								<input size="30" maxlength="30" name="editname" />
   2.723 -							</td>
   2.724 -						</tr>
   2.725 -						<tr>
   2.726 -							<td align="right">
   2.727 -								]] .. self.locale.MENULABEL .. [[
   2.728 -							</td>
   2.729 -							<td>
   2.730 -								<input size="30" maxlength="50" name="editlabel" />
   2.731 -							</td>
   2.732 -						</tr>
   2.733 -						<tr>
   2.734 -							<td align="right">
   2.735 -								]] .. self.locale.WINDOWTITLE .. [[
   2.736 -							</td>
   2.737 -							<td>
   2.738 -								<input size="30" maxlength="50" name="edittitle" />
   2.739 -							</td>
   2.740 -						</tr>
   2.741 -						<tr>
   2.742 -							<td align="right">
   2.743 -								]] .. self.locale.INVISIBLE .. [[
   2.744 -							</td>
   2.745 -							<td>
   2.746 -								<input type="checkbox" name="editvisibility" />
   2.747 -							</td>
   2.748 -						</tr>
   2.749 -						<tr>
   2.750 -							<td align="right">
   2.751 -								]] .. self.locale.SECRET .. [[
   2.752 -							</td>
   2.753 -							<td>
   2.754 -								<input type="checkbox" name="editsecrecy" />
   2.755 -							</td>
   2.756 -						</tr>
   2.757 -						<tr>
   2.758 -							<td align="right">
   2.759 -								]] .. self.locale.SECURE_CONNECTION .. [[
   2.760 -							</td>
   2.761 -							<td>
   2.762 -								<input type="checkbox" name="editsecure" />
   2.763 -							</td>
   2.764 -						</tr>
   2.765 -						<tr>
   2.766 -							<td align="right">
   2.767 -								]] .. self.locale.REDIRECT .. [[
   2.768 -							</td>
   2.769 -							<td>
   2.770 -								<input size="30" maxlength="50" name="editredirect" />
   2.771 -							</td>
   2.772 -						</tr>
   2.773 -					</table>					
   2.774 -					<input type="submit" name="actioncreate" value="]] .. self.locale.CREATE .. [[" />
   2.775 -					]] .. hiddenvars .. [[
   2.776 -				</fieldset>
   2.777 -			</form>
   2.778 -			<hr />
   2.779 -			]])
   2.780 -		
   2.781 -		elseif self.args.actioneditprops and editkey == "main" then
   2.782 -			hidden = true
   2.783 -			if self.ispubprofile then
   2.784 -				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
   2.785 -			end
   2.786 -			self:out([[
   2.787 -			<form action="]] ..self.document .. [[" method="post" accept-charset="utf-8">
   2.788 -				<fieldset>
   2.789 -					<legend>
   2.790 -						]] .. self.locale.MODIFY_PROPERTIES_OF_SECTION .. " " .. self.sectionpath .. [[
   2.791 -					</legend>
   2.792 -					<table>
   2.793 -						<tr>
   2.794 -							<td align="right">
   2.795 -								]] .. self.locale.MENULABEL .. [[
   2.796 -							</td>
   2.797 -							<td>
   2.798 -								<input size="30" maxlength="50" name="editlabel" value="]] .. (self.section.label or "") .. [[" />
   2.799 -							</td>
   2.800 -						</tr>
   2.801 -						<tr>
   2.802 -							<td align="right">
   2.803 -								]] .. self.locale.WINDOWTITLE .. [[
   2.804 -							</td>
   2.805 -							<td>
   2.806 -								<input size="30" maxlength="50" name="edittitle" value="]] .. (self.section.title or "") .. [[" />
   2.807 -							</td>
   2.808 -						</tr>
   2.809 -						<tr>
   2.810 -							<td align="right">
   2.811 -								]] .. self.locale.INVISIBLE .. [[
   2.812 -							</td>
   2.813 -							<td>
   2.814 -								<input type="checkbox" name="editvisibility" ]] .. (self.section.hidden and 'checked="checked"' or "") .. [[/>
   2.815 -							</td>
   2.816 -						</tr>
   2.817 -						<tr>
   2.818 -							<td align="right">
   2.819 -								]] .. self.locale.SECRET .. [[
   2.820 -							</td>
   2.821 -							<td>
   2.822 -								<input type="checkbox" name="editsecrecy" ]] .. (self.section.secret and 'checked="checked"' or "") .. [[/>
   2.823 -							</td>
   2.824 -						</tr>
   2.825 -						<tr>
   2.826 -							<td align="right">
   2.827 -								]] .. self.locale.SECURE_CONNECTION .. [[
   2.828 -							</td>
   2.829 -							<td>
   2.830 -								<input type="checkbox" name="editsecure" ]] .. (self.section.secure and 'checked="checked"' or "") .. [[/>
   2.831 -							</td>
   2.832 -						</tr>
   2.833 -						<tr>
   2.834 -							<td align="right">
   2.835 -								]] .. self.locale.REDIRECT .. [[
   2.836 -							</td>
   2.837 -							<td>
   2.838 -								<input size="30" maxlength="50" name="editredirect" value="]] .. (self.section.redirect or "") .. [[" />
   2.839 -							</td>
   2.840 -						</tr>
   2.841 -					</table>
   2.842 -					<input type="submit" name="actionsaveprops" value="]] .. self.locale.SAVE .. [[" />
   2.843 -					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
   2.844 -					]] .. hiddenvars .. [[
   2.845 -				</fieldset>
   2.846 -			</form>
   2.847 -			]])
   2.848 -		
   2.849 -		elseif (self.args.actioneditprofiles or
   2.850 -			self.args.actioncreateprofile or 
   2.851 -			self.args.actionchangeprofile or 
   2.852 -			self.args.actionchangelanguage or
   2.853 -			self.args.actionpublishprofile) and editkey == "main" then
   2.854 -			hidden = true
   2.855 -			self:out([[
   2.856 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.857 -				<fieldset>
   2.858 -					<legend>
   2.859 -						]] .. self.locale.CHANGEPROFILE .. [[
   2.860 -					</legend>
   2.861 -					<select name="changeprofile" size="1">]])
   2.862 -						for _, val in ipairs(self:getprofiles()) do
   2.863 -							self:out('<option' .. (val == self.profile and ' selected="selected"' or '') .. '>')
   2.864 -							self:out(val)
   2.865 -							self:out('</option>')
   2.866 -						end
   2.867 -					self:out([[
   2.868 -					</select>							
   2.869 -					<input type="submit" name="actionchangeprofile" value="]] .. self.locale.CHANGE ..[[" />
   2.870 -					]] .. hiddenvars .. [[
   2.871 -				</fieldset>
   2.872 -			</form>
   2.873 -			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.874 -				<fieldset>
   2.875 -					<legend>
   2.876 -						]] .. self.locale.CHANGELANGUAGE .. [[
   2.877 -					</legend>
   2.878 -					<select name="changelanguage" size="1">]])
   2.879 -						for _, val in ipairs(self:getlanguages()) do
   2.880 -							self:out('<option' .. (val == self.lang and ' selected="selected"' or '') .. '>')
   2.881 -							self:out(val)
   2.882 -							self:out('</option>')
   2.883 -						end
   2.884 -					self:out([[
   2.885 -					</select>							
   2.886 -					<input type="submit" name="actionchangelanguage" value="]] .. self.locale.CHANGE ..[[" />
   2.887 -					]] .. hiddenvars .. [[
   2.888 -				</fieldset>
   2.889 -			</form>
   2.890 -			<form action="]] .. self.document ..[[" method="post" accept-charset="utf-8">
   2.891 -				<fieldset>
   2.892 -					<legend>
   2.893 -						]] .. self.locale.CREATEPROFILE .. [[
   2.894 -					</legend>
   2.895 -					<input size="20" maxlength="20" name="createprofile" />
   2.896 -					]] .. self.locale.LANGUAGE ..[[
   2.897 -					<input size="2" maxlength="2" name="createlanguage" value="]] .. self.lang ..[[" />
   2.898 -					<input type="submit" name="actioncreateprofile" value="]] .. self.locale.CREATE .. [[" />
   2.899 -					]] .. hiddenvars .. [[
   2.900 -				</fieldset>
   2.901 -			</form>
   2.902 -			]])
   2.903 -			if not self.ispubprofile then
   2.904 -				self:out([[
   2.905 -				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.906 -					<fieldset>
   2.907 -						<legend>
   2.908 -							]] .. self.locale.PUBLISHPROFILE .. [[
   2.909 -						</legend>
   2.910 -						]] .. self:hidden("publishprofile", self.profile) .. [[
   2.911 -						<input type="submit" name="actionpublishprofile" value="]] .. self.locale.PUBLISH .. [[" />
   2.912 -						]] .. hiddenvars .. [[
   2.913 -					</fieldset>
   2.914 -				</form>
   2.915 -				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
   2.916 -					<fieldset>
   2.917 -						<legend>
   2.918 -							]] .. self.locale.DELETEPROFILE .. [[
   2.919 -						</legend>
   2.920 -						]] .. self:hidden("deleteprofile", self.profile) .. [[
   2.921 -						<input type="submit" name="actiondeleteprofile" value="]] .. self.locale.DELETE .. [[" />
   2.922 -						]] .. hiddenvars .. [[
   2.923 -					</fieldset>
   2.924 -				</form>
   2.925 -				]])
   2.926 -			end
   2.927 -			
   2.928 -		elseif self.args.actionedit and editkey == self.args.editkey then
   2.929 -			if not self.section.redirect then
   2.930 -				extramsg = self.ispubprofile and
   2.931 -					self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE
   2.932 -				edit = self:loadcontent(fname):gsub("\194\160", "&nbsp;") -- TODO
   2.933 -				changed = self.section and (self.section.revisiondate or self.section.creationdate)
   2.934 -			end
   2.935 -		
   2.936 -		elseif self.args.actionpreview and editkey == self.args.editkey then
   2.937 -			edit = self.args.editform
   2.938 -			show = self:domarkup(edit:gsub("&nbsp;", "\194\160")) -- TODO
   2.939 -		
   2.940 -		elseif self.args.actionsave and editkey == self.args.editkey then
   2.941 -			local c = self.args.editform
   2.942 -			local dynamic
   2.943 -			
   2.944 -			if lockfname then
   2.945 -				self:expire(contentdir, "[^.]%S+.LOCK")
   2.946 -				if self:lockfile(lockfname) then
   2.947 -					-- lock was expired, aquired a new one
   2.948 -					extramsg = self.locale.SECTION_COULD_HAVE_CHANGED
   2.949 -					edit = c
   2.950 -				else
   2.951 -					local tab = tek.source(lockfname .. ".LOCK")
   2.952 -					if tab and tab.id == self.session.id then
   2.953 -						-- lock already held and is mine - try to save:
   2.954 -						local savec = c:gsub("&nbsp;", "\194\160") -- TODO
   2.955 -						remove(contentdir .. "/" .. savename .. ".html")
   2.956 -						self:savebody(savename, savec)
   2.957 -						-- TODO: error handling
   2.958 -						self:unlockfile(lockfname)
   2.959 -						show, dynamic = self:domarkup(savec)
   2.960 -						changed = time()
   2.961 -					else
   2.962 -						-- lock was expired and someone else has it now
   2.963 -						extramsg = self.locale.SECTION_IN_USE
   2.964 -						edit = c
   2.965 -					end
   2.966 -				end
   2.967 -			else
   2.968 -				-- new sidefile
   2.969 -				local savec = c:gsub("&nbsp;", "\194\160") -- TODO
   2.970 -				self:savebody(savename, savec)
   2.971 -				-- TODO: error handling
   2.972 -				show, dynamic = self:domarkup(savec)
   2.973 -			end
   2.974 -			
   2.975 -			-- mark dynamic text bodies
   2.976 -			if not self.section.dynamic then
   2.977 -				self.section.dynamic = { }
   2.978 -			end
   2.979 -			self.section.dynamic[editkey] = dynamic
   2.980 -			local n = 0
   2.981 -			for _ in pairs(self.section.dynamic) do
   2.982 -				n = n + 1
   2.983 -			end
   2.984 -			if n == 0 then
   2.985 -				self.section.dynamic = nil
   2.986 -			end
   2.987 -			
   2.988 -			self:saveindex()
   2.989 -			
   2.990 -		elseif self.args.actioncancel and editkey == self.args.editkey then
   2.991 -			if lockfname then
   2.992 -				self:unlockfile(lockfname) -- remove lock
   2.993 -			end
   2.994 -		end
   2.995 -		
   2.996 -		if editkey == "main" and self.section and self.section.redirect then
   2.997 -			self:out('<h2>' .. self.locale.SECTION_IS_REDIRECT ..'</h2>')
   2.998 -			self:out(self:link(self.section.redirect))
   2.999 -			self:out('<hr />')
  2.1000 -		end
  2.1001 -	
  2.1002 -		if edit then
  2.1003 -			self:expire(contentdir, "[^.]%S+.LOCK")
  2.1004 -			if fname and not self:lockfile(contentdir .. "/" .. fname) then
  2.1005 -				local tab = tek.source(contentdir .. "/" .. fname .. ".LOCK")
  2.1006 -				if tab and tab.id ~= self.session.id then
  2.1007 -					extramsg = self.locale.SECTION_IN_USE
  2.1008 -				end
  2.1009 -				-- else already owner
  2.1010 -			end
  2.1011 -			if extramsg then
  2.1012 -				self:out('<h2><span class="warn">' .. extramsg .. '</span></h2>')
  2.1013 -			end
  2.1014 -			self:out([[
  2.1015 -			<form action="]] .. self.document .. [[#preview" method="post" accept-charset="utf-8">
  2.1016 -				<fieldset>
  2.1017 -					<legend>
  2.1018 -						]] .. self.locale.EDIT_SECTION .. [[
  2.1019 -					</legend>
  2.1020 -					<textarea cols="80" rows="25" name="editform">
  2.1021 -]] .. self:encodeform(edit) .. [[</textarea>
  2.1022 -					<br />
  2.1023 -					<input type="submit" name="actionsave" value="]] .. self.locale.SAVE .. [[" />
  2.1024 -					<input type="submit" name="actionpreview" value="]] .. self.locale.PREVIEW .. [[" />
  2.1025 -					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
  2.1026 -					]] .. hiddenvars .. [[
  2.1027 -				</fieldset>
  2.1028 -			</form>
  2.1029 -			]])
  2.1030 -		end
  2.1031 -	end	
  2.1032 -	
  2.1033 -	if not hidden then
  2.1034 -		self:dosnippet(function()
  2.1035 -			if not show then
  2.1036 -				show = self:loadmarkup(fname)
  2.1037 -				changed = self.section and (self.section.revisiondate or self.section.creationdate)
  2.1038 -			end
  2.1039 -			local parsed, msg = self:loadhtml(show, "loona:out", "<parsed html>")
  2.1040 -			assert(parsed, msg and "Syntax error : " .. msg)
  2.1041 -			self:runboxed(parsed)
  2.1042 -		end)
  2.1043 -	end
  2.1044 -	
  2.1045 -	if self.authuser then
  2.1046 -		self:out([[
  2.1047 -		<hr />
  2.1048 -		<div class="edit">]])
  2.1049 -			if editkey == "main" then
  2.1050 -				self:out([[
  2.1051 -				<a name="preview"></a>
  2.1052 -				]] .. self.authuser .. [[ : 
  2.1053 -				]] .. self:uilink(self.sectionpath, "[" .. self.locale.PROFILE .. "]", "actioneditprofiles=true", "editkey=" .. editkey) .. [[ :
  2.1054 -				]] .. self.profile .. "_" .. self.lang)
  2.1055 -				if self.ispubprofile then
  2.1056 -					self:out([[
  2.1057 -						<span class="warn">[]] .. self.locale.PUBLIC .. [[]</span>]])
  2.1058 -				end
  2.1059 -				self:out(' : ' .. self.sectionpath .. ' ')
  2.1060 -			end
  2.1061 -			if self.section and not self.ispubprofile then
  2.1062 -				self:out(self:uilink(self.sectionpath, "[" .. self.locale.EDIT .. "]", "actionedit=true", "editkey=" .. editkey) .. " ")
  2.1063 -				if editkey == "main" then
  2.1064 -					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.PROPERTIES .. "]", "actioneditprops=true", "editkey=" .. editkey) .. " ")
  2.1065 -				end
  2.1066 -				if fname == savename or not self.section.subs then
  2.1067 -					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.DELETE .. "]", "actiondelete=true", "editkey=" .. editkey) .. " ")
  2.1068 -				end
  2.1069 -				if editkey == "main" then
  2.1070 -					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEUP .. "]", "actionup=true", "editkey=" .. editkey) .. " ")
  2.1071 -					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEDOWN .. "]", "actiondown=true", "editkey=" .. editkey) .. " ")
  2.1072 -				end
  2.1073 -				if changed and editkey == "main" then
  2.1074 -					self:out('- ' .. self.locale.CHANGED .. ': ' .. date("%d-%b-%Y %T", changed))
  2.1075 -				end
  2.1076 -			end
  2.1077 -		self:out('</div>')
  2.1078 -	end
  2.1079 -
  2.1080 -end
  2.1081 -
  2.1082 -
  2.1083 ---	Get pathname of an existing content file that
  2.1084 ---	the current path is determined by (or defaults to)
  2.1085 -
  2.1086 -function loona:getsectionpath(bodyname, requestpath)
  2.1087 -	local ext = (not bodyname or bodyname == "main") and "" or "." .. bodyname
  2.1088 -	local t, path, section = { }
  2.1089 -	for _, menu in ipairs(self.submenus) do
  2.1090 -		if menu.entries and menu.entries[menu.name] then
  2.1091 -			table.insert(t, menu.name)
  2.1092 -			local fn = table.concat(t, "_")
  2.1093 -			if posix.stat(self.contentdir .. "/" .. fn .. ext, 
  2.1094 -				"mode") == "file" then
  2.1095 -				path, section = fn, menu
  2.1096 -			end
  2.1097 -		end
  2.1098 -	end
  2.1099 -	return path, ext, section
  2.1100 -end
  2.1101 -
  2.1102 -
  2.1103 -function loona:body(name)
  2.1104 -	name = self:checkbodyname(name)
  2.1105 -	local path, ext = self:getsectionpath(name)
  2.1106 -	self:dosnippet(function()
  2.1107 -		self:editable(name, path and path .. ext, self.sectionname .. ext)
  2.1108 -	end)
  2.1109 -end
  2.1110 -
  2.1111 -
  2.1112 -function loona:init()
  2.1113 -	
  2.1114 -	-- get list of languages, in order of preference
  2.1115 -	-- TODO: respect quality parameter, not just order
  2.1116 -	
  2.1117 -	local l = self.requestlang or self.args.lang
  2.1118 -	self.langs = { l and l:match("^%w+$") }
  2.1119 -	local s = getenv("HTTP_ACCEPT_LANGUAGE")
  2.1120 -	while s do
  2.1121 -		local l, r = s:match("^([%w.=]+)[,;](.*)$")
  2.1122 -		l = l or s
  2.1123 -		s = r
  2.1124 -		if l:match("^%w+$") then
  2.1125 -			table.insert(self.langs, l)
  2.1126 -		end
  2.1127 -	end
  2.1128 -	table.insert(self.langs, self.config.deflang)
  2.1129 -	
  2.1130 -	-- get list of possible profiles
  2.1131 -	
  2.1132 -	local profiles = { }
  2.1133 -	for e in tek.util.readdir(self.config.contentdir) do
  2.1134 -		profiles[e] = e
  2.1135 -	end
  2.1136 -	
  2.1137 -	-- get pubprofile
  2.1138 -	
  2.1139 -	for _, lang in ipairs(self.langs) do
  2.1140 -		local p = posix.readlink(self.config.contentdir .. "/current_" .. lang)
  2.1141 -		p = p and p:match("^(%w+)_" .. lang .. "$")
  2.1142 -		if p then
  2.1143 -			self.pubprofile = p
  2.1144 -			break
  2.1145 -		end
  2.1146 -	end
  2.1147 -	
  2.1148 -	-- get profile
  2.1149 -	
  2.1150 -	local checkprofile =
  2.1151 -		self.authuser and self.args.profile or self.pubprofile or "work"
  2.1152 -	for _, lang in ipairs(self.langs) do
  2.1153 -		if profiles[checkprofile .. "_" .. lang] then
  2.1154 -			self.profile = checkprofile
  2.1155 -			self.lang = lang
  2.1156 -			break
  2.1157 -		end
  2.1158 -	end
  2.1159 -	
  2.1160 -	assert(self.profile and self.lang, "Invalid profile or language")
  2.1161 -	
  2.1162 -	
  2.1163 -	self.ispubprofile = self.profile == self.pubprofile
  2.1164 -	
  2.1165 -	-- write back language and profile
  2.1166 -	
  2.1167 -	self.args.lang = (self.explicitlang or self.lang ~= self.config.deflang)
  2.1168 -		and self.lang or nil
  2.1169 -	self.args.profile = self.profile
  2.1170 -	
  2.1171 -	-- determine content directory pathname and section filename
  2.1172 -	
  2.1173 -	self.contentdir =
  2.1174 -		("%s/%s_%s"):format(self.config.contentdir, self.profile, self.lang)
  2.1175 - 	self.indexfname = self.contentdir .. "/.sections"
  2.1176 -	
  2.1177 -	-- load sections
  2.1178 -	
  2.1179 - 	self.sections = tek.source(self.indexfname)
  2.1180 -	
  2.1181 -	-- index sections, determine visibility in menu
  2.1182 -	
  2.1183 -	self:indexsections()
  2.1184 -	
  2.1185 -	-- decompose request path, produce a stack of sections
  2.1186 -	
  2.1187 -	self.submenus, self.section = self:getsection(self.requestpath)
  2.1188 -
  2.1189 -	-- handle redirects if not logged on
  2.1190 -	
  2.1191 -	if not self.authuser and self.section and self.section.redirect then
  2.1192 -		self.submenus, self.section = self:getsection(self.section.redirect)
  2.1193 -	end
  2.1194 -			
  2.1195 -	-- section path and document name (refined)
  2.1196 -	
  2.1197 -	self.sectionpath = self:getpath()
  2.1198 -	self.sectionname = self:getpath("_")
  2.1199 -
  2.1200 -end
  2.1201 -
  2.1202 -
  2.1203 -function loona:handlechanges()
  2.1204 -	
  2.1205 -	local save
  2.1206 -
  2.1207 -	if self.args.editkey == "main" then
  2.1208 -		
  2.1209 -		-- In main editable section:
  2.1210 -		
  2.1211 -		if self.args.actioncreate then
  2.1212 -			
  2.1213 -			-- Create new section
  2.1214 -			
  2.1215 -			local editname = self.args.editname:lower()
  2.1216 -			assert(not editname:match("%W"),
  2.1217 -				self:dbmsg("Invalid section name", editname))
  2.1218 -			if not (section and (section.subs or section)[editname]) then
  2.1219 -				local newpath = (self.sectionpath and 
  2.1220 -					(self.sectionpath .. "/")) .. editname
  2.1221 -				local s = self:addpath(newpath, { name = editname,
  2.1222 -					label = self.args.editlabel ~= "" and
  2.1223 -						self.args.editlabel or nil,
  2.1224 -					title = self.args.edittitle ~= "" and
  2.1225 -						self.args.edittitle or nil,
  2.1226 -					redirect = self.args.editredirect ~= "" and
  2.1227 -						self.args.editredirect or nil,
  2.1228 -					hidden = self.args.editvisibility and true,
  2.1229 -					secret = self.args.editsecrecy and true,
  2.1230 -					secure = self.args.editsecure and true,
  2.1231 -					creator = self.authuser,
  2.1232 -					creationdate = time() })
  2.1233 -				save = true
  2.1234 -			end
  2.1235 -		
  2.1236 -		elseif self.args.actionsave then
  2.1237 -			
  2.1238 -			-- Save section
  2.1239 -			
  2.1240 -			self.section.revisiondate = time()
  2.1241 -			self.section.revisioner = self.authuser
  2.1242 -			save = true
  2.1243 - 		
  2.1244 -		elseif self.args.actionsaveprops then
  2.1245 -			
  2.1246 -			-- Save properties
  2.1247 -			
  2.1248 -			self.section.hidden = self.args.editvisibility and true
  2.1249 -			self.section.secret = self.args.editsecrecy and true
  2.1250 -			self.section.secure = self.args.editsecure and true
  2.1251 -			self.section.label = self.args.editlabel ~= "" and
  2.1252 -				self.args.editlabel or nil
  2.1253 -			self.section.title = self.args.edittitle ~= "" and
  2.1254 -				self.args.edittitle or nil
  2.1255 -			self.section.redirect =
  2.1256 -				self.args.editredirect ~= "" and self.args.editredirect or nil
  2.1257 -			save = true
  2.1258 -		
  2.1259 -		elseif self.args.actionup then
  2.1260 -			
  2.1261 -			-- Move section up
  2.1262 -			
  2.1263 -			local t, i = self:checkpath(self.sectionpath)
  2.1264 -			if t and i > 1 then
  2.1265 -				if self.ispubprofile and not self.args.actionconfirm then
  2.1266 -					useralert = {
  2.1267 -						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
  2.1268 -						confirm =
  2.1269 -							'<input type="submit" name="actionup" value="' ..
  2.1270 -							self.locale.MOVE .. '" /> ' ..
  2.1271 -							self:hidden("actionconfirm", "true")
  2.1272 -					}
  2.1273 -				else
  2.1274 -					local item = table.remove(t, i)
  2.1275 -					table.insert(t, i - 1, item)
  2.1276 -					save = true
  2.1277 -				end
  2.1278 -			end
  2.1279 -		
  2.1280 -		elseif self.args.actiondown then
  2.1281 -			
  2.1282 -			-- Move section down
  2.1283 -			
  2.1284 -			local t, i = self:checkpath(self.sectionpath)
  2.1285 -			if t and i < #t then
  2.1286 -				if self.ispubprofile and not self.args.actionconfirm then
  2.1287 -					useralert = {
  2.1288 -						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
  2.1289 -						confirm =
  2.1290 -							'<input type="submit" name="actiondown" value="' ..
  2.1291 -							self.locale.MOVE .. '" /> ' ..
  2.1292 -							self:hidden("actionconfirm", "true")
  2.1293 -					}
  2.1294 -				else
  2.1295 -					local item = table.remove(t, i)
  2.1296 -					table.insert(t, i + 1, item)
  2.1297 -					save = true
  2.1298 -				end
  2.1299 -			end
  2.1300 -		
  2.1301 -		elseif self.args.actioncreateprofile and self.args.createprofile then
  2.1302 -			
  2.1303 -			-- Create profile
  2.1304 -			
  2.1305 -			local c = self.args.createprofile
  2.1306 -			if c == "" then
  2.1307 -				c = self.profile
  2.1308 -			end
  2.1309 -			c = self:checkprofilename(c:lower())
  2.1310 -			local l = self:checklanguage((self.args.createlanguage or self.lang):lower())
  2.1311 -			if c == self.profile and l == self.lang then
  2.1312 -				useralert = { 
  2.1313 -					text = self.locale.ALERT_CANNOT_COPY_PROFILE_TO_SELF,
  2.1314 -					returnto = self:hidden("actioneditprofiles", "true")
  2.1315 -				}
  2.1316 -			else
  2.1317 -				local profiles = self:getprofiles(l)
  2.1318 -				local text
  2.1319 -				if c == self.pubprofile then
  2.1320 -					text = self.locale.ALERT_OVERWRITE_PUBLISHED_PROFILE
  2.1321 -				elseif profiles[c] and l == self.lang then
  2.1322 -					text = self.locale.ALERT_OVERWRITE_EXISTING_PROFILE
  2.1323 -				end
  2.1324 -				if text and not self.args.actionconfirm then
  2.1325 -					useralert = {
  2.1326 -						text = text,
  2.1327 -						returnto = self:hidden("actioneditprofiles", "true"),
  2.1328 -						confirm = '<input type="submit" ' ..
  2.1329 -							'name="actioncreateprofile" value="' ..
  2.1330 -							self.locale.OVERWRITE .. '" /> ' ..
  2.1331 -							self:hidden("actionconfirm", "true") ..
  2.1332 -							self:hidden("createlanguage", l) ..
  2.1333 -							self:hidden("createprofile", c)
  2.1334 -					}
  2.1335 -				else
  2.1336 -					if profiles[c] then
  2.1337 -						self:deleteprofile(c, l)
  2.1338 -					end
  2.1339 -					self:copyprofile(c, self.profile, l, self.lang)
  2.1340 -				end
  2.1341 -			end
  2.1342 -		
  2.1343 -		elseif self.args.actiondeleteprofile and self.args.deleteprofile then
  2.1344 -			
  2.1345 -			-- Delete profile
  2.1346 -			
  2.1347 -			local c = self:checkprofilename(self.args.deleteprofile:lower())
  2.1348 -			assert(c ~= self.pubprofile,
  2.1349 -				self:dbmsg("Cannot delete published profile", c))
  2.1350 -			if self.args.actionconfirm then
  2.1351 -				self:deleteprofile(c)
  2.1352 -				self.profile = nil
  2.1353 -				self.args.profile = nil
  2.1354 -				self:init()
  2.1355 -				save = true
  2.1356 -			else
  2.1357 -				useralert = { 
  2.1358 -					text = self.locale.ALERT_DELETE_PROFILE,
  2.1359 -					returnto = self:hidden("actioneditprofiles", "true"),
  2.1360 -					confirm = '<input type="submit" ' ..
  2.1361 -						'name="actiondeleteprofile" value="' .. 
  2.1362 -						self.locale.DELETE .. '" /> ' ..
  2.1363 -						self:hidden("actionconfirm", "true") ..
  2.1364 -						self:hidden("deleteprofile", c)
  2.1365 -				}
  2.1366 -			end
  2.1367 -		
  2.1368 -		elseif self.args.actionchangeprofile and self.args.changeprofile then
  2.1369 -			
  2.1370 -			-- Change profile
  2.1371 -			
  2.1372 -			local c = self:checkprofilename(self.args.changeprofile:lower())
  2.1373 -			self.profile = c
  2.1374 -			self.args.profile = c
  2.1375 -			save = true
  2.1376 -		
  2.1377 -		elseif self.args.actionchangelanguage and self.args.changelanguage then
  2.1378 -			
  2.1379 -			-- Change language
  2.1380 -			
  2.1381 -			local l = self:checklanguage(self.args.changelanguage:lower())
  2.1382 -			self.lang = l
  2.1383 -			self.args.lang = l
  2.1384 - 			self.explicitlang = l
  2.1385 -			save = true
  2.1386 -		
  2.1387 -		elseif self.args.actionpublishprofile and self.args.publishprofile then
  2.1388 -			
  2.1389 -			-- Publish profile
  2.1390 -			
  2.1391 -			local c = self:checkprofilename(self.args.publishprofile:lower())
  2.1392 -			if c ~= self.publicprofile then
  2.1393 -				if self.args.actionconfirm then
  2.1394 -					self:publishprofile(c)
  2.1395 -					save = true
  2.1396 -				else
  2.1397 -					useralert = {
  2.1398 -						text = self.locale.ALERT_PUBLISH_PROFILE,
  2.1399 -						returnto = self:hidden("actioneditprofiles", "true"),
  2.1400 -						confirm = '<input type="submit" ' ..
  2.1401 -							'name="actionpublishprofile" value="' ..
  2.1402 -							self.locale.PUBLISH .. '" /> ' ..
  2.1403 -							self:hidden("actionconfirm", "true") ..
  2.1404 -							self:hidden("publishprofile", c)
  2.1405 -					}
  2.1406 -				end
  2.1407 -			end
  2.1408 -		end
  2.1409 -		
  2.1410 -	end
  2.1411 -	
  2.1412 -	if self.args.actiondelete then
  2.1413 -		
  2.1414 -		-- Delete section
  2.1415 -		
  2.1416 -		if not self.args.actionconfirm then
  2.1417 -			useralert = {
  2.1418 -				text = self.ispubprofile and
  2.1419 -					self.locale.ALERT_DELETE_IN_PUBLISHED_PROFILE or
  2.1420 -					self.locale.ALERT_DELETE_SECTION,
  2.1421 -				confirm =
  2.1422 -					'<input type="submit" name="actiondelete" value="' .. 
  2.1423 -					self.locale.DELETE .. '" /> ' ..
  2.1424 -					self:hidden("actionconfirm", "true")
  2.1425 -			}
  2.1426 -		else
  2.1427 -			local key = self.args.editkey
  2.1428 -			if key == "main" and not self.section.subs then
  2.1429 -				self:deletesection(self.sectionname, true) -- all bodies
  2.1430 -				self:rmpath(self.sectionpath) -- and node
  2.1431 -			else
  2.1432 -				local ext = (key == "main" and "") or "." .. key
  2.1433 -				self:deletesection(self.sectionname .. ext) -- only text
  2.1434 -				if self.section.dynamic then
  2.1435 -					self.section.dynamic[key] = nil
  2.1436 -					local n = 0
  2.1437 -					for _ in pairs(self.section.dynamic) do
  2.1438 -						n = n + 1
  2.1439 -					end
  2.1440 -					if n == 0 then
  2.1441 -						self.section.dynamic = nil
  2.1442 -					end
  2.1443 -				end
  2.1444 -			end
  2.1445 -			save = true
  2.1446 -		end
  2.1447 -	end
  2.1448 -		
  2.1449 -	if save then
  2.1450 -		self:saveindex()
  2.1451 -		self:init()
  2.1452 -	end
  2.1453 -	
  2.1454 -end
  2.1455 -
  2.1456 -
  2.1457 -function loona:encodeform(s)
  2.1458 -	return cgi.encodeform(s)
  2.1459 -end
  2.1460 -
  2.1461 -
  2.1462 -function loona:loadhtml(src, outfunc, chunkname)
  2.1463 -	return tek.web.include.load(src, outfunc, chunkname)
  2.1464 -end
  2.1465 -
  2.1466 -
  2.1467 -function loona:domarkup(s)
  2.1468 -	return tek.web.markup.main(s)
  2.1469 -end
  2.1470 -
  2.1471 -
  2.1472 -function loona:expire(dir, pat, maxage)
  2.1473 -	return tek.util.expire(dir, pat, maxage)
  2.1474 -end
  2.1475 -
  2.1476 -
  2.1477 -function loona:new(o)
  2.1478 -
  2.1479 -	local parsed, msg
  2.1480 -	
  2.1481 -	o = o or { }
  2.1482 -	o = atom.new(self, o)
  2.1483 -	
  2.1484 -	o.out = o.out or function(self, s) tek.web.out(s) end
  2.1485 -	o.setheader = o.setheader or function(self, s) tek.web.setheader(s) end
  2.1486 -	
  2.1487 -	-- Get configuration
  2.1488 -	
  2.1489 -	o.config = o.config or tek.source(o.conffile or "../etc/config.lua") or { }
  2.1490 -	o.config.defname = o.config.defname or "home"
  2.1491 -	o.config.deflang = o.config.deflang or "en"
  2.1492 -	o.config.sessionmaxage = o.config.sessionmaxage or 6000
  2.1493 -	o.config.secureport = o.config.secureport or 443
  2.1494 -	o.config.passwdfile =
  2.1495 -		posix.abspath(o.config.passwdfile or "../etc/passwd.lua")
  2.1496 -	o.config.sessiondir =
  2.1497 -		posix.abspath(o.config.sessiondir or "../var/sessions")
  2.1498 -	o.config.extdir = posix.abspath(o.config.extdir or "../extensions")
  2.1499 -	o.config.contentdir = posix.abspath(o.config.contentdir or "../content")
  2.1500 -	o.config.localedir = posix.abspath(o.config.localedir or "../locale")
  2.1501 -	o.config.htdocsdir = posix.abspath(o.config.htdocsdir or "../htdocs")
  2.1502 -	o.config.htmlcachedir = 
  2.1503 -		posix.abspath(o.config.htmlcachedir or "../var/htmlcache")
  2.1504 -	o.config.extlinkextra = o.config.extlinksamewindow and ' class="extlink"'
  2.1505 -		or ' class="extlink" onclick="void(window.open(this.href, \'\', \'\')); return false;"'
  2.1506 -	
  2.1507 -	-- Create proxy for on-demand loading of locales
  2.1508 -	
  2.1509 -	o.locale = { }
  2.1510 -	local locmt = { }
  2.1511 -	locmt.__index = function(_, key)
  2.1512 -		for _, l in ipairs(o.langs) do
  2.1513 -			locmt.__locale = tek.source(o.config.localedir .. "/" .. l)
  2.1514 -			if locmt.__locale then
  2.1515 -				break
  2.1516 -			end
  2.1517 -		end
  2.1518 -		locmt.__index = function(tab, key)
  2.1519 -			return locmt.__locale[key] or key
  2.1520 -		end
  2.1521 -		return locmt.__locale[key] or key
  2.1522 -	end
  2.1523 -	setmetatable(o.locale, locmt)
  2.1524 -	
  2.1525 -	-- Get request, args, document, script name, request path
  2.1526 -	
  2.1527 -	o.request = o.request or cgi.request
  2.1528 -	o.args = o.args or cgi.request.args
  2.1529 - 	
  2.1530 - 	o.scriptpath = o.scriptpath or cgi.document.Path
  2.1531 -	o.requesthandler = o.requesthandler or cgi.document.Handler
  2.1532 - 	o.requestdocument = o.requestdocument or cgi.document.Name
  2.1533 -	o.requestpath = o.requestpath or cgi.document.VirtualPath
  2.1534 -	o.explicitlang = not o.requestlang and o.args.lang
  2.1535 -	o.secure = not o.insecure and (o.request.Port == o.config.secureport)
  2.1536 -
  2.1537 -	-- Manage login and establish session
  2.1538 -	
  2.1539 -	if not o.nologin then
  2.1540 -		o.session = o.session or cgi.session
  2.1541 -		o.session.init(o.config.sessiondir, o.args.session, o.config.sessionmaxage)
  2.1542 -		if o.args.login then
  2.1543 -			if o.args.login == "false" then
  2.1544 -				o.session.delete()
  2.1545 -				o.session = nil
  2.1546 -			elseif o.args.password then
  2.1547 -				o.loginfailed = true
  2.1548 -				local pwddb = tek.source(o.config.passwdfile)
  2.1549 -				local pwdentry = pwddb[o.args.login]
  2.1550 -				if pwdentry and pwdentry.password == o.args.password then
  2.1551 -					o.session.data.authuser = pwdentry.username
  2.1552 -					o.session.data.id = o.session.id
  2.1553 -					o.loginfailed = nil
  2.1554 -				end
  2.1555 -			end
  2.1556 -		end
  2.1557 -		o.authuser = o.session and o.session.data.authuser
  2.1558 -	end
  2.1559 -
  2.1560 -	if o.nologin or not o.authuser then
  2.1561 -		o.authuser = nil
  2.1562 -		o.session = nil
  2.1563 -		o.args.session = nil
  2.1564 -	end
  2.1565 -
  2.1566 -	-- Get lang, locale, profile, section
  2.1567 -
  2.1568 -	o:init()
  2.1569 -	if o.authuser then
  2.1570 -		o:handlechanges()
  2.1571 -	else
  2.1572 -		o.args.profile = nil
  2.1573 -	end
  2.1574 -	
  2.1575 -	-- Current document
  2.1576 -	
  2.1577 -	o.document = o.requestdocument .. "/" .. o.sectionpath
  2.1578 -	if o.authuser then
  2.1579 -		o.getdocname = function(self, path)
  2.1580 -			return self.requestdocument .. "/" .. (path or self.sectionpath)
  2.1581 -		end
  2.1582 -	else
  2.1583 -		o.getdocname = function(self, path, haveargs)
  2.1584 -			local dyn
  2.1585 -			dyn, path = self:isdynamic(path or self.sectionpath)
  2.1586 -			if dyn or haveargs then
  2.1587 -				return self.requestdocument .. "/" .. path
  2.1588 -			end
  2.1589 -			path = path == self.config.defname and "index" or path
  2.1590 -			return "/" .. path:gsub("/", "_") .. ".html"
  2.1591 -		end
  2.1592 -	end
  2.1593 -	
  2.1594 -	-- Save session state
  2.1595 -	
  2.1596 -	if o.session then
  2.1597 -		o.session.save()
  2.1598 -	end
  2.1599 -	
  2.1600 -	return o
  2.1601 -end
  2.1602 -
  2.1603 -
  2.1604 -function loona:execute(fname)
  2.1605 -	self:indexdynamic()
  2.1606 -	fname = fname or self.requesthandler
  2.1607 -	local parsed, msg = self:loadhtml(open(fname), "loona:out", fname)
  2.1608 -	assert(parsed, self:dbmsg("HTML/Lua parsing failed", msg))
  2.1609 -	self:runboxed(parsed)
  2.1610 -	return self
  2.1611 -end
  2.1612 -
  2.1613 -
  2.1614 -function loona:indexdynamic()
  2.1615 -	self:recursesections(self.sections, function(self, s, e, path, dynamic)
  2.1616 -		path = path and path .. "_" .. e.name or e.name
  2.1617 -		dynamic = dynamic or { }
  2.1618 -		for k in pairs(e.dynamic or { }) do
  2.1619 -			dynamic[k] = true
  2.1620 -		end
  2.1621 -		for k in pairs(dynamic) do
  2.1622 -			local ext = (k == "main" and "") or "." .. k
  2.1623 -			if posix.stat(self.contentdir .. "/" .. path .. ext,
  2.1624 -				"mode") == "file" then
  2.1625 -				dynamic[k] = e.dynamic and e.dynamic[k]
  2.1626 -			end
  2.1627 -		end
  2.1628 -		local n = 0
  2.1629 -		for k in pairs(dynamic) do
  2.1630 -			n = n + 1
  2.1631 -		end
  2.1632 -		if n > 0 then
  2.1633 -			e.dynamic = { }
  2.1634 -			for k in pairs(dynamic) do
  2.1635 -				e.dynamic[k] = true
  2.1636 -			end
  2.1637 -		else
  2.1638 -			e.dynamic = nil
  2.1639 -		end
  2.1640 -		return path, dynamic
  2.1641 -	end)
  2.1642 -end
  2.1643 -
  2.1644 -
  2.1645 -function loona:isdynamic(path)
  2.1646 -	path = path or self.sectionpath
  2.1647 -	local t, i = self:checkpath(path)
  2.1648 -	if t and t[i].redirect then
  2.1649 -		path = t[i].redirect
  2.1650 -		t, i = self:isdynamic(path) -- TODO: prohibit endless recursion
  2.1651 -	end
  2.1652 -	return t and t[i].dynamic, path
  2.1653 -end
  2.1654 -
  2.1655 -
  2.1656 -function loona:dumphtml(o)
  2.1657 -	local outbuf = { }
  2.1658 -	o = o or { }
  2.1659 -	o.nologin = true
  2.1660 -	o.out = function(self, s) table.insert(outbuf, s) end
  2.1661 -	o.setheader = function(self, s) end
  2.1662 -	o = self:new(o):execute()
  2.1663 -	if not o:isdynamic() then
  2.1664 -		local path = o.sectionname
  2.1665 -		path = path == o.config.defname and "index" or path
  2.1666 -		local srcname = o.config.htdocsdir .. "/" .. path .. o.htmlext
  2.1667 -		local fh, msg = open(srcname .. ".tmp", "wb")
  2.1668 -		assert(fh, self:dbmsg("Could not write cached HTML", msg))
  2.1669 -		fh:write(unpack(outbuf))
  2.1670 -		fh:close()
  2.1671 -		local dstname = o.config.htmlcachedir .. "/" .. path .. o.htmlext
  2.1672 -		local success, msg = posix.symlink(srcname, dstname .. ".tmp")
  2.1673 --- 		assert(success, self:dbmsg("Could not link to cached HTML", msg))
  2.1674 -	end
  2.1675 -end
     3.1 --- a/cgi-bin/tek.lua	Sun May 20 18:22:42 2007 +0200
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,302 +0,0 @@
     3.4 -
     3.5 ---
     3.6 ---	tek - TEK library
     3.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     3.8 ---	See copyright notice in COPYRIGHT
     3.9 ---
    3.10 -
    3.11 -local type, unpack, table, pairs, ipairs = type, unpack, table, pairs, ipairs
    3.12 -local error, xpcall, traceback = error, xpcall, debug.traceback
    3.13 -local open, floor, char = io.open, math.floor, string.char
    3.14 -local loadstring, tostring = loadstring, tostring
    3.15 -
    3.16 -
    3.17 -module "tek"
    3.18 -
    3.19 -
    3.20 -_VERSION = 2
    3.21 -_REVISION = 1
    3.22 -
    3.23 -
    3.24 ---	Throw error
    3.25 -
    3.26 -function throw(code, text, detail, trace)
    3.27 -	error { code = code, text = text, detail = detail,
    3.28 -		trace = trace or traceback("", 2) }
    3.29 -end
    3.30 -
    3.31 -
    3.32 ---	Catch errors when executing a function; returns an error code (0 okay,
    3.33 ---	<0 execution error, >0 user error), followed by a string representing
    3.34 ---	the error, followed by the regular results from the function
    3.35 -
    3.36 -
    3.37 ---	local error handler preserves traceback + errmessage:
    3.38 -
    3.39 -local function errhandler(obj)
    3.40 -	if type(obj) == "table" then
    3.41 -		obj.trace = obj.trace or traceback("", 2)
    3.42 -	else
    3.43 -		obj = { text = obj, trace = traceback("", 2) }
    3.44 -	end
    3.45 -	return obj
    3.46 -end
    3.47 -
    3.48 -function catch(func, ...)
    3.49 -
    3.50 -	local function callargs()
    3.51 -		func(arg) -- closure with arguments to catch
    3.52 -	end
    3.53 -
    3.54 -	local res = { xpcall(callargs, errhandler) }
    3.55 -
    3.56 -	if table.remove(res, 1) then
    3.57 -		return 0, "ok", unpack(res)
    3.58 -	end
    3.59 -
    3.60 -	-- custom error
    3.61 -	return res[1].code, res[1].text, res[1].detail, res[1].trace
    3.62 -end
    3.63 -
    3.64 -
    3.65 ---	Source a file into a table (in empty environment, by default)
    3.66 -
    3.67 -function source(filename, env)
    3.68 -	local f, msg = open(filename)
    3.69 -	if f then
    3.70 -		local chunk = f:read("*a")
    3.71 -		f:close()
    3.72 -		if chunk then
    3.73 -			chunk, msg = loadstring("do return {\n" .. chunk .. "\n} end")
    3.74 -			if chunk then
    3.75 -				if env then
    3.76 -					setfenv(chunk, env)
    3.77 -				end
    3.78 -				return chunk()
    3.79 -			end
    3.80 -		end
    3.81 -	end
    3.82 -	return nil, msg
    3.83 -end
    3.84 -
    3.85 -
    3.86 ---	Recursive serialize - 
    3.87 ---	note that cyclic dependencies are silently dropped
    3.88 -
    3.89 -local function serialize(tab, sorted, indent, outfunc, saved)
    3.90 -	saved[tab] = tab
    3.91 -	local is = ("\t"):rep(indent)
    3.92 -	local set = { }
    3.93 -	for key, val in pairs(tab) do
    3.94 -		local t = type(val)
    3.95 -		if t ~= "table" or not saved[val] then
    3.96 -			table.insert(set, {
    3.97 -				cmp = tostring(key):lower(), key = key, val = val })
    3.98 -		end
    3.99 -	end
   3.100 -	if sorted then
   3.101 -		table.sort(set, function(a, b)
   3.102 -			if type(a.key) == "number" then
   3.103 -				if type(b.key) == "number" then
   3.104 -					return a.key < b.key
   3.105 -				end
   3.106 -				return true
   3.107 -			end
   3.108 -			if type(b.key) == "number" then
   3.109 -				return
   3.110 -			end
   3.111 -			return a.cmp < b.cmp
   3.112 -		end)
   3.113 -	end
   3.114 -	for _, e in ipairs(set) do
   3.115 -		local key, val = e.key, e.val
   3.116 -		local t = type(val)
   3.117 -		
   3.118 -		if not saved[val] then
   3.119 -			outfunc(is)
   3.120 -			if type(key) == "number" then
   3.121 -				outfunc('[' .. key .. '] = ')
   3.122 -			else
   3.123 -				if key:match("[^%a_]") then
   3.124 -					outfunc('["' .. key .. '"] = ')
   3.125 -				else
   3.126 -					outfunc(key .. ' = ')
   3.127 -				end
   3.128 -			end
   3.129 -			if t == "table" then
   3.130 -				outfunc('{\n')
   3.131 -				serialize(val, sorted, indent + 1, outfunc, saved)
   3.132 -				outfunc(is .. '},\n')
   3.133 -			elseif t == "string" then
   3.134 -				outfunc('"' .. val:gsub("([%z\001-\031])", function(c)
   3.135 -					return ("\\%03d"):format(c:byte())
   3.136 -				end) .. '",\n')
   3.137 -			elseif t == "number" or t == "boolean" then
   3.138 -				outfunc(tostring(val) .. ',\n')
   3.139 -			else
   3.140 -				outfunc('"' .. tostring(val) .. '",\n')
   3.141 -			end
   3.142 -		end
   3.143 -	end
   3.144 -end
   3.145 -
   3.146 -
   3.147 ---	Dump table via outfunc
   3.148 -
   3.149 -function dump(tab, outfunc)
   3.150 -	return serialize(tab, true, 0, outfunc, { })
   3.151 -end
   3.152 -
   3.153 -
   3.154 ---	Encode to UTF-8
   3.155 -
   3.156 -function encodeutf8(c)
   3.157 -	if c < 128 then
   3.158 -		return char(c)
   3.159 -	elseif c < 2048 then
   3.160 -		return char(192 + floor(c / 64), 
   3.161 -			128 + c % 64)
   3.162 -	elseif c < 65536 then
   3.163 -		return char(224 + floor(c / 4096), 
   3.164 -			128 + floor((c % 4096) / 64), 
   3.165 -			128 + c % 64)
   3.166 -	elseif c < 2097152 then
   3.167 -		return char(240 + floor(c / 262144), 
   3.168 -			128 + floor((c % 262144) / 4096), 
   3.169 -			128 + floor((c % 4096) / 64), 
   3.170 -			128 + c % 64)
   3.171 -	elseif c < 67108864 then
   3.172 -		return char(248 + floor(c / 16777216), 
   3.173 -			128 + floor((c % 16777216) / 262144), 
   3.174 -			128 + floor((c % 262144) / 4096),
   3.175 -			128 + floor((c % 4096) / 64),
   3.176 -			128 + c % 64)
   3.177 -	else
   3.178 -		return char(252 + floor(c / 1073741824), 
   3.179 -			128 + floor((c % 1073741824) / 16777216), 
   3.180 -			128 + floor((c % 16777216) / 262144), 
   3.181 -			128 + floor((c % 262144) / 4096), 
   3.182 -			128 + floor((c % 4096) / 64), 
   3.183 -			128 + c % 64)
   3.184 -	end
   3.185 -end
   3.186 -
   3.187 -
   3.188 ---	Iterate over UTF-8 character values in a string or file
   3.189 -
   3.190 -function utf8values(s)
   3.191 -	
   3.192 -	local readc
   3.193 -	local i = 0
   3.194 -	if type(s) == "string" then
   3.195 -		readc = function()
   3.196 -			i = i + 1
   3.197 -			return s:byte(i)
   3.198 -		end
   3.199 -	else
   3.200 -		readc = function()
   3.201 -			local c = s:read(1)
   3.202 -			return c and c:byte(1)
   3.203 -		end
   3.204 -	end
   3.205 -	
   3.206 -	local accu = 0
   3.207 -	local numa = 0
   3.208 -	local min, buf
   3.209 -	
   3.210 -	return function()
   3.211 -		local c
   3.212 -		while true do
   3.213 -			if buf then
   3.214 -				c = buf
   3.215 -				buf = nil
   3.216 -			else
   3.217 -				c = readc()
   3.218 -			end
   3.219 -			if not c then
   3.220 -				return
   3.221 -			end
   3.222 -			if c == 254 or c == 255 then
   3.223 -				break
   3.224 -			end
   3.225 -			if c < 128 then
   3.226 -				if numa > 0 then
   3.227 -					buf = c
   3.228 -					break
   3.229 -				end
   3.230 -				return c
   3.231 -			elseif c < 192 then
   3.232 -				if numa == 0 then break end
   3.233 -				accu = accu * 64 + c - 128
   3.234 -				numa = numa - 1
   3.235 -				if numa == 0 then
   3.236 -					if accu == 0 or accu < min or 
   3.237 -						(accu >= 55296 and accu <= 57343) then
   3.238 -						break
   3.239 -					end
   3.240 -					c = accu
   3.241 -					accu = 0
   3.242 -					return c
   3.243 -				end
   3.244 -			else
   3.245 -				if numa > 0 then
   3.246 -					buf = c
   3.247 -					break
   3.248 -				end
   3.249 -				if c < 224 then
   3.250 -					min = 128
   3.251 -					accu = c - 192
   3.252 -					numa = 1
   3.253 -				elseif c < 240 then
   3.254 -					min = 2048
   3.255 -					accu = c - 224
   3.256 -					numa = 2
   3.257 -				elseif c < 248 then
   3.258 -					min = 65536
   3.259 -					accu = c - 240
   3.260 -					numa = 3
   3.261 -				elseif c < 252 then
   3.262 -					min = 2097152
   3.263 -					accu = c - 248
   3.264 -					numa = 4
   3.265 -				else
   3.266 -					min = 67108864
   3.267 -					accu = c - 252
   3.268 -					numa = 5
   3.269 -				end
   3.270 -			end
   3.271 -		end
   3.272 -		accu = 0
   3.273 -		numa = 0
   3.274 -		return 65533 -- bad character
   3.275 -	end
   3.276 -end
   3.277 -
   3.278 -
   3.279 ---	Copy file - arguments may be filenames or open filehandles
   3.280 -
   3.281 -function copyfile(s, d, bufsize)
   3.282 -	bufsize = bufsize or 4096
   3.283 -	local success, msg
   3.284 -	if s and type(s) == "string" then
   3.285 -		s, msg = open(s, "rb")
   3.286 -	end
   3.287 -	if s then
   3.288 -		if d and type(d) == "string" then
   3.289 -			d, msg = open(d, "wb")
   3.290 -		end
   3.291 -		if d then
   3.292 -			while true do
   3.293 -				local b = s:read(bufsize)
   3.294 -				if not b then
   3.295 -					break
   3.296 -				end
   3.297 -				d:write(b)
   3.298 -			end
   3.299 -			success = true
   3.300 -			d:close()
   3.301 -		end
   3.302 -		s:close()
   3.303 -	end
   3.304 -	return success, msg
   3.305 -end
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/cgi-bin/tek/app/loona.lua	Sun May 20 18:28:42 2007 +0200
     4.3 @@ -0,0 +1,65 @@
     4.4 +
     4.5 +--
     4.6 +--	tek.app.loona - LOona Tiny CMS main
     4.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
     4.8 +--	See copyright notice in COPYRIGHT
     4.9 +--
    4.10 +
    4.11 +local lib = require "tek.lib"
    4.12 +local http = require "tek.class.http"
    4.13 +local Request = require "tek.class.http.request"
    4.14 +local Buffer = require "tek.class.loona.buffer"
    4.15 +local require = require
    4.16 +
    4.17 +
    4.18 +module "tek.app.loona"
    4.19 +
    4.20 +
    4.21 +_VERSION = 1
    4.22 +_REVISION = 0
    4.23 +
    4.24 +
    4.25 +function main()
    4.26 +	local res, msg = -1, "No handler to serve the addressed document"
    4.27 +	local document = Request:new():getdocument()
    4.28 +	local buf = Buffer:new()
    4.29 +	if document.Handler then
    4.30 +		local loona, detail, trace
    4.31 +		res, msg, detail, trace = lib.catch(function()
    4.32 +			local Loona = require "tek.class.loona"
    4.33 +			loona = Loona:new { buf = buf }
    4.34 +			loona:run()
    4.35 +		end)
    4.36 +		if res ~= 0 then
    4.37 +			buf:discard()
    4.38 +			buf:setheader "Content-Type: text/html; charset=utf-8\n\n"
    4.39 +			buf:out([[
    4.40 +<?xml version="1.0"?>
    4.41 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    4.42 +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    4.43 +<head>
    4.44 +	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
    4.45 +	<title>Application error</title>
    4.46 +</head>
    4.47 +<body>
    4.48 +	<h2>Error ]] .. (res or "").. [[</h2>
    4.49 +	<pre>]] .. http.encodeform(msg) .. [[</pre>
    4.50 +]])
    4.51 +-- 			if loona and (loona.authuser or loona.config.debug) then
    4.52 +				buf:out([[
    4.53 +	<pre>]] .. http.encodeform(detail) .. [[</pre>
    4.54 +	<pre>]] .. http.encodeform(trace) .. [[</pre>
    4.55 +]])
    4.56 +-- 			end
    4.57 +			buf:out([[
    4.58 +</body>
    4.59 +</html>
    4.60 +]])
    4.61 +		end
    4.62 +	else
    4.63 +		buf:setheader "Content-Type: text/html; charset=utf-8\n\n"
    4.64 +		buf:out(msg)
    4.65 +	end
    4.66 +	buf:flush()
    4.67 +	return res, msg
    4.68 +end
     5.1 --- a/cgi-bin/tek/cgi.lua	Sun May 20 18:22:42 2007 +0200
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,73 +0,0 @@
     5.4 -
     5.5 ---
     5.6 ---	tek.cgi - CGI utilities
     5.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     5.8 ---	See copyright notice in COPYRIGHT
     5.9 ---
    5.10 -
    5.11 -local tek = require "tek"
    5.12 -
    5.13 -local type, tonumber, utf8values = type, tonumber, tek.utf8values
    5.14 -local insert, concat, char = table.insert, table.concat, string.char
    5.15 -
    5.16 -
    5.17 -module "tek.cgi"
    5.18 -
    5.19 -
    5.20 -_VERSION = 1
    5.21 -_REVISION = 5
    5.22 -
    5.23 -
    5.24 ---	Encode for forms (display '<', '>', '&', '"' literally)
    5.25 -
    5.26 -function encodeform(s)
    5.27 -	local tab = { }
    5.28 -	if s then
    5.29 -		for c in utf8values(s) do
    5.30 -			if c == 34 then
    5.31 -				insert(tab, "&quot;")
    5.32 -			elseif c == 38 then
    5.33 -				insert(tab, "&amp;")
    5.34 -			elseif c == 60 then
    5.35 -				insert(tab, "&lt;")
    5.36 -			elseif c == 62 then
    5.37 -				insert(tab, "&gt;")
    5.38 -			elseif c == 91 or c == 93 or c > 126 then
    5.39 -				insert(tab, ("&#%03d;"):format(c))
    5.40 -			else
    5.41 -				insert(tab, char(c))
    5.42 -			end
    5.43 -		end
    5.44 -	end
    5.45 -	return concat(tab)
    5.46 -end
    5.47 -
    5.48 -
    5.49 ---	URL encode/decode
    5.50 -
    5.51 -function encodeurl(s, keep)
    5.52 -	local matchset = "$&+,/:;=?@" -- reserved chars with special meaning
    5.53 -	if keep then 
    5.54 -		if type(keep) == "string" then -- delete specified from set:
    5.55 -			matchset = matchset:gsub("[" .. keep:gsub(".", "%%%1") .. "]", "")
    5.56 -		elseif keep == true then -- delete whole set:
    5.57 -			matchset = "" 
    5.58 -		end
    5.59 -	end
    5.60 -	-- always substitute unsafe chars:
    5.61 -	matchset = matchset .. '"<>#%{}|\\^~[]`]'
    5.62 -	matchset = "[%z\001-\032\127-\255" .. matchset:gsub(".", "%%%1") .. "]"
    5.63 -	s = s:gsub(matchset, function(c)
    5.64 -		return ("%%%x"):format(c:byte())
    5.65 -	end)
    5.66 -	return s
    5.67 -end
    5.68 -
    5.69 -
    5.70 -function decodeurl(s)
    5.71 -	s = s:gsub("+", " ")
    5.72 -	return s:gsub("%%(%x%x)", function(h)
    5.73 -		return char(tonumber(h, 16))
    5.74 -	end)
    5.75 -end
    5.76 -
     6.1 --- a/cgi-bin/tek/cgi/document.lua	Sun May 20 18:22:42 2007 +0200
     6.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.3 @@ -1,49 +0,0 @@
     6.4 ---
     6.5 ---	tek.cgi.document - Determine document attributes
     6.6 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     6.7 ---	See copyright notice in COPYRIGHT
     6.8 ---
     6.9 -
    6.10 -require "tek.cgi.request"
    6.11 -
    6.12 -local request = tek.cgi.request
    6.13 -local open = io.open
    6.14 -
    6.15 -module "tek.cgi.document"
    6.16 -
    6.17 -if request.PathTranslated then
    6.18 -	
    6.19 -	--
    6.20 -	--	gather DOCUMENT information ("Name" and "Path"), additionally 
    6.21 -	--	provide a "VirtualPath" that is past the end of the script path.
    6.22 -	--	"Handler" denotes the script in the path.
    6.23 -	
    6.24 -	local pt, vp, f = request.PathTranslated
    6.25 -	
    6.26 -	repeat
    6.27 -		f = open(pt)
    6.28 -		if f then
    6.29 -			f:close()
    6.30 -			-- now matches a filesystem object - the rest is considered 'virtual'
    6.31 -			break
    6.32 -		end
    6.33 -		pt = pt:gsub("^(.*)(/.-)$", function(a, b)
    6.34 -			vp = b .. (vp or "")
    6.35 -			return a
    6.36 -		end)
    6.37 -	until not pt
    6.38 -	
    6.39 -	Handler = pt
    6.40 -	
    6.41 -	Name = request.PathInfo
    6.42 -	if vp then
    6.43 -		-- isolate document name by matching virtual path at end of string:
    6.44 -		if Name:sub(-vp:len()) == vp then
    6.45 -			Name = Name:sub(1, Name:len() - vp:len())
    6.46 -		end
    6.47 -		VirtualPath = vp:gsub("^/?(.*)$", "%1")
    6.48 -	end
    6.49 -	
    6.50 -	Path = pt:gsub("^(.*/).-$", "%1")
    6.51 -
    6.52 -end
     7.1 --- a/cgi-bin/tek/cgi/header.lua	Sun May 20 18:22:42 2007 +0200
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,22 +0,0 @@
     7.4 -
     7.5 ---
     7.6 ---	tek.cgi.header - Collect header variables
     7.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     7.8 ---	See copyright notice in COPYRIGHT
     7.9 ---
    7.10 -
    7.11 -local getenv = os.getenv
    7.12 -
    7.13 -
    7.14 -module "tek.cgi.header"
    7.15 -
    7.16 -
    7.17 -_VERSION = 1
    7.18 -_REVISION = 0
    7.19 -
    7.20 -
    7.21 -Accept = getenv("HTTP_ACCEPT")
    7.22 -UserAgent = getenv("HTTP_USER_AGENT")
    7.23 -Referer = getenv("HTTP_REFERER")
    7.24 -Host = getenv("HTTP_HOST")
    7.25 -CookieString = getenv("HTTP_COOKIE")
     8.1 --- a/cgi-bin/tek/cgi/header/cookies.lua	Sun May 20 18:22:42 2007 +0200
     8.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.3 @@ -1,31 +0,0 @@
     8.4 -
     8.5 ---
     8.6 ---	tek.cgi.header.cookies - Collect cookies
     8.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     8.8 ---	See copyright notice in COPYRIGHT
     8.9 ---
    8.10 -
    8.11 -require "tek.cgi.header"
    8.12 -
    8.13 -local getfenv, cookiestring = getfenv, tek.cgi.header.CookieString
    8.14 -
    8.15 -
    8.16 -module "tek.cgi.header.cookies"
    8.17 -
    8.18 -
    8.19 -_VERSION = 1
    8.20 -_REVISION = 0
    8.21 -
    8.22 -
    8.23 -if cookiestring then
    8.24 -	local self = getfenv()
    8.25 -	local pos, name, id = 0
    8.26 -	while true do
    8.27 -		_, pos, name, id = cookiestring:find(
    8.28 -			"[%s]*([%w_]+)=([%w%.%-%+%%_]+)[;%s]*", pos + 1)
    8.29 -		if not pos then
    8.30 -			break
    8.31 -		end
    8.32 -		self[name] = id
    8.33 -	end
    8.34 -end
     9.1 --- a/cgi-bin/tek/cgi/request.lua	Sun May 20 18:22:42 2007 +0200
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,33 +0,0 @@
     9.4 -
     9.5 ---
     9.6 ---	tek.cgi.request - Collect request variables
     9.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
     9.8 ---	See copyright notice in COPYRIGHT
     9.9 ---
    9.10 -
    9.11 -local getenv, tonumber = os.getenv, tonumber
    9.12 -
    9.13 -
    9.14 -module "tek.cgi.request"
    9.15 -
    9.16 -
    9.17 -_VERSION = 1
    9.18 -_REVISION = 0
    9.19 -
    9.20 -
    9.21 -Protocol = getenv("SERVER_PROTOCOL")
    9.22 -Port = tonumber(getenv("SERVER_PORT"))
    9.23 -Method = getenv("REQUEST_METHOD")
    9.24 -PathInfo = getenv("PATH_INFO")
    9.25 -PathTranslated = getenv("PATH_TRANSLATED")
    9.26 -ScriptName = getenv("SCRIPT_NAME")
    9.27 -QueryString = getenv("QUERY_STRING")
    9.28 -RemoteHost = getenv("REMOTE_HOST")
    9.29 -RemoteAddr = getenv("REMOTE_ADDR")
    9.30 -AuthType = getenv("AUTH_TYPE")
    9.31 -RemoteUser = getenv("REMOTE_USER")
    9.32 -RemoteIdent = getenv("REMOTE_IDENT")
    9.33 -ContentType = getenv("CONTENT_TYPE")
    9.34 -ContentLength = getenv("CONTENT_LENGTH")
    9.35 -DocumentName = getenv("DOCUMENT_NAME")
    9.36 -UniqueID = getenv("UNIQUE_ID")
    10.1 --- a/cgi-bin/tek/cgi/request/args.lua	Sun May 20 18:22:42 2007 +0200
    10.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.3 @@ -1,40 +0,0 @@
    10.4 -
    10.5 ---
    10.6 ---	tek.cgi.request.args - Collect CGI arguments
    10.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
    10.8 ---	See copyright notice in COPYRIGHT
    10.9 ---
   10.10 -
   10.11 -local cgi = require "tek.cgi"
   10.12 -local request = require "tek.cgi.request"
   10.13 -require "cgilua.post"
   10.14 -
   10.15 -local parsedata, read, pairs, getfenv =
   10.16 -	cgilua.post.parsedata, io.read, pairs, getfenv
   10.17 -
   10.18 -
   10.19 -module "tek.cgi.request.args"
   10.20 -
   10.21 -
   10.22 -_VERSION = 1
   10.23 -_REVISION = 0
   10.24 -
   10.25 -
   10.26 -local self = getfenv()
   10.27 -
   10.28 -if request.Method == "GET" then
   10.29 -	request.QueryString:gsub("([^&=]+)=([^&=]+)", function(key, value)
   10.30 -		self[key] = cgi.decodeurl(value)
   10.31 -	end)
   10.32 -elseif request.Method == "POST" then
   10.33 -	local foo = {}
   10.34 -	parsedata {
   10.35 -		read = read,
   10.36 -		discardinput = nil,
   10.37 -		content_type = request.ContentType,
   10.38 -		content_length = request.ContentLength,
   10.39 -		maxinput = 1048575,
   10.40 -		maxfilesize = 524288,
   10.41 -		args = self
   10.42 -	}
   10.43 -end
    11.1 --- a/cgi-bin/tek/cgi/session.lua	Sun May 20 18:22:42 2007 +0200
    11.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.3 @@ -1,51 +0,0 @@
    11.4 -
    11.5 ---
    11.6 ---	tek.cgi.session - Session state
    11.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
    11.8 ---	See copyright notice in COPYRIGHT
    11.9 ---
   11.10 -
   11.11 -local tek = require "tek"
   11.12 -local util = require "tek.util"
   11.13 -local request = require "tek.cgi.request"
   11.14 -
   11.15 -local unpack, assert, open, remove = unpack, assert, io.open, os.remove
   11.16 -
   11.17 -
   11.18 -module "tek.cgi.session"
   11.19 -
   11.20 -
   11.21 -_VERSION = 1
   11.22 -_REVISION = 0
   11.23 -
   11.24 -
   11.25 -function save()
   11.26 -	local f = open(filename, "wb")
   11.27 -	assert(f, "Failed to open session file for writing")
   11.28 -	tek.dump(data, function(...) 
   11.29 -		f:write(unpack(arg)) 
   11.30 -	end)
   11.31 -	f:close()
   11.32 -end
   11.33 -
   11.34 -
   11.35 -function delete()
   11.36 -	remove(filename)
   11.37 -end
   11.38 -
   11.39 -
   11.40 -function init(sessiondir, sessionid, maxage)
   11.41 -	id = sessionid or request.UniqueID
   11.42 -	assert(id, "Could not determine session ID")
   11.43 -	name = id:gsub("(.)", function(a)
   11.44 -		return ("%02x"):format(a:byte())
   11.45 -	end)
   11.46 -	filename = sessiondir .. "/" .. name
   11.47 -	-- remove non-dotted files (expired sessions) from sessions dir:
   11.48 -	util.expire(sessiondir, "[^.]%S+", maxage)
   11.49 -	-- load session state:
   11.50 -	data = tek.source(filename) or { }
   11.51 -	-- write back session ID into request args:
   11.52 -	request.args.session = id
   11.53 -end
   11.54 -
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/cgi-bin/tek/class/http.lua	Sun May 20 18:28:42 2007 +0200
    12.3 @@ -0,0 +1,71 @@
    12.4 +
    12.5 +--
    12.6 +--	tek.class.http - HTTP baseclass
    12.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    12.8 +--	See copyright notice in COPYRIGHT
    12.9 +--
   12.10 +
   12.11 +local lib = require "tek.lib"
   12.12 +local type, tonumber, utf8values = type, tonumber, lib.utf8values
   12.13 +local insert, concat, char = table.insert, table.concat, string.char
   12.14 +
   12.15 +
   12.16 +module "tek.class.http"
   12.17 +
   12.18 +
   12.19 +_VERSION = 2
   12.20 +_REVISION = 0
   12.21 +
   12.22 +
   12.23 +--	Encode for forms (display '<', '>', '&', '"' literally)
   12.24 +
   12.25 +function encodeform(s)
   12.26 +	local tab = { }
   12.27 +	if s then
   12.28 +		for c in utf8values(s) do
   12.29 +			if c == 34 then
   12.30 +				insert(tab, "&quot;")
   12.31 +			elseif c == 38 then
   12.32 +				insert(tab, "&amp;")
   12.33 +			elseif c == 60 then
   12.34 +				insert(tab, "&lt;")
   12.35 +			elseif c == 62 then
   12.36 +				insert(tab, "&gt;")
   12.37 +			elseif c == 91 or c == 93 or c > 126 then
   12.38 +				insert(tab, ("&#%03d;"):format(c))
   12.39 +			else
   12.40 +				insert(tab, char(c))
   12.41 +			end
   12.42 +		end
   12.43 +	end
   12.44 +	return concat(tab)
   12.45 +end
   12.46 +
   12.47 +
   12.48 +--	URL encode/decode
   12.49 +
   12.50 +function encodeurl(s, keep)
   12.51 +	local matchset = "$&+,/:;=?@" -- reserved chars with special meaning
   12.52 +	if keep then 
   12.53 +		if type(keep) == "string" then -- delete specified from set:
   12.54 +			matchset = matchset:gsub("[" .. keep:gsub(".", "%%%1") .. "]", "")
   12.55 +		elseif keep == true then -- delete whole set:
   12.56 +			matchset = "" 
   12.57 +		end
   12.58 +	end
   12.59 +	-- always substitute unsafe chars:
   12.60 +	matchset = matchset .. '"<>#%{}|\\^~[]`]'
   12.61 +	matchset = "[%z\001-\032\127-\255" .. matchset:gsub(".", "%%%1") .. "]"
   12.62 +	s = s:gsub(matchset, function(c)
   12.63 +		return ("%%%02x"):format(c:byte())
   12.64 +	end)
   12.65 +	return s
   12.66 +end
   12.67 +
   12.68 +
   12.69 +function decodeurl(s)
   12.70 +	s = s:gsub("+", " ")
   12.71 +	return s:gsub("%%(%x%x)", function(h)
   12.72 +		return char(tonumber(h, 16))
   12.73 +	end)
   12.74 +end
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/cgi-bin/tek/class/http/header.lua	Sun May 20 18:28:42 2007 +0200
    13.3 @@ -0,0 +1,48 @@
    13.4 +
    13.5 +--
    13.6 +--	tek.cgi.header - Collect header variables
    13.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    13.8 +--	See copyright notice in COPYRIGHT
    13.9 +--
   13.10 +
   13.11 +local getenv = os.getenv
   13.12 +
   13.13 +
   13.14 +module "tek.cgi.header"
   13.15 +
   13.16 +
   13.17 +_VERSION = 2
   13.18 +_REVISION = 0
   13.19 +
   13.20 +
   13.21 +local Header = getfenv()
   13.22 +
   13.23 +
   13.24 +function Header:new(o)
   13.25 +	o = o or { }
   13.26 + 	setmetatable(o, self)
   13.27 + 	self.__index = self
   13.28 +	o.Accept = getenv("HTTP_ACCEPT")
   13.29 +	o.UserAgent = getenv("HTTP_USER_AGENT")
   13.30 +	o.Referer = getenv("HTTP_REFERER")
   13.31 +	o.Host = getenv("HTTP_HOST")
   13.32 +	o.CookieString = getenv("HTTP_COOKIE")
   13.33 +	return o
   13.34 +end
   13.35 +
   13.36 +
   13.37 +function Header:getcookies()
   13.38 +	if not self.cookies and self.CookieString then
   13.39 +		self.cookies = { }
   13.40 +		local pos, name, id = 0
   13.41 +		while true do
   13.42 +			_, pos, name, id = self.CookieString:find(
   13.43 +				"[%s]*([%w_]+)=([%w%.%-%+%%_]+)[;%s]*", pos + 1)
   13.44 +			if not pos then
   13.45 +				break
   13.46 +			end
   13.47 +			self.cookies[name] = id
   13.48 +		end
   13.49 +	end
   13.50 +	return self.cookies
   13.51 +end
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/cgi-bin/tek/class/http/request.lua	Sun May 20 18:28:42 2007 +0200
    14.3 @@ -0,0 +1,153 @@
    14.4 +
    14.5 +--
    14.6 +--	tek.cgi.request - Collect request variables
    14.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    14.8 +--	See copyright notice in COPYRIGHT
    14.9 +--
   14.10 +--	TODO: args can be instantiated only once, because POST data
   14.11 +--	is read from a stream
   14.12 +--
   14.13 +
   14.14 +require "cgilua.post"
   14.15 +local http = require "tek.class.http"
   14.16 +
   14.17 +local getfenv, setmetatable = getfenv, setmetatable
   14.18 +local getenv, tonumber = os.getenv, tonumber
   14.19 +local parsedata, read = cgilua.post.parsedata, io.read
   14.20 +local open = io.open
   14.21 +
   14.22 +
   14.23 +module "tek.class.http.request"
   14.24 +
   14.25 +
   14.26 +_VERSION = 4
   14.27 +_REVISION = 0
   14.28 +
   14.29 +
   14.30 +local Request = getfenv()
   14.31 +
   14.32 +
   14.33 +function Request:new(o)
   14.34 +	o = o or { }
   14.35 +	o.maxinput = o.maxinput or 1048575
   14.36 +	o.maxfilesize = o.maxfilesize or 524288
   14.37 + 	setmetatable(o, self)
   14.38 + 	self.__index = self
   14.39 +	o.Protocol = getenv("SERVER_PROTOCOL")
   14.40 +	o.Port = tonumber(getenv("SERVER_PORT"))
   14.41 +	o.Method = getenv("REQUEST_METHOD")
   14.42 +	o.PathInfo = getenv("PATH_INFO")
   14.43 +	o.PathTranslated = getenv("PATH_TRANSLATED")
   14.44 +	o.ScriptName = getenv("SCRIPT_NAME")
   14.45 +	o.QueryString = getenv("QUERY_STRING")
   14.46 +	o.RemoteHost = getenv("REMOTE_HOST")
   14.47 +	o.RemoteAddr = getenv("REMOTE_ADDR")
   14.48 +	o.AuthType = getenv("AUTH_TYPE")
   14.49 +	o.RemoteUser = getenv("REMOTE_USER")
   14.50 +	o.RemoteIdent = getenv("REMOTE_IDENT")
   14.51 +	o.ContentType = getenv("CONTENT_TYPE")
   14.52 +	o.ContentLength = getenv("CONTENT_LENGTH")
   14.53 +	o.DocumentName = getenv("DOCUMENT_NAME")
   14.54 +	o.UniqueID = getenv("UNIQUE_ID")
   14.55 +	return o
   14.56 +end
   14.57 +
   14.58 +
   14.59 +function Request:getargs(maxinput, maxfilesize)
   14.60 +	if not self.args then
   14.61 +		self.args = { }
   14.62 +		if self.Method == "GET" then
   14.63 +			self.QueryString:gsub("([^&=]+)=([^&=]+)", function(key, value)
   14.64 +				self.args[key] = http.decodeurl(value)
   14.65 +			end)
   14.66 +		elseif self.Method == "POST" then
   14.67 +			parsedata {
   14.68 +				read = read,
   14.69 +				discardinput = nil,
   14.70 +				content_type = self.ContentType,
   14.71 +				content_length = self.ContentLength,
   14.72 +				maxinput = maxinput or self.maxinput,
   14.73 +				maxfilesize = maxfilesize or self.maxfilesize,
   14.74 +				args = self.args
   14.75 +			}
   14.76 +		end
   14.77 +	end
   14.78 +	return self.args
   14.79 +end
   14.80 +
   14.81 +
   14.82 +function Request:getdocument()
   14.83 +
   14.84 +	if not self.document and self.PathTranslated then
   14.85 +	
   14.86 +		self.document = { }
   14.87 +		
   14.88 +		--	Gather document information "Name" and "Path", additionally
   14.89 +		--	provide a "VirtualPath" that is past the end of the script path.
   14.90 +		--	"Handler" denotes the script in the path.
   14.91 +		
   14.92 +		local pt, vp, f = self.PathTranslated
   14.93 +		
   14.94 +		repeat
   14.95 +			f = open(pt)
   14.96 +			if f then
   14.97 +				f:close()
   14.98 +				-- now matches a filesystem object - the rest is 'virtual'
   14.99 +				break
  14.100 +			end
  14.101 +			pt = pt:gsub("^(.*)(/.-)$", function(a, b)
  14.102 +				vp = b .. (vp or "")
  14.103 +				return a
  14.104 +			end)
  14.105 +		until not pt
  14.106 +		
  14.107 +		self.document.Handler = pt
  14.108 +		
  14.109 +		local name = self.PathInfo
  14.110 +		if vp then
  14.111 +			-- isolate document name by matching virtual path at end:
  14.112 +			if name:sub(-vp:len()) == vp then
  14.113 +				name = name:sub(1, name:len() - vp:len())
  14.114 +			end
  14.115 +			self.document.VirtualPath = vp:gsub("^/?(.*)$", "%1")
  14.116 +		end
  14.117 +		self.document.Name = name
  14.118 +		self.document.Path = pt:gsub("^(.*/).-$", "%1")
  14.119 +	end
  14.120 +	
  14.121 +	return self.document
  14.122 +end
  14.123 +
  14.124 +
  14.125 +function Request:getheader()
  14.126 +	if not self.header then
  14.127 +		self.header = {
  14.128 +			Accept = getenv("HTTP_ACCEPT"),
  14.129 +			UserAgent = getenv("HTTP_USER_AGENT"),
  14.130 +			Referer = getenv("HTTP_REFERER"),
  14.131 +			Host = getenv("HTTP_HOST"),
  14.132 +			CookieString = getenv("HTTP_COOKIE"),
  14.133 +		}
  14.134 +	end
  14.135 +	return self.header
  14.136 +end
  14.137 +
  14.138 +
  14.139 +function Request:getcookies()
  14.140 +	if not self.cookies then
  14.141 +		local header = self:getheader()
  14.142 +		if header.CookieString then
  14.143 +			self.cookies = { }
  14.144 +			local pos, name, id = 0
  14.145 +			while true do
  14.146 +				_, pos, name, id = header.CookieString:find(
  14.147 +					"[%s]*([%w_]+)=([%w%.%-%+%%_]+)[;%s]*", pos + 1)
  14.148 +				if not pos then
  14.149 +					break
  14.150 +				end
  14.151 +				self.cookies[name] = id
  14.152 +			end
  14.153 +		end
  14.154 +	end
  14.155 +	return self.cookies
  14.156 +end
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/cgi-bin/tek/class/loona.lua	Sun May 20 18:28:42 2007 +0200
    15.3 @@ -0,0 +1,1717 @@
    15.4 +
    15.5 +--
    15.6 +--	loona - tiny CMS
    15.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    15.8 +--	See copyright notice in COPYRIGHT
    15.9 +--
   15.10 +
   15.11 +local lib = require "tek.lib"
   15.12 +local luahtml = require "tek.lib.luahtml"
   15.13 +local posix = require "tek.os.posix"
   15.14 +local http = require "tek.class.http"
   15.15 +local Request = require "tek.class.http.request"
   15.16 +local util = require "tek.class.loona.util"
   15.17 +local markup = require "tek.class.loona.markup"
   15.18 +
   15.19 +local boxed_G = { 
   15.20 +	string = string, table = table,
   15.21 +	assert = assert, collectgarbage = collectgarbage, dofile = dofile,
   15.22 +	error = error, getfenv = getfenv, getmetatable = getmetatable,
   15.23 +	ipairs = ipairs, load = load, loadfile = loadfile, loadstring = loadstring,
   15.24 +	next = next, pairs = pairs, pcall = pcall, print = print,
   15.25 +	rawequal = rawequal, rawget = rawget, rawset = rawset, require = require,
   15.26 +	select = select, setfenv = setfenv, setmetatable = setmetatable,
   15.27 +	tonumber = tonumber, tostring = tostring, type = type, unpack = unpack,
   15.28 +	xpcall = xpcall
   15.29 +}
   15.30 +
   15.31 +local table, string, assert, unpack, ipairs, pairs, type, require =
   15.32 +	table, string, assert, unpack, ipairs, pairs, type, require
   15.33 +local setmetatable, setfenv, getfenv = setmetatable, setfenv, getfenv
   15.34 +local open, remove, rename, getenv, time, date =
   15.35 +	io.open, os.remove, os.rename, os.getenv, os.time, os.date
   15.36 +
   15.37 +
   15.38 +module "tek.class.loona"
   15.39 +
   15.40 +
   15.41 +_VERSION = 4
   15.42 +_REVISION = 1
   15.43 +
   15.44 +
   15.45 +-- Session
   15.46 +
   15.47 +
   15.48 +local Session = { }
   15.49 +
   15.50 +
   15.51 +function Session:new(o)
   15.52 +
   15.53 +	o = o or { }
   15.54 + 	setmetatable(o, self)
   15.55 + 	self.__index = self
   15.56 +	
   15.57 +	assert(o.id, "No session Id")
   15.58 + 	assert(o.sessiondir, "No session directory")
   15.59 +	
   15.60 +	o.name = o.id:gsub("(.)", function(a)
   15.61 +		return ("%02x"):format(a:byte())
   15.62 +	end)
   15.63 +	o.filename = o.sessiondir .. "/" .. o.name
   15.64 +	-- remove non-dotted files (expired sessions) from sessions dir:
   15.65 +	util.expire(o.sessiondir, "[^.]%S+", o.maxage or 600)
   15.66 +	-- load session state:
   15.67 +	o.data = lib.source(o.filename) or { }
   15.68 +	
   15.69 +	return o
   15.70 +end
   15.71 +
   15.72 +
   15.73 +function Session:save()
   15.74 +	local f = open(self.filename, "wb")
   15.75 +	assert(f, "Failed to open session file for writing")
   15.76 +	lib.dump(self.data, function(...) 
   15.77 +		f:write(unpack(arg)) 
   15.78 +	end)
   15.79 +	f:close()
   15.80 +end
   15.81 +
   15.82 +
   15.83 +function Session:delete()
   15.84 +	remove(self.filename)
   15.85 +end
   15.86 +
   15.87 +
   15.88 +-- LOona
   15.89 +
   15.90 +
   15.91 +local Loona = getfenv()
   15.92 +
   15.93 +
   15.94 +function Loona:dbmsg(msg, detail)
   15.95 + 	return (msg and detail and self.authuser) and
   15.96 + 		("%s : %s"):format(msg, detail) or msg
   15.97 +end
   15.98 +
   15.99 +
  15.100 +function Loona:checkprofilename(n)
  15.101 +	assert(n:match("^%w+$") and n ~= "current",
  15.102 +		self:dbmsg("Invalid profile name", n))
  15.103 +	return n
  15.104 +end
  15.105 +
  15.106 +
  15.107 +function Loona:checklanguage(n)
  15.108 +	assert(n:match("^%l%l$"), self:dbmsg("Invalid language code", n))
  15.109 +	return n
  15.110 +end
  15.111 +
  15.112 +
  15.113 +function Loona:checkbodyname(s)
  15.114 +	s = s or "main"
  15.115 +	assert(s:match("^[%w_]*%w+[%w_]*$"), self:dbmsg("Invalid body name", s))
  15.116 +	return s
  15.117 +end
  15.118 +
  15.119 +
  15.120 +function Loona:deleteprofile(p, lang)
  15.121 +	p = self.config.contentdir .. "/" .. p .. "_" .. (lang or self.lang)
  15.122 +	for e in util.readdir(p) do
  15.123 + 		local success, msg = remove(p .. "/" .. e)
  15.124 +		assert(success, self:dbmsg("Error removing entry in profile", msg))
  15.125 +	end
  15.126 +	return remove(p)
  15.127 +end
  15.128 +
  15.129 +
  15.130 +function Loona:copyprofile(dstprof, srcprof, dstlang, srclang)
  15.131 +	local contentdir = self.config.contentdir
  15.132 +	local src = ("%s/%s_%s"):format(contentdir,
  15.133 +		srcprof or self.profile, srclang or self.lang)
  15.134 +	local dst = ("%s/%s_%s"):format(contentdir,
  15.135 +		dstprof or self.profile, dstlang or self.lang)
  15.136 +	assert(src ~= dst, self:dbmsg("Attempt to copy profile over itself"))
  15.137 +	assert(posix.stat(src, "mode") == "directory",
  15.138 +		self:dbmsg("Source profile not a directory", src))
  15.139 +	local success, msg = posix.mkdir(dst)
  15.140 +	assert(success, self:dbmsg("Error creating profile directory " .. dst, msg))
  15.141 +	for e in util.readdir(src) do
  15.142 +		local ext = e:match("^[^.].*%.([^.]*)$")
  15.143 +		if ext ~= "LOCK" then
  15.144 +			local f = src .. "/" .. e
  15.145 +			if posix.stat(f, "mode") == "file" then
  15.146 +				success, msg = lib.copyfile(f, dst .. "/" .. e)
  15.147 +				assert(success, self:dbmsg("Error copying file", msg))
  15.148 +			end
  15.149 +		end
  15.150 +	end
  15.151 +	-- create "current" symlink if none exists for new profile/language
  15.152 +	if not posix.readlink(contentdir .. "/current_" .. dstlang) then
  15.153 +		self:makecurrent(dstprof, dstlang)
  15.154 +	end
  15.155 +end
  15.156 +
  15.157 +
  15.158 +function Loona:makecurrent(prof, lang)
  15.159 +	prof = prof or self.profile
  15.160 +	lang = lang or self.lang
  15.161 +	local contentdir = self.config.contentdir
  15.162 +	local newpath = ("%s/current_%s"):format(contentdir, lang)
  15.163 +	local tmppath = newpath .. "." .. self.session.name
  15.164 +	local success, msg = posix.symlink(prof .. "_" .. lang, tmppath)
  15.165 +	assert(success, self:dbmsg("Cannot create symlink", msg))
  15.166 +	success, msg = rename(tmppath, newpath)
  15.167 +	assert(success, self:dbmsg("Cannot put symlink in place", msg))
  15.168 +	return true
  15.169 +end
  15.170 +
  15.171 +
  15.172 +function Loona:publishprofile(profile, lang)
  15.173 +	lang = lang or self.lang
  15.174 +	local contentdir = self.config.contentdir
  15.175 +	
  15.176 +	-- Get languages for the current profile
  15.177 +	
  15.178 +	local plangs = { }
  15.179 +	local lmatch = "^" .. self.profile .. "_(%w+)$"
  15.180 +	for e in util.readdir(self.config.contentdir) do
  15.181 +		local l = e:match(lmatch)
  15.182 +		if l then
  15.183 +			table.insert(plangs, l)
  15.184 +		end
  15.185 +	end
  15.186 +	
  15.187 +	-- For all languages, update "current" symlink
  15.188 +	
  15.189 +	for _, lang in ipairs(plangs) do
  15.190 +		self:makecurrent(profile, lang)
  15.191 +	end
  15.192 +	
  15.193 +	-- These arguments are overwritten globally and need to get restored
  15.194 +	
  15.195 +	local save_args = { self.args.lang, self.args.profile, self.args.session }
  15.196 +	
  15.197 +	-- For all languages, unroll site to static HTML
  15.198 +	
  15.199 +	for _, lang in ipairs(plangs) do
  15.200 +		local ext = (#plangs == 1 and ".html") or (".html." .. lang)
  15.201 +		self:recursesections(self.sections, function(self, s, e, path)
  15.202 +			path = path and path .. "/" .. e.name or e.name
  15.203 +			if not e.notvisible then
  15.204 +				Loona:dumphtml { requestpath = path, requestlang = lang,
  15.205 +					htmlext = ext, insecure = true }
  15.206 +			end
  15.207 +			return path
  15.208 +		end)
  15.209 +	end
  15.210 +	
  15.211 +	-- Restore arguments
  15.212 +	
  15.213 +	self.args.lang, self.args.profile, self.args.session = unpack(save_args)
  15.214 +	
  15.215 +	-- Update file cache
  15.216 +
  15.217 +	local htdocs = self.config.htdocsdir
  15.218 +	local cache = self.config.htmlcachedir
  15.219 +
  15.220 +	for e in util.readdir(cache) do
  15.221 +		local f = e:match("^.*%.html%.?(%w*)$")
  15.222 +		if f and f ~= "tmp" then
  15.223 +			local success, msg = remove(htdocs .. "/" .. e)
  15.224 +			success, msg = remove(cache .. "/" .. e)
  15.225 + 			assert(success,
  15.226 + 				self:dbmsg("Could not purge cached HTML file", msg))
  15.227 +		end
  15.228 +	end
  15.229 +	
  15.230 +	for e in util.readdir(cache) do
  15.231 +		local f = e:match("^(.*%.html%.?%w*)%.tmp$")
  15.232 +		if f then
  15.233 +			local success, msg = rename(cache .. "/" .. e, cache .. "/" .. f)
  15.234 +			assert(success,
  15.235 +				self:dbmsg("Could not update cached HTML file", msg))
  15.236 +			success, msg = rename(htdocs .. "/" .. e, htdocs .. "/" .. f)
  15.237 +			assert(success,
  15.238 +				self:dbmsg("Could not update cached HTML file", msg))
  15.239 +		end
  15.240 +	end
  15.241 +end
  15.242 +
  15.243 +
  15.244 +function Loona:recursesections(s, func, ...)
  15.245 +	for _, e in ipairs(s) do
  15.246 +		local udata = { func(self, s, e, unpack(arg)) }
  15.247 +		if e.subs then
  15.248 +			self:recursesections(e.subs, func, unpack(udata))
  15.249 +		end
  15.250 +	end
  15.251 +end
  15.252 +
  15.253 +
  15.254 +function Loona:indexsections()
  15.255 +	self:recursesections(self.sections, function(self, s, e)
  15.256 +		e.notvalid = (not self.secure and e.secure) or 
  15.257 +			(not self.authuser and e.secret) or nil
  15.258 +		e.notvisible = e.notvalid or not self.authuser and e.hidden or nil
  15.259 +		s[e.name] = e
  15.260 +	end)
  15.261 +end
  15.262 +
  15.263 +
  15.264 +--	Decompose section path into a stack of sections, returning only up to
  15.265 +--	the last valid element in the path. additionally returns the table of
  15.266 +--	the last section path element (or the default section)
  15.267 +
  15.268 +function Loona:getsection(path)
  15.269 +	local default = not self.authuser and self.config.defname
  15.270 +	local tab = { { entries = self.sections, name = default } }
  15.271 +	local ss = self.sections
  15.272 +	local sectionpath
  15.273 +	(path or ""):gsub("(%w+)/?", function(a)
  15.274 +		if ss then
  15.275 +			local s = ss[a]
  15.276 +			if s and not s.notvalid then
  15.277 +				sectionpath = s
  15.278 +				tab[#tab].name = a
  15.279 +				ss = s.subs
  15.280 +				if ss then
  15.281 +					table.insert(tab, { entries = ss })
  15.282 +				end
  15.283 +			else
  15.284 +				ss = nil -- stop.
  15.285 +			end
  15.286 +		end
  15.287 +	end)
  15.288 +	if not self.section and not sectionpath then
  15.289 +		sectionpath = self.sections[default]
  15.290 +		if sectionpath then
  15.291 +			table.insert(tab, { entries = sectionpath.subs })
  15.292 +		end
  15.293 +	end
  15.294 +	return tab, sectionpath
  15.295 +end
  15.296 +
  15.297 +
  15.298 +function Loona:getpath(delimiter, maxdepth)
  15.299 +	local t = { }
  15.300 +	local d = 0
  15.301 +	maxdepth = maxdepth or #self.submenus
  15.302 +	for _, menu in ipairs(self.submenus) do
  15.303 +		if menu.name then
  15.304 +			table.insert(t, menu.name)
  15.305 +		end
  15.306 +		d = d + 1
  15.307 +		if d == maxdepth then
  15.308 +			break
  15.309 +		end
  15.310 +	end
  15.311 +	return table.concat(t, delimiter or "/")
  15.312 +end
  15.313 +
  15.314 +
  15.315 +function Loona:deletesection(fname, all_bodies)
  15.316 +	local fullname = self.contentdir .. "/" .. fname
  15.317 +	local success, msg = remove(fullname)
  15.318 +	if all_bodies then
  15.319 +		local pat = "^" .. 
  15.320 +			fname:gsub("%^%$%(%)%%%.%[%]%*%+%-%?", "%%%1") .. "%..*$"
  15.321 +		for e in util.readdir(self.contentdir) do
  15.322 +			if e:match(pat) then
  15.323 +				remove(self.contentdir .. "/" .. e)
  15.324 +			end
  15.325 +		end
  15.326 +	end
  15.327 +	return success, msg
  15.328 +end
  15.329 +
  15.330 +
  15.331 +function Loona:addpath(path, e)
  15.332 +	local tab = self.sections
  15.333 +	path:gsub("(%w+)/?", function(a)
  15.334 +		if tab then
  15.335 +			local s = tab[a]
  15.336 +			if s then
  15.337 +				if not s.subs then
  15.338 +					s.subs = { }
  15.339 +				end
  15.340 +				tab = s.subs
  15.341 +			else
  15.342 +				table.insert(tab, e)
  15.343 +				tab[a] = e
  15.344 + 				tab = nil -- stop
  15.345 +			end
  15.346 +		end
  15.347 +	end)
  15.348 +	return e
  15.349 +end
  15.350 +
  15.351 +
  15.352 +local function lookupname(tab, val)
  15.353 +	for i, v in ipairs(tab) do
  15.354 +		if v.name == val then
  15.355 +			return i
  15.356 +		end
  15.357 +	end
  15.358 +end
  15.359 +
  15.360 +
  15.361 +function Loona:rmpath(path)
  15.362 +	local parent
  15.363 +	local tab = self.sections
  15.364 +	path:gsub("(%w+)/?", function(a)
  15.365 +		if tab then
  15.366 +			local idx = lookupname(tab, a)
  15.367 +			if idx then
  15.368 +				if tab[idx].subs then
  15.369 +					parent = tab[idx]
  15.370 +					tab = tab[idx].subs
  15.371 +				else
  15.372 +					table.remove(tab, idx)
  15.373 +					tab[a] = nil
  15.374 +					if #tab == 0 and parent then
  15.375 +						parent.subs = nil
  15.376 +					end
  15.377 +					tab = nil
  15.378 +				end
  15.379 +			end
  15.380 +		end
  15.381 +	end)
  15.382 +end
  15.383 +
  15.384 +
  15.385 +function Loona:checkpath(path)
  15.386 +	if path ~= "index" then -- "index" is reserved
  15.387 +		local res, idx
  15.388 +		local tab = self.sections
  15.389 +		path:gsub("(%w+)/?", function(a)
  15.390 +			if tab then
  15.391 +				local i = lookupname(tab, a)
  15.392 +				if i then
  15.393 +					res, idx = tab, i
  15.394 +					tab = tab[i].subs
  15.395 +				else
  15.396 +					res, idx = nil, nil
  15.397 +				end
  15.398 +			end
  15.399 +		end)
  15.400 +		return res, idx
  15.401 +	end
  15.402 +end
  15.403 +
  15.404 +
  15.405 +function Loona:title()
  15.406 +	return self.section and (self.section.title or self.section.label or
  15.407 +		self.section.name) or ""
  15.408 +end
  15.409 +
  15.410 +
  15.411 +--	Run a site function snippet, with full error recovery
  15.412 +--	(also recovers from errors in error handling function)
  15.413 +
  15.414 +function Loona:dosnippet(func, errfunc)
  15.415 +	local ret = { lib.catch(func) }
  15.416 +	if ret[1] == 0 or (errfunc and lib.catch(errfunc) == 0) then
  15.417 +		return unpack(ret)
  15.418 +	end
  15.419 +	self:out("<h2>Error</h2>")
  15.420 +	self:out("<h3>" .. self:encodeform(ret[2] or "") .. "</h3>")
  15.421 +	if self.authuser then
  15.422 +		if type(ret[3]) == "string" then
  15.423 +			self:out("<p>" .. self:encodeform(ret[3]) .. "</p>")
  15.424 +		end
  15.425 +		if ret[4] then
  15.426 +			self:out("<pre>" .. self:encodeform(ret[4]) .. "</pre>")
  15.427 +		end
  15.428 +	end
  15.429 +end	
  15.430 +
  15.431 +
  15.432 +function Loona:lockfile(file)
  15.433 +	return not self.session and true or 
  15.434 +		posix.symlink(self.session.filename, file .. ".LOCK")
  15.435 +end
  15.436 +
  15.437 +
  15.438 +function Loona:unlockfile(file)
  15.439 +	return not self.session and true or remove(file .. ".LOCK")
  15.440 +end
  15.441 +
  15.442 +
  15.443 +function Loona:saveindex()
  15.444 +	local tempname = self.indexfname .. "." .. self.session.name
  15.445 +	local f, msg = open(tempname, "wb")
  15.446 +	assert(f, self:dbmsg("Error opening section file for writing", msg))
  15.447 +	lib.dump(self.sections, function(...)
  15.448 +		f:write(unpack(arg))
  15.449 +	end)
  15.450 +	f:close()
  15.451 +	local success, msg = rename(tempname, self.indexfname)
  15.452 +	assert(success, self:dbmsg("Error renaming section file", msg))
  15.453 +end
  15.454 +
  15.455 +
  15.456 +function Loona:savebody(fname, content)
  15.457 +	fname = self.contentdir .. "/" .. fname
  15.458 +	local f, msg = open(fname, "wb")
  15.459 +	assert(f, self:dbmsg("Could not open file for writing", msg))
  15.460 +	f:write(content or "")
  15.461 +	f:close()
  15.462 +end
  15.463 +
  15.464 +
  15.465 +function Loona:runboxed(func, envitems, ...)
  15.466 +	local fenv = {
  15.467 + 		arg = arg,
  15.468 + 		loona = self
  15.469 + 	}
  15.470 + 	if envitems then
  15.471 +	 	for k, v in pairs(envitems) do
  15.472 + 			fenv[k] = v
  15.473 + 		end
  15.474 + 	end
  15.475 + 	setmetatable(fenv, { __index = boxed_G }) -- boxed global environment
  15.476 +	setfenv(func, fenv)
  15.477 +	return func()
  15.478 +end
  15.479 +
  15.480 +
  15.481 +function Loona:include(fname, ...)
  15.482 +	assert(not fname:match("%W"), self:dbmsg("Invalid include name", fname))
  15.483 +	local fname2 = ("%s/%s.lua"):format(self.config.extdir, fname)
  15.484 +	local f, msg = open(fname2)
  15.485 +	assert(f, self:dbmsg("Cannot open file", msg))
  15.486 +	local parsed, msg = self:loadhtml(f, "loona:out", fname2)
  15.487 +	assert(parsed, self:dbmsg("Syntax error", msg))
  15.488 +	return self:runboxed(parsed, nil, unpack(arg))
  15.489 +end
  15.490 +
  15.491 +
  15.492 +--	produce link target (simple)
  15.493 +
  15.494 +function Loona:shref(section, arg)
  15.495 +	local args2 = { } -- propagated or new arguments
  15.496 +	for _, a in ipairs(arg) do
  15.497 +		local key, val = a:match("^(%w+)=(.*)$")
  15.498 +		if key and val then -- "arg=val" sets/overrides argument
  15.499 +			table.insert(args2, { name = key, value = val })
  15.500 +		elseif self.args[a] then -- just "arg" propagates argument
  15.501 +			table.insert(args2, { name = a, value = self.args[a] })
  15.502 +		end
  15.503 +	end
  15.504 +	local doc = self:getdocname(section, #args2 > 0)
  15.505 +	local url, anch = doc:match("^(.+)(#.+)$")
  15.506 +	local notfirst = doc:match("%?")
  15.507 +	local href = { anch and url or doc }
  15.508 +	for i, arg in ipairs(args2) do
  15.509 +		if i > 1 or notfirst then
  15.510 +			table.insert(href, "&amp;")
  15.511 +		else
  15.512 +			table.insert(href, "?")
  15.513 +		end
  15.514 +		table.insert(href, arg.name .. "=" .. http.encodeurl(arg.value))
  15.515 +	end
  15.516 +	if anch then
  15.517 +		insert(href, anch)
  15.518 +	end
  15.519 +	return table.concat(href)
  15.520 +end
  15.521 +
  15.522 +
  15.523 +--	produce link target, implicit propagation of lang, profile, session
  15.524 +
  15.525 +function Loona:href(section, ...)
  15.526 +	if self.session then
  15.527 +		table.insert(arg, 1, "profile")
  15.528 +		table.insert(arg, 1, "session")
  15.529 +	end
  15.530 +	if self.explicitlang then
  15.531 +		table.insert(arg, 1, "lang")
  15.532 +	end
  15.533 +	return self:shref(section, arg)
  15.534 +end
  15.535 +
  15.536 +
  15.537 +function Loona:ilink(target, text, extra)
  15.538 +	return ('<a href="%s"%s>%s</a>'):format(target, extra or "", text)
  15.539 +end
  15.540 +
  15.541 +
  15.542 +--	internal link, implicit propagation of lang, profile, session
  15.543 +
  15.544 +function Loona:link(section, text, ...)
  15.545 +	return self:ilink(self:href(section, unpack(arg)), text or section,
  15.546 +	' class="intlink"')
  15.547 +end
  15.548 +
  15.549 +
  15.550 +--	external link (opens in a new window), no argument propagation
  15.551 +
  15.552 +function Loona:elink(target, text)
  15.553 +	return self:ilink(target, text or target, self.config.extlinkextra)
  15.554 +end
  15.555 +
  15.556 +
  15.557 +--	plain link, no implicit argument propagation
  15.558 +
  15.559 +function Loona:plink(section, text, ...)
  15.560 +	return self:ilink(self:shref(section, arg), text or section)
  15.561 +end
  15.562 +
  15.563 +
  15.564 +--	user interface link, implicit propagation of lang, profile, session
  15.565 +
  15.566 +function Loona:uilink(section, text, ...)
  15.567 +	return self:ilink(self:href(section, unpack(arg)), text or section,
  15.568 +	' class="uilink"')
  15.569 +end
  15.570 +
  15.571 +
  15.572 +--	produce a hidden input value in forms
  15.573 +
  15.574 +function Loona:hidden(name, value)
  15.575 +	return not value and "" or
  15.576 +		('<input type="hidden" name="%s" value="%s" />'):format(name, value)
  15.577 +end
  15.578 +
  15.579 +
  15.580 +function Loona:scanprofiles(func)
  15.581 +	local tab = { }
  15.582 +	local dir = self.config.contentdir
  15.583 +	for f in util.readdir(dir) do
  15.584 +		if posix.lstat(dir .. "/" .. f, "mode") == "directory" then
  15.585 +			f = func(f)
  15.586 +			if f then
  15.587 +				table.insert(tab, f)
  15.588 +			end
  15.589 +		end
  15.590 +	end
  15.591 +	table.sort(tab)
  15.592 +	for _, v in ipairs(tab) do
  15.593 +		tab[v] = v	
  15.594 +	end
  15.595 +	return tab
  15.596 +end
  15.597 +
  15.598 +
  15.599 +function Loona:getprofiles(lang)
  15.600 +	lang = lang or self.lang
  15.601 +	return self:scanprofiles(function(f)
  15.602 +		return f:match("^(%w+)_" .. lang .. "$")
  15.603 +	end)
  15.604 +end
  15.605 +
  15.606 +
  15.607 +function Loona:getlanguages(prof)
  15.608 +	prof = prof or self.profile
  15.609 +	return self:scanprofiles(function(f)
  15.610 +		return f:match("^" .. prof .. "_(%l%l)$")
  15.611 +	end)
  15.612 +end
  15.613 +
  15.614 +
  15.615 +--	Functions to produce a navigation menu
  15.616 +
  15.617 +local newent = { name = "new", label = "[+]", action="actionnew=true" }
  15.618 +
  15.619 +function Loona:rmenu(level, render, path, addnew, recurse)
  15.620 +	local sub = (addnew and level == #self.submenus + 1) and
  15.621 +		{ name = "new", entries = { }} or self.submenus[level]
  15.622 + 	if sub and sub.entries then
  15.623 +		local visible = { }
  15.624 +		for _, e in ipairs(sub.entries) do
  15.625 +			if not e.notvisible then
  15.626 +				table.insert(visible, e)
  15.627 +			end
  15.628 +		end
  15.629 +		if addnew then
  15.630 +			table.insert(visible, newent)
  15.631 +		end
  15.632 +		local numvis = #visible
  15.633 +		if numvis > 0 then
  15.634 +			render.listbegin(self, level, numvis, path)
  15.635 +			for idx, e in ipairs(visible) do
  15.636 +				local label = self:encodeform(e.label or e.name)
  15.637 +				local newpath = path and path .. "/" .. e.name or e.name
  15.638 +				local active = (e.name == sub.name)
  15.639 +				render.itembegin(self, level, idx)
  15.640 +				render.link(self, level, newpath, label, active, e.action)
  15.641 +				if recurse and active then
  15.642 +					self:rmenu(level + 1, render, newpath, addnew, recurse)
  15.643 +				end
  15.644 +				render.itemend(self)
  15.645 +			end
  15.646 +			render.listend(self)
  15.647 +		end
  15.648 +	end
  15.649 +end
  15.650 +
  15.651 +
  15.652 +function Loona:menu(level, recurse, render)
  15.653 +	level = level or 1
  15.654 +	render = render or { }
  15.655 +	render.link = render.link or 
  15.656 +		function(self, level, path, label, active, ...)
  15.657 +			self:out(('<a %shref="%s">%s</a>\n'):format(active and 
  15.658 +				'class="active" ' or "", self:href(path, unpack(arg)), label))
  15.659 +		end
  15.660 +	render.listbegin = render.listbegin or
  15.661 +		function(self, level) -- , numvis, path
  15.662 +			self:out('<ul id="menulevel' .. level .. '">\n')
  15.663 +		end
  15.664 +	render.listend = render.listend or
  15.665 +		function(self)
  15.666 +			self:out('</ul>\n')
  15.667 +		end
  15.668 +	render.itembegin = render.itembegin or
  15.669 +		function(self) -- , level, idx
  15.670 +			self:out('<li>\n')
  15.671 +		end
  15.672 +	render.itemend = render.itemend or
  15.673 +		function(self)
  15.674 +			self:out('</li>\n')
  15.675 +		end
  15.676 +	recurse = recurse == nil and true or recurse
  15.677 +	local path = level > 1 and self:getpath("/", level - 1) or nil
  15.678 +	local addnew = self.authuser and not self.ispubprofile
  15.679 +	self:rmenu(level, render, path, addnew, recurse)
  15.680 +end
  15.681 +
  15.682 +
  15.683 +function Loona:loadcontent(fname)
  15.684 +	if fname then
  15.685 +		local f = open(self.contentdir .. "/" .. fname)
  15.686 +		local c = f:read("*a")
  15.687 +		f:close()
  15.688 +		return c
  15.689 +	end
  15.690 +	return ""
  15.691 +end
  15.692 +
  15.693 +
  15.694 +function Loona:loadmarkup(fname)
  15.695 +	return (fname and fname ~= "") and
  15.696 +		self:domarkup(self:loadcontent(fname)) or ""
  15.697 +end
  15.698 +
  15.699 +
  15.700 +function Loona:editable(editkey, fname, savename)
  15.701 +	
  15.702 +	local contentdir = self.contentdir
  15.703 +	local edit, show, hidden, extramsg, changed
  15.704 +	
  15.705 +	if self.authuser then
  15.706 +		
  15.707 +		local hiddenvars = table.concat( {
  15.708 +			self:hidden("lang", self.args.lang),
  15.709 +			self:hidden("profile", self.profile),
  15.710 +			self:hidden("session", self.session.id),
  15.711 +			self:hidden("editkey", editkey) }, " ")
  15.712 +	
  15.713 +		local lockfname = fname and (contentdir .. "/" .. fname)
  15.714 +		
  15.715 +		if self.useralert and editkey == self.args.editkey then
  15.716 +			
  15.717 +			--	display user alert/request/confirmation
  15.718 +			
  15.719 +			hidden = true
  15.720 +			self:out([[
  15.721 +			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.722 +				<fieldset>
  15.723 +					<legend>]] .. self.useralert.text ..[[</legend>
  15.724 +					]] .. (self.useralert.confirm or "") .. [[
  15.725 +					]] .. (self.useralert.returnto or "") .. [[
  15.726 +					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL  ..[[" />
  15.727 +					]] .. hiddenvars .. [[
  15.728 +				</fieldset>
  15.729 +			</form>
  15.730 +			]])
  15.731 +			
  15.732 +		elseif self.args.actionnew and editkey == "main" then
  15.733 +			
  15.734 +			--	form for creating a new section
  15.735 +			
  15.736 +			hidden = true
  15.737 +			if self.ispubprofile then
  15.738 +				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
  15.739 +			end
  15.740 +			self:out([[
  15.741 +			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.742 +				<fieldset>
  15.743 +					<legend>
  15.744 +						]] .. self.locale.CREATE_NEW_SECTION_UNDER .. " " .. self.sectionpath .. [[
  15.745 +					</legend>
  15.746 +					<table>
  15.747 +						<tr>
  15.748 +							<td align="right">
  15.749 +								]] .. self.locale.PATHNAME .. [[
  15.750 +							</td>
  15.751 +							<td>
  15.752 +								<input size="30" maxlength="30" name="editname" />
  15.753 +							</td>
  15.754 +						</tr>
  15.755 +						<tr>
  15.756 +							<td align="right">
  15.757 +								]] .. self.locale.MENULABEL .. [[
  15.758 +							</td>
  15.759 +							<td>
  15.760 +								<input size="30" maxlength="50" name="editlabel" />
  15.761 +							</td>
  15.762 +						</tr>
  15.763 +						<tr>
  15.764 +							<td align="right">
  15.765 +								]] .. self.locale.WINDOWTITLE .. [[
  15.766 +							</td>
  15.767 +							<td>
  15.768 +								<input size="30" maxlength="50" name="edittitle" />
  15.769 +							</td>
  15.770 +						</tr>
  15.771 +						<tr>
  15.772 +							<td align="right">
  15.773 +								]] .. self.locale.INVISIBLE .. [[
  15.774 +							</td>
  15.775 +							<td>
  15.776 +								<input type="checkbox" name="editvisibility" />
  15.777 +							</td>
  15.778 +						</tr>
  15.779 +						<tr>
  15.780 +							<td align="right">
  15.781 +								]] .. self.locale.SECRET .. [[
  15.782 +							</td>
  15.783 +							<td>
  15.784 +								<input type="checkbox" name="editsecrecy" />
  15.785 +							</td>
  15.786 +						</tr>
  15.787 +						<tr>
  15.788 +							<td align="right">
  15.789 +								]] .. self.locale.SECURE_CONNECTION .. [[
  15.790 +							</td>
  15.791 +							<td>
  15.792 +								<input type="checkbox" name="editsecure" />
  15.793 +							</td>
  15.794 +						</tr>
  15.795 +						<tr>
  15.796 +							<td align="right">
  15.797 +								]] .. self.locale.REDIRECT .. [[
  15.798 +							</td>
  15.799 +							<td>
  15.800 +								<input size="30" maxlength="50" name="editredirect" />
  15.801 +							</td>
  15.802 +						</tr>
  15.803 +					</table>					
  15.804 +					<input type="submit" name="actioncreate" value="]] .. self.locale.CREATE .. [[" />
  15.805 +					]] .. hiddenvars .. [[
  15.806 +				</fieldset>
  15.807 +			</form>
  15.808 +			<hr />
  15.809 +			]])
  15.810 +		
  15.811 +		elseif self.args.actioneditprops and editkey == "main" then
  15.812 +			hidden = true
  15.813 +			if self.ispubprofile then
  15.814 +				self:out([[<h2><span class="warn">]] .. self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE ..[[</span></h2>]])
  15.815 +			end
  15.816 +			self:out([[
  15.817 +			<form action="]] ..self.document .. [[" method="post" accept-charset="utf-8">
  15.818 +				<fieldset>
  15.819 +					<legend>
  15.820 +						]] .. self.locale.MODIFY_PROPERTIES_OF_SECTION .. " " .. self.sectionpath .. [[
  15.821 +					</legend>
  15.822 +					<table>
  15.823 +						<tr>
  15.824 +							<td align="right">
  15.825 +								]] .. self.locale.MENULABEL .. [[
  15.826 +							</td>
  15.827 +							<td>
  15.828 +								<input size="30" maxlength="50" name="editlabel" value="]] .. (self.section.label or "") .. [[" />
  15.829 +							</td>
  15.830 +						</tr>
  15.831 +						<tr>
  15.832 +							<td align="right">
  15.833 +								]] .. self.locale.WINDOWTITLE .. [[
  15.834 +							</td>
  15.835 +							<td>
  15.836 +								<input size="30" maxlength="50" name="edittitle" value="]] .. (self.section.title or "") .. [[" />
  15.837 +							</td>
  15.838 +						</tr>
  15.839 +						<tr>
  15.840 +							<td align="right">
  15.841 +								]] .. self.locale.INVISIBLE .. [[
  15.842 +							</td>
  15.843 +							<td>
  15.844 +								<input type="checkbox" name="editvisibility" ]] .. (self.section.hidden and 'checked="checked"' or "") .. [[/>
  15.845 +							</td>
  15.846 +						</tr>
  15.847 +						<tr>
  15.848 +							<td align="right">
  15.849 +								]] .. self.locale.SECRET .. [[
  15.850 +							</td>
  15.851 +							<td>
  15.852 +								<input type="checkbox" name="editsecrecy" ]] .. (self.section.secret and 'checked="checked"' or "") .. [[/>
  15.853 +							</td>
  15.854 +						</tr>
  15.855 +						<tr>
  15.856 +							<td align="right">
  15.857 +								]] .. self.locale.SECURE_CONNECTION .. [[
  15.858 +							</td>
  15.859 +							<td>
  15.860 +								<input type="checkbox" name="editsecure" ]] .. (self.section.secure and 'checked="checked"' or "") .. [[/>
  15.861 +							</td>
  15.862 +						</tr>
  15.863 +						<tr>
  15.864 +							<td align="right">
  15.865 +								]] .. self.locale.REDIRECT .. [[
  15.866 +							</td>
  15.867 +							<td>
  15.868 +								<input size="30" maxlength="50" name="editredirect" value="]] .. (self.section.redirect or "") .. [[" />
  15.869 +							</td>
  15.870 +						</tr>
  15.871 +					</table>
  15.872 +					<input type="submit" name="actionsaveprops" value="]] .. self.locale.SAVE .. [[" />
  15.873 +					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
  15.874 +					]] .. hiddenvars .. [[
  15.875 +				</fieldset>
  15.876 +			</form>
  15.877 +			]])
  15.878 +		
  15.879 +		elseif (self.args.actioneditprofiles or
  15.880 +			self.args.actioncreateprofile or 
  15.881 +			self.args.actionchangeprofile or 
  15.882 +			self.args.actionchangelanguage or
  15.883 +			self.args.actionpublishprofile) and editkey == "main" then
  15.884 +			hidden = true
  15.885 +			self:out([[
  15.886 +			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.887 +				<fieldset>
  15.888 +					<legend>
  15.889 +						]] .. self.locale.CHANGEPROFILE .. [[
  15.890 +					</legend>
  15.891 +					<select name="changeprofile" size="1">]])
  15.892 +						for _, val in ipairs(self:getprofiles()) do
  15.893 +							self:out('<option' .. (val == self.profile and ' selected="selected"' or '') .. '>')
  15.894 +							self:out(val)
  15.895 +							self:out('</option>')
  15.896 +						end
  15.897 +					self:out([[
  15.898 +					</select>							
  15.899 +					<input type="submit" name="actionchangeprofile" value="]] .. self.locale.CHANGE ..[[" />
  15.900 +					]] .. hiddenvars .. [[
  15.901 +				</fieldset>
  15.902 +			</form>
  15.903 +			<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.904 +				<fieldset>
  15.905 +					<legend>
  15.906 +						]] .. self.locale.CHANGELANGUAGE .. [[
  15.907 +					</legend>
  15.908 +					<select name="changelanguage" size="1">]])
  15.909 +						for _, val in ipairs(self:getlanguages()) do
  15.910 +							self:out('<option' .. (val == self.lang and ' selected="selected"' or '') .. '>')
  15.911 +							self:out(val)
  15.912 +							self:out('</option>')
  15.913 +						end
  15.914 +					self:out([[
  15.915 +					</select>							
  15.916 +					<input type="submit" name="actionchangelanguage" value="]] .. self.locale.CHANGE ..[[" />
  15.917 +					]] .. hiddenvars .. [[
  15.918 +				</fieldset>
  15.919 +			</form>
  15.920 +			<form action="]] .. self.document ..[[" method="post" accept-charset="utf-8">
  15.921 +				<fieldset>
  15.922 +					<legend>
  15.923 +						]] .. self.locale.CREATEPROFILE .. [[
  15.924 +					</legend>
  15.925 +					<input size="20" maxlength="20" name="createprofile" />
  15.926 +					]] .. self.locale.LANGUAGE ..[[
  15.927 +					<input size="2" maxlength="2" name="createlanguage" value="]] .. self.lang ..[[" />
  15.928 +					<input type="submit" name="actioncreateprofile" value="]] .. self.locale.CREATE .. [[" />
  15.929 +					]] .. hiddenvars .. [[
  15.930 +				</fieldset>
  15.931 +			</form>
  15.932 +			]])
  15.933 +			if not self.ispubprofile then
  15.934 +				self:out([[
  15.935 +				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.936 +					<fieldset>
  15.937 +						<legend>
  15.938 +							]] .. self.locale.PUBLISHPROFILE .. [[
  15.939 +						</legend>
  15.940 +						]] .. self:hidden("publishprofile", self.profile) .. [[
  15.941 +						<input type="submit" name="actionpublishprofile" value="]] .. self.locale.PUBLISH .. [[" />
  15.942 +						]] .. hiddenvars .. [[
  15.943 +					</fieldset>
  15.944 +				</form>
  15.945 +				<form action="]] .. self.document .. [[" method="post" accept-charset="utf-8">
  15.946 +					<fieldset>
  15.947 +						<legend>
  15.948 +							]] .. self.locale.DELETEPROFILE .. [[
  15.949 +						</legend>
  15.950 +						]] .. self:hidden("deleteprofile", self.profile) .. [[
  15.951 +						<input type="submit" name="actiondeleteprofile" value="]] .. self.locale.DELETE .. [[" />
  15.952 +						]] .. hiddenvars .. [[
  15.953 +					</fieldset>
  15.954 +				</form>
  15.955 +				]])
  15.956 +			end
  15.957 +			
  15.958 +		elseif self.args.actionedit and editkey == self.args.editkey then
  15.959 +			if not self.section.redirect then
  15.960 +				extramsg = self.ispubprofile and
  15.961 +					self.locale.WARNING_YOU_ARE_IN_PUBLISHED_PROFILE
  15.962 +				edit = self:loadcontent(fname):gsub("\194\160", "&nbsp;") -- TODO
  15.963 +				changed = self.section and (self.section.revisiondate or self.section.creationdate)
  15.964 +			end
  15.965 +		
  15.966 +		elseif self.args.actionpreview and editkey == self.args.editkey then
  15.967 +			edit = self.args.editform
  15.968 +			show = self:domarkup(edit:gsub("&nbsp;", "\194\160")) -- TODO
  15.969 +		
  15.970 +		elseif self.args.actionsave and editkey == self.args.editkey then
  15.971 +			local c = self.args.editform
  15.972 +			local dynamic
  15.973 +			
  15.974 +			if lockfname then
  15.975 +				self:expire(contentdir, "[^.]%S+.LOCK")
  15.976 +				if self:lockfile(lockfname) then
  15.977 +					-- lock was expired, aquired a new one
  15.978 +					extramsg = self.locale.SECTION_COULD_HAVE_CHANGED
  15.979 +					edit = c
  15.980 +				else
  15.981 +					local tab = lib.source(lockfname .. ".LOCK")
  15.982 +					if tab and tab.id == self.session.id then
  15.983 +						-- lock already held and is mine - try to save:
  15.984 +						local savec = c:gsub("&nbsp;", "\194\160") -- TODO
  15.985 +						remove(contentdir .. "/" .. savename .. ".html")
  15.986 +						self:savebody(savename, savec)
  15.987 +						-- TODO: error handling
  15.988 +						self:unlockfile(lockfname)
  15.989 +						show, dynamic = self:domarkup(savec)
  15.990 +						changed = time()
  15.991 +					else
  15.992 +						-- lock was expired and someone else has it now
  15.993 +						extramsg = self.locale.SECTION_IN_USE
  15.994 +						edit = c
  15.995 +					end
  15.996 +				end
  15.997 +			else
  15.998 +				-- new sidefile
  15.999 +				local savec = c:gsub("&nbsp;", "\194\160") -- TODO
 15.1000 +				self:savebody(savename, savec)
 15.1001 +				-- TODO: error handling
 15.1002 +				show, dynamic = self:domarkup(savec)
 15.1003 +			end
 15.1004 +			
 15.1005 +			-- mark dynamic text bodies
 15.1006 +			if not self.section.dynamic then
 15.1007 +				self.section.dynamic = { }
 15.1008 +			end
 15.1009 +			self.section.dynamic[editkey] = dynamic
 15.1010 +			local n = 0
 15.1011 +			for _ in pairs(self.section.dynamic) do
 15.1012 +				n = n + 1
 15.1013 +			end
 15.1014 +			if n == 0 then
 15.1015 +				self.section.dynamic = nil
 15.1016 +			end
 15.1017 +			
 15.1018 +			self:saveindex()
 15.1019 +			
 15.1020 +		elseif self.args.actioncancel and editkey == self.args.editkey then
 15.1021 +			if lockfname then
 15.1022 +				self:unlockfile(lockfname) -- remove lock
 15.1023 +			end
 15.1024 +		end
 15.1025 +		
 15.1026 +		if editkey == "main" and self.section and self.section.redirect then
 15.1027 +			self:out('<h2>' .. self.locale.SECTION_IS_REDIRECT ..'</h2>')
 15.1028 +			self:out(self:link(self.section.redirect))
 15.1029 +			self:out('<hr />')
 15.1030 +		end
 15.1031 +	
 15.1032 +		if edit then
 15.1033 +			self:expire(contentdir, "[^.]%S+.LOCK")
 15.1034 +			if fname and not self:lockfile(contentdir .. "/" .. fname) then
 15.1035 +				local tab = lib.source(contentdir .. "/" .. fname .. ".LOCK")
 15.1036 +				if tab and tab.id ~= self.session.id then
 15.1037 +					extramsg = self.locale.SECTION_IN_USE
 15.1038 +				end
 15.1039 +				-- else already owner
 15.1040 +			end
 15.1041 +			if extramsg then
 15.1042 +				self:out('<h2><span class="warn">' .. extramsg .. '</span></h2>')
 15.1043 +			end
 15.1044 +			self:out([[
 15.1045 +			<form action="]] .. self.document .. [[#preview" method="post" accept-charset="utf-8">
 15.1046 +				<fieldset>
 15.1047 +					<legend>
 15.1048 +						]] .. self.locale.EDIT_SECTION .. [[
 15.1049 +					</legend>
 15.1050 +					<textarea cols="80" rows="25" name="editform">
 15.1051 +]] .. self:encodeform(edit) .. [[</textarea>
 15.1052 +					<br />
 15.1053 +					<input type="submit" name="actionsave" value="]] .. self.locale.SAVE .. [[" />
 15.1054 +					<input type="submit" name="actionpreview" value="]] .. self.locale.PREVIEW .. [[" />
 15.1055 +					<input type="submit" name="actioncancel" value="]] .. self.locale.CANCEL .. [[" />
 15.1056 +					]] .. hiddenvars .. [[
 15.1057 +				</fieldset>
 15.1058 +			</form>
 15.1059 +			]])
 15.1060 +		end
 15.1061 +	end	
 15.1062 +	
 15.1063 +	if not hidden then
 15.1064 +		self:dosnippet(function()
 15.1065 +			if not show then
 15.1066 +				show = self:loadmarkup(fname)
 15.1067 +				changed = self.section and (self.section.revisiondate or self.section.creationdate)
 15.1068 +			end
 15.1069 +			local parsed, msg = self:loadhtml(show, "loona:out", "<parsed html>")
 15.1070 +			assert(parsed, msg and "Syntax error : " .. msg)
 15.1071 +			self:runboxed(parsed)
 15.1072 +		end)
 15.1073 +	end
 15.1074 +	
 15.1075 +	if self.authuser then
 15.1076 +		self:out([[
 15.1077 +		<hr />
 15.1078 +		<div class="edit">]])
 15.1079 +			if editkey == "main" then
 15.1080 +				self:out([[
 15.1081 +				<a name="preview"></a>
 15.1082 +				]] .. self.authuser .. [[ : 
 15.1083 +				]] .. self:uilink(self.sectionpath, "[" .. self.locale.PROFILE .. "]", "actioneditprofiles=true", "editkey=" .. editkey) .. [[ :
 15.1084 +				]] .. self.profile .. "_" .. self.lang)
 15.1085 +				if self.ispubprofile then
 15.1086 +					self:out([[
 15.1087 +						<span class="warn">[]] .. self.locale.PUBLIC .. [[]</span>]])
 15.1088 +				end
 15.1089 +				self:out(' : ' .. self.sectionpath .. ' ')
 15.1090 +			end
 15.1091 +			if self.section and not self.ispubprofile then
 15.1092 +				self:out(self:uilink(self.sectionpath, "[" .. self.locale.EDIT .. "]", "actionedit=true", "editkey=" .. editkey) .. " ")
 15.1093 +				if editkey == "main" then
 15.1094 +					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.PROPERTIES .. "]", "actioneditprops=true", "editkey=" .. editkey) .. " ")
 15.1095 +				end
 15.1096 +				if fname == savename or not self.section.subs then
 15.1097 +					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.DELETE .. "]", "actiondelete=true", "editkey=" .. editkey) .. " ")
 15.1098 +				end
 15.1099 +				if editkey == "main" then
 15.1100 +					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEUP .. "]", "actionup=true", "editkey=" .. editkey) .. " ")
 15.1101 +					self:out('- ' .. self:uilink(self.sectionpath, "[" .. self.locale.MOVEDOWN .. "]", "actiondown=true", "editkey=" .. editkey) .. " ")
 15.1102 +				end
 15.1103 +				if changed and editkey == "main" then
 15.1104 +					self:out('- ' .. self.locale.CHANGED .. ': ' .. date("%d-%b-%Y %T", changed))
 15.1105 +				end
 15.1106 +			end
 15.1107 +		self:out('</div>')
 15.1108 +	end
 15.1109 +
 15.1110 +end
 15.1111 +
 15.1112 +
 15.1113 +--	Get pathname of an existing content file that
 15.1114 +--	the current path is determined by (or defaults to)
 15.1115 +
 15.1116 +function Loona:getsectionpath(bodyname, requestpath)
 15.1117 +	local ext = (not bodyname or bodyname == "main") and "" or "." .. bodyname
 15.1118 +	local t, path, section = { }
 15.1119 +	for _, menu in ipairs(self.submenus) do
 15.1120 +		if menu.entries and menu.entries[menu.name] then
 15.1121 +			table.insert(t, menu.name)
 15.1122 +			local fn = table.concat(t, "_")
 15.1123 +			if posix.stat(self.contentdir .. "/" .. fn .. ext, 
 15.1124 +				"mode") == "file" then
 15.1125 +				path, section = fn, menu
 15.1126 +			end
 15.1127 +		end
 15.1128 +	end
 15.1129 +	return path, ext, section
 15.1130 +end
 15.1131 +
 15.1132 +
 15.1133 +function Loona:body(name)
 15.1134 +	name = self:checkbodyname(name)
 15.1135 +	local path, ext = self:getsectionpath(name)
 15.1136 +	self:dosnippet(function()
 15.1137 +		self:editable(name, path and path .. ext, self.sectionname .. ext)
 15.1138 +	end)
 15.1139 +end
 15.1140 +
 15.1141 +
 15.1142 +function Loona:init()
 15.1143 +	
 15.1144 +	-- get list of languages, in order of preference
 15.1145 +	-- TODO: respect quality parameter, not just order
 15.1146 +	
 15.1147 +	local l = self.requestlang or self.args.lang
 15.1148 +	self.langs = { l and l:match("^%w+$") }
 15.1149 +	local s = getenv("HTTP_ACCEPT_LANGUAGE")
 15.1150 +	while s do
 15.1151 +		local l, r = s:match("^([%w.=]+)[,;](.*)$")
 15.1152 +		l = l or s
 15.1153 +		s = r
 15.1154 +		if l:match("^%w+$") then
 15.1155 +			table.insert(self.langs, l)
 15.1156 +		end
 15.1157 +	end
 15.1158 +	table.insert(self.langs, self.config.deflang)
 15.1159 +	
 15.1160 +	-- get list of possible profiles
 15.1161 +	
 15.1162 +	local profiles = { }
 15.1163 +	for e in util.readdir(self.config.contentdir) do
 15.1164 +		profiles[e] = e
 15.1165 +	end
 15.1166 +	
 15.1167 +	-- get pubprofile
 15.1168 +	
 15.1169 +	for _, lang in ipairs(self.langs) do
 15.1170 +		local p = posix.readlink(self.config.contentdir .. "/current_" .. lang)
 15.1171 +		p = p and p:match("^(%w+)_" .. lang .. "$")
 15.1172 +		if p then
 15.1173 +			self.pubprofile = p
 15.1174 +			break
 15.1175 +		end
 15.1176 +	end
 15.1177 +	
 15.1178 +	-- get profile
 15.1179 +	
 15.1180 +	local checkprofile =
 15.1181 +		self.authuser and self.args.profile or self.pubprofile or "work"
 15.1182 +	for _, lang in ipairs(self.langs) do
 15.1183 +		if profiles[checkprofile .. "_" .. lang] then
 15.1184 +			self.profile = checkprofile
 15.1185 +			self.lang = lang
 15.1186 +			break
 15.1187 +		end
 15.1188 +	end
 15.1189 +	
 15.1190 +	assert(self.profile and self.lang, "Invalid profile or language")
 15.1191 +	
 15.1192 +	
 15.1193 +	self.ispubprofile = self.profile == self.pubprofile
 15.1194 +	
 15.1195 +	-- write back language and profile
 15.1196 +	
 15.1197 +	self.args.lang = (self.explicitlang or self.lang ~= self.config.deflang)
 15.1198 +		and self.lang or nil
 15.1199 +	self.args.profile = self.profile
 15.1200 +	
 15.1201 +	-- determine content directory pathname and section filename
 15.1202 +	
 15.1203 +	self.contentdir =
 15.1204 +		("%s/%s_%s"):format(self.config.contentdir, self.profile, self.lang)
 15.1205 + 	self.indexfname = self.contentdir .. "/.sections"
 15.1206 +	
 15.1207 +	-- load sections
 15.1208 +	
 15.1209 + 	self.sections = lib.source(self.indexfname)
 15.1210 +	
 15.1211 +	-- index sections, determine visibility in menu
 15.1212 +	
 15.1213 +	self:indexsections()
 15.1214 +	
 15.1215 +	-- decompose request path, produce a stack of sections
 15.1216 +	
 15.1217 +	self.submenus, self.section = self:getsection(self.requestpath)
 15.1218 +
 15.1219 +	-- handle redirects if not logged on
 15.1220 +	
 15.1221 +	if not self.authuser and self.section and self.section.redirect then
 15.1222 +		self.submenus, self.section = self:getsection(self.section.redirect)
 15.1223 +	end
 15.1224 +			
 15.1225 +	-- section path and document name (refined)
 15.1226 +	
 15.1227 +	self.sectionpath = self:getpath()
 15.1228 +	self.sectionname = self:getpath("_")
 15.1229 +
 15.1230 +end
 15.1231 +
 15.1232 +
 15.1233 +function Loona:handlechanges()
 15.1234 +	
 15.1235 +	local save
 15.1236 +
 15.1237 +	if self.args.editkey == "main" then
 15.1238 +		
 15.1239 +		-- In main editable section:
 15.1240 +		
 15.1241 +		if self.args.actioncreate then
 15.1242 +			
 15.1243 +			-- Create new section
 15.1244 +			
 15.1245 +			local editname = self.args.editname:lower()
 15.1246 +			assert(not editname:match("%W"),
 15.1247 +				self:dbmsg("Invalid section name", editname))
 15.1248 +			if not (section and (section.subs or section)[editname]) then
 15.1249 +				local newpath = (self.sectionpath and 
 15.1250 +					(self.sectionpath .. "/")) .. editname
 15.1251 +				local s = self:addpath(newpath, { name = editname,
 15.1252 +					label = self.args.editlabel ~= "" and
 15.1253 +						self.args.editlabel or nil,
 15.1254 +					title = self.args.edittitle ~= "" and
 15.1255 +						self.args.edittitle or nil,
 15.1256 +					redirect = self.args.editredirect ~= "" and
 15.1257 +						self.args.editredirect or nil,
 15.1258 +					hidden = self.args.editvisibility and true,
 15.1259 +					secret = self.args.editsecrecy and true,
 15.1260 +					secure = self.args.editsecure and true,
 15.1261 +					creator = self.authuser,
 15.1262 +					creationdate = time() })
 15.1263 +				save = true
 15.1264 +			end
 15.1265 +		
 15.1266 +		elseif self.args.actionsave then
 15.1267 +			
 15.1268 +			-- Save section
 15.1269 +			
 15.1270 +			self.section.revisiondate = time()
 15.1271 +			self.section.revisioner = self.authuser
 15.1272 +			save = true
 15.1273 + 		
 15.1274 +		elseif self.args.actionsaveprops then
 15.1275 +			
 15.1276 +			-- Save properties
 15.1277 +			
 15.1278 +			self.section.hidden = self.args.editvisibility and true
 15.1279 +			self.section.secret = self.args.editsecrecy and true
 15.1280 +			self.section.secure = self.args.editsecure and true
 15.1281 +			self.section.label = self.args.editlabel ~= "" and
 15.1282 +				self.args.editlabel or nil
 15.1283 +			self.section.title = self.args.edittitle ~= "" and
 15.1284 +				self.args.edittitle or nil
 15.1285 +			self.section.redirect =
 15.1286 +				self.args.editredirect ~= "" and self.args.editredirect or nil
 15.1287 +			save = true
 15.1288 +		
 15.1289 +		elseif self.args.actionup then
 15.1290 +			
 15.1291 +			-- Move section up
 15.1292 +			
 15.1293 +			local t, i = self:checkpath(self.sectionpath)
 15.1294 +			if t and i > 1 then
 15.1295 +				if self.ispubprofile and not self.args.actionconfirm then
 15.1296 +					useralert = {
 15.1297 +						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
 15.1298 +						confirm =
 15.1299 +							'<input type="submit" name="actionup" value="' ..
 15.1300 +							self.locale.MOVE .. '" /> ' ..
 15.1301 +							self:hidden("actionconfirm", "true")
 15.1302 +					}
 15.1303 +				else
 15.1304 +					local item = table.remove(t, i)
 15.1305 +					table.insert(t, i - 1, item)
 15.1306 +					save = true
 15.1307 +				end
 15.1308 +			end
 15.1309 +		
 15.1310 +		elseif self.args.actiondown then
 15.1311 +			
 15.1312 +			-- Move section down
 15.1313 +			
 15.1314 +			local t, i = self:checkpath(self.sectionpath)
 15.1315 +			if t and i < #t then
 15.1316 +				if self.ispubprofile and not self.args.actionconfirm then
 15.1317 +					useralert = {
 15.1318 +						text = self.locale.ALERT_MOVE_IN_PUBLISHED_PROFILE,
 15.1319 +						confirm =
 15.1320 +							'<input type="submit" name="actiondown" value="' ..
 15.1321 +							self.locale.MOVE .. '" /> ' ..
 15.1322 +							self:hidden("actionconfirm", "true")
 15.1323 +					}
 15.1324 +				else
 15.1325 +					local item = table.remove(t, i)
 15.1326 +					table.insert(t, i + 1, item)
 15.1327 +					save = true
 15.1328 +				end
 15.1329 +			end
 15.1330 +		
 15.1331 +		elseif self.args.actioncreateprofile and self.args.createprofile then
 15.1332 +			
 15.1333 +			-- Create profile
 15.1334 +			
 15.1335 +			local c = self.args.createprofile
 15.1336 +			if c == "" then
 15.1337 +				c = self.profile
 15.1338 +			end
 15.1339 +			c = self:checkprofilename(c:lower())
 15.1340 +			local l = self:checklanguage((self.args.createlanguage or self.lang):lower())
 15.1341 +			if c == self.profile and l == self.lang then
 15.1342 +				useralert = { 
 15.1343 +					text = self.locale.ALERT_CANNOT_COPY_PROFILE_TO_SELF,
 15.1344 +					returnto = self:hidden("actioneditprofiles", "true")
 15.1345 +				}
 15.1346 +			else
 15.1347 +				local profiles = self:getprofiles(l)
 15.1348 +				local text
 15.1349 +				if c == self.pubprofile then
 15.1350 +					text = self.locale.ALERT_OVERWRITE_PUBLISHED_PROFILE
 15.1351 +				elseif profiles[c] and l == self.lang then
 15.1352 +					text = self.locale.ALERT_OVERWRITE_EXISTING_PROFILE
 15.1353 +				end
 15.1354 +				if text and not self.args.actionconfirm then
 15.1355 +					useralert = {
 15.1356 +						text = text,
 15.1357 +						returnto = self:hidden("actioneditprofiles", "true"),
 15.1358 +						confirm = '<input type="submit" ' ..
 15.1359 +							'name="actioncreateprofile" value="' ..
 15.1360 +							self.locale.OVERWRITE .. '" /> ' ..
 15.1361 +							self:hidden("actionconfirm", "true") ..
 15.1362 +							self:hidden("createlanguage", l) ..
 15.1363 +							self:hidden("createprofile", c)
 15.1364 +					}
 15.1365 +				else
 15.1366 +					if profiles[c] then
 15.1367 +						self:deleteprofile(c, l)
 15.1368 +					end
 15.1369 +					self:copyprofile(c, self.profile, l, self.lang)
 15.1370 +				end
 15.1371 +			end
 15.1372 +		
 15.1373 +		elseif self.args.actiondeleteprofile and self.args.deleteprofile then
 15.1374 +			
 15.1375 +			-- Delete profile
 15.1376 +			
 15.1377 +			local c = self:checkprofilename(self.args.deleteprofile:lower())
 15.1378 +			assert(c ~= self.pubprofile,
 15.1379 +				self:dbmsg("Cannot delete published profile", c))
 15.1380 +			if self.args.actionconfirm then
 15.1381 +				self:deleteprofile(c)
 15.1382 +				self.profile = nil
 15.1383 +				self.args.profile = nil
 15.1384 +				self:init()
 15.1385 +				save = true
 15.1386 +			else
 15.1387 +				useralert = { 
 15.1388 +					text = self.locale.ALERT_DELETE_PROFILE,
 15.1389 +					returnto = self:hidden("actioneditprofiles", "true"),
 15.1390 +					confirm = '<input type="submit" ' ..
 15.1391 +						'name="actiondeleteprofile" value="' .. 
 15.1392 +						self.locale.DELETE .. '" /> ' ..
 15.1393 +						self:hidden("actionconfirm", "true") ..
 15.1394 +						self:hidden("deleteprofile", c)
 15.1395 +				}
 15.1396 +			end
 15.1397 +		
 15.1398 +		elseif self.args.actionchangeprofile and self.args.changeprofile then
 15.1399 +			
 15.1400 +			-- Change profile
 15.1401 +			
 15.1402 +			local c = self:checkprofilename(self.args.changeprofile:lower())
 15.1403 +			self.profile = c
 15.1404 +			self.args.profile = c
 15.1405 +			save = true
 15.1406 +		
 15.1407 +		elseif self.args.actionchangelanguage and self.args.changelanguage then
 15.1408 +			
 15.1409 +			-- Change language
 15.1410 +			
 15.1411 +			local l = self:checklanguage(self.args.changelanguage:lower())
 15.1412 +			self.lang = l
 15.1413 +			self.args.lang = l
 15.1414 + 			self.explicitlang = l
 15.1415 +			save = true
 15.1416 +		
 15.1417 +		elseif self.args.actionpublishprofile and self.args.publishprofile then
 15.1418 +			
 15.1419 +			-- Publish profile
 15.1420 +			
 15.1421 +			local c = self:checkprofilename(self.args.publishprofile:lower())
 15.1422 +			if c ~= self.publicprofile then
 15.1423 +				if self.args.actionconfirm then
 15.1424 +					self:publishprofile(c)
 15.1425 +					save = true
 15.1426 +				else
 15.1427 +					useralert = {
 15.1428 +						text = self.locale.ALERT_PUBLISH_PROFILE,
 15.1429 +						returnto = self:hidden("actioneditprofiles", "true"),
 15.1430 +						confirm = '<input type="submit" ' ..
 15.1431 +							'name="actionpublishprofile" value="' ..
 15.1432 +							self.locale.PUBLISH .. '" /> ' ..
 15.1433 +							self:hidden("actionconfirm", "true") ..
 15.1434 +							self:hidden("publishprofile", c)
 15.1435 +					}
 15.1436 +				end
 15.1437 +			end
 15.1438 +		end
 15.1439 +		
 15.1440 +	end
 15.1441 +	
 15.1442 +	if self.args.actiondelete then
 15.1443 +		
 15.1444 +		-- Delete section
 15.1445 +		
 15.1446 +		if not self.args.actionconfirm then
 15.1447 +			useralert = {
 15.1448 +				text = self.ispubprofile and
 15.1449 +					self.locale.ALERT_DELETE_IN_PUBLISHED_PROFILE or
 15.1450 +					self.locale.ALERT_DELETE_SECTION,
 15.1451 +				confirm =
 15.1452 +					'<input type="submit" name="actiondelete" value="' .. 
 15.1453 +					self.locale.DELETE .. '" /> ' ..
 15.1454 +					self:hidden("actionconfirm", "true")
 15.1455 +			}
 15.1456 +		else
 15.1457 +			local key = self.args.editkey
 15.1458 +			if key == "main" and not self.section.subs then
 15.1459 +				self:deletesection(self.sectionname, true) -- all bodies
 15.1460 +				self:rmpath(self.sectionpath) -- and node
 15.1461 +			else
 15.1462 +				local ext = (key == "main" and "") or "." .. key
 15.1463 +				self:deletesection(self.sectionname .. ext) -- only text
 15.1464 +				if self.section.dynamic then
 15.1465 +					self.section.dynamic[key] = nil
 15.1466 +					local n = 0
 15.1467 +					for _ in pairs(self.section.dynamic) do
 15.1468 +						n = n + 1
 15.1469 +					end
 15.1470 +					if n == 0 then
 15.1471 +						self.section.dynamic = nil
 15.1472 +					end
 15.1473 +				end
 15.1474 +			end
 15.1475 +			save = true
 15.1476 +		end
 15.1477 +	end
 15.1478 +		
 15.1479 +	if save then
 15.1480 +		self:saveindex()
 15.1481 +		self:init()
 15.1482 +	end
 15.1483 +	
 15.1484 +end
 15.1485 +
 15.1486 +
 15.1487 +function Loona:encodeform(s)
 15.1488 +	return http.encodeform(s)
 15.1489 +end
 15.1490 +
 15.1491 +
 15.1492 +function Loona:loadhtml(src, outfunc, chunkname)
 15.1493 + 	return luahtml.load(src, outfunc, chunkname)
 15.1494 +end
 15.1495 +
 15.1496 +
 15.1497 +function Loona:domarkup(s)
 15.1498 +	return markup.load(s)
 15.1499 +end
 15.1500 +
 15.1501 +
 15.1502 +function Loona:expire(dir, pat, maxage)
 15.1503 +	return util.expire(dir, pat, maxage or self.config.sessionmaxage)
 15.1504 +end
 15.1505 +
 15.1506 +
 15.1507 +function Loona:new(o)
 15.1508 +
 15.1509 +	local parsed, msg
 15.1510 +	
 15.1511 +	o = o or { }
 15.1512 +	setmetatable(o, self)
 15.1513 +	self.__index = self
 15.1514 +-- 	o = atom.new(self, o)
 15.1515 +	
 15.1516 +	-- Buffer
 15.1517 +	
 15.1518 +	o.out = o.out or function(self, s)
 15.1519 +		self.buf:out(s)
 15.1520 +	end
 15.1521 +	o.setheader = o.setheader or function(self, s)
 15.1522 +		self.buf:setheader(s)
 15.1523 +	end
 15.1524 +	
 15.1525 +	-- Get configuration
 15.1526 +	
 15.1527 +	o.config = o.config or lib.source(o.conffile or "../etc/config.lua") or { }
 15.1528 +	o.config.defname = o.config.defname or "home"
 15.1529 +	o.config.deflang = o.config.deflang or "en"
 15.1530 +	o.config.sessionmaxage = o.config.sessionmaxage or 6000
 15.1531 +	o.config.secureport = o.config.secureport or 443
 15.1532 +	o.config.passwdfile =
 15.1533 +		posix.abspath(o.config.passwdfile or "../etc/passwd.lua")
 15.1534 +	o.config.sessiondir =
 15.1535 +		posix.abspath(o.config.sessiondir or "../var/sessions")
 15.1536 +	o.config.extdir = posix.abspath(o.config.extdir or "../extensions")
 15.1537 +	o.config.contentdir = posix.abspath(o.config.contentdir or "../content")
 15.1538 +	o.config.localedir = posix.abspath(o.config.localedir or "../locale")
 15.1539 +	o.config.htdocsdir = posix.abspath(o.config.htdocsdir or "../htdocs")
 15.1540 +	o.config.htmlcachedir = 
 15.1541 +		posix.abspath(o.config.htmlcachedir or "../var/htmlcache")
 15.1542 +	o.config.extlinkextra = o.config.extlinksamewindow and ' class="extlink"'
 15.1543 +		or ' class="extlink" onclick="void(window.open(this.href, \'\', \'\')); return false;"'
 15.1544 +	
 15.1545 +	-- Create proxy for on-demand loading of locales
 15.1546 +	
 15.1547 +	o.locale = { }
 15.1548 +	local locmt = { }
 15.1549 +	locmt.__index = function(_, key)
 15.1550 +		for _, l in ipairs(o.langs) do
 15.1551 +			locmt.__locale = lib.source(o.config.localedir .. "/" .. l)
 15.1552 +			if locmt.__locale then
 15.1553 +				break
 15.1554 +			end
 15.1555 +		end
 15.1556 +		locmt.__index = function(tab, key)
 15.1557 +			return locmt.__locale[key] or key
 15.1558 +		end
 15.1559 +		return locmt.__locale[key] or key
 15.1560 +	end
 15.1561 +	setmetatable(o.locale, locmt)
 15.1562 +	
 15.1563 +	-- Get request, args, document, script name, request path
 15.1564 +	
 15.1565 +	o.request = o.request or Request:new()
 15.1566 +	o.args = o.request:getargs()
 15.1567 +	o.cgi_document = o.request:getdocument()
 15.1568 + 	
 15.1569 + 	o.scriptpath = o.scriptpath or o.cgi_document.Path
 15.1570 +	o.requesthandler = o.requesthandler or o.cgi_document.Handler
 15.1571 + 	o.requestdocument = o.requestdocument or o.cgi_document.Name
 15.1572 +	o.requestpath = o.requestpath or o.cgi_document.VirtualPath
 15.1573 +	o.explicitlang = not o.requestlang and o.args.lang
 15.1574 +	o.secure = not o.insecure and (o.request.Port == o.config.secureport)
 15.1575 +
 15.1576 +	-- Manage login and establish session
 15.1577 +	
 15.1578 +	if not o.nologin then
 15.1579 +		local sid = o.args.session or o.request.UniqueID
 15.1580 +		o.session = o.session or Session:new {
 15.1581 +			id = sid,
 15.1582 +			sessiondir = o.config.sessiondir,
 15.1583 +			maxage = o.config.sessionmaxage
 15.1584 +		}
 15.1585 +		if o.args.login then
 15.1586 +			-- write back session ID into request args:
 15.1587 +			o.args.session = sid -- !
 15.1588 +			if o.args.login == "false" then
 15.1589 +				o.session:delete()
 15.1590 +				o.session = nil
 15.1591 +			elseif o.args.password then
 15.1592 +				o.loginfailed = true
 15.1593 +				local pwddb = lib.source(o.config.passwdfile)
 15.1594 +				local pwdentry = pwddb[o.args.login]
 15.1595 +				if pwdentry and pwdentry.password == o.args.password then
 15.1596 +					o.session.data.authuser = pwdentry.username
 15.1597 +					o.session.data.id = o.session.id
 15.1598 +					o.loginfailed = nil
 15.1599 +				end
 15.1600 +			end
 15.1601 +		end
 15.1602 +		o.authuser = o.session and o.session.data.authuser
 15.1603 +	end
 15.1604 +
 15.1605 +	if o.nologin or not o.authuser then
 15.1606 +		o.authuser = nil
 15.1607 +		o.session = nil
 15.1608 +		o.args.session = nil
 15.1609 +	end
 15.1610 +
 15.1611 +	-- Get lang, locale, profile, section
 15.1612 +
 15.1613 +	o:init()
 15.1614 +	if o.authuser then
 15.1615 +		o:handlechanges()
 15.1616 +	else
 15.1617 +		o.args.profile = nil
 15.1618 +	end
 15.1619 +	
 15.1620 +	-- Current document
 15.1621 +	
 15.1622 +	o.document = o.requestdocument .. "/" .. o.sectionpath
 15.1623 +	if o.authuser then
 15.1624 +		o.getdocname = function(self, path)
 15.1625 +			return self.requestdocument .. "/" .. (path or self.sectionpath)
 15.1626 +		end
 15.1627 +	else
 15.1628 +		o.getdocname = function(self, path, haveargs)
 15.1629 +			local dyn
 15.1630 +			dyn, path = self:isdynamic(path or self.sectionpath)
 15.1631 +			if dyn or haveargs then
 15.1632 +				return self.requestdocument .. "/" .. path
 15.1633 +			end
 15.1634 +			path = path == self.config.defname and "index" or path
 15.1635 +			return "/" .. path:gsub("/", "_") .. ".html"
 15.1636 +		end
 15.1637 +	end
 15.1638 +	
 15.1639 +	-- Save session state
 15.1640 +	
 15.1641 +	if o.session then
 15.1642 +		o.session:save()
 15.1643 +	end
 15.1644 +	
 15.1645 +	return o
 15.1646 +end
 15.1647 +
 15.1648 +
 15.1649 +function Loona:run(fname)
 15.1650 +	self:indexdynamic()
 15.1651 +	fname = fname or self.requesthandler
 15.1652 +	local parsed, msg = self:loadhtml(open(fname), "loona:out", fname)
 15.1653 +	assert(parsed, self:dbmsg("HTML/Lua parsing failed", msg))
 15.1654 +	self:runboxed(parsed)
 15.1655 +	return self
 15.1656 +end
 15.1657 +
 15.1658 +
 15.1659 +function Loona:indexdynamic()
 15.1660 +	self:recursesections(self.sections, function(self, s, e, path, dynamic)
 15.1661 +		path = path and path .. "_" .. e.name or e.name
 15.1662 +		dynamic = dynamic or { }
 15.1663 +		for k in pairs(e.dynamic or { }) do
 15.1664 +			dynamic[k] = true
 15.1665 +		end
 15.1666 +		for k in pairs(dynamic) do
 15.1667 +			local ext = (k == "main" and "") or "." .. k
 15.1668 +			if posix.stat(self.contentdir .. "/" .. path .. ext,
 15.1669 +				"mode") == "file" then
 15.1670 +				dynamic[k] = e.dynamic and e.dynamic[k]
 15.1671 +			end
 15.1672 +		end
 15.1673 +		local n = 0
 15.1674 +		for k in pairs(dynamic) do
 15.1675 +			n = n + 1
 15.1676 +		end
 15.1677 +		if n > 0 then
 15.1678 +			e.dynamic = { }
 15.1679 +			for k in pairs(dynamic) do
 15.1680 +				e.dynamic[k] = true
 15.1681 +			end
 15.1682 +		else
 15.1683 +			e.dynamic = nil
 15.1684 +		end
 15.1685 +		return path, dynamic
 15.1686 +	end)
 15.1687 +end
 15.1688 +
 15.1689 +
 15.1690 +function Loona:isdynamic(path)
 15.1691 +	path = path or self.sectionpath
 15.1692 +	local t, i = self:checkpath(path)
 15.1693 +	if t and t[i].redirect then
 15.1694 +		path = t[i].redirect
 15.1695 +		t, i = self:isdynamic(path) -- TODO: prohibit endless recursion
 15.1696 +	end
 15.1697 +	return t and t[i].dynamic, path
 15.1698 +end
 15.1699 +
 15.1700 +
 15.1701 +function Loona:dumphtml(o)
 15.1702 +	local outbuf = { }
 15.1703 +	o = o or { }
 15.1704 +	o.nologin = true
 15.1705 +	o.out = function(self, s) table.insert(outbuf, s) end
 15.1706 +	o.setheader = function(self, s) end
 15.1707 +	o = self:new(o):run()
 15.1708 +	if not o:isdynamic() then
 15.1709 +		local path = o.sectionname
 15.1710 +		path = path == o.config.defname and "index" or path
 15.1711 +		local srcname = o.config.htdocsdir .. "/" .. path .. o.htmlext
 15.1712 +		local fh, msg = open(srcname .. ".tmp", "wb")
 15.1713 +		assert(fh, self:dbmsg("Could not write cached HTML", msg))
 15.1714 +		fh:write(unpack(outbuf))
 15.1715 +		fh:close()
 15.1716 +		local dstname = o.config.htmlcachedir .. "/" .. path .. o.htmlext
 15.1717 +		local success, msg = posix.symlink(srcname, dstname .. ".tmp")
 15.1718 +-- 		assert(success, self:dbmsg("Could not link to cached HTML", msg))
 15.1719 +	end
 15.1720 +end
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/cgi-bin/tek/class/loona/buffer.lua	Sun May 20 18:28:42 2007 +0200
    16.3 @@ -0,0 +1,52 @@
    16.4 +
    16.5 +--
    16.6 +--	tek.class.loona.buffer - LOona outbuffer class
    16.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    16.8 +--	See copyright notice in COPYRIGHT
    16.9 +--
   16.10 +
   16.11 +local getfenv, setmetatable, unpack, insert, stdout =
   16.12 +	getfenv, setmetatable, unpack, table.insert, io.stdout
   16.13 +
   16.14 +
   16.15 +module "tek.class.loona.buffer"
   16.16 +
   16.17 +
   16.18 +_VERSION = 1
   16.19 +_REVISION = 0
   16.20 +
   16.21 +
   16.22 +local buffer = getfenv()
   16.23 +
   16.24 +
   16.25 +function buffer:out(s)
   16.26 +	insert(self.outbuf, s)
   16.27 +end
   16.28 +
   16.29 +
   16.30 +function buffer:setheader(s)
   16.31 +	insert(self.headerbuf, 1, s)
   16.32 +end
   16.33 +
   16.34 +
   16.35 +function buffer:discard()
   16.36 +	self.headerbuf = { }
   16.37 +	self.outbuf = { }
   16.38 +end
   16.39 +
   16.40 +
   16.41 +function buffer:flush(discard)
   16.42 +	stdout:write(unpack(self.headerbuf))
   16.43 +	stdout:write(unpack(self.outbuf))
   16.44 +	if discard ~= false then
   16.45 +		self:discard()
   16.46 +	end
   16.47 +end
   16.48 +
   16.49 +
   16.50 +function buffer:new()
   16.51 +	local o = { outbuf = { }, headerbuf = { } }
   16.52 +	setmetatable(o, self)
   16.53 +	self.__index = self
   16.54 +	return o
   16.55 +end
    17.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.2 +++ b/cgi-bin/tek/class/loona/markup.lua	Sun May 20 18:28:42 2007 +0200
    17.3 @@ -0,0 +1,682 @@
    17.4 +
    17.5 +--
    17.6 +--	tek.class.loona.markup - LOona WIKI-style markup parser
    17.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    17.8 +--	See copyright notice in COPYRIGHT
    17.9 +--
   17.10 +
   17.11 +local http = require "tek.class.http"
   17.12 +
   17.13 +local unpack, min, ipairs = unpack, math.min, ipairs
   17.14 +local remove, insert, concat, foreachi =
   17.15 +	table.remove, table.insert, table.concat, table.foreachi
   17.16 +
   17.17 +
   17.18 +module "tek.class.loona.markup"
   17.19 +
   17.20 +
   17.21 +_VERSION = 2
   17.22 +_REVISION = 0
   17.23 +
   17.24 +
   17.25 +--	iterate over lines in a string
   17.26 +
   17.27 +local function lines(s)
   17.28 +	local pos = 1
   17.29 +	return function()
   17.30 +		if pos then
   17.31 +			local a, e = s:find("[\r]?\n", pos)
   17.32 +			local o = pos
   17.33 +			if not a then
   17.34 +				pos = nil
   17.35 +				return s:sub(o)
   17.36 +			else
   17.37 +				pos = e + 1
   17.38 +				return s:sub(o, a - 1)
   17.39 +			end
   17.40 +		end
   17.41 +	end
   17.42 +end
   17.43 +
   17.44 +
   17.45 +--	get a SGML/XML tag
   17.46 +
   17.47 +local function gettagml(state, id, tag, open)
   17.48 +	if tag then
   17.49 +		local tab = { tag }
   17.50 +		if id == "link" or id == "emphasis" or id == "code" then
   17.51 +			if not state.brpend and not state.inline then
   17.52 +				insert(tab, 1, ("\t"):rep(state.depth))
   17.53 +			end
   17.54 +			state.inline = true
   17.55 +			state.brpend = nil
   17.56 +		else
   17.57 +			if id == "image" then
   17.58 +				if not state.inline then
   17.59 +					insert(tab, 1, ("\t"):rep(state.depth))
   17.60 +				end
   17.61 +				state.brpend = true
   17.62 +			else
   17.63 +				if id == "pre" or id == "preline" then
   17.64 +					state.brpend = nil
   17.65 +				elseif open or id ~= "preline" then
   17.66 +					insert(tab, 1, ("\t"):rep(state.depth))
   17.67 +					if not open and state.inline then
   17.68 +						state.brpend = true
   17.69 +					end
   17.70 +				end
   17.71 +				if state.brpend then
   17.72 +					insert(tab, 1, "\n")
   17.73 +				end
   17.74 +				if id ~= "preline" or not open then
   17.75 +					insert(tab, "\n")
   17.76 +				end
   17.77 +				state.inline = nil
   17.78 +				state.brpend = nil
   17.79 +			end
   17.80 +		end
   17.81 +		return concat(tab)
   17.82 +	end
   17.83 +end
   17.84 +
   17.85 +
   17.86 +--	get a SGML/XML line of text
   17.87 +
   17.88 +local function gettextml(state, line, id)
   17.89 +	local tab = { }
   17.90 +	if state.brpend then
   17.91 +		insert(tab, "\n")
   17.92 +		insert(tab, ("\t"):rep(state.depth))
   17.93 +	else
   17.94 +		if id ~= "link" and id ~= "emphasis" and id ~= "code" then
   17.95 +			if not state.inline then
   17.96 +				insert(tab, ("\t"):rep(state.depth))
   17.97 +			end
   17.98 +		end
   17.99 +	end
  17.100 +	line:gsub("^(%s*)(.-)(%s*)$", function(a, b, c)
  17.101 +		if a ~= "" then
  17.102 +			insert(tab, " ")
  17.103 +		end
  17.104 +		insert(tab, b)
  17.105 +		if c ~= "" then
  17.106 +			insert(tab, " ")
  17.107 +		end
  17.108 +	end)
  17.109 +	state.brpend = true
  17.110 +	return concat(tab)
  17.111 +end
  17.112 +
  17.113 +
  17.114 +--	html out
  17.115 +
  17.116 +local gen_html =
  17.117 +{
  17.118 +	out = function(state, ...)
  17.119 +		state.out(concat(arg))
  17.120 +	end,
  17.121 +
  17.122 +	init = function(state)
  17.123 +		state.depth = 1
  17.124 +		return '' -- header
  17.125 +	end,
  17.126 +
  17.127 +	exit = function(state)
  17.128 +		return '' -- footer
  17.129 +	end,
  17.130 +
  17.131 +	gettag = function(state, id, tag, open)
  17.132 +		return gettagml(state, id, tag, open)
  17.133 +	end,
  17.134 +
  17.135 +	gettext = function(state, line, id)
  17.136 +		return http.encodeform(gettextml(state, line, id))
  17.137 +	end,
  17.138 +
  17.139 +	getpre = function(state, line)
  17.140 +		return http.encodeform(line), '\n'
  17.141 +	end,
  17.142 +
  17.143 +	getcode = function(state, line)
  17.144 +		return http.encodeform(line)
  17.145 +	end,
  17.146 +
  17.147 +	head = function(state, level, text)
  17.148 +		return '<h' .. level .. '>', '</h' .. level .. '>'
  17.149 +	end,
  17.150 +
  17.151 +	argument = function(state, name, text)
  17.152 +		return ', function()%>', '<%end'
  17.153 +	end,
  17.154 +
  17.155 +	func = function(state, dynamic, args)
  17.156 +		if dynamic then
  17.157 +			state.is_dynamic_content = true
  17.158 +		end
  17.159 +		local t = { '<%loona:include("', args[1], '"' }
  17.160 +		remove(args, 1)
  17.161 +		for _, v in ipairs(args) do
  17.162 +			insert(t, ',' .. v)
  17.163 +		end
  17.164 +		return concat(t), ')%>'
  17.165 +	end,
  17.166 +
  17.167 +	indent = function()
  17.168 +		return '<div class="indent">', '</div>'
  17.169 +	end,
  17.170 +
  17.171 +	list = function()
  17.172 +		return '<ul>', '</ul>'
  17.173 +	end,
  17.174 +
  17.175 +	item = function(state, bullet)
  17.176 +		if bullet then
  17.177 +			return '<li>', '</li>'
  17.178 +		end
  17.179 +		return '<li style="list-style-type: none;">', '</li>'
  17.180 +	end,
  17.181 +
  17.182 +	block = function()
  17.183 +		return '<p>', '</p>'
  17.184 +	end,
  17.185 +
  17.186 +	rule = function()
  17.187 +		return '<hr />'
  17.188 +	end,
  17.189 +
  17.190 +	pre = function()
  17.191 +		return '<pre>', '</pre>'
  17.192 +	end,
  17.193 +
  17.194 +	code = function()
  17.195 +		return '<code>', '</code>'
  17.196 +	end,
  17.197 +
  17.198 +	emphasis = function(state, len, text)
  17.199 +		if len == 1 then
  17.200 +			return '<em>', '</em>'
  17.201 +		elseif len == 2 then
  17.202 +			return '<strong>', '</strong>'
  17.203 +		else
  17.204 +			return '<em><strong>', '</strong></em>'
  17.205 +		end
  17.206 +	end,
  17.207 +
  17.208 +	link = function(state, link, isurl)
  17.209 +		link = link:lower()
  17.210 +		if isurl then
  17.211 +			return '<%=loona:elink("' .. http.encodeurl(link, true) .. '", [[', ']])%>'
  17.212 +		else
  17.213 +			return '<%=loona:link("' .. http.encodeurl(link, true) .. '", [[', ']])%>'
  17.214 +		end
  17.215 +	end,
  17.216 +
  17.217 +	table = function(state, border)
  17.218 +		return '<table>', '</table>'
  17.219 +	end,
  17.220 +
  17.221 +	row = function(state)
  17.222 +		state.column = 0
  17.223 +		return '<tr>', '</tr>'
  17.224 +	end,
  17.225 +
  17.226 +	cell = function(state, border)
  17.227 +		state.column = state.column + 1
  17.228 +		return '<td class="column' .. state.column .. '">', '</td>'
  17.229 +	end,
  17.230 +
  17.231 +	image = function(state, link)
  17.232 +		return '<img src="' .. http.encodeurl(link, true) .. '" />'
  17.233 +	end,
  17.234 +}
  17.235 +
  17.236 +
  17.237 +--	parser
  17.238 +
  17.239 +local function parse(state, ...)
  17.240 +
  17.241 +	--	do a generator function by ID
  17.242 +	
  17.243 +	local function doid(id, ...)
  17.244 +		if state.genfunc and state.genfunc[id] then
  17.245 +			return state.genfunc[id](state, unpack(arg))
  17.246 +		end
  17.247 +	end
  17.248 +	
  17.249 +	local function doout(id, ...)
  17.250 +		doid("out", doid(id, unpack(arg)))
  17.251 +	end
  17.252 +
  17.253 +	local function push(id, ...)
  17.254 +		local opentag, closetag = doid(id, unpack(arg))
  17.255 +		doout("gettag", id, opentag, true)
  17.256 +		insert(state.stack, { id = id, closetag = closetag })
  17.257 +		state.depth = state.depth + 1
  17.258 +	end
  17.259 +
  17.260 +	local function pop()
  17.261 +		local e = remove(state.stack)
  17.262 +		if e then
  17.263 +			state.depth = state.depth - 1
  17.264 +			doout("gettag", e.id, e.closetag)
  17.265 +			return e.id
  17.266 +		end
  17.267 +	end
  17.268 +
  17.269 +	local function top()
  17.270 +		local e = state.stack[#state.stack]
  17.271 +		if e then
  17.272 +			return e.id
  17.273 +		end
  17.274 +	end
  17.275 +
  17.276 +	local function popuntil(...)
  17.277 +		local i
  17.278 +		repeat
  17.279 +			i = pop()
  17.280 +			for j = 1, arg.n do
  17.281 +				if i == arg[j] then
  17.282 +					return
  17.283 +				end
  17.284 +			end
  17.285 +		until not i
  17.286 +	end
  17.287 +
  17.288 +	local function popwhilenot(...)
  17.289 +		local i
  17.290 +		repeat
  17.291 +			i = top()
  17.292 +			for j = 1, arg.n do
  17.293 +				if i == arg[j] then
  17.294 +					return
  17.295 +				end
  17.296 +			end
  17.297 +			pop()
  17.298 +		until not i
  17.299 +	end
  17.300 +
  17.301 +	local function popwhile(...)
  17.302 +		local cont
  17.303 +		repeat
  17.304 +			local id = top()
  17.305 +			cont = false
  17.306 +			for i = 1, arg.n do
  17.307 +				if arg[i] == id then
  17.308 +					local i = pop()
  17.309 +					cont = true
  17.310 +					break
  17.311 +				end
  17.312 +			end
  17.313 +		until not cont
  17.314 +	end
  17.315 +
  17.316 +	local function popif(...)
  17.317 +		local i = top()
  17.318 +		for j = 1, arg.n do
  17.319 +			if i == arg[j] then
  17.320 +				pop()
  17.321 +				return
  17.322 +			end
  17.323 +		end
  17.324 +	end
  17.325 +
  17.326 +	local function checktop(...)
  17.327 +		local i = top()
  17.328 +		return foreachi(arg, function(_, a)
  17.329 +			if i == a then
  17.330 +				return true
  17.331 +			end
  17.332 +		end)
  17.333 +	end
  17.334 +
  17.335 +	local function pushfragment(...)
  17.336 +		line = concat(arg)
  17.337 +		if not state.context.fragments then
  17.338 +			state.context.fragments = { }
  17.339 +		end
  17.340 +		state.id = (state.id or 0) + 1
  17.341 +		insert(state.context.fragments, 1, { line = line, id = state.id })
  17.342 +		state.context.topid = state.id
  17.343 +	end
  17.344 +
  17.345 +	local function popfragment()
  17.346 +		state.context.firstfragment = nil
  17.347 +		local frag = remove(state.context.fragments, 1)
  17.348 +		if frag then
  17.349 +			state.line = frag.line
  17.350 +			if frag.id == state.context.topid then
  17.351 +				state.context.firstfragment = state.context.parentfirstfragment
  17.352 +			end
  17.353 +		else
  17.354 +			state.line = nil
  17.355 +		end
  17.356 +		return state.line
  17.357 +	end
  17.358 +
  17.359 +	local function pushcontext(id, line)
  17.360 +		insert(state.cstack, state.context)
  17.361 +		if id then
  17.362 +			state.context = { id = id, fragments = { } }
  17.363 +			pushfragment(line)
  17.364 +			insert(state.cstack, state.context)
  17.365 +		end
  17.366 +	end
  17.367 +
  17.368 +	local function popcontext()
  17.369 +		state.context = remove(state.cstack)
  17.370 +		return state.context
  17.371 +	end
  17.372 +
  17.373 +	-- parse
  17.374 +
  17.375 +	state.lnr = 1
  17.376 +	state.previndent = 0
  17.377 +	state.stack = { }
  17.378 +	state.depth = 0
  17.379 +	state.table = nil
  17.380 +
  17.381 +	doout("init")
  17.382 +	push("document")
  17.383 +
  17.384 +	for line in lines(state.inbuf) do
  17.385 +
  17.386 +		state.indent = 0
  17.387 +		line = line:gsub("^( *)(.-)%s*$", function(s, t)
  17.388 +			if t ~= "" then
  17.389 +				state.indent = s:len()
  17.390 +			else
  17.391 +				state.indent = state.previndent
  17.392 +			end
  17.393 +			return t
  17.394 +		end)
  17.395 +
  17.396 +		state.istabline = line:find("||", 1, 1)
  17.397 +		if state.istabline then
  17.398 +			state.indent = state.previndent
  17.399 +		end
  17.400 +		if state.table then
  17.401 +			popuntil("row")
  17.402 +			if not state.istabline then
  17.403 +				popuntil("table")
  17.404 +				state.table = nil
  17.405 +			end
  17.406 +		end
  17.407 +		
  17.408 +		
  17.409 +		if state.indent < state.previndent then
  17.410 +			if not state.preindent or state.indent < state.preindent then
  17.411 +				local i = state.indent
  17.412 +				while i < state.previndent do
  17.413 +					popuntil("indent", "pre")
  17.414 +					i = i + 1
  17.415 +				end
  17.416 +				state.preindent = nil
  17.417 +			end
  17.418 +		elseif state.indent == state.previndent + 1 then
  17.419 +			popif("block")
  17.420 +			push("indent")
  17.421 +		elseif state.indent >= state.previndent + 2 then
  17.422 +			if not state.preindent then
  17.423 +				state.preindent = state.previndent + 2
  17.424 +				push("indent")
  17.425 +				push("pre")
  17.426 +			end
  17.427 +		end
  17.428 +
  17.429 +
  17.430 +		if not state.preindent then
  17.431 +
  17.432 +			-- function ( INCLUDE(name) )
  17.433 +
  17.434 +			line = line:gsub("^%s*(INCLUDE_?%a*)%(%s*(.*)%s*%)%s*$", function(key, line)
  17.435 +				if key == "INCLUDE" or key == "INCLUDE_STATIC" then
  17.436 +					local args = { }
  17.437 +					line:gsub(",?([^,]*)", function(a)
  17.438 +						a = a:match("^%s*(%S.-)%s*$")
  17.439 +						if a then
  17.440 +							insert(args, a)
  17.441 +						end
  17.442 +					end)
  17.443 +					popwhilenot("document")
  17.444 +					push("func", key == "INCLUDE", args)
  17.445 +					return ""
  17.446 +				end
  17.447 +			end)
  17.448 +			
  17.449 +			-- argument ( ======== )
  17.450 +
  17.451 +			line = line:gsub("^%s*========+%s*$", function()
  17.452 +				popwhilenot("func")
  17.453 +				push("argument")
  17.454 +				return ""
  17.455 +			end)
  17.456 +
  17.457 +			-- rule ( ----.. )
  17.458 +
  17.459 +			line = line:gsub("^%s*(%-%-%-%-+)%s*$", function(s)
  17.460 +				popwhile("block", "list", "item")
  17.461 +				push("rule")
  17.462 +				pop()
  17.463 +				return ""
  17.464 +			end)
  17.465 +
  17.466 +		end
  17.467 +
  17.468 +		--
  17.469 +
  17.470 +		state.cstack = { }
  17.471 +		state.context = { parentfirstfragment = true }
  17.472 +		pushfragment(line)
  17.473 +		pushcontext()
  17.474 +
  17.475 +		if line == "" then
  17.476 +			popwhile("block")
  17.477 +		end
  17.478 +
  17.479 +		while popcontext() do
  17.480 +			while popfragment() do
  17.481 +				line = state.line
  17.482 +
  17.483 +				if state.preindent then
  17.484 +
  17.485 +					if state.prepend then
  17.486 +						push("preline")
  17.487 +						doout("getpre", "")
  17.488 +						pop()
  17.489 +						state.prepend = nil
  17.490 +					end
  17.491 +
  17.492 +					if line == "" then
  17.493 +						state.prepend = true
  17.494 +					else
  17.495 +						push("preline")
  17.496 +						doout("getpre", (" "):rep(state.indent - 
  17.497 +							state.preindent) .. line)
  17.498 +						pop()
  17.499 +					end
  17.500 +
  17.501 +				else
  17.502 +					state.prepend = nil
  17.503 +
  17.504 +					if line ~= "" then
  17.505 +
  17.506 +						-- list/item ( * ... )
  17.507 +
  17.508 +						local _, _, b, a = line:find("^([%*%-])%s+(.*)$")
  17.509 +						if b and a then
  17.510 +							local inlist = checktop("item", "list")
  17.511 +							popwhile("item", "block")
  17.512 +							if not inlist then
  17.513 +								push("list")
  17.514 +							end
  17.515 +							if b == "*" then
  17.516 +								push("item", true)
  17.517 +							else
  17.518 +								push("item")
  17.519 +							end
  17.520 +							pushcontext("item", a)
  17.521 +							break
  17.522 +						end
  17.523 +
  17.524 +						-- table cells
  17.525 +
  17.526 +						local _, pos, a, b =
  17.527 +							line:find("^%s*(.-)%s*||%s*(.*)%s*$")
  17.528 +						if pos then
  17.529 +							if state.context.id == "table" then
  17.530 +								popuntil("cell")
  17.531 +							else
  17.532 +								state.context.id = "table"
  17.533 +								if not state.table then
  17.534 +									state.table = true
  17.535 +									push("table")
  17.536 +								end
  17.537 +								push("row")
  17.538 +							end
  17.539 +							pushfragment(b)
  17.540 +							push("cell")
  17.541 +							pushcontext("cell", a)
  17.542 +							break
  17.543 +						elseif state.context.id == "table" then
  17.544 +							popuntil("cell")
  17.545 +							push("cell")
  17.546 +							pushcontext("cell", line)
  17.547 +							break
  17.548 +						end
  17.549 +
  17.550 +						-- code
  17.551 +
  17.552 +						local _, _, a, text, b =
  17.553 +							line:find("^(.-){{(.-)}}(.*)%s*$")
  17.554 +						if text then
  17.555 +							if a == "" then
  17.556 +								if not checktop("block", "item", "cell") then
  17.557 +									push("block")
  17.558 +								end
  17.559 +								if state.context.firstfragment then
  17.560 +									doout("gettext", "")
  17.561 +								end
  17.562 +								push("code")
  17.563 +								if b == "" then b = " " end
  17.564 +								pushfragment(b)
  17.565 +								pushcontext("code", text)
  17.566 +							else
  17.567 +								pushfragment("{{", text, "}}", b)
  17.568 +								pushfragment(a)
  17.569 +								pushcontext()
  17.570 +							end
  17.571 +							break
  17.572 +						end
  17.573 +
  17.574 +						-- emphasis
  17.575 +
  17.576 +						local _, _, a, x, text, y, b =
  17.577 +							line:find("^(.-)(''+)(.-)(''+)(.*)$")
  17.578 +						if text then
  17.579 +							if a == "" then
  17.580 +								x, y = x:len(), y:len()
  17.581 +								local len = min(x, y, 4)
  17.582 +								if not checktop("block", "item", "cell") then
  17.583 +									push("block")
  17.584 +								end
  17.585 +								if state.context.firstfragment then
  17.586 +									doout("gettext", "")
  17.587 +								end
  17.588 +								push("emphasis", len - 1)
  17.589 +								if b == "" then b = " " end
  17.590 +								pushfragment(b)
  17.591 +								pushcontext("emphasis", text)
  17.592 +							else
  17.593 +								pushfragment(x, text, y, b)
  17.594 +								pushfragment(a)
  17.595 +								pushcontext()
  17.596 +							end
  17.597 +							break
  17.598 +						end
  17.599 +
  17.600 +						-- [[link]], [[title][link]]
  17.601 +
  17.602 +						if state.context.id ~= "link"
  17.603 +							and state.context.id ~= "code" then
  17.604 +							local a, title, link, b
  17.605 +							a, title, link, b = -- [[text][...]]
  17.606 +								line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
  17.607 +							if not link then -- [[../..]]
  17.608 +								a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
  17.609 +							end
  17.610 +							if link then
  17.611 +								local isurl = link:match("^%a*:/?/?.*$")
  17.612 +								if a == "" then
  17.613 +									if not checktop("block", "item", "cell") then
  17.614 +										push("block")
  17.615 +									end
  17.616 +									if state.context.firstfragment then
  17.617 +										doout("gettext", "")
  17.618 +									end
  17.619 +									push("link", link, isurl)
  17.620 +									if b == "" then b = " " end
  17.621 +									pushfragment(b)
  17.622 +									pushcontext("link", title or link)
  17.623 +								else
  17.624 +									pushfragment("[[", title or link, "][", link, "]]", b)
  17.625 +									pushfragment(a)
  17.626 +									pushcontext()
  17.627 +								end
  17.628 +								break
  17.629 +							end
  17.630 +						end
  17.631 +
  17.632 +						-- imglink (@@/images/partner/aladdin.gif@@)
  17.633 +
  17.634 +						line = line:gsub("@@(.-)@@", function(link)
  17.635 +							push("image", link)
  17.636 +							pop()
  17.637 +							return ""
  17.638 +						end)
  17.639 +
  17.640 +						-- head ( = ... = )
  17.641 +
  17.642 +						line = line:gsub("(=+)%s+(.*)%s+(=+)",
  17.643 +							function(s1, text, s2)
  17.644 +							local l = min(s1:len(), s2:len(), 5)
  17.645 +							popwhile("block", "item", "list")
  17.646 +							push("head", l)
  17.647 +							return text
  17.648 +						end)
  17.649 +
  17.650 +						-- output
  17.651 +
  17.652 +						if line ~= "" then
  17.653 +							if not checktop("item", "block", "cell", "pre",
  17.654 +								"head", "emphasis", "link", "code") then
  17.655 +								popwhile("item", "list", "pre", "code")
  17.656 +								push("block")
  17.657 +							end
  17.658 +							if top() == "code" then
  17.659 +								doout("getcode", line, top())
  17.660 +							else
  17.661 +								doout("gettext", line, top())
  17.662 +							end
  17.663 +						end
  17.664 +						popif("emphasis", "head", "link", "code")
  17.665 +					end
  17.666 +				end
  17.667 +			end
  17.668 +		end
  17.669 +
  17.670 +		state.previndent = state.indent
  17.671 +		state.lnr = state.lnr + 1
  17.672 +	end
  17.673 +
  17.674 +	popuntil()
  17.675 +	doout("exit")
  17.676 +end
  17.677 +
  17.678 +
  17.679 +function load(s)
  17.680 +	local t = { }
  17.681 +	local state = { inbuf = s, genfunc = gen_html,
  17.682 +		out = function(s) insert(t, s) end }
  17.683 +	parse(state)
  17.684 + 	return concat(t), state.is_dynamic_content
  17.685 +end
    18.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.2 +++ b/cgi-bin/tek/class/loona/util.lua	Sun May 20 18:28:42 2007 +0200
    18.3 @@ -0,0 +1,59 @@
    18.4 +
    18.5 +--
    18.6 +--	tek.class.loona.util - LOona class utilities
    18.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    18.8 +--	See copyright notice in COPYRIGHT
    18.9 +--
   18.10 +
   18.11 +local posix = require "tek.os.posix"
   18.12 +
   18.13 +local assert, time, remove = assert, os.time, os.remove
   18.14 +
   18.15 +
   18.16 +module "tek.class.loona.util"
   18.17 +
   18.18 +
   18.19 +_VERSION = 3
   18.20 +_REVISION = 0
   18.21 +
   18.22 +
   18.23 +--	Directory iterator
   18.24 +
   18.25 +function readdir(path)
   18.26 +	assert(path)
   18.27 +	local d, msg = posix.opendir(path)
   18.28 +	assert(d, msg and "Cannot open directory " .. path .. " : " .. msg)
   18.29 +	return function()
   18.30 +		local e
   18.31 +		repeat
   18.32 +			e = d:read()
   18.33 +		until e ~= "." and e ~= ".."
   18.34 +		return e
   18.35 +	end
   18.36 +end
   18.37 +
   18.38 +
   18.39 +--	Remove directory entries matching a pattern
   18.40 +--	whose atime is older than maxtime seconds
   18.41 +
   18.42 +function expire(dir, pat, maxtime)
   18.43 +	assert(dir, "No directory")
   18.44 +	assert(pat, "No pattern")
   18.45 +	assert(maxtime, "No maxtime")
   18.46 +	if posix.stat(dir, "mode") == "directory" then
   18.47 +		local nowtime = time()
   18.48 +		for e in readdir(dir) do
   18.49 +			if not pat or e:match(pat) then
   18.50 +				local fname = dir .. "/" .. e
   18.51 +				-- assuming here that if an entry is a dangling softlink
   18.52 +				-- (in which case stat() returns nil), it can be deleted anyway
   18.53 +				local atime = posix.stat(fname, "access")
   18.54 +				if not atime or (nowtime - atime > maxtime) then
   18.55 + 					local res, msg = remove(fname)
   18.56 + 					assert(res, msg and "Failed to delete item : " .. msg)
   18.57 +				end
   18.58 + 			end
   18.59 + 		end
   18.60 + 		return true
   18.61 +	end
   18.62 +end
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/cgi-bin/tek/lib.lua	Sun May 20 18:28:42 2007 +0200
    19.3 @@ -0,0 +1,303 @@
    19.4 +
    19.5 +--
    19.6 +--	tek.lib - TEK base library
    19.7 +--	Written by Timm S. Mueller <tmueller at neoscientists.org>
    19.8 +--	See copyright notice in COPYRIGHT
    19.9 +--
   19.10 +
   19.11 +local type, unpack, table, pairs, ipairs = type, unpack, table, pairs, ipairs
   19.12 +local error, xpcall, traceback = error, xpcall, debug.traceback
   19.13 +local open, floor, char = io.open, math.floor, string.char
   19.14 +local loadstring, tostring = loadstring, tostring
   19.15 +
   19.16 +
   19.17 +module "tek.lib"
   19.18 +
   19.19 +
   19.20 +_VERSION = 1
   19.21 +_REVISION = 0
   19.22 +
   19.23 +
   19.24 +--	Catch errors when executing a function; returns an error code (0 okay,
   19.25 +--	<0 execution error, >0 user error), followed by a string representing
   19.26 +--	the error, followed by the regular results from the function
   19.27 +
   19.28 +
   19.29 +--	local error handler preserves traceback + errmessage:
   19.30 +
   19.31 +local function errhandler(obj)
   19.32 +	if type(obj) == "table" then
   19.33 +		obj.trace = obj.trace or traceback("", 2)
   19.34 +	else
   19.35 +		obj = { text = obj, trace = traceback("", 2) }
   19.36 +	end
   19.37 +	return obj
   19.38 +end
   19.39 +
   19.40 +
   19.41 +function catch(func, ...)
   19.42 +
   19.43 +	local function callargs()
   19.44 +		func(arg) -- closure with arguments to catch
   19.45 +	end
   19.46 +
   19.47 +	local res = { xpcall(callargs, errhandler) }
   19.48 +
   19.49 +	if table.remove(res, 1) then
   19.50 +		return 0, "ok", unpack(res)
   19.51 +	end
   19.52 +
   19.53 +	-- custom error
   19.54 +	return res[1].code, res[1].text, res[1].detail, res[1].trace
   19.55 +end
   19.56 +
   19.57 +
   19.58 +--	Throw error
   19.59 +
   19.60 +function throw(code, text, detail, trace)
   19.61 +	error { code = code, text = text, detail = detail,
   19.62 +		trace = trace or traceback("", 2) }
   19.63 +end
   19.64 +
   19.65 +
   19.66 +--	Source a file into a table (in empty environment, by default)
   19.67 +
   19.68 +function source(filename, env)
   19.69 +	local f, msg = open(filename)
   19.70 +	if f then
   19.71 +		local chunk = f:read("*a")
   19.72 +		f:close()
   19.73 +		if chunk then
   19.74 +			chunk, msg = loadstring("do return {\n" .. chunk .. "\n} end")
   19.75 +			if chunk then
   19.76 +				if env then
   19.77 +					setfenv(chunk, env)
   19.78 +				end
   19.79 +				return chunk()
   19.80 +			end
   19.81 +		end
   19.82 +	end
   19.83 +	return nil, msg
   19.84 +end
   19.85 +
   19.86 +
   19.87 +--	Recursive serialize - 
   19.88 +--	note that cyclic dependencies are silently dropped
   19.89 +
   19.90 +local function serialize(tab, sorted, indent, outfunc, saved)
   19.91 +	saved[tab] = tab
   19.92 +	local is = ("\t"):rep(indent)
   19.93 +	local set = { }
   19.94 +	for key, val in pairs(tab) do
   19.95 +		local t = type(val)
   19.96 +		if t ~= "table" or not saved[val] then
   19.97 +			table.insert(set, {
   19.98 +				cmp = tostring(key):lower(), key = key, val = val })
   19.99 +		end
  19.100 +	end
  19.101 +	if sorted then
  19.102 +		table.sort(set, function(a, b)
  19.103 +			if type(a.key) == "number" then
  19.104 +				if type(b.key) == "number" then
  19.105 +					return a.key < b.key
  19.106 +				end
  19.107 +				return true
  19.108 +			end
  19.109 +			if type(b.key) == "number" then
  19.110 +				return
  19.111 +			end
  19.112 +			return a.cmp < b.cmp
  19.113 +		end)
  19.114 +	end
  19.115 +	for _, e in ipairs(set) do
  19.116 +		local key, val = e.key, e.val
  19.117 +		local t = type(val)
  19.118 +		
  19.119 +		if not saved[val] then
  19.120 +			outfunc(is)
  19.121 +			if type(key) == "number" then
  19.122 +				outfunc('[' .. key .. '] = ')
  19.123 +			else
  19.124 +				if key:match("[^%a_]") then
  19.125 +					outfunc('["' .. key .. '"] = ')
  19.126 +				else
  19.127 +					outfunc(key .. ' = ')
  19.128 +				end
  19.129 +			end
  19.130 +			if t == "table" then
  19.131 +				outfunc('{\n')
  19.132 +				serialize(val, sorted, indent + 1, outfunc, saved)
  19.133 +				outfunc(is .. '},\n')
  19.134 +			elseif t == "string" then
  19.135 +				outfunc('"' .. val:gsub("([%z\001-\031])", function(c)
  19.136 +					return ("\\%03d"):format(c:byte())
  19.137 +				end) .. '",\n')
  19.138 +			elseif t == "number" or t == "boolean" then
  19.139 +				outfunc(tostring(val) .. ',\n')
  19.140 +			else
  19.141 +				outfunc('"' .. tostring(val) .. '",\n')
  19.142 +			end
  19.143 +		end
  19.144 +	end
  19.145 +end
  19.146 +
  19.147 +
  19.148 +--	Dump table via outfunc
  19.149 +
  19.150 +function dump(tab, outfunc)
  19.151 +	return serialize(tab, true, 0, outfunc, { })
  19.152 +end
  19.153 +
  19.154 +
  19.155 +--	Encode to UTF-8
  19.156 +
  19.157 +function encodeutf8(c)
  19.158 +	if c < 128 then
  19.159 +		return char(c)
  19.160 +	elseif c < 2048 then
  19.161 +		return char(192 + floor(c / 64), 
  19.162 +			128 + c % 64)
  19.163 +	elseif c < 65536 then
  19.164 +		return char(224 + floor(c / 4096), 
  19.165 +			128 + floor((c % 4096) / 64), 
  19.166 +			128 + c % 64)
  19.167 +	elseif c < 2097152 then
  19.168 +		return char(240 + floor(c / 262144), 
  19.169 +			128 + floor((c % 262144) / 4096), 
  19.170 +			128 + floor((c % 4096) / 64), 
  19.171 +			128 + c % 64)
  19.172 +	elseif c < 67108864 then
  19.173 +		return char(248 + floor(c / 16777216), 
  19.174 +			128 + floor((c % 16777216) / 262144), 
  19.175 +			128 + floor((c % 262144) / 4096),
  19.176 +			128 + floor((c % 4096) / 64),
  19.177 +			128 + c % 64)
  19.178 +	else
  19.179 +		return char(252 + floor(c / 1073741824), 
  19.180 +			128 + floor((c % 1073741824) / 16777216), 
  19.181 +			128 + floor((c % 16777216) / 262144), 
  19.182 +			128 + floor((c % 262144) / 4096), 
  19.183 +			128 + floor((c % 4096) / 64), 
  19.184 +			128 + c % 64)
  19.185 +	end
  19.186 +end
  19.187 +
  19.188 +
  19.189 +--	Iterate over UTF-8 character values in a string or file
  19.190 +
  19.191 +function utf8values(s)
  19.192 +	
  19.193 +	local readc
  19.194 +	local i = 0
  19.195 +	if type(s) == "string" then
  19.196 +		readc = function()
  19.197 +			i = i + 1
  19.198 +			return s:byte(i)
  19.199 +		end
  19.200 +	else
  19.201 +		readc = function()
  19.202 +			local c = s:read(1)
  19.203 +			return c and c:byte(1)
  19.204 +		end
  19.205 +	end
  19.206 +	
  19.207 +	local accu = 0
  19.208 +	local numa = 0
  19.209 +	local min, buf
  19.210 +	
  19.211 +	return function()
  19.212 +		local c
  19.213 +		while true do
  19.214 +			if buf then
  19.215 +				c = buf
  19.216 +				buf = nil
  19.217 +			else
  19.218 +				c = readc()
  19.219 +			end
  19.220 +			if not c then
  19.221 +				return
  19.222 +			end
  19.223 +			if c == 254 or c == 255 then
  19.224 +				break
  19.225 +			end
  19.226 +			if c < 128 then
  19.227 +				if numa > 0 then
  19.228 +					buf = c
  19.229 +					break
  19.230 +				end
  19.231 +				return c
  19.232 +			elseif c < 192 then
  19.233 +				if numa == 0 then break end
  19.234 +				accu = accu * 64 + c - 128
  19.235 +				numa = numa - 1
  19.236 +				if numa == 0 then
  19.237 +					if accu == 0 or accu < min or 
  19.238 +						(accu >= 55296 and accu <= 57343) then
  19.239 +						break
  19.240 +					end
  19.241 +					c = accu
  19.242 +					accu = 0
  19.243 +					return c
  19.244 +				end
  19.245 +			else
  19.246 +				if numa > 0 then
  19.247 +					buf = c
  19.248 +					break
  19.249 +				end
  19.250 +				if c < 224 then
  19.251 +					min = 128
  19.252 +					accu = c - 192
  19.253 +					numa = 1
  19.254 +				elseif c < 240 then
  19.255 +					min = 2048
  19.256 +					accu = c - 224
  19.257 +					numa = 2
  19.258 +				elseif c < 248 then
  19.259 +					min = 65536
  19.260 +					accu = c - 240
  19.261 +					numa = 3
  19.262 +				elseif c < 252 then
  19.263 +					min = 2097152
  19.264 +					accu = c - 248
  19.265 +					numa = 4
  19.266 +				else
  19.267 +					min = 67108864
  19.268 +					accu = c - 252
  19.269 +					numa = 5
  19.270 +				end
  19.271 +			end
  19.272 +		end
  19.273 +		accu = 0
  19.274 +		numa = 0
  19.275 +		return 65533 -- bad character
  19.276 +	end
  19.277 +end
  19.278 +
  19.279 +
  19.280 +--	Copy file - arguments may be filenames or open filehandles
  19.281 +
  19.282 +function copyfile(s, d, bufsize)
  19.283 +	bufsize = bufsize or 4096
  19.284 +	local success, msg
  19.285 +	if s and type(s) == "string" then
  19.286 +		s, msg = open(s, "rb")
  19.287 +	end
  19.288 +	if s then
  19.289 +		if d and type(d) == "string" then
  19.290 +			d, msg = open(d, "wb")
  19.291 +		end
  19.292 +		if d then
  19.293 +			while true do
  19.294 +				local b = s:read(bufsize)
  19.295 +				if not b then
  19.296 +					break
  19.297 +				end
  19.298 +				d:write(b)
  19.299 +			end
  19.300 +			success = true
  19.301 +			d:close()
  19.302 +		end
  19.303 +		s:close()
  19.304 +	end
  19.305 +	return success, msg
  19.306 +end
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/cgi-bin/tek/lib/luahtml.c	Sun May 20 18:28:42 2007 +0200
    20.3 @@ -0,0 +1,385 @@
    20.4 +
    20.5 +#include <ctype.h>
    20.6 +#include <stdlib.h>
    20.7 +#include <string.h>
    20.8 +#include <lua.h>
    20.9 +#include <lualib.h>
   20.10 +#include <lauxlib.h>
   20.11 +
   20.12 +
   20.13 +#define topfile(L)	((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE))
   20.14 +
   20.15 +
   20.16 +static FILE *tofile (lua_State *L) {
   20.17 +  FILE **f = topfile(L);
   20.18 +  if (*f == NULL)
   20.19 +    luaL_error(L, "attempt to use a closed file");
   20.20 +  return *f;
   20.21 +}
   20.22 +
   20.23 +
   20.24 +static unsigned char *encodeutf8(unsigned char *buf, int c)
   20.25 +{
   20.26 +	if (c < 128)
   20.27 +	{
   20.28 +		*buf++ = c;
   20.29 +	}
   20.30 +	else if (c < 2048)
   20.31 +	{
   20.32 +		*buf++ = 0xc0 + (c >> 6);
   20.33 +		*buf++ = 0x80 + (c & 0x3f);
   20.34 +	}
   20.35 +	else if (c < 65536)
   20.36 +	{
   20.37 +		*buf++ = 0xe0 + (c >> 12);
   20.38 +		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   20.39 +		*buf++ = 0x80 + (c & 0x3f);
   20.40 +	}
   20.41 +	else if (c < 2097152)
   20.42 +	{
   20.43 +		*buf++ = 0xf0 + (c >> 18);
   20.44 +		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   20.45 +		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   20.46 +		*buf++ = 0x80 + (c & 0x3f);
   20.47 +	}
   20.48 +	else if (c < 67108864)
   20.49 +	{
   20.50 +		*buf++ = 0xf8 + (c >> 24);
   20.51 +		*buf++ = 0x80 + ((c & 0xffffff) >> 18);
   20.52 +		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   20.53 +		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   20.54 +		*buf++ = 0x80 + (c & 0x3f);
   20.55 +	}
   20.56 +	else
   20.57 +	{
   20.58 +		*buf++ = 0xfc + (c >> 30);
   20.59 +		*buf++ = 0x80 + ((c & 0x3fffffff) >> 24);
   20.60 +		*buf++ = 0x80 + ((c & 0xffffff) >> 18);
   20.61 +		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   20.62 +		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   20.63 +		*buf++ = 0x80 + (c & 0x3f);
   20.64 +	}
   20.65 +	return buf;
   20.66 +}
   20.67 +
   20.68 +
   20.69 +struct utf8reader
   20.70 +{
   20.71 +	int (*readchar)(struct utf8reader *);
   20.72 +	int accu, numa, min, bufc;
   20.73 +	const unsigned char *src;
   20.74 +	size_t srclen;
   20.75 +	FILE *file;
   20.76 +	void *udata;
   20.77 +};
   20.78 +
   20.79 +static int readstring(struct utf8reader *rd)
   20.80 +{
   20.81 +	if (rd->srclen == 0)
   20.82 +		return -1;
   20.83 +	rd->srclen--;
   20.84 +	return *rd->src++;
   20.85 +}
   20.86 +
   20.87 +static int readfile(struct utf8reader *rd)
   20.88 +{
   20.89 +	return fgetc(rd->file);
   20.90 +}
   20.91 +
   20.92 +static int readutf8(struct utf8reader *rd)
   20.93 +{
   20.94 +	int c;
   20.95 +	for (;;)
   20.96 +	{
   20.97 +		if (rd->bufc >= 0)
   20.98 +		{
   20.99 +			c = rd->bufc;
  20.100 +			rd->bufc = -1;
  20.101 +		}
  20.102 +		else
  20.103 +			c = rd->readchar(rd);
  20.104 +		
  20.105 +		if (c < 0)
  20.106 +			return c;
  20.107 +
  20.108 +		if (c == 254 || c == 255)
  20.109 +			break;
  20.110 +		
  20.111 +		if (c < 128)
  20.112 +		{
  20.113 +			if (rd->numa > 0)
  20.114 +			{
  20.115 +				rd->bufc = c;
  20.116 +				break;
  20.117 +			}
  20.118 +			return c;
  20.119 +		}
  20.120 +		else if (c < 192)
  20.121 +		{
  20.122 +			if (rd->numa == 0)
  20.123 +				break;
  20.124 +			rd->accu <<= 6;
  20.125 +			rd->accu += c - 128;
  20.126 +			rd->numa--;
  20.127 +			if (rd->numa == 0)
  20.128 +			{
  20.129 +				if (rd->accu == 0 || rd->accu < rd->min ||
  20.130 +					(rd->accu >= 55296 && rd->accu <= 57343))
  20.131 +					break;
  20.132 +				c = rd->accu;
  20.133 +				rd->accu = 0;
  20.134 +				return c;
  20.135 +			}
  20.136 +		}
  20.137 +		else
  20.138 +		{
  20.139 +			if (rd->numa > 0)
  20.140 +			{
  20.141 +				rd->bufc = c;
  20.142 +				break;
  20.143 +			}
  20.144 +			
  20.145 +			if (c < 224)
  20.146 +			{
  20.147 +				rd->min = 128;
  20.148 +				rd->accu = c - 192;
  20.149 +				rd->numa = 1;
  20.150 +			}
  20.151 +			else if (c < 240)
  20.152 +			{
  20.153 +				rd->min = 2048;
  20.154 +				rd->accu = c - 224;
  20.155 +				rd->numa = 2;
  20.156 +			}
  20.157 +			else if (c < 248)
  20.158 +			{
  20.159 +				rd->min = 65536;
  20.160 +				rd->accu = c - 240;
  20.161 +				rd->numa = 3;
  20.162 +			}
  20.163 +			else if (c < 252)
  20.164 +			{
  20.165 +				rd->min = 2097152;
  20.166 +				rd->accu = c - 248;
  20.167 +				rd->numa = 4;
  20.168 +			}
  20.169 +			else
  20.170 +			{
  20.171 +				rd->min = 67108864;
  20.172 +				rd->accu = c - 252;
  20.173 +				rd->numa = 5;
  20.174 +			}
  20.175 +		}
  20.176 +	}
  20.177 +	/* bad char */
  20.178 +	rd->accu = 0;
  20.179 +	rd->numa = 0;
  20.180 +	return 65533;
  20.181 +}
  20.182 +
  20.183 +
  20.184 +typedef enum { PARSER_UNDEF = -1, PARSER_HTML, PARSER_OPEN1, PARSER_OPEN2,
  20.185 +PARSER_CODE, PARSER_VAR, PARSER_CLOSE } parser_state_t;
  20.186 +
  20.187 +
  20.188 +static unsigned char *outchar(lua_State *L, unsigned char *buf, parser_state_t state, int c)
  20.189 +{
  20.190 +	if (state == PARSER_HTML)
  20.191 +	{
  20.192 +		if (c > 127 || c == '[' || c == ']')
  20.193 +			return buf + sprintf((char *) buf, "&#%02d;", c);
  20.194 +	}
  20.195 +	else if (state == PARSER_CODE)
  20.196 +	{
  20.197 +		if (c > 127)
  20.198 +			return encodeutf8(buf, c);
  20.199 +	}
  20.200 +	else if (c > 127)
  20.201 +		luaL_error(L, "Non-ASCII character outside code or HTML context");
  20.202 +	
  20.203 +	*buf++ = c;
  20.204 +	return buf;
  20.205 +}
  20.206 +
  20.207 +
  20.208 +struct readdata
  20.209 +{
  20.210 +	/* buffer including " " .. outfunc .. "(": */
  20.211 +	unsigned char buf0[256];
  20.212 +	/* pointer into buf0 past outfunc: */
  20.213 +	unsigned char *buf;
  20.214 +	/* html+lua parser state: */
  20.215 +	parser_state_t state;
  20.216 +	/* utf8 reader state: */
  20.217 +	struct utf8reader utf8;
  20.218 +};
  20.219 +
  20.220 +
  20.221 +static const char *readparsed(lua_State *L, void *udata, size_t *sz)
  20.222 +{
  20.223 +	struct readdata *rd = udata;
  20.224 +	parser_state_t news = rd->state;
  20.225 +	int c;
  20.226 +	
  20.227 +	while ((c = readutf8(&rd->utf8)) >= 0)
  20.228 +	{
  20.229 +		switch (news)
  20.230 +		{
  20.231 +			case PARSER_UNDEF:
  20.232 +				if (c == '<')
  20.233 +				{
  20.234 +					news = PARSER_OPEN1;
  20.235 +					continue;
  20.236 +				}
  20.237 +				rd->state = PARSER_HTML;
  20.238 +				rd->buf[0] = '[';
  20.239 +				rd->buf[1] = '[';
  20.240 +				*sz = outchar(L, rd->buf + 2, rd->state, c) - rd->buf0;
  20.241 +				return (char *) rd->buf0;
  20.242 +			
  20.243 +			case PARSER_HTML:
  20.244 +				if (c == '<')
  20.245 +				{
  20.246 +					news = PARSER_OPEN1;
  20.247 +					continue;
  20.248 +				}
  20.249 +				break;
  20.250 +			
  20.251 +			case PARSER_OPEN1:
  20.252 +				if (c == '%')
  20.253 +				{
  20.254 +					news = PARSER_OPEN2;
  20.255 +					continue;
  20.256 +				}
  20.257 +				rd->buf[0] = '<';
  20.258 +				rd->buf[1] = c;
  20.259 +				*sz = 2;
  20.260 +				return (char *) rd->buf;
  20.261 +
  20.262 +			case PARSER_OPEN2:
  20.263 +				if (c == '=')
  20.264 +				{
  20.265 +					if (rd->state == PARSER_UNDEF)
  20.266 +					{
  20.267 +						rd->state = PARSER_VAR;
  20.268 +						*sz = rd->buf - rd->buf0;
  20.269 +						return (char *) rd->buf0;
  20.270 +					}
  20.271 +					rd->state = PARSER_VAR;
  20.272 +					strcpy((char *) rd->buf, "]]..(");
  20.273 +					*sz = 5;
  20.274 +					return (char *) rd->buf;
  20.275 +				}
  20.276 +				
  20.277 +				if (rd->state == PARSER_UNDEF)
  20.278 +					rd->state = PARSER_CODE;
  20.279 +				else
  20.280 +				{
  20.281 +					rd->state = PARSER_CODE;
  20.282 +					rd->buf[0] = ']';
  20.283 +					rd->buf[1] = ']';
  20.284 +					rd->buf[2] = ')';
  20.285 +					rd->buf[3] = ' ';
  20.286 +					rd->buf[4] = c;
  20.287 +					*sz = 5;
  20.288 +					return (char *) rd->buf;
  20.289 +				}
  20.290 +				break;
  20.291 +			
  20.292 +			case PARSER_VAR:
  20.293 +			case PARSER_CODE:
  20.294 +				if (c == '%')
  20.295 +				{
  20.296 +					news = PARSER_CLOSE;
  20.297 +					continue;
  20.298 +				}
  20.299 +				break;
  20.300 +			
  20.301 +			case PARSER_CLOSE:
  20.302 +				if (c == '>')
  20.303 +				{
  20.304 +					if (rd->state == PARSER_CODE)
  20.305 +					{
  20.306 +						rd->state = PARSER_HTML;
  20.307 +						rd->buf[0] = '[';
  20.308 +						rd->buf[1] = '[';
  20.309 +						*sz = rd->buf + 2 - rd->buf0;
  20.310 +						return (char *) rd->buf0;
  20.311 +					}
  20.312 +					rd->state = PARSER_HTML;
  20.313 +					strcpy((char *) rd->buf, " or \"nil\")..[[");
  20.314 +					*sz = 14;
  20.315 +					return (char *) rd->buf;
  20.316 +				}
  20.317 +				rd->buf[0] = '%';
  20.318 +				rd->buf[1] = c;
  20.319 +				*sz = 2;
  20.320 +				return (char *) rd->buf;
  20.321 +		}
  20.322 +		
  20.323 +		*sz = outchar(L, rd->buf, rd->state, c) - rd->buf;
  20.324 +		return (char *) rd->buf;
  20.325 +	}
  20.326 +			
  20.327 +	rd->state = PARSER_UNDEF;
  20.328 +	if (news == PARSER_HTML)
  20.329 +	{
  20.330 +		*sz = 4;
  20.331 +		return "]]) ";
  20.332 +	}
  20.333 +	
  20.334 +	return NULL;
  20.335 +}
  20.336 +
  20.337 +
  20.338 +static int load(lua_State *L)
  20.339 +{
  20.340 +	struct readdata rd;
  20.341 +	const char *outfunc = lua_tostring(L, 2);
  20.342 +	const char *chunkname = lua_tostring(L, 3);
  20.343 +	int res;
  20.344 +	
  20.345 +	if (lua_isuserdata(L, 1))
  20.346 +	{
  20.347 +		rd.utf8.file = tofile(L);
  20.348 +		rd.utf8.readchar = readfile;
  20.349 +	}
  20.350 +	else
  20.351 +	{
  20.352 +		rd.utf8.src = (unsigned char *) lua_tolstring(L, 1, &rd.utf8.srclen);
  20.353 +		rd.utf8.readchar = readstring;
  20.354 +	}
  20.355 +	
  20.356 +	rd.utf8.accu = 0;
  20.357 +	rd.utf8.numa = 0;
  20.358 +	rd.utf8.bufc = -1;
  20.359 +	
  20.360 +	rd.state = PARSER_UNDEF;
  20.361 +	strcpy((char *) rd.buf0, " ");
  20.362 +	strcat((char *) rd.buf0, outfunc);
  20.363 +	strcat((char *) rd.buf0, "(");
  20.364 +	rd.buf = rd.buf0 + strlen((char *) rd.buf0);
  20.365 +	
  20.366 +	res = lua_load(L, readparsed, &rd, chunkname);
  20.367 +	if (res == 0)
  20.368 +		return 1;
  20.369 +	
  20.370 +	lua_pushnil(L);
  20.371 +	lua_insert(L, -2);
  20.372 +	/* nil, message on stack */
  20.373 +	return 2;
  20.374 +}
  20.375 +
  20.376 +
  20.377 +static const luaL_Reg lib[] =
  20.378 +{
  20.379 +	{ "load", load },
  20.380 +	{ NULL, NULL }
  20.381 +};
  20.382 +
  20.383 +
  20.384 +int luaopen_tek_lib_luahtml(lua_State *L)
  20.385 +{
  20.386 +	luaL_register(L, "tek.lib.luahtml", lib);
  20.387 +	return 0;
  20.388 +}
    21.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.2 +++ b/cgi-bin/tek/os/posix.c	Sun May 20 18:28:42 2007 +0200
    21.3 @@ -0,0 +1,435 @@
    21.4 +
    21.5 +#include <stdlib.h>
    21.6 +#include <string.h>
    21.7 +#include <sys/types.h>
    21.8 +#include <sys/stat.h>
    21.9 +#include <unistd.h>
   21.10 +#include <dirent.h>
   21.11 +#include <errno.h>
   21.12 +#include <math.h>
   21.13 +#include <time.h>
   21.14 +#include <lua.h>
   21.15 +#include <lualib.h>
   21.16 +#include <lauxlib.h>
   21.17 +
   21.18 +
   21.19 +#define DIRCLASSNAME "dir*"
   21.20 +
   21.21 +
   21.22 +static void
   21.23 +setfield(lua_State *L, const char *attr, const char *key, lua_Integer n)
   21.24 +{
   21.25 +	if (attr == NULL || strcmp(attr, key) == 0)
   21.26 +		lua_pushinteger(L, n);
   21.27 +	if (attr == NULL)
   21.28 +		lua_setfield(L, -2, key);
   21.29 +}
   21.30 +
   21.31 +
   21.32 +static int
   21.33 +setstat(lua_State *L, int result, struct stat *s, const char *attr)
   21.34 +{
   21.35 +	if (result != 0)
   21.36 +	{
   21.37 +		lua_pushnil(L);
   21.38 +		lua_pushstring(L, strerror(errno));
   21.39 +		return 2;
   21.40 +	}
   21.41 +
   21.42 +	if (attr == NULL)
   21.43 +		lua_newtable(L);
   21.44 +		
   21.45 +	setfield(L, attr, "dev", s->st_dev);
   21.46 +	setfield(L, attr, "ino", s->st_ino);
   21.47 +	
   21.48 +	if (attr == NULL || strcmp(attr, "mode") == 0)
   21.49 +	{
   21.50 +		if (S_ISREG(s->st_mode))
   21.51 +			lua_pushstring(L, "file");
   21.52 +		else if (S_ISDIR(s->st_mode))
   21.53 +			lua_pushstring(L, "directory");
   21.54 +		else if (S_ISLNK(s->st_mode))
   21.55 +			lua_pushstring(L, "link");
   21.56 +		else if (S_ISSOCK(s->st_mode))
   21.57 +			lua_pushstring(L, "socket");
   21.58 +		else if (S_ISFIFO(s->st_mode))
   21.59 +			lua_pushstring(L, "named pipe");
   21.60 +		else if (S_ISCHR(s->st_mode))
   21.61 +			lua_pushstring(L, "char device");
   21.62 +		else if (S_ISBLK(s->st_mode))
   21.63 +			lua_pushstring(L, "block device");
   21.64 +		else
   21.65 +			lua_pushstring(L, "other");
   21.66 +		if (attr == NULL)
   21.67 +			lua_setfield(L, -2, "mode");
   21.68 +	}
   21.69 +	
   21.70 +	setfield(L, attr, "nlink", s->st_nlink);
   21.71 +	setfield(L, attr, "uid", s->st_uid);
   21.72 +	setfield(L, attr, "gid", s->st_gid);
   21.73 +	setfield(L, attr, "rdev", s->st_rdev);
   21.74 +	setfield(L, attr, "access", s->st_atime);
   21.75 +	setfield(L, attr, "modifications", s->st_mtime);
   21.76 +	setfield(L, attr, "change", s->st_ctime);
   21.77 +	setfield(L, attr, "size", s->st_size);
   21.78 +	setfield(L, attr, "blocks", s->st_blocks);
   21.79 +	setfield(L, attr, "blksize", s->st_blksize);
   21.80 +	
   21.81 +	return 1;
   21.82 +}
   21.83 +
   21.84 +
   21.85 +static int 
   21.86 +posix_stat(lua_State *L)
   21.87 +{
   21.88 +	const char *path = luaL_checkstring(L, 1);
   21.89 +	const char *attr = luaL_optstring(L, 2, NULL);
   21.90 +	struct stat s;
   21.91 +	return setstat(L, stat(path, &s), &s, attr);
   21.92 +}	
   21.93 +
   21.94 +
   21.95 +static int 
   21.96 +posix_lstat(lua_State *L)
   21.97 +{
   21.98 +	const char *path = luaL_checkstring(L, 1);
   21.99 +	const char *attr = luaL_optstring(L, 2, NULL);
  21.100 +	struct stat s;
  21.101 +	return setstat(L, lstat(path, &s), &s, attr);
  21.102 +}
  21.103 +
  21.104 +
  21.105 +static int 
  21.106 +posix_opendir(lua_State *L)
  21.107 +{
  21.108 +	const char *path = luaL_checkstring(L, 1);
  21.109 +	DIR **pdir = lua_newuserdata(L, sizeof(void *));
  21.110 +	*pdir = NULL;
  21.111 +	lua_pushvalue(L, lua_upvalueindex(2)); /* class metatable */
  21.112 +	/* attach metatable to the userdata object */
  21.113 +	lua_setmetatable(L, -2); /* s: udata */
  21.114 +	/* create class instance */
  21.115 +	*pdir = opendir(path);
  21.116 +	if (*pdir == NULL)
  21.117 +	{
  21.118 +		lua_pushnil(L);
  21.119 +		lua_pushstring(L, strerror(errno));
  21.120 +		return 2;
  21.121 +	}
  21.122 +	return 1;
  21.123 +}
  21.124 +
  21.125 +
  21.126 +static DIR **
  21.127 +getinstptr(lua_State *L, int narg, const char *classname)
  21.128 +{
  21.129 +	DIR **pinst = luaL_checkudata(L, narg, classname);
  21.130 +	if (*pinst) return pinst;
  21.131 +	luaL_argerror(L, narg, "Closed handle");
  21.132 +	return NULL;
  21.133 +}
  21.134 +
  21.135 +
  21.136 +static int 
  21.137 +posix_closedir(lua_State *L)
  21.138 +{
  21.139 +	DIR **pdir = getinstptr(L, 1, DIRCLASSNAME);
  21.140 +	closedir(*pdir);
  21.141 +	*pdir = NULL;
  21.142 +	return 0;
  21.143 +}
  21.144 +
  21.145 +
  21.146 +static int 
  21.147 +posix_readdir(lua_State *L)
  21.148 +{
  21.149 +	DIR *dir = *getinstptr(L, 1, DIRCLASSNAME);
  21.150 +	struct dirent *de = readdir(dir);
  21.151 +	if (de)
  21.152 +		lua_pushstring(L, de->d_name);
  21.153 +	else
  21.154 +		lua_pushnil(L);
  21.155 +	return 1;
  21.156 +}
  21.157 +
  21.158 +
  21.159 +static int 
  21.160 +posix_readlink(lua_State *L)
  21.161 +{
  21.162 +	const char *path = luaL_checkstring(L, 1);
  21.163 +	char buf[PATH_MAX + 1];
  21.164 +	ssize_t len = readlink(path, buf, sizeof(buf) - 1);
  21.165 +	if (len < 0)
  21.166 +	{
  21.167 +		lua_pushnil(L);
  21.168 +		lua_pushstring(L, strerror(errno));
  21.169 +		return 2;
  21.170 +	}
  21.171 +	buf[len] = 0;
  21.172 +	lua_pushstring(L, buf);
  21.173 +	return 1;
  21.174 +}
  21.175 +
  21.176 +
  21.177 +static int 
  21.178 +posix_mkdir(lua_State *L)
  21.179 +{
  21.180 +	const char *path = luaL_checkstring(L, 1);
  21.181 +	if (mkdir(path, 0775) == 0)
  21.182 +	{
  21.183 +		lua_pushboolean(L, 1);
  21.184 +		return 1;
  21.185 +	}
  21.186 +	lua_pushnil(L);
  21.187 +	lua_pushstring(L, strerror(errno));
  21.188 +	return 2;
  21.189 +}
  21.190 +
  21.191 +
  21.192 +static int 
  21.193 +posix_symlink(lua_State *L)
  21.194 +{
  21.195 +	const char *oldpath = luaL_checkstring(L, 1);
  21.196 +	const char *newpath = luaL_checkstring(L, 2);
  21.197 +	if (symlink(oldpath, newpath) == 0)
  21.198 +	{
  21.199 +		lua_pushboolean(L, 1);
  21.200 +		return 1;
  21.201 +	}
  21.202 +	lua_pushnil(L);
  21.203 +	lua_pushstring(L, strerror(errno));
  21.204 +	return 2;
  21.205 +}
  21.206 +
  21.207 +
  21.208 +static int
  21.209 +resolvepath(const char *src, char *dest)
  21.210 +{
  21.211 +	int len = strlen(src);
  21.212 +	const char *sp = src + len;
  21.213 +	char *dp = dest;
  21.214 +	int dc = 0, slc = 0, eac = 0, wc = 0;
  21.215 +	int i, c;
  21.216 +	
  21.217 +	while (len--)	
  21.218 +	{
  21.219 +		c = *(--sp);
  21.220 +		switch (c)
  21.221 +		{
  21.222 +			case '/':
  21.223 +				if (dc == 2)
  21.224 +					eac++;
  21.225 +				dc = 0;
  21.226 +				slc = 1;
  21.227 +				wc = 0;
  21.228 +				break;
  21.229 +
  21.230 +			case '.':
  21.231 +				if (slc)
  21.232 +				{
  21.233 +					dc++;
  21.234 +					break;
  21.235 +				}
  21.236 +				/* fallthru: */
  21.237 +
  21.238 +			default:
  21.239 +				if (wc)
  21.240 +					break;
  21.241 +			
  21.242 +				if (slc)
  21.243 +				{
  21.244 +					slc = 0;
  21.245 +					
  21.246 +					if (eac > 0)
  21.247 +					{
  21.248 +						/* resolve one eatcount */
  21.249 +						eac--;
  21.250 +						/* now wait for next path part */
  21.251 +						wc = 1;
  21.252 +						break;
  21.253 +					}
  21.254 +					
  21.255 +					*dp++ = '/';
  21.256 +				}
  21.257 +				
  21.258 +				while (dc == 2 || dc == 1)
  21.259 +				{
  21.260 +					*dp++ = '.';
  21.261 +					dc--;
  21.262 +				}
  21.263 +				dc = 0;
  21.264 +				
  21.265 +				*dp++ = c;
  21.266 +				break;
  21.267 +		}
  21.268 +	}
  21.269 +	
  21.270 +	/* unresolved eatcount */
  21.271 +	if (eac)
  21.272 +		return 0;
  21.273 +	
  21.274 +	/* resolve remaining slash */
  21.275 +	if (slc)
  21.276 +		*dp++ = '/';
  21.277 +	
  21.278 +	*dp = 0;
  21.279 +	
  21.280 +	len = dp - dest;
  21.281 +	for (i = 0; i < len / 2; ++i)
  21.282 +	{
  21.283 +		char t = dest[i];
  21.284 +		dest[i] = dest[len - i - 1]; 
  21.285 +		dest[len - i - 1] = t;
  21.286 +	}
  21.287 +
  21.288 +	return 1;
  21.289 +}
  21.290 +
  21.291 +
  21.292 +static int 
  21.293 +posix_abspath(lua_State *L)
  21.294 +{
  21.295 +	const char *path = luaL_checkstring(L, 1);
  21.296 +	char *pwd = NULL;
  21.297 +	size_t pwdsize = 16;
  21.298 +	
  21.299 +	for (;;)
  21.300 +	{
  21.301 +		char *newpwd = realloc(pwd, pwdsize);
  21.302 +		if (newpwd == NULL)
  21.303 +			break;
  21.304 +		
  21.305 +		pwd = newpwd;
  21.306 +		if (getcwd(pwd, pwdsize))
  21.307 +		{
  21.308 +			size_t len1 = strlen(pwd);
  21.309 +			size_t len2 = strlen(path);
  21.310 +			char *srcpath = malloc(len1 + 1 + len2 + 1);
  21.311 +			char *dstpath = malloc(len1 + 1 + len2 + 1);
  21.312 +			if (srcpath && dstpath)
  21.313 +			{
  21.314 +				int res;
  21.315 +				
  21.316 +				strcpy(srcpath, pwd);
  21.317 +				free(pwd);
  21.318 +				srcpath[len1] = '/';
  21.319 +				strcpy(srcpath + len1 + 1, path);
  21.320 +				res = resolvepath(srcpath, dstpath);
  21.321 +				free(srcpath);
  21.322 +				
  21.323 +				if (res)
  21.324 +				{
  21.325 +					lua_pushstring(L, dstpath);
  21.326 +					free(dstpath);
  21.327 +					return 1;			
  21.328 +				}
  21.329 +				
  21.330 +				free(dstpath);
  21.331 +				lua_pushnil(L);
  21.332 +				lua_pushstring(L, "Not a valid path");
  21.333 +				return 2;
  21.334 +			}
  21.335 +			
  21.336 +			free(srcpath);
  21.337 +			free(dstpath);
  21.338 +			break;
  21.339 +		}
  21.340 +		
  21.341 +		if (errno == ERANGE)
  21.342 +		{
  21.343 +			pwdsize <<= 1;
  21.344 +			continue;
  21.345 +		}
  21.346 +		
  21.347 +		lua_pushnil(L);
  21.348 +		lua_pushstring(L, strerror(errno));
  21.349 +		return 2;
  21.350 +	}
  21.351 +	
  21.352 +	free(pwd);
  21.353 +	luaL_error(L, "Out of memory");
  21.354 +	return 0;
  21.355 +}
  21.356 +
  21.357 +
  21.358 +static int 
  21.359 +posix_nanosleep(lua_State *L)
  21.360 +{
  21.361 +	struct timespec req;
  21.362 +	lua_Number d = luaL_checknumber(L, 1);
  21.363 +	
  21.364 +	req.tv_sec = (time_t) d;
  21.365 +	req.tv_nsec = (long) ((d - req.tv_sec) * 1000000000);
  21.366 +	
  21.367 +	if (nanosleep(&req, NULL) == 0)
  21.368 +	{
  21.369 +		lua_pushboolean(L, 1);
  21.370 +		return 1;
  21.371 +	}
  21.372 +	
  21.373 +	lua_pushnil(L);
  21.374 +	lua_pushstring(L, strerror(errno));
  21.375 +	return 2;
  21.376 +}
  21.377 +
  21.378 +
  21.379 +static const luaL_Reg lib[] =
  21.380 +{
  21.381 +	{ "stat", posix_stat },
  21.382 +	{ "lstat", posix_lstat },
  21.383 +	{ "opendir", posix_opendir },
  21.384 +	{ "closedir", posix_closedir },
  21.385 +	{ "readdir", posix_readdir },
  21.386 +	{ "mkdir", posix_mkdir },
  21.387 +	{ "readlink", posix_readlink },
  21.388 +	{ "symlink", posix_symlink },
  21.389 +	{ "abspath", posix_abspath },
  21.390 +	{ "nanosleep", posix_nanosleep },
  21.391 +	{ NULL, NULL }
  21.392 +};
  21.393 +
  21.394 +
  21.395 +static const luaL_Reg methods[] =
  21.396 +{
  21.397 +	{"read", posix_readdir },
  21.398 +	{"close", posix_closedir },
  21.399 +	{"__gc", posix_closedir },
  21.400 +	{NULL, NULL}
  21.401 +};
  21.402 +
  21.403 +
  21.404 +static void
  21.405 +addclass(lua_State *L, const char *libname, const char *classname,
  21.406 +	luaL_Reg *functions, luaL_Reg *methods, void *userdata)
  21.407 +{
  21.408 +	luaL_newmetatable(L, classname); /* classtab */
  21.409 +	lua_pushliteral(L, "__index"); /* classtab, "__index" */
  21.410 +
  21.411 +	/* insert self: classtab.__index = classtab */
  21.412 +	lua_pushvalue(L, -2); /* classtab, "__index", classtab */
  21.413 +	lua_rawset(L, -3); /* classtab */
  21.414 +
  21.415 +	/* insert methods. consume 1 userdata. do not create a new tab */
  21.416 +	lua_pushlightuserdata(L, userdata); /* classtab, userdata */
  21.417 +	luaL_openlib(L, NULL, methods, 1); /* classtab */
  21.418 +
  21.419 +	/* first upvalue: userdata */
  21.420 +	lua_pushlightuserdata(L, userdata); /* classtab, userdata */
  21.421 +
  21.422 +	/* duplicate table argument to be used as second upvalue for cclosure */
  21.423 +	lua_pushvalue(L, -2); /* classtab, userdata, classtab */
  21.424 +
  21.425 +	/* insert functions */
  21.426 +	luaL_openlib(L, libname, functions, 2);	/* classtab, libtab */
  21.427 +
  21.428 +	/* adjust stack */
  21.429 +	lua_pop(L, 2);	
  21.430 +}
  21.431 +
  21.432 +
  21.433 +int luaopen_tek_os_posix(lua_State *L)
  21.434 +{
  21.435 +	addclass(L, "tek.os.posix", DIRCLASSNAME,
  21.436 +		(luaL_Reg *) lib, (luaL_Reg *) methods, NULL);
  21.437 +	return 0;
  21.438 +}
    22.1 --- a/cgi-bin/tek/posix.c	Sun May 20 18:22:42 2007 +0200
    22.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.3 @@ -1,435 +0,0 @@
    22.4 -
    22.5 -#include <stdlib.h>
    22.6 -#include <string.h>
    22.7 -#include <sys/types.h>
    22.8 -#include <sys/stat.h>
    22.9 -#include <unistd.h>
   22.10 -#include <dirent.h>
   22.11 -#include <errno.h>
   22.12 -#include <math.h>
   22.13 -#include <time.h>
   22.14 -#include <lua.h>
   22.15 -#include <lualib.h>
   22.16 -#include <lauxlib.h>
   22.17 -
   22.18 -
   22.19 -#define DIRCLASSNAME "dir*"
   22.20 -
   22.21 -
   22.22 -static void
   22.23 -setfield(lua_State *L, const char *attr, const char *key, lua_Integer n)
   22.24 -{
   22.25 -	if (attr == NULL || strcmp(attr, key) == 0)
   22.26 -		lua_pushinteger(L, n);
   22.27 -	if (attr == NULL)
   22.28 -		lua_setfield(L, -2, key);
   22.29 -}
   22.30 -
   22.31 -
   22.32 -static int
   22.33 -setstat(lua_State *L, int result, struct stat *s, const char *attr)
   22.34 -{
   22.35 -	if (result != 0)
   22.36 -	{
   22.37 -		lua_pushnil(L);
   22.38 -		lua_pushstring(L, strerror(errno));
   22.39 -		return 2;
   22.40 -	}
   22.41 -
   22.42 -	if (attr == NULL)
   22.43 -		lua_newtable(L);
   22.44 -		
   22.45 -	setfield(L, attr, "dev", s->st_dev);
   22.46 -	setfield(L, attr, "ino", s->st_ino);
   22.47 -	
   22.48 -	if (attr == NULL || strcmp(attr, "mode") == 0)
   22.49 -	{
   22.50 -		if (S_ISREG(s->st_mode))
   22.51 -			lua_pushstring(L, "file");
   22.52 -		else if (S_ISDIR(s->st_mode))
   22.53 -			lua_pushstring(L, "directory");
   22.54 -		else if (S_ISLNK(s->st_mode))
   22.55 -			lua_pushstring(L, "link");
   22.56 -		else if (S_ISSOCK(s->st_mode))
   22.57 -			lua_pushstring(L, "socket");
   22.58 -		else if (S_ISFIFO(s->st_mode))
   22.59 -			lua_pushstring(L, "named pipe");
   22.60 -		else if (S_ISCHR(s->st_mode))
   22.61 -			lua_pushstring(L, "char device");
   22.62 -		else if (S_ISBLK(s->st_mode))
   22.63 -			lua_pushstring(L, "block device");
   22.64 -		else
   22.65 -			lua_pushstring(L, "other");
   22.66 -		if (attr == NULL)
   22.67 -			lua_setfield(L, -2, "mode");
   22.68 -	}
   22.69 -	
   22.70 -	setfield(L, attr, "nlink", s->st_nlink);
   22.71 -	setfield(L, attr, "uid", s->st_uid);
   22.72 -	setfield(L, attr, "gid", s->st_gid);
   22.73 -	setfield(L, attr, "rdev", s->st_rdev);
   22.74 -	setfield(L, attr, "access", s->st_atime);
   22.75 -	setfield(L, attr, "modifications", s->st_mtime);
   22.76 -	setfield(L, attr, "change", s->st_ctime);
   22.77 -	setfield(L, attr, "size", s->st_size);
   22.78 -	setfield(L, attr, "blocks", s->st_blocks);
   22.79 -	setfield(L, attr, "blksize", s->st_blksize);
   22.80 -	
   22.81 -	return 1;
   22.82 -}
   22.83 -
   22.84 -
   22.85 -static int 
   22.86 -posix_stat(lua_State *L)
   22.87 -{
   22.88 -	const char *path = luaL_checkstring(L, 1);
   22.89 -	const char *attr = luaL_optstring(L, 2, NULL);
   22.90 -	struct stat s;
   22.91 -	return setstat(L, stat(path, &s), &s, attr);
   22.92 -}	
   22.93 -
   22.94 -
   22.95 -static int 
   22.96 -posix_lstat(lua_State *L)
   22.97 -{
   22.98 -	const char *path = luaL_checkstring(L, 1);
   22.99 -	const char *attr = luaL_optstring(L, 2, NULL);
  22.100 -	struct stat s;
  22.101 -	return setstat(L, lstat(path, &s), &s, attr);
  22.102 -}
  22.103 -
  22.104 -
  22.105 -static int 
  22.106 -posix_opendir(lua_State *L)
  22.107 -{
  22.108 -	const char *path = luaL_checkstring(L, 1);
  22.109 -	DIR **pdir = lua_newuserdata(L, sizeof(void *));
  22.110 -	*pdir = NULL;
  22.111 -	lua_pushvalue(L, lua_upvalueindex(2)); /* class metatable */
  22.112 -	/* attach metatable to the userdata object */
  22.113 -	lua_setmetatable(L, -2); /* s: udata */
  22.114 -	/* create class instance */
  22.115 -	*pdir = opendir(path);
  22.116 -	if (*pdir == NULL)
  22.117 -	{
  22.118 -		lua_pushnil(L);
  22.119 -		lua_pushstring(L, strerror(errno));
  22.120 -		return 2;
  22.121 -	}
  22.122 -	return 1;
  22.123 -}
  22.124 -
  22.125 -
  22.126 -static DIR **
  22.127 -getinstptr(lua_State *L, int narg, const char *classname)
  22.128 -{
  22.129 -	DIR **pinst = luaL_checkudata(L, narg, classname);
  22.130 -	if (*pinst) return pinst;
  22.131 -	luaL_argerror(L, narg, "Closed handle");
  22.132 -	return NULL;
  22.133 -}
  22.134 -
  22.135 -
  22.136 -static int 
  22.137 -posix_closedir(lua_State *L)
  22.138 -{
  22.139 -	DIR **pdir = getinstptr(L, 1, DIRCLASSNAME);
  22.140 -	closedir(*pdir);
  22.141 -	*pdir = NULL;
  22.142 -	return 0;
  22.143 -}
  22.144 -
  22.145 -
  22.146 -static int 
  22.147 -posix_readdir(lua_State *L)
  22.148 -{
  22.149 -	DIR *dir = *getinstptr(L, 1, DIRCLASSNAME);
  22.150 -	struct dirent *de = readdir(dir);
  22.151 -	if (de)
  22.152 -		lua_pushstring(L, de->d_name);
  22.153 -	else
  22.154 -		lua_pushnil(L);
  22.155 -	return 1;
  22.156 -}
  22.157 -
  22.158 -
  22.159 -static int 
  22.160 -posix_readlink(lua_State *L)
  22.161 -{
  22.162 -	const char *path = luaL_checkstring(L, 1);
  22.163 -	char buf[PATH_MAX + 1];
  22.164 -	ssize_t len = readlink(path, buf, sizeof(buf) - 1);
  22.165 -	if (len < 0)
  22.166 -	{
  22.167 -		lua_pushnil(L);
  22.168 -		lua_pushstring(L, strerror(errno));
  22.169 -		return 2;
  22.170 -	}
  22.171 -	buf[len] = 0;
  22.172 -	lua_pushstring(L, buf);
  22.173 -	return 1;
  22.174 -}
  22.175 -
  22.176 -
  22.177 -static int 
  22.178 -posix_mkdir(lua_State *L)
  22.179 -{
  22.180 -	const char *path = luaL_checkstring(L, 1);
  22.181 -	if (mkdir(path, 0775) == 0)
  22.182 -	{
  22.183 -		lua_pushboolean(L, 1);
  22.184 -		return 1;
  22.185 -	}
  22.186 -	lua_pushnil(L);
  22.187 -	lua_pushstring(L, strerror(errno));
  22.188 -	return 2;
  22.189 -}
  22.190 -
  22.191 -
  22.192 -static int 
  22.193 -posix_symlink(lua_State *L)
  22.194 -{
  22.195 -	const char *oldpath = luaL_checkstring(L, 1);
  22.196 -	const char *newpath = luaL_checkstring(L, 2);
  22.197 -	if (symlink(oldpath, newpath) == 0)
  22.198 -	{
  22.199 -		lua_pushboolean(L, 1);
  22.200 -		return 1;
  22.201 -	}
  22.202 -	lua_pushnil(L);
  22.203 -	lua_pushstring(L, strerror(errno));
  22.204 -	return 2;
  22.205 -}
  22.206 -
  22.207 -
  22.208 -static int
  22.209 -resolvepath(const char *src, char *dest)
  22.210 -{
  22.211 -	int len = strlen(src);
  22.212 -	const char *sp = src + len;
  22.213 -	char *dp = dest;
  22.214 -	int dc = 0, slc = 0, eac = 0, wc = 0;
  22.215 -	int i, c;
  22.216 -	
  22.217 -	while (len--)	
  22.218 -	{
  22.219 -		c = *(--sp);
  22.220 -		switch (c)
  22.221 -		{
  22.222 -			case '/':
  22.223 -				if (dc == 2)
  22.224 -					eac++;
  22.225 -				dc = 0;
  22.226 -				slc = 1;
  22.227 -				wc = 0;
  22.228 -				break;
  22.229 -
  22.230 -			case '.':
  22.231 -				if (slc)
  22.232 -				{
  22.233 -					dc++;
  22.234 -					break;
  22.235 -				}
  22.236 -				/* fallthru: */
  22.237 -
  22.238 -			default:
  22.239 -				if (wc)
  22.240 -					break;
  22.241 -			
  22.242 -				if (slc)
  22.243 -				{
  22.244 -					slc = 0;
  22.245 -					
  22.246 -					if (eac > 0)
  22.247 -					{
  22.248 -						/* resolve one eatcount */
  22.249 -						eac--;
  22.250 -						/* now wait for next path part */
  22.251 -						wc = 1;
  22.252 -						break;
  22.253 -					}
  22.254 -					
  22.255 -					*dp++ = '/';
  22.256 -				}
  22.257 -				
  22.258 -				while (dc == 2 || dc == 1)
  22.259 -				{
  22.260 -					*dp++ = '.';
  22.261 -					dc--;
  22.262 -				}
  22.263 -				dc = 0;
  22.264 -				
  22.265 -				*dp++ = c;
  22.266 -				break;
  22.267 -		}
  22.268 -	}
  22.269 -	
  22.270 -	/* unresolved eatcount */
  22.271 -	if (eac)
  22.272 -		return 0;
  22.273 -	
  22.274 -	/* resolve remaining slash */
  22.275 -	if (slc)
  22.276 -		*dp++ = '/';
  22.277 -	
  22.278 -	*dp = 0;
  22.279 -	
  22.280 -	len = dp - dest;
  22.281 -	for (i = 0; i < len / 2; ++i)
  22.282 -	{
  22.283 -		char t = dest[i];
  22.284 -		dest[i] = dest[len - i - 1]; 
  22.285 -		dest[len - i - 1] = t;
  22.286 -	}
  22.287 -
  22.288 -	return 1;
  22.289 -}
  22.290 -
  22.291 -
  22.292 -static int 
  22.293 -posix_abspath(lua_State *L)
  22.294 -{
  22.295 -	const char *path = luaL_checkstring(L, 1);
  22.296 -	char *pwd = NULL;
  22.297 -	size_t pwdsize = 16;
  22.298 -	
  22.299 -	for (;;)
  22.300 -	{
  22.301 -		char *newpwd = realloc(pwd, pwdsize);
  22.302 -		if (newpwd == NULL)
  22.303 -			break;
  22.304 -		
  22.305 -		pwd = newpwd;
  22.306 -		if (getcwd(pwd, pwdsize))
  22.307 -		{
  22.308 -			size_t len1 = strlen(pwd);
  22.309 -			size_t len2 = strlen(path);
  22.310 -			char *srcpath = malloc(len1 + 1 + len2 + 1);
  22.311 -			char *dstpath = malloc(len1 + 1 + len2 + 1);
  22.312 -			if (srcpath && dstpath)
  22.313 -			{
  22.314 -				int res;
  22.315 -				
  22.316 -				strcpy(srcpath, pwd);
  22.317 -				free(pwd);
  22.318 -				srcpath[len1] = '/';
  22.319 -				strcpy(srcpath + len1 + 1, path);
  22.320 -				res = resolvepath(srcpath, dstpath);
  22.321 -				free(srcpath);
  22.322 -				
  22.323 -				if (res)
  22.324 -				{
  22.325 -					lua_pushstring(L, dstpath);
  22.326 -					free(dstpath);
  22.327 -					return 1;			
  22.328 -				}
  22.329 -				
  22.330 -				free(dstpath);
  22.331 -				lua_pushnil(L);
  22.332 -				lua_pushstring(L, "Not a valid path");
  22.333 -				return 2;
  22.334 -			}
  22.335 -			
  22.336 -			free(srcpath);
  22.337 -			free(dstpath);
  22.338 -			break;
  22.339 -		}
  22.340 -		
  22.341 -		if (errno == ERANGE)
  22.342 -		{
  22.343 -			pwdsize <<= 1;
  22.344 -			continue;
  22.345 -		}
  22.346 -		
  22.347 -		lua_pushnil(L);
  22.348 -		lua_pushstring(L, strerror(errno));
  22.349 -		return 2;
  22.350 -	}
  22.351 -	
  22.352 -	free(pwd);
  22.353 -	luaL_error(L, "Out of memory");
  22.354 -	return 0;
  22.355 -}
  22.356 -
  22.357 -
  22.358 -static int 
  22.359 -posix_nanosleep(lua_State *L)
  22.360 -{
  22.361 -	struct timespec req;
  22.362 -	lua_Number d = luaL_checknumber(L, 1);
  22.363 -	
  22.364 -	req.tv_sec = (time_t) d;
  22.365 -	req.tv_nsec = (long) ((d - req.tv_sec) * 1000000000);
  22.366 -	
  22.367 -	if (nanosleep(&req, NULL) == 0)
  22.368 -	{
  22.369 -		lua_pushboolean(L, 1);
  22.370 -		return 1;
  22.371 -	}
  22.372 -	
  22.373 -	lua_pushnil(L);
  22.374 -	lua_pushstring(L, strerror(errno));
  22.375 -	return 2;
  22.376 -}
  22.377 -
  22.378 -
  22.379 -static const luaL_Reg lib[] =
  22.380 -{
  22.381 -	{ "stat", posix_stat },
  22.382 -	{ "lstat", posix_lstat },
  22.383 -	{ "opendir", posix_opendir },
  22.384 -	{ "closedir", posix_closedir },
  22.385 -	{ "readdir", posix_readdir },
  22.386 -	{ "mkdir", posix_mkdir },
  22.387 -	{ "readlink", posix_readlink },
  22.388 -	{ "symlink", posix_symlink },
  22.389 -	{ "abspath", posix_abspath },
  22.390 -	{ "nanosleep", posix_nanosleep },
  22.391 -	{ NULL, NULL }
  22.392 -};
  22.393 -
  22.394 -
  22.395 -static const luaL_Reg methods[] =
  22.396 -{
  22.397 -	{"read", posix_readdir },
  22.398 -	{"close", posix_closedir },
  22.399 -	{"__gc", posix_closedir },
  22.400 -	{NULL, NULL}
  22.401 -};
  22.402 -
  22.403 -
  22.404 -static void
  22.405 -addclass(lua_State *L, const char *libname, const char *classname,
  22.406 -	luaL_Reg *functions, luaL_Reg *methods, void *userdata)
  22.407 -{
  22.408 -	luaL_newmetatable(L, classname); /* classtab */
  22.409 -	lua_pushliteral(L, "__index"); /* classtab, "__index" */
  22.410 -
  22.411 -	/* insert self: classtab.__index = classtab */
  22.412 -	lua_pushvalue(L, -2); /* classtab, "__index", classtab */
  22.413 -	lua_rawset(L, -3); /* classtab */
  22.414 -
  22.415 -	/* insert methods. consume 1 userdata. do not create a new tab */
  22.416 -	lua_pushlightuserdata(L, userdata); /* classtab, userdata */
  22.417 -	luaL_openlib(L, NULL, methods, 1); /* classtab */
  22.418 -
  22.419 -	/* first upvalue: userdata */
  22.420 -	lua_pushlightuserdata(L, userdata); /* classtab, userdata */
  22.421 -
  22.422 -	/* duplicate table argument to be used as second upvalue for cclosure */
  22.423 -	lua_pushvalue(L, -2); /* classtab, userdata, classtab */
  22.424 -
  22.425 -	/* insert functions */
  22.426 -	luaL_openlib(L, libname, functions, 2);	/* classtab, libtab */
  22.427 -
  22.428 -	/* adjust stack */
  22.429 -	lua_pop(L, 2);	
  22.430 -}
  22.431 -
  22.432 -
  22.433 -int luaopen_tek_posix(lua_State *L)
  22.434 -{
  22.435 -	addclass(L, "tek.posix", DIRCLASSNAME,
  22.436 -		(luaL_Reg *) lib, (luaL_Reg *) methods, NULL);
  22.437 -	return 0;
  22.438 -}
    23.1 --- a/cgi-bin/tek/util.lua	Sun May 20 18:22:42 2007 +0200
    23.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    23.3 @@ -1,57 +0,0 @@
    23.4 -
    23.5 ---
    23.6 ---	tek.util - Utilities
    23.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
    23.8 ---	See copyright notice in COPYRIGHT
    23.9 ---
   23.10 -
   23.11 -local posix = require "tek.posix"
   23.12 -
   23.13 -local assert, time, remove = assert, os.time, os.remove
   23.14 -
   23.15 -
   23.16 -module "tek.util"
   23.17 -
   23.18 -
   23.19 -_VERSION = 1
   23.20 -_REVISION = 0
   23.21 -
   23.22 -
   23.23 ---	Directory iterator
   23.24 -
   23.25 -function readdir(path)
   23.26 -	assert(path)
   23.27 -	local d, msg = posix.opendir(path)
   23.28 -	assert(d, msg and "Cannot open directory " .. path .. " : " .. msg)
   23.29 -	return function()
   23.30 -		local e
   23.31 -		repeat
   23.32 -			e = d:read()
   23.33 -		until e ~= "." and e ~= ".."
   23.34 -		return e
   23.35 -	end
   23.36 -end
   23.37 -
   23.38 -
   23.39 ---	Remove directory entries matching a pattern
   23.40 ---	whose atime is older than maxtime seconds
   23.41 -
   23.42 -function expire(dir, pat, maxtime)
   23.43 -	if posix.stat(dir, "mode") == "directory" then
   23.44 -		maxtime = maxtime or 600
   23.45 -		local nowtime = time()
   23.46 -		for e in readdir(dir) do
   23.47 -			if not pat or e:match(pat) then
   23.48 -				local fname = dir .. "/" .. e
   23.49 -				-- assuming here that if an entry is a dangling softlink
   23.50 -				-- (in which case stat() returns nil), it can be deleted anyway
   23.51 -				local atime = posix.stat(fname, "access")
   23.52 -				if not atime or (nowtime - atime > maxtime) then
   23.53 - 					local res, msg = remove(fname)
   23.54 - 					assert(res, msg and "Failed to delete item : " .. msg)
   23.55 -				end
   23.56 - 			end
   23.57 - 		end
   23.58 - 		return true
   23.59 -	end
   23.60 -end
    24.1 --- a/cgi-bin/tek/web.lua	Sun May 20 18:22:42 2007 +0200
    24.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    24.3 @@ -1,184 +0,0 @@
    24.4 -
    24.5 ---
    24.6 ---	tek.web - HTML utilities
    24.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
    24.8 ---	See copyright notice in COPYRIGHT
    24.9 ---
   24.10 -
   24.11 -local cgi = require "tek.cgi"
   24.12 -
   24.13 -local stdout, unpack, ipairs = io.stdout, unpack, ipairs
   24.14 -local insert, remove, concat = table.insert, table.remove, table.concat
   24.15 -
   24.16 -
   24.17 -module "tek.web"
   24.18 -
   24.19 -
   24.20 -_VERSION = 2
   24.21 -_REVISION = 2
   24.22 -
   24.23 -
   24.24 -local function getargs(args, notfirstarg)
   24.25 -	local args2 = { }
   24.26 -	for _, a in ipairs(args) do
   24.27 -		local key, val = a:match("^(%w+)=(.*)$")
   24.28 -		if key and val then
   24.29 -			-- "arg=val" sets/overrides argument key
   24.30 -			insert(args2, { name = key, value = val })
   24.31 -		elseif cgi.request.args[a] then
   24.32 -			-- just "arg" propagates existing request.args[arg]
   24.33 -			insert(args2, { name = a, value = cgi.request.args[a] })
   24.34 -		end
   24.35 -	end
   24.36 -	local out = { }
   24.37 -	for i, arg in ipairs(args2) do
   24.38 -		if i > 1 or notfirstarg then
   24.39 -			insert(out, "&amp;")
   24.40 -		else
   24.41 -			insert(out, "?")
   24.42 -		end
   24.43 -		insert(out, arg.name)
   24.44 -		insert(out, "=")
   24.45 -		insert(out, cgi.encodeurl(arg.value))
   24.46 -	end
   24.47 -	return concat(out)
   24.48 -end
   24.49 -
   24.50 -
   24.51 -function gethref(doc, args)
   24.52 -	local key, val
   24.53 -	local first = true
   24.54 -	local t = { }
   24.55 -	local url, anch = doc:match("^(.+)(#.+)$")
   24.56 -	local notfirstarg = doc:match("%?")
   24.57 -	if url and anch then
   24.58 -		insert(t, url)
   24.59 -		insert(t, getargs(args, notfirstarg))
   24.60 -		insert(t, anch)
   24.61 -	else
   24.62 -		insert(t, doc)
   24.63 -		insert(t, getargs(args, notfirstarg))
   24.64 -	end
   24.65 -	return concat(t)
   24.66 -end
   24.67 -
   24.68 -
   24.69 -outbuf = { }
   24.70 -headerbuf = { }
   24.71 -
   24.72 -
   24.73 ---	The output function gathers the output in a buffer which needs
   24.74 ---	to get flushed (or can be discarded)
   24.75 -
   24.76 -function out(s)
   24.77 -	insert(outbuf, s)
   24.78 -end
   24.79 -
   24.80 -
   24.81 -function setheader(s)
   24.82 -	insert(headerbuf, 1, s)
   24.83 -end
   24.84 -
   24.85 -
   24.86 -function discard()
   24.87 -	headerbuf = { }
   24.88 -	outbuf = { }
   24.89 -end
   24.90 -
   24.91 -
   24.92 -function htmlerr(msg)
   24.93 -	stdout:write("Content-Type: text/html\n\n")
   24.94 -	stdout:write("<html><body>\n")
   24.95 -	stdout:write("<div><h2>Error</h2>Malformed XHTML : "..msg.."</div>\n")
   24.96 -	stdout:write("</body></html>\n")
   24.97 -end
   24.98 -
   24.99 -
  24.100 -function flush(tidy)
  24.101 -	
  24.102 -	stdout:write(unpack(headerbuf))
  24.103 -	
  24.104 -	if not tidy then
  24.105 -		
  24.106 -		stdout:write(unpack(outbuf))
  24.107 -	
  24.108 -	else
  24.109 -		
  24.110 -		-- check for valid xhtml and create tidy formatting
  24.111 -		
  24.112 -		local stack = { }
  24.113 -		local out = { }
  24.114 -		local curtag = { }
  24.115 -		local indent = 0
  24.116 -		
  24.117 -		concat(outbuf):gsub("(.-)(%b<>)", function(a, b)
  24.118 -			
  24.119 -			local ta, t, te = b:match("^<%s*([%!%/%?]?)%s*(.-)%s*([/%?]?)%s*>$")
  24.120 -			local tag = t:match("^(%a%w*)")
  24.121 -
  24.122 -			if curtag.literal then
  24.123 -				insert(out, a)
  24.124 -			elseif a:match("%S") then
  24.125 -				local outl = { }
  24.126 -				a:gsub("(%S+)", function(a) insert(outl, a) end)
  24.127 -				outl = concat(outl, " ")
  24.128 -				if curtag.nobreak then
  24.129 -					insert(out, outl)
  24.130 -				else
  24.131 -					insert(out, ("\t"):rep(indent))
  24.132 -					insert(out, outl)
  24.133 -					insert(out, "\n")
  24.134 -				end
  24.135 -			end
  24.136 -
  24.137 -			if t ~= "" then
  24.138 -				if ta == "" and te == "" then
  24.139 -					insert(stack, curtag)
  24.140 -					curtag = { tag = tag, 
  24.141 -						literal = tag == "pre" or tag == "textarea",
  24.142 -						nobreak = tag == "a" or tag == "span" or (tag and tag:match("^h%d$")) }
  24.143 -					insert(out, ("\t"):rep(indent))
  24.144 -					insert(out, "<" .. t .. ">")
  24.145 -					if not curtag.literal and not curtag.nobreak then
  24.146 -						insert(out, "\n")
  24.147 -					end
  24.148 -					indent = indent + 1
  24.149 -				elseif ta == "/" and te == "" then
  24.150 -					if curtag.tag ~= tag then
  24.151 -						if not msg then
  24.152 -							msg = "Expected : &lt;/" .. (tag or "nil") .. "&gt;, found : &lt;/" .. (curtag.tag or "nil") .. "&gt;"
  24.153 -						end
  24.154 -					end
  24.155 -					indent = indent - 1
  24.156 -					if not curtag.literal and not curtag.nobreak then
  24.157 -						insert(out, ("\t"):rep(indent))
  24.158 -					end
  24.159 -					insert(out, "</" .. t .. ">\n")
  24.160 -					curtag = remove(stack)
  24.161 -				elseif ta == "" and te == "/" then
  24.162 -					insert(out, ("\t"):rep(indent))
  24.163 -					insert(out, "<" .. t .. " />\n")
  24.164 -				elseif ta == "!" and te == "" then
  24.165 -					insert(out, ("\t"):rep(indent))
  24.166 -					insert(out, "<!" .. t .. ">\n")
  24.167 -				elseif ta == "?" and te == "?" then
  24.168 -					insert(out, ("\t"):rep(indent))
  24.169 -					insert(out, "<?" .. t .. "?>\n")
  24.170 -				end
  24.171 -			end
  24.172 -				
  24.173 -		end)
  24.174 -		
  24.175 -		if msg then
  24.176 -			htmlerr(msg)
  24.177 -		else
  24.178 -			stdout:write(concat(out))
  24.179 -		end
  24.180 -	
  24.181 -	end
  24.182 -	
  24.183 -	headerbuf = { }
  24.184 -	outbuf = { }
  24.185 -
  24.186 -end
  24.187 -
    25.1 --- a/cgi-bin/tek/web/include.c	Sun May 20 18:22:42 2007 +0200
    25.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    25.3 @@ -1,385 +0,0 @@
    25.4 -
    25.5 -#include <ctype.h>
    25.6 -#include <stdlib.h>
    25.7 -#include <string.h>
    25.8 -#include <lua.h>
    25.9 -#include <lualib.h>
   25.10 -#include <lauxlib.h>
   25.11 -
   25.12 -
   25.13 -#define topfile(L)	((FILE **)luaL_checkudata(L, 1, LUA_FILEHANDLE))
   25.14 -
   25.15 -
   25.16 -static FILE *tofile (lua_State *L) {
   25.17 -  FILE **f = topfile(L);
   25.18 -  if (*f == NULL)
   25.19 -    luaL_error(L, "attempt to use a closed file");
   25.20 -  return *f;
   25.21 -}
   25.22 -
   25.23 -
   25.24 -static unsigned char *encodeutf8(unsigned char *buf, int c)
   25.25 -{
   25.26 -	if (c < 128)
   25.27 -	{
   25.28 -		*buf++ = c;
   25.29 -	}
   25.30 -	else if (c < 2048)
   25.31 -	{
   25.32 -		*buf++ = 0xc0 + (c >> 6);
   25.33 -		*buf++ = 0x80 + (c & 0x3f);
   25.34 -	}
   25.35 -	else if (c < 65536)
   25.36 -	{
   25.37 -		*buf++ = 0xe0 + (c >> 12);
   25.38 -		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   25.39 -		*buf++ = 0x80 + (c & 0x3f);
   25.40 -	}
   25.41 -	else if (c < 2097152)
   25.42 -	{
   25.43 -		*buf++ = 0xf0 + (c >> 18);
   25.44 -		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   25.45 -		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   25.46 -		*buf++ = 0x80 + (c & 0x3f);
   25.47 -	}
   25.48 -	else if (c < 67108864)
   25.49 -	{
   25.50 -		*buf++ = 0xf8 + (c >> 24);
   25.51 -		*buf++ = 0x80 + ((c & 0xffffff) >> 18);
   25.52 -		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   25.53 -		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   25.54 -		*buf++ = 0x80 + (c & 0x3f);
   25.55 -	}
   25.56 -	else
   25.57 -	{
   25.58 -		*buf++ = 0xfc + (c >> 30);
   25.59 -		*buf++ = 0x80 + ((c & 0x3fffffff) >> 24);
   25.60 -		*buf++ = 0x80 + ((c & 0xffffff) >> 18);
   25.61 -		*buf++ = 0x80 + ((c & 0x3ffff) >> 12);
   25.62 -		*buf++ = 0x80 + ((c & 0xfff) >> 6);
   25.63 -		*buf++ = 0x80 + (c & 0x3f);
   25.64 -	}
   25.65 -	return buf;
   25.66 -}
   25.67 -
   25.68 -
   25.69 -struct utf8reader
   25.70 -{
   25.71 -	int (*readchar)(struct utf8reader *);
   25.72 -	int accu, numa, min, bufc;
   25.73 -	const unsigned char *src;
   25.74 -	size_t srclen;
   25.75 -	FILE *file;
   25.76 -	void *udata;
   25.77 -};
   25.78 -
   25.79 -static int readstring(struct utf8reader *rd)
   25.80 -{
   25.81 -	if (rd->srclen == 0)
   25.82 -		return -1;
   25.83 -	rd->srclen--;
   25.84 -	return *rd->src++;
   25.85 -}
   25.86 -
   25.87 -static int readfile(struct utf8reader *rd)
   25.88 -{
   25.89 -	return fgetc(rd->file);
   25.90 -}
   25.91 -
   25.92 -static int readutf8(struct utf8reader *rd)
   25.93 -{
   25.94 -	int c;
   25.95 -	for (;;)
   25.96 -	{
   25.97 -		if (rd->bufc >= 0)
   25.98 -		{
   25.99 -			c = rd->bufc;
  25.100 -			rd->bufc = -1;
  25.101 -		}
  25.102 -		else
  25.103 -			c = rd->readchar(rd);
  25.104 -		
  25.105 -		if (c < 0)
  25.106 -			return c;
  25.107 -
  25.108 -		if (c == 254 || c == 255)
  25.109 -			break;
  25.110 -		
  25.111 -		if (c < 128)
  25.112 -		{
  25.113 -			if (rd->numa > 0)
  25.114 -			{
  25.115 -				rd->bufc = c;
  25.116 -				break;
  25.117 -			}
  25.118 -			return c;
  25.119 -		}
  25.120 -		else if (c < 192)
  25.121 -		{
  25.122 -			if (rd->numa == 0)
  25.123 -				break;
  25.124 -			rd->accu <<= 6;
  25.125 -			rd->accu += c - 128;
  25.126 -			rd->numa--;
  25.127 -			if (rd->numa == 0)
  25.128 -			{
  25.129 -				if (rd->accu == 0 || rd->accu < rd->min ||
  25.130 -					(rd->accu >= 55296 && rd->accu <= 57343))
  25.131 -					break;
  25.132 -				c = rd->accu;
  25.133 -				rd->accu = 0;
  25.134 -				return c;
  25.135 -			}
  25.136 -		}
  25.137 -		else
  25.138 -		{
  25.139 -			if (rd->numa > 0)
  25.140 -			{
  25.141 -				rd->bufc = c;
  25.142 -				break;
  25.143 -			}
  25.144 -			
  25.145 -			if (c < 224)
  25.146 -			{
  25.147 -				rd->min = 128;
  25.148 -				rd->accu = c - 192;
  25.149 -				rd->numa = 1;
  25.150 -			}
  25.151 -			else if (c < 240)
  25.152 -			{
  25.153 -				rd->min = 2048;
  25.154 -				rd->accu = c - 224;
  25.155 -				rd->numa = 2;
  25.156 -			}
  25.157 -			else if (c < 248)
  25.158 -			{
  25.159 -				rd->min = 65536;
  25.160 -				rd->accu = c - 240;
  25.161 -				rd->numa = 3;
  25.162 -			}
  25.163 -			else if (c < 252)
  25.164 -			{
  25.165 -				rd->min = 2097152;
  25.166 -				rd->accu = c - 248;
  25.167 -				rd->numa = 4;
  25.168 -			}
  25.169 -			else
  25.170 -			{
  25.171 -				rd->min = 67108864;
  25.172 -				rd->accu = c - 252;
  25.173 -				rd->numa = 5;
  25.174 -			}
  25.175 -		}
  25.176 -	}
  25.177 -	/* bad char */
  25.178 -	rd->accu = 0;
  25.179 -	rd->numa = 0;
  25.180 -	return 65533;
  25.181 -}
  25.182 -
  25.183 -
  25.184 -typedef enum { PARSER_UNDEF = -1, PARSER_HTML, PARSER_OPEN1, PARSER_OPEN2,
  25.185 -PARSER_CODE, PARSER_VAR, PARSER_CLOSE } parser_state_t;
  25.186 -
  25.187 -
  25.188 -static unsigned char *outchar(lua_State *L, unsigned char *buf, parser_state_t state, int c)
  25.189 -{
  25.190 -	if (state == PARSER_HTML)
  25.191 -	{
  25.192 -		if (c > 127 || c == '[' || c == ']')
  25.193 -			return buf + sprintf((char *) buf, "&#%02d;", c);
  25.194 -	}
  25.195 -	else if (state == PARSER_CODE)
  25.196 -	{
  25.197 -		if (c > 127)
  25.198 -			return encodeutf8(buf, c);
  25.199 -	}
  25.200 -	else if (c > 127)
  25.201 -		luaL_error(L, "Non-ASCII character outside code or HTML context");
  25.202 -	
  25.203 -	*buf++ = c;
  25.204 -	return buf;
  25.205 -}
  25.206 -
  25.207 -
  25.208 -struct readdata
  25.209 -{
  25.210 -	/* buffer including " " .. outfunc .. "(": */
  25.211 -	unsigned char buf0[256];
  25.212 -	/* pointer into buf0 past outfunc: */
  25.213 -	unsigned char *buf;
  25.214 -	/* html+lua parser state: */
  25.215 -	parser_state_t state;
  25.216 -	/* utf8 reader state: */
  25.217 -	struct utf8reader utf8;
  25.218 -};
  25.219 -
  25.220 -
  25.221 -static const char *readparsed(lua_State *L, void *udata, size_t *sz)
  25.222 -{
  25.223 -	struct readdata *rd = udata;
  25.224 -	parser_state_t news = rd->state;
  25.225 -	int c;
  25.226 -	
  25.227 -	while ((c = readutf8(&rd->utf8)) >= 0)
  25.228 -	{
  25.229 -		switch (news)
  25.230 -		{
  25.231 -			case PARSER_UNDEF:
  25.232 -				if (c == '<')
  25.233 -				{
  25.234 -					news = PARSER_OPEN1;
  25.235 -					continue;
  25.236 -				}
  25.237 -				rd->state = PARSER_HTML;
  25.238 -				rd->buf[0] = '[';
  25.239 -				rd->buf[1] = '[';
  25.240 -				*sz = outchar(L, rd->buf + 2, rd->state, c) - rd->buf0;
  25.241 -				return (char *) rd->buf0;
  25.242 -			
  25.243 -			case PARSER_HTML:
  25.244 -				if (c == '<')
  25.245 -				{
  25.246 -					news = PARSER_OPEN1;
  25.247 -					continue;
  25.248 -				}
  25.249 -				break;
  25.250 -			
  25.251 -			case PARSER_OPEN1:
  25.252 -				if (c == '%')
  25.253 -				{
  25.254 -					news = PARSER_OPEN2;
  25.255 -					continue;
  25.256 -				}
  25.257 -				rd->buf[0] = '<';
  25.258 -				rd->buf[1] = c;
  25.259 -				*sz = 2;
  25.260 -				return (char *) rd->buf;
  25.261 -
  25.262 -			case PARSER_OPEN2:
  25.263 -				if (c == '=')
  25.264 -				{
  25.265 -					if (rd->state == PARSER_UNDEF)
  25.266 -					{
  25.267 -						rd->state = PARSER_VAR;
  25.268 -						*sz = rd->buf - rd->buf0;
  25.269 -						return (char *) rd->buf0;
  25.270 -					}
  25.271 -					rd->state = PARSER_VAR;
  25.272 -					strcpy((char *) rd->buf, "]]..(");
  25.273 -					*sz = 5;
  25.274 -					return (char *) rd->buf;
  25.275 -				}
  25.276 -				
  25.277 -				if (rd->state == PARSER_UNDEF)
  25.278 -					rd->state = PARSER_CODE;
  25.279 -				else
  25.280 -				{
  25.281 -					rd->state = PARSER_CODE;
  25.282 -					rd->buf[0] = ']';
  25.283 -					rd->buf[1] = ']';
  25.284 -					rd->buf[2] = ')';
  25.285 -					rd->buf[3] = ' ';
  25.286 -					rd->buf[4] = c;
  25.287 -					*sz = 5;
  25.288 -					return (char *) rd->buf;
  25.289 -				}
  25.290 -				break;
  25.291 -			
  25.292 -			case PARSER_VAR:
  25.293 -			case PARSER_CODE:
  25.294 -				if (c == '%')
  25.295 -				{
  25.296 -					news = PARSER_CLOSE;
  25.297 -					continue;
  25.298 -				}
  25.299 -				break;
  25.300 -			
  25.301 -			case PARSER_CLOSE:
  25.302 -				if (c == '>')
  25.303 -				{
  25.304 -					if (rd->state == PARSER_CODE)
  25.305 -					{
  25.306 -						rd->state = PARSER_HTML;
  25.307 -						rd->buf[0] = '[';
  25.308 -						rd->buf[1] = '[';
  25.309 -						*sz = rd->buf + 2 - rd->buf0;
  25.310 -						return (char *) rd->buf0;
  25.311 -					}
  25.312 -					rd->state = PARSER_HTML;
  25.313 -					strcpy((char *) rd->buf, " or \"nil\")..[[");
  25.314 -					*sz = 14;
  25.315 -					return (char *) rd->buf;
  25.316 -				}
  25.317 -				rd->buf[0] = '%';
  25.318 -				rd->buf[1] = c;
  25.319 -				*sz = 2;
  25.320 -				return (char *) rd->buf;
  25.321 -		}
  25.322 -		
  25.323 -		*sz = outchar(L, rd->buf, rd->state, c) - rd->buf;
  25.324 -		return (char *) rd->buf;
  25.325 -	}
  25.326 -			
  25.327 -	rd->state = PARSER_UNDEF;
  25.328 -	if (news == PARSER_HTML)
  25.329 -	{
  25.330 -		*sz = 4;
  25.331 -		return "]]) ";
  25.332 -	}
  25.333 -	
  25.334 -	return NULL;
  25.335 -}
  25.336 -
  25.337 -
  25.338 -static int load(lua_State *L)
  25.339 -{
  25.340 -	struct readdata rd;
  25.341 -	const char *outfunc = lua_tostring(L, 2);
  25.342 -	const char *chunkname = lua_tostring(L, 3);
  25.343 -	int res;
  25.344 -	
  25.345 -	if (lua_isuserdata(L, 1))
  25.346 -	{
  25.347 -		rd.utf8.file = tofile(L);
  25.348 -		rd.utf8.readchar = readfile;
  25.349 -	}
  25.350 -	else
  25.351 -	{
  25.352 -		rd.utf8.src = (unsigned char *) lua_tolstring(L, 1, &rd.utf8.srclen);
  25.353 -		rd.utf8.readchar = readstring;
  25.354 -	}
  25.355 -	
  25.356 -	rd.utf8.accu = 0;
  25.357 -	rd.utf8.numa = 0;
  25.358 -	rd.utf8.bufc = -1;
  25.359 -	
  25.360 -	rd.state = PARSER_UNDEF;
  25.361 -	strcpy((char *) rd.buf0, " ");
  25.362 -	strcat((char *) rd.buf0, outfunc);
  25.363 -	strcat((char *) rd.buf0, "(");
  25.364 -	rd.buf = rd.buf0 + strlen((char *) rd.buf0);
  25.365 -	
  25.366 -	res = lua_load(L, readparsed, &rd, chunkname);
  25.367 -	if (res == 0)
  25.368 -		return 1;
  25.369 -	
  25.370 -	lua_pushnil(L);
  25.371 -	lua_insert(L, -2);
  25.372 -	/* nil, message on stack */
  25.373 -	return 2;
  25.374 -}
  25.375 -
  25.376 -
  25.377 -static const luaL_Reg lib[] =
  25.378 -{
  25.379 -	{ "load", load },
  25.380 -	{ NULL, NULL }
  25.381 -};
  25.382 -
  25.383 -
  25.384 -int luaopen_tek_web_include(lua_State *L)
  25.385 -{
  25.386 -	luaL_register(L, "tek.web.include", lib);
  25.387 -	return 0;
  25.388 -}
    26.1 --- a/cgi-bin/tek/web/markup.lua	Sun May 20 18:22:42 2007 +0200
    26.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.3 @@ -1,682 +0,0 @@
    26.4 -
    26.5 ---
    26.6 ---	tek.web.markup - WIKI-style markup parser
    26.7 ---	Written by Timm S. Mueller <tmueller at neoscientists.org>
    26.8 ---	See copyright notice in COPYRIGHT
    26.9 ---
   26.10 -
   26.11 -local cgi = require "tek.cgi"
   26.12 -
   26.13 -local unpack, min, ipairs = unpack, math.min, ipairs
   26.14 -local remove, insert, concat, foreachi =
   26.15 -	table.remove, table.insert, table.concat, table.foreachi
   26.16 -
   26.17 -
   26.18 -module "tek.web.markup"
   26.19 -
   26.20 -
   26.21 -_VERSION = 1
   26.22 -_REVISION = 1
   26.23 -
   26.24 -
   26.25 ---	iterate over lines in a string
   26.26 -
   26.27 -local function lines(s)
   26.28 -	local pos = 1
   26.29 -	return function()
   26.30 -		if pos then
   26.31 -			local a, e = s:find("[\r]?\n", pos)
   26.32 -			local o = pos
   26.33 -			if not a then
   26.34 -				pos = nil
   26.35 -				return s:sub(o)
   26.36 -			else
   26.37 -				pos = e + 1
   26.38 -				return s:sub(o, a - 1)
   26.39 -			end
   26.40 -		end
   26.41 -	end
   26.42 -end
   26.43 -
   26.44 -
   26.45 ---	get a SGML/XML tag
   26.46 -
   26.47 -local function gettagml(state, id, tag, open)
   26.48 -	if tag then
   26.49 -		local tab = { tag }
   26.50 -		if id == "link" or id == "emphasis" or id == "code" then
   26.51 -			if not state.brpend and not state.inline then
   26.52 -				insert(tab, 1, ("\t"):rep(state.depth))
   26.53 -			end
   26.54 -			state.inline = true
   26.55 -			state.brpend = nil
   26.56 -		else
   26.57 -			if id == "image" then
   26.58 -				if not state.inline then
   26.59 -					insert(tab, 1, ("\t"):rep(state.depth))
   26.60 -				end
   26.61 -				state.brpend = true
   26.62 -			else
   26.63 -				if id == "pre" or id == "preline" then
   26.64 -					state.brpend = nil
   26.65 -				elseif open or id ~= "preline" then
   26.66 -					insert(tab, 1, ("\t"):rep(state.depth))
   26.67 -					if not open and state.inline then
   26.68 -						state.brpend = true
   26.69 -					end
   26.70 -				end
   26.71 -				if state.brpend then
   26.72 -					insert(tab, 1, "\n")
   26.73 -				end
   26.74 -				if id ~= "preline" or not open then
   26.75 -					insert(tab, "\n")
   26.76 -				end
   26.77 -				state.inline = nil
   26.78 -				state.brpend = nil
   26.79 -			end
   26.80 -		end
   26.81 -		return concat(tab)
   26.82 -	end
   26.83 -end
   26.84 -
   26.85 -
   26.86 ---	get a SGML/XML line of text
   26.87 -
   26.88 -local function gettextml(state, line, id)
   26.89 -	local tab = { }
   26.90 -	if state.brpend then
   26.91 -		insert(tab, "\n")
   26.92 -		insert(tab, ("\t"):rep(state.depth))
   26.93 -	else
   26.94 -		if id ~= "link" and id ~= "emphasis" and id ~= "code" then
   26.95 -			if not state.inline then
   26.96 -				insert(tab, ("\t"):rep(state.depth))
   26.97 -			end
   26.98 -		end
   26.99 -	end
  26.100 -	line:gsub("^(%s*)(.-)(%s*)$", function(a, b, c)
  26.101 -		if a ~= "" then
  26.102 -			insert(tab, " ")
  26.103 -		end
  26.104 -		insert(tab, b)
  26.105 -		if c ~= "" then
  26.106 -			insert(tab, " ")
  26.107 -		end
  26.108 -	end)
  26.109 -	state.brpend = true
  26.110 -	return concat(tab)
  26.111 -end
  26.112 -
  26.113 -
  26.114 ---	html out
  26.115 -
  26.116 -local gen_html =
  26.117 -{
  26.118 -	out = function(state, ...)
  26.119 -		state.out(concat(arg))
  26.120 -	end,
  26.121 -
  26.122 -	init = function(state)
  26.123 -		state.depth = 1
  26.124 -		return '' -- header
  26.125 -	end,
  26.126 -
  26.127 -	exit = function(state)
  26.128 -		return '' -- footer
  26.129 -	end,
  26.130 -
  26.131 -	gettag = function(state, id, tag, open)
  26.132 -		return gettagml(state, id, tag, open)
  26.133 -	end,
  26.134 -
  26.135 -	gettext = function(state, line, id)
  26.136 -		return cgi.encodeform(gettextml(state, line, id))
  26.137 -	end,
  26.138 -
  26.139 -	getpre = function(state, line)
  26.140 -		return cgi.encodeform(line), '\n'
  26.141 -	end,
  26.142 -
  26.143 -	getcode = function(state, line)
  26.144 -		return cgi.encodeform(line)
  26.145 -	end,
  26.146 -
  26.147 -	head = function(state, level, text)
  26.148 -		return '<h' .. level .. '>', '</h' .. level .. '>'
  26.149 -	end,
  26.150 -
  26.151 -	argument = function(state, name, text)
  26.152 -		return ', function()%>', '<%end'
  26.153 -	end,
  26.154 -
  26.155 -	func = function(state, dynamic, args)
  26.156 -		if dynamic then
  26.157 -			state.is_dynamic_content = true
  26.158 -		end
  26.159 -		local t = { '<%loona:include("', args[1], '"' }
  26.160 -		remove(args, 1)
  26.161 -		for _, v in ipairs(args) do
  26.162 -			insert(t, ',' .. v)
  26.163 -		end
  26.164 -		return concat(t), ')%>'
  26.165 -	end,
  26.166 -
  26.167 -	indent = function()
  26.168 -		return '<div class="indent">', '</div>'
  26.169 -	end,
  26.170 -
  26.171 -	list = function()
  26.172 -		return '<ul>', '</ul>'
  26.173 -	end,
  26.174 -
  26.175 -	item = function(state, bullet)
  26.176 -		if bullet then
  26.177 -			return '<li>', '</li>'
  26.178 -		end
  26.179 -		return '<li style="list-style-type: none;">', '</li>'
  26.180 -	end,
  26.181 -
  26.182 -	block = function()
  26.183 -		return '<p>', '</p>'
  26.184 -	end,
  26.185 -
  26.186 -	rule = function()
  26.187 -		return '<hr />'
  26.188 -	end,
  26.189 -
  26.190 -	pre = function()
  26.191 -		return '<pre>', '</pre>'
  26.192 -	end,
  26.193 -
  26.194 -	code = function()
  26.195 -		return '<code>', '</code>'
  26.196 -	end,
  26.197 -
  26.198 -	emphasis = function(state, len, text)
  26.199 -		if len == 1 then
  26.200 -			return '<em>', '</em>'
  26.201 -		elseif len == 2 then
  26.202 -			return '<strong>', '</strong>'
  26.203 -		else
  26.204 -			return '<em><strong>', '</strong></em>'
  26.205 -		end
  26.206 -	end,
  26.207 -
  26.208 -	link = function(state, link, isurl)
  26.209 -		link = link:lower()
  26.210 -		if isurl then
  26.211 -			return '<%=loona:elink("' .. cgi.encodeurl(link, true) .. '", [[', ']])%>'
  26.212 -		else
  26.213 -			return '<%=loona:link("' .. cgi.encodeurl(link, true) .. '", [[', ']])%>'
  26.214 -		end
  26.215 -	end,
  26.216 -
  26.217 -	table = function(state, border)
  26.218 -		return '<table>', '</table>'
  26.219 -	end,
  26.220 -
  26.221 -	row = function(state)
  26.222 -		state.column = 0
  26.223 -		return '<tr>', '</tr>'
  26.224 -	end,
  26.225 -
  26.226 -	cell = function(state, border)
  26.227 -		state.column = state.column + 1
  26.228 -		return '<td class="column' .. state.column .. '">', '</td>'
  26.229 -	end,
  26.230 -
  26.231 -	image = function(state, link)
  26.232 -		return '<img src="' .. cgi.encodeurl(link, true) .. '" />'
  26.233 -	end,
  26.234 -}
  26.235 -
  26.236 -
  26.237 ---	parser
  26.238 -
  26.239 -local function parse(state, ...)
  26.240 -
  26.241 -	--	do a generator function by ID
  26.242 -	
  26.243 -	local function doid(id, ...)
  26.244 -		if state.genfunc and state.genfunc[id] then
  26.245 -			return state.genfunc[id](state, unpack(arg))
  26.246 -		end
  26.247 -	end
  26.248 -	
  26.249 -	local function doout(id, ...)
  26.250 -		doid("out", doid(id, unpack(arg)))
  26.251 -	end
  26.252 -
  26.253 -	local function push(id, ...)
  26.254 -		local opentag, closetag = doid(id, unpack(arg))
  26.255 -		doout("gettag", id, opentag, true)
  26.256 -		insert(state.stack, { id = id, closetag = closetag })
  26.257 -		state.depth = state.depth + 1
  26.258 -	end
  26.259 -
  26.260 -	local function pop()
  26.261 -		local e = remove(state.stack)
  26.262 -		if e then
  26.263 -			state.depth = state.depth - 1
  26.264 -			doout("gettag", e.id, e.closetag)
  26.265 -			return e.id
  26.266 -		end
  26.267 -	end
  26.268 -
  26.269 -	local function top()
  26.270 -		local e = state.stack[#state.stack]
  26.271 -		if e then
  26.272 -			return e.id
  26.273 -		end
  26.274 -	end
  26.275 -
  26.276 -	local function popuntil(...)
  26.277 -		local i
  26.278 -		repeat
  26.279 -			i = pop()
  26.280 -			for j = 1, arg.n do
  26.281 -				if i == arg[j] then
  26.282 -					return
  26.283 -				end
  26.284 -			end
  26.285 -		until not i
  26.286 -	end
  26.287 -
  26.288 -	local function popwhilenot(...)
  26.289 -		local i
  26.290 -		repeat
  26.291 -			i = top()
  26.292 -			for j = 1, arg.n do
  26.293 -				if i == arg[j] then
  26.294 -					return
  26.295 -				end
  26.296 -			end
  26.297 -			pop()
  26.298 -		until not i
  26.299 -	end
  26.300 -
  26.301 -	local function popwhile(...)
  26.302 -		local cont
  26.303 -		repeat
  26.304 -			local id = top()
  26.305 -			cont = false
  26.306 -			for i = 1, arg.n do
  26.307 -				if arg[i] == id then
  26.308 -					local i = pop()
  26.309 -					cont = true
  26.310 -					break
  26.311 -				end
  26.312 -			end
  26.313 -		until not cont
  26.314 -	end
  26.315 -
  26.316 -	local function popif(...)
  26.317 -		local i = top()
  26.318 -		for j = 1, arg.n do
  26.319 -			if i == arg[j] then
  26.320 -				pop()
  26.321 -				return
  26.322 -			end
  26.323 -		end
  26.324 -	end
  26.325 -
  26.326 -	local function checktop(...)
  26.327 -		local i = top()
  26.328 -		return foreachi(arg, function(_, a)
  26.329 -			if i == a then
  26.330 -				return true
  26.331 -			end
  26.332 -		end)
  26.333 -	end
  26.334 -
  26.335 -	local function pushfragment(...)
  26.336 -		line = concat(arg)
  26.337 -		if not state.context.fragments then
  26.338 -			state.context.fragments = { }
  26.339 -		end
  26.340 -		state.id = (state.id or 0) + 1
  26.341 -		insert(state.context.fragments, 1, { line = line, id = state.id })
  26.342 -		state.context.topid = state.id
  26.343 -	end
  26.344 -
  26.345 -	local function popfragment()
  26.346 -		state.context.firstfragment = nil
  26.347 -		local frag = remove(state.context.fragments, 1)
  26.348 -		if frag then
  26.349 -			state.line = frag.line
  26.350 -			if frag.id == state.context.topid then
  26.351 -				state.context.firstfragment = state.context.parentfirstfragment
  26.352 -			end
  26.353 -		else
  26.354 -			state.line = nil
  26.355 -		end
  26.356 -		return state.line
  26.357 -	end
  26.358 -
  26.359 -	local function pushcontext(id, line)
  26.360 -		insert(state.cstack, state.context)
  26.361 -		if id then
  26.362 -			state.context = { id = id, fragments = { } }
  26.363 -			pushfragment(line)
  26.364 -			insert(state.cstack, state.context)
  26.365 -		end
  26.366 -	end
  26.367 -
  26.368 -	local function popcontext()
  26.369 -		state.context = remove(state.cstack)
  26.370 -		return state.context
  26.371 -	end
  26.372 -
  26.373 -	-- parse
  26.374 -
  26.375 -	state.lnr = 1
  26.376 -	state.previndent = 0
  26.377 -	state.stack = { }
  26.378 -	state.depth = 0
  26.379 -	state.table = nil
  26.380 -
  26.381 -	doout("init")
  26.382 -	push("document")
  26.383 -
  26.384 -	for line in lines(state.inbuf) do
  26.385 -
  26.386 -		state.indent = 0
  26.387 -		line = line:gsub("^( *)(.-)%s*$", function(s, t)
  26.388 -			if t ~= "" then
  26.389 -				state.indent = s:len()
  26.390 -			else
  26.391 -				state.indent = state.previndent
  26.392 -			end
  26.393 -			return t
  26.394 -		end)
  26.395 -
  26.396 -		state.istabline = line:find("||", 1, 1)
  26.397 -		if state.istabline then
  26.398 -			state.indent = state.previndent
  26.399 -		end
  26.400 -		if state.table then
  26.401 -			popuntil("row")
  26.402 -			if not state.istabline then
  26.403 -				popuntil("table")
  26.404 -				state.table = nil
  26.405 -			end
  26.406 -		end
  26.407 -		
  26.408 -		
  26.409 -		if state.indent < state.previndent then
  26.410 -			if not state.preindent or state.indent < state.preindent then
  26.411 -				local i = state.indent
  26.412 -				while i < state.previndent do
  26.413 -					popuntil("indent", "pre")
  26.414 -					i = i + 1
  26.415 -				end
  26.416 -				state.preindent = nil
  26.417 -			end
  26.418 -		elseif state.indent == state.previndent + 1 then
  26.419 -			popif("block")
  26.420 -			push("indent")
  26.421 -		elseif state.indent >= state.previndent + 2 then
  26.422 -			if not state.preindent then
  26.423 -				state.preindent = state.previndent + 2
  26.424 -				push("indent")
  26.425 -				push("pre")
  26.426 -			end
  26.427 -		end
  26.428 -
  26.429 -
  26.430 -		if not state.preindent then
  26.431 -
  26.432 -			-- function ( INCLUDE(name) )
  26.433 -
  26.434 -			line = line:gsub("^%s*(INCLUDE_?%a*)%(%s*(.*)%s*%)%s*$", function(key, line)
  26.435 -				if key == "INCLUDE" or key == "INCLUDE_STATIC" then
  26.436 -					local args = { }
  26.437 -					line:gsub(",?([^,]*)", function(a)
  26.438 -						a = a:match("^%s*(%S.-)%s*$")
  26.439 -						if a then
  26.440 -							insert(args, a)
  26.441 -						end
  26.442 -					end)
  26.443 -					popwhilenot("document")
  26.444 -					push("func", key == "INCLUDE", args)
  26.445 -					return ""
  26.446 -				end
  26.447 -			end)
  26.448 -			
  26.449 -			-- argument ( ======== )
  26.450 -
  26.451 -			line = line:gsub("^%s*========+%s*$", function()
  26.452 -				popwhilenot("func")
  26.453 -				push("argument")
  26.454 -				return ""
  26.455 -			end)
  26.456 -
  26.457 -			-- rule ( ----.. )
  26.458 -
  26.459 -			line = line:gsub("^%s*(%-%-%-%-+)%s*$", function(s)
  26.460 -				popwhile("block", "list", "item")
  26.461 -				push("rule")
  26.462 -				pop()
  26.463 -				return ""
  26.464 -			end)
  26.465 -
  26.466 -		end
  26.467 -
  26.468 -		--
  26.469 -
  26.470 -		state.cstack = { }
  26.471 -		state.context = { parentfirstfragment = true }
  26.472 -		pushfragment(line)
  26.473 -		pushcontext()
  26.474 -
  26.475 -		if line == "" then
  26.476 -			popwhile("block")
  26.477 -		end
  26.478 -
  26.479 -		while popcontext() do
  26.480 -			while popfragment() do
  26.481 -				line = state.line
  26.482 -
  26.483 -				if state.preindent then
  26.484 -
  26.485 -					if state.prepend then
  26.486 -						push("preline")
  26.487 -						doout("getpre", "")
  26.488 -						pop()
  26.489 -						state.prepend = nil
  26.490 -					end
  26.491 -
  26.492 -					if line == "" then
  26.493 -						state.prepend = true
  26.494 -					else
  26.495 -						push("preline")
  26.496 -						doout("getpre", (" "):rep(state.indent - 
  26.497 -							state.preindent) .. line)
  26.498 -						pop()
  26.499 -					end
  26.500 -
  26.501 -				else
  26.502 -					state.prepend = nil
  26.503 -
  26.504 -					if line ~= "" then
  26.505 -
  26.506 -						-- list/item ( * ... )
  26.507 -
  26.508 -						local _, _, b, a = line:find("^([%*%-])%s+(.*)$")
  26.509 -						if b and a then
  26.510 -							local inlist = checktop("item", "list")
  26.511 -							popwhile("item", "block")
  26.512 -							if not inlist then
  26.513 -								push("list")
  26.514 -							end
  26.515 -							if b == "*" then
  26.516 -								push("item", true)
  26.517 -							else
  26.518 -								push("item")
  26.519 -							end
  26.520 -							pushcontext("item", a)
  26.521 -							break
  26.522 -						end
  26.523 -
  26.524 -						-- table cells
  26.525 -
  26.526 -						local _, pos, a, b =
  26.527 -							line:find("^%s*(.-)%s*||%s*(.*)%s*$")
  26.528 -						if pos then
  26.529 -							if state.context.id == "table" then
  26.530 -								popuntil("cell")
  26.531 -							else
  26.532 -								state.context.id = "table"
  26.533 -								if not state.table then
  26.534 -									state.table = true
  26.535 -									push("table")
  26.536 -								end
  26.537 -								push("row")
  26.538 -							end
  26.539 -							pushfragment(b)
  26.540 -							push("cell")
  26.541 -							pushcontext("cell", a)
  26.542 -							break
  26.543 -						elseif state.context.id == "table" then
  26.544 -							popuntil("cell")
  26.545 -							push("cell")
  26.546 -							pushcontext("cell", line)
  26.547 -							break
  26.548 -						end
  26.549 -
  26.550 -						-- code
  26.551 -
  26.552 -						local _, _, a, text, b =
  26.553 -							line:find("^(.-){{(.-)}}(.*)%s*$")
  26.554 -						if text then
  26.555 -							if a == "" then
  26.556 -								if not checktop("block", "item", "cell") then
  26.557 -									push("block")
  26.558 -								end
  26.559 -								if state.context.firstfragment then
  26.560 -									doout("gettext", "")
  26.561 -								end
  26.562 -								push("code")
  26.563 -								if b == "" then b = " " end
  26.564 -								pushfragment(b)
  26.565 -								pushcontext("code", text)
  26.566 -							else
  26.567 -								pushfragment("{{", text, "}}", b)
  26.568 -								pushfragment(a)
  26.569 -								pushcontext()
  26.570 -							end
  26.571 -							break
  26.572 -						end
  26.573 -
  26.574 -						-- emphasis
  26.575 -
  26.576 -						local _, _, a, x, text, y, b =
  26.577 -							line:find("^(.-)(''+)(.-)(''+)(.*)$")
  26.578 -						if text then
  26.579 -							if a == "" then
  26.580 -								x, y = x:len(), y:len()
  26.581 -								local len = min(x, y, 4)
  26.582 -								if not checktop("block", "item", "cell") then
  26.583 -									push("block")
  26.584 -								end
  26.585 -								if state.context.firstfragment then
  26.586 -									doout("gettext", "")
  26.587 -								end
  26.588 -								push("emphasis", len - 1)
  26.589 -								if b == "" then b = " " end
  26.590 -								pushfragment(b)
  26.591 -								pushcontext("emphasis", text)
  26.592 -							else
  26.593 -								pushfragment(x, text, y, b)
  26.594 -								pushfragment(a)
  26.595 -								pushcontext()
  26.596 -							end
  26.597 -							break
  26.598 -						end
  26.599 -
  26.600 -						-- [[link]], [[title][link]]
  26.601 -
  26.602 -						if state.context.id ~= "link"
  26.603 -							and state.context.id ~= "code" then
  26.604 -							local a, title, link, b
  26.605 -							a, title, link, b = -- [[text][...]]
  26.606 -								line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
  26.607 -							if not link then -- [[../..]]
  26.608 -								a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
  26.609 -							end
  26.610 -							if link then
  26.611 -								local isurl = link:match("^%a*:/?/?.*$")
  26.612 -								if a == "" then
  26.613 -									if not checktop("block", "item", "cell") then
  26.614 -										push("block")
  26.615 -									end
  26.616 -									if state.context.firstfragment then
  26.617 -										doout("gettext", "")
  26.618 -									end
  26.619 -									push("link", link, isurl)
  26.620 -									if b == "" then b = " " end
  26.621 -									pushfragment(b)
  26.622 -									pushcontext("link", title or link)
  26.623 -								else
  26.624 -									pushfragment("[[", title or link, "][", link, "]]", b)
  26.625 -									pushfragment(a)
  26.626 -									pushcontext()
  26.627 -								end
  26.628 -								break
  26.629 -							end
  26.630 -						end
  26.631 -
  26.632 -						-- imglink (@@/images/partner/aladdin.gif@@)
  26.633 -
  26.634 -						line = line:gsub("@@(.-)@@", function(link)
  26.635 -							push("image", link)
  26.636 -							pop()
  26.637 -							return ""
  26.638 -						end)
  26.639 -
  26.640 -						-- head ( = ... = )
  26.641 -
  26.642 -						line = line:gsub("(=+)%s+(.*)%s+(=+)",
  26.643 -							function(s1, text, s2)
  26.644 -							local l = min(s1:len(), s2:len(), 5)
  26.645 -							popwhile("block", "item", "list")
  26.646 -							push("head", l)
  26.647 -							return text
  26.648 -						end)
  26.649 -
  26.650 -						-- output
  26.651 -
  26.652 -						if line ~= "" then
  26.653 -							if not checktop("item", "block", "cell", "pre",
  26.654 -								"head", "emphasis", "link", "code") then
  26.655 -								popwhile("item", "list", "pre", "code")
  26.656 -								push("block")
  26.657 -							end
  26.658 -							if top() == "code" then
  26.659 -								doout("getcode", line, top())
  26.660 -							else
  26.661 -								doout("gettext", line, top())
  26.662 -							end
  26.663 -						end
  26.664 -						popif("emphasis", "head", "link", "code")
  26.665 -					end
  26.666 -				end
  26.667 -			end
  26.668 -		end
  26.669 -
  26.670 -		state.previndent = state.indent
  26.671 -		state.lnr = state.lnr + 1
  26.672 -	end
  26.673 -
  26.674 -	popuntil()
  26.675 -	doout("exit")
  26.676 -end
  26.677 -
  26.678 -
  26.679 -function main(s)
  26.680 -	local t = { }
  26.681 -	local state = { inbuf = s, genfunc = gen_html,
  26.682 -		out = function(s) insert(t, s) end }
  26.683 -	parse(state)
  26.684 - 	return concat(t), state.is_dynamic_content
  26.685 -end
    27.1 --- a/extensions/checkaccept.lua	Sun May 20 18:22:42 2007 +0200
    27.2 +++ b/extensions/checkaccept.lua	Sun May 20 18:28:42 2007 +0200
    27.3 @@ -1,20 +1,18 @@
    27.4  <%
    27.5  
    27.6  os = require "os"
    27.7 -cgi = require "tek.cgi"
    27.8 -args = require "tek.cgi.request.args"
    27.9 -cookies = require "tek.cgi.header.cookies"
   27.10 -
   27.11 +http = require "tek.class.http"
   27.12 +cookies = require "tek.class.http.request":new():getcookies()
   27.13  
   27.14  local cookiename = arg[1]
   27.15  local introtext = arg[2] -- diclaimer
   27.16  local sorrytext = arg[3] -- when declined
   27.17  local regulartext = arg[#arg] -- when first option accepted
   27.18 -local accept = args.accept
   27.19 +local accept = loona.args.accept
   27.20  
   27.21  
   27.22 -if cookies[cookiename] then
   27.23 -	local cdata = cgi.decodeurl(cookies[cookiename])
   27.24 +if cookies and cookies[cookiename] then
   27.25 +	local cdata = http.decodeurl(cookies[cookiename])
   27.26  	accept = accept or cdata:match("(.+)")
   27.27  end
   27.28  
   27.29 @@ -23,7 +21,7 @@
   27.30  		"(%w+)%s(%w+)%s(.+)%s(%d+):(%d+):(%d+)%s(%d+)", function(wdy,mon,d,h,m,s,y)
   27.31  			return wdy..", "..d.."-"..mon.."-"..(y+1).." "..h..":"..m..":"..s.." GMT"
   27.32  		end)
   27.33 -	cdata = cgi.encodeurl(accept)
   27.34 +	cdata = http.encodeurl(accept)
   27.35  	loona:setheader("Set-cookie: "..cookiename.."="..cdata.."; expires="..expdate.."; path=/;\n")
   27.36  end
   27.37  
   27.38 @@ -51,7 +49,7 @@
   27.39  					<%end
   27.40  				end%>
   27.41  				<input type="submit" name="submit" value="<%=loona.locale.ACCEPTFORM_CONTINUE%>" />
   27.42 -				<%=loona:hidden("lang", args.lang)%>
   27.43 +				<%=loona:hidden("lang", loona.args.lang)%>
   27.44  				<%=loona:hidden("profile", loona.profile)%>
   27.45  				<%=loona:hidden("session", loona.session and loona.session.id)%>
   27.46  			</fieldset>
    28.1 --- a/extensions/contactform.lua	Sun May 20 18:22:42 2007 +0200
    28.2 +++ b/extensions/contactform.lua	Sun May 20 18:28:42 2007 +0200
    28.3 @@ -1,9 +1,7 @@
    28.4  <%
    28.5  
    28.6 -io = require "io"
    28.7 -cgi = require "tek.cgi"
    28.8 -args = require "tek.cgi.request.args"
    28.9 -
   28.10 +local io = require "io"
   28.11 +local args = loona.args
   28.12  
   28.13  local function purify(s, maxlen)
   28.14  	s = s:sub(1, maxlen)
   28.15 @@ -68,7 +66,6 @@
   28.16  
   28.17  %>
   28.18  
   28.19 -
   28.20  <%if ret == 1 then%>
   28.21  	
   28.22  	<%printsuccessmsg()%>
   28.23 @@ -106,7 +103,7 @@
   28.24  				<textarea cols="65" rows="10" name="mailbody"></textarea>
   28.25  				<br />
   28.26  				<input type="submit" name="sendform" value="<%=loona.locale.CONTACTFORM_SEND%>" />
   28.27 -				<%=loona:hidden("lang", args.lang)%>
   28.28 +				<%=loona:hidden("lang", loona.args.lang)%>
   28.29  				<%=loona:hidden("profile", loona.profile)%>
   28.30  				<%=loona:hidden("session", loona.session and loona.session.id)%>
   28.31  			</fieldset>
    29.1 --- a/extensions/login.lua	Sun May 20 18:22:42 2007 +0200
    29.2 +++ b/extensions/login.lua	Sun May 20 18:28:42 2007 +0200
    29.3 @@ -1,8 +1,5 @@
    29.4  <%
    29.5  
    29.6 -local tek = require "tek"
    29.7 -local args = require "tek.cgi.request.args"
    29.8 -
    29.9  if loona.authuser then%>
   29.10  
   29.11  	<h3><%=loona.locale.LOGINFORM_LOGGEDAS%> <%=loona.authuser%>.</h3>
   29.12 @@ -36,7 +33,7 @@
   29.13  				<input type="password" size="20" maxlength="40" name="password" />
   29.14  				<br />
   29.15  				<input type="submit" value="<%=loona.locale.LOGINFORM_LOGIN%>" />
   29.16 -				<%=loona:hidden("lang", args.lang)%>
   29.17 +				<%=loona:hidden("lang", loona.args.lang)%>
   29.18  			</fieldset>
   29.19  		</form>
   29.20  	</div>
    30.1 --- a/extensions/search.lua	Sun May 20 18:22:42 2007 +0200
    30.2 +++ b/extensions/search.lua	Sun May 20 18:28:42 2007 +0200
    30.3 @@ -1,12 +1,12 @@
    30.4  <%
    30.5  
    30.6  io = require "io"
    30.7 -tek = require "tek"
    30.8 -util = require "tek.util"
    30.9 -cgi = require "tek.cgi"
   30.10 -args = require "tek.cgi.request.args"
   30.11 +lib = require "tek.lib"
   30.12 +util = require "tek.class.loona.util"
   30.13 +http = require "tek.class.http"
   30.14 +args = loona.args
   30.15  
   30.16 -local searchtext = cgi.encodeform(args.searchtext or "")
   30.17 +local searchtext = http.encodeform(args.searchtext or "")
   30.18  
   30.19  
   30.20  --
   30.21 @@ -22,7 +22,7 @@
   30.22  
   30.23  local function normalize(s)
   30.24  	local t = { }
   30.25 -	for c in tek.utf8values(s) do
   30.26 +	for c in lib.utf8values(s) do
   30.27  		if transtab[c] then
   30.28  			table.insert(t, transtab[c])
   30.29  		elseif (c >= 32 and c <= 126) or (c >= 161 and c <= 255) then
   30.30 @@ -117,7 +117,7 @@
   30.31  			</label>
   30.32  			<input class="searchtext" type="text" size="40" maxlength="40" name="searchtext" value="<%=searchtext%>" />
   30.33  			<input type="submit" value="<%=loona.locale.SEARCHFORM_SEARCH%>" />
   30.34 -			<%=loona:hidden("lang", args.lang)%>
   30.35 +			<%=loona:hidden("lang", loona.args.lang)%>
   30.36  			<%=loona:hidden("profile", loona.profile)%>
   30.37  			<%=loona:hidden("session", loona.session and loona.session.id)%>
   30.38  		</fieldset>