Moved markup class to a more common place; anchors in local documents should
authorTimm S. Mueller <tmueller@neoscientists.org>
Sat, 15 Dec 2007 02:35:05 +0100
changeset 2197cb83473b4dd
parent 218 72b5905b36e8
child 220 b2e0af2b5a8f
Moved markup class to a more common place; anchors in local documents should
be supported now (even in an unrolled site's documents)
cgi-bin/tek/class/loona.lua
cgi-bin/tek/class/loona/markup.lua
cgi-bin/tek/class/markup.lua
     1.1 --- a/cgi-bin/tek/class/loona.lua	Tue Dec 11 18:09:21 2007 +0100
     1.2 +++ b/cgi-bin/tek/class/loona.lua	Sat Dec 15 02:35:05 2007 +0100
     1.3 @@ -12,9 +12,7 @@
     1.4  local cgi = require "tek.class.cgi"
     1.5  local Request = require "tek.class.cgi.request"
     1.6  local util = require "tek.class.loona.util"
     1.7 -local markup = require "tek.class.loona.markup"
     1.8 --- local db = require "tek.lib.debug"
     1.9 --- db.level = 4
    1.10 +local Markup = require "tek.class.markup"
    1.11  
    1.12  local boxed_G = {
    1.13  	string = string, table = table,
    1.14 @@ -39,7 +37,49 @@
    1.15  -------------------------------------------------------------------------------
    1.16  
    1.17  module("tek.class.loona", Class)
    1.18 -_VERSION = "LOona Class 5.0"
    1.19 +_VERSION = "LOona Class 5.1"
    1.20 +
    1.21 +-------------------------------------------------------------------------------
    1.22 +--	Markup:
    1.23 +-------------------------------------------------------------------------------
    1.24 +
    1.25 +Markup = Markup:newClass()
    1.26 +
    1.27 +function Markup:init()
    1.28 +	self.depth = 1
    1.29 +	return ''
    1.30 +end
    1.31 +
    1.32 +function Markup:exit()
    1.33 +	return ''
    1.34 +end
    1.35 +
    1.36 +function Markup:argument(name, text)
    1.37 +	return ', function()%>', '<%end'
    1.38 +end
    1.39 +
    1.40 +function Markup:func(is_dynamic, args)
    1.41 +	if is_dynamic then
    1.42 +		self.is_dynamic_content = true
    1.43 +	end
    1.44 +	local t = { '<%loona:include("', args[1], '"' }
    1.45 +	table.remove(args, 1)
    1.46 +	for _, v in ipairs(args) do
    1.47 +		insert(t, ',' .. v)
    1.48 +	end
    1.49 +	return table.concat(t), ')%>'
    1.50 +end
    1.51 +
    1.52 +function Markup:link(link)
    1.53 +	local func = link:match("^(.*)%(%)$")
    1.54 +	if func then
    1.55 +		return '<%=loona:link("' .. '#' .. self:encodeurl(func, true) .. '", [[', ']])%>'
    1.56 +	elseif link:match("^%a*://.*$") then
    1.57 +		return '<%=loona:elink("' .. self:encodeurl(link, true) .. '", [[', ']])%>'
    1.58 +	else
    1.59 +		return '<%=loona:link("' .. link .. '", [[', ']])%>'
    1.60 +	end
    1.61 +end
    1.62  
    1.63  -------------------------------------------------------------------------------
    1.64  --	class Session:
    1.65 @@ -526,7 +566,7 @@
    1.66  		table.insert(href, arg.name .. "=" .. cgi.encodeurl(arg.value))
    1.67  	end
    1.68  	if anch then
    1.69 -		insert(href, anch)
    1.70 +		table.insert(href, anch)
    1.71  	end
    1.72  	return table.concat(href)
    1.73  end
    1.74 @@ -1544,7 +1584,11 @@
    1.75  
    1.76  
    1.77  function Loona:domarkup(s)
    1.78 -	return markup.load(s)
    1.79 +	local t = { }
    1.80 +	local is_dynamic = Markup:new {
    1.81 +		input = s, features = "hespcadlintf",
    1.82 +		indentchar = " ", wrfunc = function(s) table.insert(t, s) end }:run()
    1.83 +	return table.concat(t), is_dynamic
    1.84  end
    1.85  
    1.86  
    1.87 @@ -1682,17 +1726,23 @@
    1.88  	self.document = self.requestdocument .. "/" .. self.sectionpath
    1.89  	if self.authuser then
    1.90  		self.getdocname = function(self, path)
    1.91 -			return self.requestdocument .. "/" .. (path or self.sectionpath)
    1.92 +			local url, anch = path:match("^([^#]*)(#?.*)$")
    1.93 +			path = url ~= "" and url
    1.94 +			anch = anch or ""
    1.95 +			return self.requestdocument .. "/" .. (path or self.sectionpath) .. anch
    1.96  		end
    1.97  	else
    1.98  		self.getdocname = function(self, path, haveargs)
    1.99 +			local url, anch = path:match("^([^#]*)(#?.*)$")
   1.100 +			path = url ~= "" and url
   1.101 +			anch = anch or ""
   1.102  			local dyn, exists
   1.103  			dyn, path, exists = self:isdynamic(path or self.sectionpath)
   1.104  			if dyn or haveargs or not exists then
   1.105 -				return self.requestdocument .. "/" .. path
   1.106 +				return self.requestdocument .. "/" .. path .. anch
   1.107  			end
   1.108  			path = path == self.config.defname and "index" or path
   1.109 -			return "/" .. path:gsub("/", "_") .. ".html"
   1.110 +			return "/" .. path:gsub("/", "_") .. ".html" .. anch
   1.111  		end
   1.112  	end
   1.113  
     2.1 --- a/cgi-bin/tek/class/loona/markup.lua	Tue Dec 11 18:09:21 2007 +0100
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,761 +0,0 @@
     2.4 -
     2.5 ---
     2.6 ---	tek.class.loona.markup - LOona WIKI-style markup parser
     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 cgi = require "tek.class.cgi"
    2.12 -local util = require "tek.class.loona.util"
    2.13 -
    2.14 -local unpack, min, ipairs = unpack, math.min, ipairs
    2.15 -local remove, insert, concat, foreachi =
    2.16 -	table.remove, table.insert, table.concat, table.foreachi
    2.17 -
    2.18 -
    2.19 -module "tek.class.loona.markup"
    2.20 -_VERSION = "LOona markup parser 2.1"
    2.21 -
    2.22 -
    2.23 ---	iterate over lines in a string
    2.24 -
    2.25 -local function lines(s)
    2.26 -	local pos = 1
    2.27 -	return function()
    2.28 -		if pos then
    2.29 -			local a, e = s:find("[\r]?\n", pos)
    2.30 -			local o = pos
    2.31 -			if not a then
    2.32 -				pos = nil
    2.33 -				return s:sub(o)
    2.34 -			else
    2.35 -				pos = e + 1
    2.36 -				return s:sub(o, a - 1)
    2.37 -			end
    2.38 -		end
    2.39 -	end
    2.40 -end
    2.41 -
    2.42 -
    2.43 ---	get a SGML/XML tag
    2.44 -
    2.45 -local function gettagml(state, id, tag, open)
    2.46 -	if tag then
    2.47 -		local tab = { tag }
    2.48 -		if id == "link" or id == "emphasis" or id == "code" then
    2.49 -			if not state.brpend and not state.inline then
    2.50 -				insert(tab, 1, ("\t"):rep(state.depth))
    2.51 -			end
    2.52 -			state.inline = true
    2.53 -			state.brpend = nil
    2.54 -		else
    2.55 -			if id == "image" then
    2.56 -				if not state.inline then
    2.57 -					insert(tab, 1, ("\t"):rep(state.depth))
    2.58 -				end
    2.59 -				state.brpend = true
    2.60 -			else
    2.61 -				if id == "pre" or id == "preline" then
    2.62 -					state.brpend = nil
    2.63 -				elseif open or id ~= "preline" then
    2.64 -					insert(tab, 1, ("\t"):rep(state.depth))
    2.65 -					if not open and state.inline then
    2.66 -						state.brpend = true
    2.67 -					end
    2.68 -				end
    2.69 -				if state.brpend then
    2.70 -					insert(tab, 1, "\n")
    2.71 -				end
    2.72 -				if id ~= "preline" or not open then
    2.73 -					insert(tab, "\n")
    2.74 -				end
    2.75 -				state.inline = nil
    2.76 -				state.brpend = nil
    2.77 -			end
    2.78 -		end
    2.79 -		return concat(tab)
    2.80 -	end
    2.81 -end
    2.82 -
    2.83 -
    2.84 ---	get a SGML/XML line of text
    2.85 -
    2.86 -local function gettextml(state, line, id)
    2.87 -	local tab = { }
    2.88 -	if state.brpend then
    2.89 -		insert(tab, "\n")
    2.90 -		insert(tab, ("\t"):rep(state.depth))
    2.91 -	else
    2.92 -		if id ~= "link" and id ~= "emphasis" and id ~= "code" then
    2.93 -			if not state.inline then
    2.94 -				insert(tab, ("\t"):rep(state.depth))
    2.95 -			end
    2.96 -		end
    2.97 -	end
    2.98 -	line:gsub("^(%s*)(.-)(%s*)$", function(a, b, c)
    2.99 -		if a ~= "" then
   2.100 -			insert(tab, " ")
   2.101 -		end
   2.102 -		insert(tab, b)
   2.103 -		if c ~= "" then
   2.104 -			insert(tab, " ")
   2.105 -		end
   2.106 -	end)
   2.107 -	state.brpend = true
   2.108 -	return concat(tab)
   2.109 -end
   2.110 -
   2.111 -
   2.112 ---	html out
   2.113 -
   2.114 -local gen_html =
   2.115 -{
   2.116 -	out = function(state, ...)
   2.117 -		state.out(concat(arg))
   2.118 -	end,
   2.119 -
   2.120 -	init = function(state)
   2.121 -		state.depth = 1
   2.122 -		return '' -- header
   2.123 -	end,
   2.124 -
   2.125 -	exit = function(state)
   2.126 -		return '' -- footer
   2.127 -	end,
   2.128 -
   2.129 -	gettag = function(state, id, tag, open)
   2.130 -		return gettagml(state, id, tag, open)
   2.131 -	end,
   2.132 -
   2.133 -	gettext = function(state, line, id)
   2.134 -		return util.encodeform(gettextml(state, line, id))
   2.135 -	end,
   2.136 -
   2.137 -	getpre = function(state, line)
   2.138 -		return util.encodeform(line), '\n'
   2.139 -	end,
   2.140 -
   2.141 -	getcode = function(state, line)
   2.142 -		return util.encodeform(line)
   2.143 -	end,
   2.144 -
   2.145 -	head = function(state, level, text)
   2.146 -		return '<h' .. level .. '>', '</h' .. level .. '>'
   2.147 -	end,
   2.148 -
   2.149 -	node = function(state, id, text)
   2.150 -		return ('<div class="node"><h3><a name="%s" id="%s"><code>%s</code></a></h3>'):format(
   2.151 -			id, id, text), '</div>'
   2.152 -	end,
   2.153 -
   2.154 -	headnode = function(state, id, text, len, is_code)
   2.155 -		if is_code then
   2.156 -			return ('<hr /><div class="node"><h%d><a name="%s" id="%s"><code>%s</code></a></h%d>'):format(
   2.157 -				len, id, id, text, len), '</div>'
   2.158 -		else
   2.159 -			return ('<hr /><div class="node"><h%d><a name="%s" id="%s">%s</a></h%d>'):format(
   2.160 -				len, id, id, text, len), '</div>'
   2.161 -		end
   2.162 -	end,
   2.163 -
   2.164 -	def = function(state, name)
   2.165 -		state.prebr = nil
   2.166 -		return '<div class="definition"><dfn>' .. name .. '</dfn>', '</div>'
   2.167 -	end,
   2.168 -
   2.169 -	argument = function(state, name, text)
   2.170 -		return ', function()%>', '<%end'
   2.171 -	end,
   2.172 -
   2.173 -	func = function(state, dynamic, args)
   2.174 -		if dynamic then
   2.175 -			state.is_dynamic_content = true
   2.176 -		end
   2.177 -		local t = { '<%loona:include("', args[1], '"' }
   2.178 -		remove(args, 1)
   2.179 -		for _, v in ipairs(args) do
   2.180 -			insert(t, ',' .. v)
   2.181 -		end
   2.182 -		return concat(t), ')%>'
   2.183 -	end,
   2.184 -
   2.185 -	indent = function()
   2.186 -		return '<div class="indent">', '</div>'
   2.187 -	end,
   2.188 -
   2.189 -	list = function()
   2.190 -		return '<ul>', '</ul>'
   2.191 -	end,
   2.192 -
   2.193 -	item = function(state, bullet)
   2.194 -		if bullet then
   2.195 -			return '<li>', '</li>'
   2.196 -		end
   2.197 -		return '<li style="list-style-type: none;">', '</li>'
   2.198 -	end,
   2.199 -
   2.200 -	block = function()
   2.201 -		return '<p>', '</p>'
   2.202 -	end,
   2.203 -
   2.204 -	rule = function()
   2.205 -		return '<hr />'
   2.206 -	end,
   2.207 -
   2.208 -	pre = function()
   2.209 -		return '<pre>', '</pre>'
   2.210 -	end,
   2.211 -
   2.212 -	code = function()
   2.213 -		return '<code>', '</code>'
   2.214 -	end,
   2.215 -
   2.216 -	emphasis = function(state, len, text)
   2.217 -		if len == 1 then
   2.218 -			return '<em>', '</em>'
   2.219 -		elseif len == 2 then
   2.220 -			return '<strong>', '</strong>'
   2.221 -		else
   2.222 -			return '<em><strong>', '</strong></em>'
   2.223 -		end
   2.224 -	end,
   2.225 -
   2.226 -	link = function(state, link, isurl)
   2.227 -		local isurl = link:match("^%a*://.*$")
   2.228 -		local iscode = link:match("^.*%(%)$")
   2.229 -		local func = link:match("^(.*)%(%)$")
   2.230 -		if func then
   2.231 -			return '<a href="#' .. func .. '"><code>', '</code></a>'
   2.232 -		elseif link:match("^%a*://.*$") then
   2.233 -			return '<a href="' .. link .. '">', '</a>'
   2.234 -		else
   2.235 -			return '<a href="#' .. link .. '">', '</a>'
   2.236 -		end
   2.237 -	end,
   2.238 -
   2.239 -	table = function(state, border)
   2.240 -		return '<table>', '</table>'
   2.241 -	end,
   2.242 -
   2.243 -	row = function(state)
   2.244 -		state.column = 0
   2.245 -		return '<tr>', '</tr>'
   2.246 -	end,
   2.247 -
   2.248 -	cell = function(state, border)
   2.249 -		state.column = state.column + 1
   2.250 -		return '<td class="column' .. state.column .. '">', '</td>'
   2.251 -	end,
   2.252 -
   2.253 -	image = function(state, link)
   2.254 -		return '<img src="' .. cgi.encodeurl(link, true) .. '" />'
   2.255 -	end,
   2.256 -}
   2.257 -
   2.258 -
   2.259 ---	parser
   2.260 -
   2.261 -local function parse(state, ...)
   2.262 -
   2.263 -	--	do a generator function by ID
   2.264 -
   2.265 -	local function doid(id, ...)
   2.266 -		if state.genfunc and state.genfunc[id] then
   2.267 -			return state.genfunc[id](state, unpack(arg))
   2.268 -		end
   2.269 -	end
   2.270 -
   2.271 -	local function doout(id, ...)
   2.272 -		doid("out", doid(id, unpack(arg)))
   2.273 -	end
   2.274 -
   2.275 -	local function push(id, ...)
   2.276 -		local opentag, closetag = doid(id, unpack(arg))
   2.277 -		doout("gettag", id, opentag, true)
   2.278 -		insert(state.stack, { id = id, closetag = closetag })
   2.279 -		state.depth = state.depth + 1
   2.280 -	end
   2.281 -
   2.282 -	local function pop()
   2.283 -		local e = remove(state.stack)
   2.284 -		if e then
   2.285 -			state.depth = state.depth - 1
   2.286 -			doout("gettag", e.id, e.closetag)
   2.287 -			return e.id
   2.288 -		end
   2.289 -	end
   2.290 -
   2.291 -	local function top()
   2.292 -		local e = state.stack[#state.stack]
   2.293 -		if e then
   2.294 -			return e.id
   2.295 -		end
   2.296 -	end
   2.297 -
   2.298 -	local function popuntil(...)
   2.299 -		local i
   2.300 -		repeat
   2.301 -			i = pop()
   2.302 -			for j = 1, arg.n do
   2.303 -				if i == arg[j] then
   2.304 -					return
   2.305 -				end
   2.306 -			end
   2.307 -		until not i
   2.308 -	end
   2.309 -
   2.310 -	local function popwhilenot(...)
   2.311 -		local i
   2.312 -		repeat
   2.313 -			i = top()
   2.314 -			for j = 1, arg.n do
   2.315 -				if i == arg[j] then
   2.316 -					return
   2.317 -				end
   2.318 -			end
   2.319 -			pop()
   2.320 -		until not i
   2.321 -	end
   2.322 -
   2.323 -	local function popwhile(...)
   2.324 -		local cont
   2.325 -		repeat
   2.326 -			local id = top()
   2.327 -			cont = false
   2.328 -			for i = 1, arg.n do
   2.329 -				if arg[i] == id then
   2.330 -					local i = pop()
   2.331 -					cont = true
   2.332 -					break
   2.333 -				end
   2.334 -			end
   2.335 -		until not cont
   2.336 -	end
   2.337 -
   2.338 -	local function popif(...)
   2.339 -		local i = top()
   2.340 -		for j = 1, arg.n do
   2.341 -			if i == arg[j] then
   2.342 -				pop()
   2.343 -				return
   2.344 -			end
   2.345 -		end
   2.346 -	end
   2.347 -
   2.348 -	local function checktop(...)
   2.349 -		local i = top()
   2.350 -		return foreachi(arg, function(_, a)
   2.351 -			if i == a then
   2.352 -				return true
   2.353 -			end
   2.354 -		end)
   2.355 -	end
   2.356 -
   2.357 -	local function pushfragment(...)
   2.358 -		local line = concat(arg)
   2.359 -		if not state.context.fragments then
   2.360 -			state.context.fragments = { }
   2.361 -		end
   2.362 -		state.id = (state.id or 0) + 1
   2.363 -		insert(state.context.fragments, 1, { line = line, id = state.id })
   2.364 -		state.context.topid = state.id
   2.365 -	end
   2.366 -
   2.367 -	local function popfragment()
   2.368 -		state.context.firstfragment = nil
   2.369 -		local frag = remove(state.context.fragments, 1)
   2.370 -		if frag then
   2.371 -			state.line = frag.line
   2.372 -			if frag.id == state.context.topid then
   2.373 -				state.context.firstfragment = state.context.parentfirstfragment
   2.374 -			end
   2.375 -		else
   2.376 -			state.line = nil
   2.377 -		end
   2.378 -		return state.line
   2.379 -	end
   2.380 -
   2.381 -	local function pushcontext(id, line)
   2.382 -		insert(state.cstack, state.context)
   2.383 -		if id then
   2.384 -			state.context = { id = id, fragments = { } }
   2.385 -			pushfragment(line)
   2.386 -			insert(state.cstack, state.context)
   2.387 -		end
   2.388 -	end
   2.389 -
   2.390 -	local function popcontext()
   2.391 -		state.context = remove(state.cstack)
   2.392 -		return state.context
   2.393 -	end
   2.394 -
   2.395 -	-- parse
   2.396 -
   2.397 -	state.lnr = 1
   2.398 -	state.previndent = 0
   2.399 -	state.stack = { }
   2.400 -	state.depth = 0
   2.401 -	state.table = nil
   2.402 -
   2.403 -	doout("init")
   2.404 -	push("document")
   2.405 -
   2.406 -	for line in lines(state.inbuf) do
   2.407 -
   2.408 -		state.indent = 0
   2.409 -		line = line:gsub("^( *)(.-)%s*$", function(s, t)
   2.410 -			if t ~= "" then
   2.411 -				state.indent = s:len()
   2.412 -			else
   2.413 -				state.indent = state.previndent
   2.414 -			end
   2.415 -			return t
   2.416 -		end)
   2.417 -
   2.418 -		state.istabline = line:find("||", 1, 1)
   2.419 -		if state.istabline then
   2.420 -			state.indent = state.previndent
   2.421 -		end
   2.422 -		if state.table then
   2.423 -			popuntil("row")
   2.424 -			if not state.istabline then
   2.425 -				popuntil("table")
   2.426 -				state.table = nil
   2.427 -			end
   2.428 -		end
   2.429 -
   2.430 -
   2.431 -		if state.indent < state.previndent then
   2.432 -			if not state.preindent or state.indent < state.preindent then
   2.433 -				local i = state.indent
   2.434 -				while i < state.previndent do
   2.435 -					popuntil("indent", "pre")
   2.436 -					i = i + 1
   2.437 -				end
   2.438 -				state.preindent = nil
   2.439 -			end
   2.440 -		elseif state.indent == state.previndent + 1 then
   2.441 -			popif("block")
   2.442 -			push("indent")
   2.443 -		elseif state.indent >= state.previndent + 2 then
   2.444 -			if not state.preindent then
   2.445 -				state.preindent = state.previndent + 2
   2.446 -				popif("block")
   2.447 -				push("pre")
   2.448 -			end
   2.449 -		end
   2.450 -
   2.451 -
   2.452 -		if not state.preindent then
   2.453 -
   2.454 -			-- def ( SYNOPSIS )
   2.455 -			line = line:gsub("^(%u[%u%d%s_]+)::$", function(name)
   2.456 -				popwhile("def", "item", "list", "block", "pre")
   2.457 -				push("def", name)
   2.458 - 				return ""
   2.459 -			end)
   2.460 -
   2.461 -			-- function ( INCLUDE(name) )
   2.462 -
   2.463 -			line = line:gsub("^%s*(INCLUDE_?%a*)%(%s*(.*)%s*%)%s*$", function(key, line)
   2.464 -				if key == "INCLUDE" or key == "INCLUDE_STATIC" then
   2.465 -					local args = { }
   2.466 -					line:gsub(",?([^,]*)", function(a)
   2.467 -						a = a:match("^%s*(%S.-)%s*$")
   2.468 -						if a then
   2.469 -							insert(args, a)
   2.470 -						end
   2.471 -					end)
   2.472 -					popwhilenot("document")
   2.473 -					push("func", key == "INCLUDE", args)
   2.474 -					return ""
   2.475 -				end
   2.476 -			end)
   2.477 -
   2.478 -			-- argument ( ======== )
   2.479 -
   2.480 -			line = line:gsub("^%s*========+%s*$", function()
   2.481 -				popwhilenot("func")
   2.482 -				push("argument")
   2.483 -				return ""
   2.484 -			end)
   2.485 -
   2.486 -			-- node ( @@ ... : ... @@ )
   2.487 -
   2.488 -			line = line:gsub(
   2.489 -				"^%s*@@%s+(.+)%s+:%s+(.-)%s+@@%s*$", function(text, id)
   2.490 -				popwhilenot("document")
   2.491 -				id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
   2.492 -				push("node", id, text)
   2.493 -				return ""
   2.494 -			end)
   2.495 -
   2.496 -			-- node ( @@ ... @@ )
   2.497 -
   2.498 -			line = line:gsub("^%s*@@%s+(.+)%s+@@%s*$", function(text)
   2.499 -				popwhilenot("document")
   2.500 -				local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
   2.501 -				push("node", id, text)
   2.502 -				return ""
   2.503 -			end)
   2.504 -
   2.505 -			-- headnode ( ==( id : text )== )
   2.506 -
   2.507 -			line = line:gsub("^%s*(=+[%(%[])%s+(.+)%s+:%s+(.+)%s+([%)%]]=+)%s*$",
   2.508 -				function(s1, id, text, s2)
   2.509 -				local l = min(s1:len() - 1, s2:len() - 1, 5)
   2.510 -				popwhilenot("document")
   2.511 -				local id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
   2.512 -				push("headnode", id, text, l, s2:sub(1, 1) == "]")
   2.513 -				return ""
   2.514 -			end)
   2.515 -
   2.516 -			-- headnode ( ==( text )== )
   2.517 -
   2.518 -			line = line:gsub("^%s*(=+[%(%[])%s+(.*)%s+([%)%]]=+)%s*$",
   2.519 -				function(s1, text, s2)
   2.520 -				local l = min(s1:len(), s2:len(), 5)
   2.521 -				popwhilenot("document")
   2.522 -				local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
   2.523 -				push("headnode", id, text, l, s2:sub(1, 1) == "]")
   2.524 -				return ""
   2.525 -			end)
   2.526 -
   2.527 -			-- rule ( ----.. )
   2.528 -
   2.529 -			line = line:gsub("^%s*(%-%-%-%-+)%s*$", function(s)
   2.530 -				popwhile("block", "list", "item")
   2.531 -				push("rule")
   2.532 -				pop()
   2.533 -				return ""
   2.534 -			end)
   2.535 -
   2.536 -		end
   2.537 -
   2.538 -		--
   2.539 -
   2.540 -		state.cstack = { }
   2.541 -		state.context = { parentfirstfragment = true }
   2.542 -		pushfragment(line)
   2.543 -		pushcontext()
   2.544 -
   2.545 -		if line == "" then
   2.546 -			popwhile("block")
   2.547 -		end
   2.548 -
   2.549 -		while popcontext() do
   2.550 -			while popfragment() do
   2.551 -				line = state.line
   2.552 -
   2.553 -				if state.preindent then
   2.554 -
   2.555 -					if state.prepend then
   2.556 -						push("preline")
   2.557 -						doout("getpre", "")
   2.558 -						pop()
   2.559 -						state.prepend = nil
   2.560 -					end
   2.561 -
   2.562 -					if line == "" then
   2.563 -						state.prepend = true
   2.564 -					else
   2.565 -						push("preline")
   2.566 -						doout("getpre", (" "):rep(state.indent -
   2.567 -							state.preindent) .. line)
   2.568 -						pop()
   2.569 -					end
   2.570 -
   2.571 -				else
   2.572 -					state.prepend = nil
   2.573 -
   2.574 -					if line ~= "" then
   2.575 -
   2.576 -						-- list/item ( * ... )
   2.577 -
   2.578 -						local _, _, b, a = line:find("^([%*%-])%s+(.*)$")
   2.579 -						if b and a then
   2.580 -							local inlist = checktop("item", "list")
   2.581 -							popwhile("item", "block")
   2.582 -							if not inlist then
   2.583 -								push("list")
   2.584 -							end
   2.585 -							if b == "*" then
   2.586 -								push("item", true)
   2.587 -							else
   2.588 -								push("item")
   2.589 -							end
   2.590 -							pushcontext("item", a)
   2.591 -							break
   2.592 -						end
   2.593 -
   2.594 -						-- table cells
   2.595 -
   2.596 -						local _, pos, a, b =
   2.597 -							line:find("^%s*(.-)%s*||%s*(.*)%s*$")
   2.598 -						if pos then
   2.599 -							if state.context.id == "table" then
   2.600 -								popuntil("cell")
   2.601 -							else
   2.602 -								state.context.id = "table"
   2.603 -								if not state.table then
   2.604 -									state.table = true
   2.605 -									push("table")
   2.606 -								end
   2.607 -								push("row")
   2.608 -							end
   2.609 -							pushfragment(b)
   2.610 -							push("cell")
   2.611 -							pushcontext("cell", a)
   2.612 -							break
   2.613 -						elseif state.context.id == "table" then
   2.614 -							popuntil("cell")
   2.615 -							push("cell")
   2.616 -							pushcontext("cell", line)
   2.617 -							break
   2.618 -						end
   2.619 -
   2.620 -						-- code
   2.621 -
   2.622 -						local _, _, a, text, b =
   2.623 -							line:find("^(.-){{(.-)}}(.*)%s*$")
   2.624 -						if text then
   2.625 -							if a == "" then
   2.626 -								if not checktop("block", "item", "cell") then
   2.627 -									push("block")
   2.628 -								end
   2.629 -								if state.context.firstfragment then
   2.630 -									doout("gettext", "")
   2.631 -								end
   2.632 -								push("code")
   2.633 -								if b == "" then b = " " end
   2.634 -								pushfragment(b)
   2.635 -								pushcontext("code", text)
   2.636 -							else
   2.637 -								pushfragment("{{", text, "}}", b)
   2.638 -								pushfragment(a)
   2.639 -								pushcontext()
   2.640 -							end
   2.641 -							break
   2.642 -						end
   2.643 -
   2.644 -						-- emphasis
   2.645 -
   2.646 -						local _, _, a, x, text, y, b =
   2.647 -							line:find("^(.-)(''+)(.-)(''+)(.*)$")
   2.648 -						if text then
   2.649 -							if a == "" then
   2.650 -								x, y = x:len(), y:len()
   2.651 -								local len = min(x, y, 4)
   2.652 -								if not checktop("block", "item", "cell") then
   2.653 -									push("block")
   2.654 -								end
   2.655 -								if state.context.firstfragment then
   2.656 -									doout("gettext", "")
   2.657 -								end
   2.658 -								push("emphasis", len - 1)
   2.659 -								if b == "" then b = " " end
   2.660 -								pushfragment(b)
   2.661 -								pushcontext("emphasis", text)
   2.662 -							else
   2.663 -								pushfragment(x, text, y, b)
   2.664 -								pushfragment(a)
   2.665 -								pushcontext()
   2.666 -							end
   2.667 -							break
   2.668 -						end
   2.669 -
   2.670 -						-- [[link]], [[title][link]], function(), [[link : title]]
   2.671 -						if state.context.id ~= "link"
   2.672 -							and state.context.id ~= "code" then
   2.673 -							local a, title, link, b
   2.674 -							a, link, title, b = -- [[link : title]]
   2.675 -								line:match("^(.-)%[%[(.-)%s+:%s+(.-)%]%](.*)%s*$")
   2.676 -							if not link then
   2.677 -								a, title, link, b = -- [[text][...]]
   2.678 -									line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
   2.679 -							end
   2.680 -							if not link then -- [[....]]
   2.681 -								a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
   2.682 -							end
   2.683 -							if not link then -- class:function()
   2.684 -								a, link, b = line:match("^(.-)(%a[%w_:]-%(%))(.*)%s*$")
   2.685 -							end
   2.686 -							if not link then -- prot://foo/bar
   2.687 -								a, link, b = line:match(
   2.688 -									"^(.-)(%a*://[%w_%-%.,:;/%?=~]*)(.*)%s*$")
   2.689 -							end
   2.690 -							if link then
   2.691 -								if a == "" then
   2.692 -									if not checktop("block", "item", "cell") then
   2.693 -										push("block")
   2.694 -									end
   2.695 -									if state.context.firstfragment then
   2.696 -										doout("gettext", "")
   2.697 -									end
   2.698 -									push("link", link)
   2.699 -									if b == "" then b = " " end
   2.700 -									pushfragment(b)
   2.701 -									pushcontext("link", title or link)
   2.702 -								else
   2.703 -									pushfragment("[[", title or link, "][", link, "]]", b)
   2.704 -									pushfragment(a)
   2.705 -									pushcontext()
   2.706 -								end
   2.707 -								break
   2.708 -							end
   2.709 -						end
   2.710 -
   2.711 -						-- imglink (@@/images/partner/aladdin.gif@@)
   2.712 -
   2.713 -						line = line:gsub("@@(.-)@@", function(link)
   2.714 -							push("image", link)
   2.715 -							pop()
   2.716 -							return ""
   2.717 -						end)
   2.718 -
   2.719 -						-- head ( = ... = )
   2.720 -
   2.721 -						line = line:gsub("(=+)%s+(.*)%s+(=+)",
   2.722 -							function(s1, text, s2)
   2.723 -							local l = min(s1:len(), s2:len(), 5)
   2.724 -							popwhile("block", "item", "list")
   2.725 -							push("head", l)
   2.726 -							return text
   2.727 -						end)
   2.728 -
   2.729 -						-- output
   2.730 -
   2.731 -						if line ~= "" then
   2.732 -							if not checktop("item", "block", "cell", "pre",
   2.733 -								"head", "emphasis", "link", "code") then
   2.734 -								popwhile("item", "list", "pre", "code")
   2.735 -								push("block")
   2.736 -							end
   2.737 -							if top() == "code" then
   2.738 -								doout("getcode", line, top())
   2.739 -							else
   2.740 -								doout("gettext", line, top())
   2.741 -							end
   2.742 -						end
   2.743 -						popif("emphasis", "head", "link", "code")
   2.744 -					end
   2.745 -				end
   2.746 -			end
   2.747 -		end
   2.748 -
   2.749 -		state.previndent = state.indent
   2.750 -		state.lnr = state.lnr + 1
   2.751 -	end
   2.752 -
   2.753 -	popuntil()
   2.754 -	doout("exit")
   2.755 -end
   2.756 -
   2.757 -
   2.758 -function load(s)
   2.759 -	local t = { }
   2.760 -	local state = { inbuf = s, genfunc = gen_html,
   2.761 -		out = function(s) insert(t, s) end }
   2.762 -	parse(state)
   2.763 - 	return concat(t), state.is_dynamic_content
   2.764 -end
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/cgi-bin/tek/class/markup.lua	Sat Dec 15 02:35:05 2007 +0100
     3.3 @@ -0,0 +1,983 @@
     3.4 +-------------------------------------------------------------------------------
     3.5 +--
     3.6 +--	tek.class.markup
     3.7 +--	Written by Timm S. Mueller <tmueller@schulze-mueller.de>
     3.8 +--	See copyright notice in COPYRIGHT
     3.9 +--
    3.10 +--	OVERVIEW::
    3.11 +--	Markup parser - produces fancy HTML from plain text with special
    3.12 +--	formattings.
    3.13 +--
    3.14 +-------------------------------------------------------------------------------
    3.15 +
    3.16 +local Class = require "tek.class"
    3.17 +
    3.18 +local type = type
    3.19 +local insert = table.insert
    3.20 +local remove = table.remove
    3.21 +local concat = table.concat
    3.22 +local min = math.min
    3.23 +local max = math.max
    3.24 +local char = string.char
    3.25 +local stdin = io.stdin
    3.26 +local stdout = io.stdout
    3.27 +local ipairs = ipairs
    3.28 +
    3.29 +module("tek.class.markup", tek.class)
    3.30 +
    3.31 +_VERSION = "Markup 1.0"
    3.32 +local Markup = _M
    3.33 +
    3.34 +-------------------------------------------------------------------------------
    3.35 +--	iterate over lines in a string
    3.36 +-------------------------------------------------------------------------------
    3.37 +
    3.38 +local function rd_string(s)
    3.39 +	local pos = 1
    3.40 +	return function()
    3.41 +		if pos then
    3.42 +			local a, e = s:find("[\r]?\n", pos)
    3.43 +			local o = pos
    3.44 +			if not a then
    3.45 +				pos = nil
    3.46 +				return s:sub(o)
    3.47 +			else
    3.48 +				pos = e + 1
    3.49 +				return s:sub(o, a - 1)
    3.50 +			end
    3.51 +		end
    3.52 +	end
    3.53 +end
    3.54 +
    3.55 +-------------------------------------------------------------------------------
    3.56 +--	utf8values: iterator over UTF-8 encoded Unicode chracter codes
    3.57 +-------------------------------------------------------------------------------
    3.58 +
    3.59 +local function utf8values(s)
    3.60 +
    3.61 +	local readc
    3.62 +	local i = 0
    3.63 +	if type(s) == "string" then
    3.64 +		readc = function()
    3.65 +			i = i + 1
    3.66 +			return s:byte(i)
    3.67 +		end
    3.68 +	else
    3.69 +		readc = function()
    3.70 +			local c = s:read(1)
    3.71 +			return c and c:byte(1)
    3.72 +		end
    3.73 +	end
    3.74 +
    3.75 +	local accu = 0
    3.76 +	local numa = 0
    3.77 +	local min, buf
    3.78 +
    3.79 +	return function()
    3.80 +		local c
    3.81 +		while true do
    3.82 +			if buf then
    3.83 +				c = buf
    3.84 +				buf = nil
    3.85 +			else
    3.86 +				c = readc()
    3.87 +			end
    3.88 +			if not c then
    3.89 +				return
    3.90 +			end
    3.91 +			if c == 254 or c == 255 then
    3.92 +				break
    3.93 +			end
    3.94 +			if c < 128 then
    3.95 +				if numa > 0 then
    3.96 +					buf = c
    3.97 +					break
    3.98 +				end
    3.99 +				return c
   3.100 +			elseif c < 192 then
   3.101 +				if numa == 0 then break end
   3.102 +				accu = accu * 64 + c - 128
   3.103 +				numa = numa - 1
   3.104 +				if numa == 0 then
   3.105 +					if accu == 0 or accu < min or
   3.106 +						(accu >= 55296 and accu <= 57343) then
   3.107 +						break
   3.108 +					end
   3.109 +					c = accu
   3.110 +					accu = 0
   3.111 +					return c
   3.112 +				end
   3.113 +			else
   3.114 +				if numa > 0 then
   3.115 +					buf = c
   3.116 +					break
   3.117 +				end
   3.118 +				if c < 224 then
   3.119 +					min = 128
   3.120 +					accu = c - 192
   3.121 +					numa = 1
   3.122 +				elseif c < 240 then
   3.123 +					min = 2048
   3.124 +					accu = c - 224
   3.125 +					numa = 2
   3.126 +				elseif c < 248 then
   3.127 +					min = 65536
   3.128 +					accu = c - 240
   3.129 +					numa = 3
   3.130 +				elseif c < 252 then
   3.131 +					min = 2097152
   3.132 +					accu = c - 248
   3.133 +					numa = 4
   3.134 +				else
   3.135 +					min = 67108864
   3.136 +					accu = c - 252
   3.137 +					numa = 5
   3.138 +				end
   3.139 +			end
   3.140 +		end
   3.141 +		accu = 0
   3.142 +		numa = 0
   3.143 +		return 65533 -- bad character
   3.144 +	end
   3.145 +end
   3.146 +
   3.147 +-------------------------------------------------------------------------------
   3.148 +--	encodeform: encode for forms (display '<', '>', '&', '"' literally)
   3.149 +-------------------------------------------------------------------------------
   3.150 +
   3.151 +local function encodeform(s)
   3.152 +	local tab = { }
   3.153 +	if s then
   3.154 +		for c in utf8values(s) do
   3.155 +			if c == 34 then
   3.156 +				insert(tab, "&quot;")
   3.157 +			elseif c == 38 then
   3.158 +				insert(tab, "&amp;")
   3.159 +			elseif c == 60 then
   3.160 +				insert(tab, "&lt;")
   3.161 +			elseif c == 62 then
   3.162 +				insert(tab, "&gt;")
   3.163 +			elseif c == 91 or c == 93 or c > 126 then
   3.164 +				insert(tab, ("&#%03d;"):format(c))
   3.165 +			else
   3.166 +				insert(tab, char(c))
   3.167 +			end
   3.168 +		end
   3.169 +	end
   3.170 +	return concat(tab)
   3.171 +end
   3.172 +
   3.173 +-------------------------------------------------------------------------------
   3.174 +--	encodeurl: encode string to url; optionally specify a string with a
   3.175 +--	set of characters that should be left unmodified, from: $&+,/:;=?@
   3.176 +-------------------------------------------------------------------------------
   3.177 +
   3.178 +local function encodefunc(c)
   3.179 +	return ("%%%02x"):format(c:byte())
   3.180 +end
   3.181 +
   3.182 +local function encodeurl(s, excl)
   3.183 +	-- reserved chars with special meaning:
   3.184 +	local matchset = "$&+,/:;=?@"
   3.185 +	if excl == true then
   3.186 +		matchset = ""
   3.187 +	elseif excl and type(excl) == "string" then
   3.188 +		matchset = matchset:gsub("[" .. excl:gsub(".", "%%%1") .. "]", "")
   3.189 +	end
   3.190 +	-- unsafe chars are always substituted:
   3.191 +	matchset = matchset .. '"<>#%{}|\\^~[]`]'
   3.192 +	matchset = "[%z\001-\032\127-\255" .. matchset:gsub(".", "%%%1") .. "]"
   3.193 +	return s:gsub(matchset, encodefunc)
   3.194 +end
   3.195 +
   3.196 +-------------------------------------------------------------------------------
   3.197 +--	get a SGML/XML tag
   3.198 +-------------------------------------------------------------------------------
   3.199 +
   3.200 +-- local function gettagml(state, id, tag, open)
   3.201 +function Markup:getTagML(id, tag, open)
   3.202 +	if tag then
   3.203 +		local tab = { tag }
   3.204 +		if id == "link" or id == "emphasis" or id == "code" then
   3.205 +			if not self.brpend and not self.inline then
   3.206 +				insert(tab, 1, ("\t"):rep(self.depth))
   3.207 +			end
   3.208 +			self.inline = true
   3.209 +			self.brpend = nil
   3.210 +		else
   3.211 +			if id == "image" then
   3.212 +				if not self.inline then
   3.213 +					insert(tab, 1, ("\t"):rep(self.depth))
   3.214 +				end
   3.215 +				self.brpend = true
   3.216 +			else
   3.217 +				if id == "pre" or id == "preline" then
   3.218 +					self.brpend = nil
   3.219 +				elseif open or id ~= "preline" then
   3.220 +					insert(tab, 1, ("\t"):rep(self.depth))
   3.221 +					if not open and self.inline then
   3.222 +						self.brpend = true
   3.223 +					end
   3.224 +				end
   3.225 +				if self.brpend then
   3.226 +					insert(tab, 1, "\n")
   3.227 +				end
   3.228 +				if id ~= "preline" or not open then
   3.229 +					insert(tab, "\n")
   3.230 +				end
   3.231 +				self.inline = nil
   3.232 +				self.brpend = nil
   3.233 +			end
   3.234 +		end
   3.235 +		return concat(tab)
   3.236 +	end
   3.237 +end
   3.238 +
   3.239 +-------------------------------------------------------------------------------
   3.240 +--	get a SGML/XML line of text
   3.241 +-------------------------------------------------------------------------------
   3.242 +
   3.243 +function Markup:getTextML(line, id)
   3.244 +	local tab = { }
   3.245 +	if self.brpend then
   3.246 +		insert(tab, "\n")
   3.247 +		insert(tab, ("\t"):rep(self.depth))
   3.248 +	else
   3.249 +		if id ~= "link" and id ~= "emphasis" and id ~= "code" then
   3.250 +			if not self.inline then
   3.251 +				insert(tab, ("\t"):rep(self.depth))
   3.252 +			end
   3.253 +		end
   3.254 +	end
   3.255 +	line:gsub("^(%s*)(.-)(%s*)$", function(a, b, c)
   3.256 +		if a ~= "" then
   3.257 +			insert(tab, " ")
   3.258 +		end
   3.259 +		insert(tab, b)
   3.260 +		if c ~= "" then
   3.261 +			insert(tab, " ")
   3.262 +		end
   3.263 +	end)
   3.264 +	self.brpend = true
   3.265 +	return concat(tab)
   3.266 +end
   3.267 +
   3.268 +-------------------------------------------------------------------------------
   3.269 +--	definitions for output as XHTML 1.0 strict
   3.270 +-------------------------------------------------------------------------------
   3.271 +
   3.272 +function Markup:out(...)
   3.273 +	self.wrfunc(concat { ... })
   3.274 +end
   3.275 +
   3.276 +function Markup:init(docname)
   3.277 +	self.depth = 1
   3.278 +	return [[
   3.279 +<?xml version="1.0" encoding="utf-8" ?>
   3.280 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   3.281 +	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   3.282 +<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
   3.283 +	<head>
   3.284 +		<title>]] .. docname .. [[</title>
   3.285 +		<link rel="stylesheet" href="manual.css" />
   3.286 +	</head>
   3.287 +	<body>
   3.288 +]]
   3.289 +end
   3.290 +
   3.291 +function Markup:exit()
   3.292 +	return [[
   3.293 +	</body>
   3.294 +</html>
   3.295 +]]
   3.296 +end
   3.297 +
   3.298 +function Markup:gettag(id, tag, open)
   3.299 +	return self:getTagML(id, tag, open)
   3.300 +end
   3.301 +
   3.302 +function Markup:gettext(line, id)
   3.303 +	return encodeform(self:getTextML(line, id))
   3.304 +end
   3.305 +
   3.306 +function Markup:getpre(line)
   3.307 +	return encodeform(line), '\n'
   3.308 +end
   3.309 +
   3.310 +function Markup:getcode(line)
   3.311 +	return encodeform(line)
   3.312 +end
   3.313 +
   3.314 +function Markup:head(level, text)
   3.315 +	return '<h' .. level .. '>', '</h' .. level .. '>'
   3.316 +end
   3.317 +
   3.318 +function Markup:node(id, text)
   3.319 +	return ('<div class="node"><h3><a name="%s" id="%s"><code>%s</code></a></h3>'):format(
   3.320 +		id, id, text), '</div>'
   3.321 +end
   3.322 +
   3.323 +function Markup:headnode(id, text, len, is_code)
   3.324 +	if is_code then
   3.325 +		return ('<hr /><div class="node"><h%d><a name="%s" id="%s"><code>%s</code></a></h%d>'):format(
   3.326 +			len, id, id, text, len), '</div>'
   3.327 +	else
   3.328 +		return ('<hr /><div class="node"><h%d><a name="%s" id="%s">%s</a></h%d>'):format(
   3.329 +			len, id, id, text, len), '</div>'
   3.330 +	end
   3.331 +end
   3.332 +
   3.333 +function Markup:def(name)
   3.334 +	self.prebr = nil
   3.335 +	return '<div class="definition"><dfn>' .. name .. '</dfn>', '</div>'
   3.336 +end
   3.337 +
   3.338 +function Markup:argument(name, text)
   3.339 +	return ', function()%>', '<%end'
   3.340 +end
   3.341 +
   3.342 +function Markup:func(is_dynamic, args)
   3.343 +	if is_dynamic then
   3.344 +		self.is_dynamic_content = true
   3.345 +	end
   3.346 +	local t = { '<%loona:include("', args[1], '"' }
   3.347 +	remove(args, 1)
   3.348 +	for _, v in ipairs(args) do
   3.349 +		insert(t, ',' .. v)
   3.350 +	end
   3.351 +	return concat(t), ')%>'
   3.352 +end
   3.353 +
   3.354 +function Markup:indent()
   3.355 +	return '<blockquote>', '</blockquote>'
   3.356 +end
   3.357 +
   3.358 +function Markup:list()
   3.359 +	return '<ul>', '</ul>'
   3.360 +end
   3.361 +
   3.362 +function Markup:item(bullet)
   3.363 +	if bullet then
   3.364 +		return '<li>', '</li>'
   3.365 +	end
   3.366 +	return '<li style="list-style-type: none">', '</li>'
   3.367 +end
   3.368 +
   3.369 +function Markup:block()
   3.370 +	return '<p>', '</p>'
   3.371 +end
   3.372 +
   3.373 +function Markup:rule()
   3.374 +	return '<hr />'
   3.375 +end
   3.376 +
   3.377 +function Markup:pre()
   3.378 +	return '<pre>', '</pre>'
   3.379 +end
   3.380 +
   3.381 +function Markup:code()
   3.382 +	return '<code>', '</code>'
   3.383 +end
   3.384 +
   3.385 +function Markup:emphasis(len, text)
   3.386 +	if len == 1 then
   3.387 +		return '<em>', '</em>'
   3.388 +	elseif len == 2 then
   3.389 +		return '<strong>', '</strong>'
   3.390 +	else
   3.391 +		return '<em><strong>', '</strong></em>'
   3.392 +	end
   3.393 +end
   3.394 +
   3.395 +function Markup:link(link)
   3.396 +	--local isurl = link:match("^%a*://.*$")
   3.397 +	local func = link:match("^(.*)%(%)$")
   3.398 +	if func then
   3.399 +		return '<a href="#' .. func .. '"><code>', '</code></a>'
   3.400 +	else
   3.401 +		return '<a href="' .. link .. '">', '</a>'
   3.402 +	end
   3.403 +end
   3.404 +
   3.405 +function Markup:table(border)
   3.406 +	return '<table>', '</table>'
   3.407 +end
   3.408 +
   3.409 +function Markup:row()
   3.410 +	self.column = 0
   3.411 +	return '<tr>', '</tr>'
   3.412 +end
   3.413 +
   3.414 +function Markup:cell(border)
   3.415 +	self.column = self.column + 1
   3.416 +	return '<td class="column' .. self.column .. '">', '</td>'
   3.417 +end
   3.418 +
   3.419 +function Markup:image(link)
   3.420 +	return '<img src="' .. encodeurl(link, true) .. '" />'
   3.421 +end
   3.422 +
   3.423 +-------------------------------------------------------------------------------
   3.424 +--	run: Runs the parser
   3.425 +-------------------------------------------------------------------------------
   3.426 +
   3.427 +function Markup:run()
   3.428 +
   3.429 +	local function doid(id, ...)
   3.430 +		if self[id] then
   3.431 +			return self[id](self, ...)
   3.432 +		end
   3.433 +	end
   3.434 +
   3.435 +	local function doout(id, ...)
   3.436 +		doid("out", doid(id, ...))
   3.437 +	end
   3.438 +
   3.439 +	local function push(id, ...)
   3.440 +		local opentag, closetag = doid(id, ...)
   3.441 +		doout("gettag", id, opentag, true)
   3.442 +		insert(self.stack, { id = id, closetag = closetag })
   3.443 +		self.depth = self.depth + 1
   3.444 +	end
   3.445 +
   3.446 +	local function pop()
   3.447 +		local e = remove(self.stack)
   3.448 +		if e then
   3.449 +			self.depth = self.depth - 1
   3.450 +			doout("gettag", e.id, e.closetag)
   3.451 +			return e.id
   3.452 +		end
   3.453 +	end
   3.454 +
   3.455 +	local function top()
   3.456 +		local e = self.stack[#self.stack]
   3.457 +		if e then
   3.458 +			return e.id
   3.459 +		end
   3.460 +	end
   3.461 +
   3.462 +	local function popuntil(...)
   3.463 +		local i
   3.464 +		repeat
   3.465 +			i = pop()
   3.466 +			for _, v in ipairs { ... } do
   3.467 +				if v == i then
   3.468 +					return
   3.469 +				end
   3.470 +			end
   3.471 +		until not i
   3.472 +	end
   3.473 +
   3.474 +	local function popwhilenot(...)
   3.475 +		local i
   3.476 +		repeat
   3.477 +			i = top()
   3.478 +			for _, v in ipairs { ... } do
   3.479 +				if v == i then
   3.480 +					return
   3.481 +				end
   3.482 +			end
   3.483 +			pop()
   3.484 +		until not i
   3.485 +	end
   3.486 +
   3.487 +	local function popwhile(...)
   3.488 +		local cont
   3.489 +		repeat
   3.490 +			local id = top()
   3.491 +			cont = false
   3.492 +			for _, v in ipairs { ... } do
   3.493 +				if v == id then
   3.494 +					local i = pop()
   3.495 +					cont = true
   3.496 +					break
   3.497 +				end
   3.498 +			end
   3.499 +		until not cont
   3.500 +	end
   3.501 +
   3.502 +	local function popif(...)
   3.503 +		local i = top()
   3.504 +		for _, v in ipairs { ... } do
   3.505 +			if v == i then
   3.506 +				pop()
   3.507 +				return
   3.508 +			end
   3.509 +		end
   3.510 +	end
   3.511 +
   3.512 +	local function checktop(...)
   3.513 +		local i = top()
   3.514 +		for _, v in ipairs { ... } do
   3.515 +			if v == i then
   3.516 +				return true
   3.517 +			end
   3.518 +		end
   3.519 +	end
   3.520 +
   3.521 +	local function pushfragment(...)
   3.522 +		local line = concat { ... }
   3.523 +		if not self.context.fragments then
   3.524 +			self.context.fragments = { }
   3.525 +		end
   3.526 +		self.id = (self.id or 0) + 1
   3.527 +		insert(self.context.fragments, 1, { line = line, id = self.id })
   3.528 +		self.context.topid = self.id
   3.529 +	end
   3.530 +
   3.531 +	local function popfragment()
   3.532 +		self.context.firstfragment = nil
   3.533 +		local frag = remove(self.context.fragments, 1)
   3.534 +		if frag then
   3.535 +			self.line = frag.line
   3.536 +			if frag.id == self.context.topid then
   3.537 +				self.context.firstfragment = self.context.parentfirstfragment
   3.538 +			end
   3.539 +		else
   3.540 +			self.line = nil
   3.541 +		end
   3.542 +		return self.line
   3.543 +	end
   3.544 +
   3.545 +	local function pushcontext(id, line)
   3.546 +		insert(self.cstack, self.context)
   3.547 +		if id then
   3.548 +			self.context = { id = id, fragments = { } }
   3.549 +			pushfragment(line)
   3.550 +			insert(self.cstack, self.context)
   3.551 +		end
   3.552 +	end
   3.553 +
   3.554 +	local function popcontext()
   3.555 +		self.context = remove(self.cstack)
   3.556 +		return self.context
   3.557 +	end
   3.558 +
   3.559 +	-- parse
   3.560 +
   3.561 +	self.lnr = 1
   3.562 +	self.previndent = 0
   3.563 +	self.stack = { }
   3.564 +	self.depth = 0
   3.565 +	self.in_table = nil
   3.566 +	self.prebr = nil
   3.567 +	self.is_dynamic_content = false
   3.568 +
   3.569 +	doout("init", self.docname)
   3.570 +	push("document")
   3.571 +
   3.572 +	local linematch = ("^(%s*)(.-)%%s*$"):format(self.indentchar)
   3.573 +
   3.574 +	local feature = { }
   3.575 +	self.features:gsub("%a", function(f)
   3.576 +		feature[f] = true
   3.577 +	end)
   3.578 +
   3.579 +	for line in self.rdfunc(self.input) do
   3.580 +
   3.581 +		self.indentlevel = 0
   3.582 +		line = line:gsub(linematch, function(s, t)
   3.583 +			if t ~= "" then
   3.584 +				self.indentlevel = s:len()
   3.585 +			else
   3.586 +				self.indentlevel = self.previndent
   3.587 +			end
   3.588 +			return t
   3.589 +		end)
   3.590 +
   3.591 +		if feature["t"] then
   3.592 +			self.istabline = line:find("||", 1, 1)
   3.593 +			if self.istabline then
   3.594 +				self.indentlevel = self.previndent
   3.595 +			end
   3.596 +			if self.in_table then
   3.597 +				popuntil("row")
   3.598 +				if not self.istabline then
   3.599 +					popuntil("table")
   3.600 +					self.in_table = nil
   3.601 +				end
   3.602 +			end
   3.603 +		end
   3.604 +
   3.605 +		if self.indentlevel < self.previndent then
   3.606 +			if not self.preindent or self.indentlevel < self.preindent then
   3.607 +				local i = self.indentlevel
   3.608 +				while i < self.previndent do
   3.609 +					popuntil("indent", "pre")
   3.610 +					i = i + 1
   3.611 +				end
   3.612 +				self.preindent = nil
   3.613 +			end
   3.614 +		elseif self.indentlevel == self.previndent + 1 then
   3.615 +			popif("block")
   3.616 +			push("indent")
   3.617 +		elseif feature["p"] and self.indentlevel >= self.previndent + 2 then
   3.618 +			if not self.preindent then
   3.619 +				self.preindent = self.previndent + 2
   3.620 +				popif("block")
   3.621 +				push("pre")
   3.622 +			end
   3.623 +		end
   3.624 +
   3.625 +		if not self.preindent then
   3.626 +
   3.627 +			if feature["d"] then
   3.628 +				-- def ( SYNOPSIS )
   3.629 +				line = line:gsub("^(%u[%u%d%s_]+)::$", function(name)
   3.630 +					popwhile("def", "item", "list", "block", "pre")
   3.631 +					push("def", name)
   3.632 +					return ""
   3.633 +				end)
   3.634 +			end
   3.635 +
   3.636 +			if feature["f"] then
   3.637 +				-- function ( INCLUDE(name) )
   3.638 +				line = line:gsub("^%s*(INCLUDE_?%a*)%(%s*(.*)%s*%)%s*$",
   3.639 +					function(key, line)
   3.640 +					if key == "INCLUDE" or key == "INCLUDE_STATIC" then
   3.641 +						local args = { }
   3.642 +						line:gsub(",?([^,]*)", function(a)
   3.643 +							a = a:match("^%s*(%S.-)%s*$")
   3.644 +							if a then
   3.645 +								insert(args, a)
   3.646 +							end
   3.647 +						end)
   3.648 +						popwhilenot("document")
   3.649 +						push("func", key == "INCLUDE", args)
   3.650 +						return ""
   3.651 +					end
   3.652 +				end)
   3.653 +				-- argument ( ======== )
   3.654 +				line = line:gsub("^%s*========+%s*$", function()
   3.655 +					popwhilenot("func")
   3.656 +					push("argument")
   3.657 +					return ""
   3.658 +				end)
   3.659 +			end
   3.660 +
   3.661 +-- 			-- node ( @@ ... : ... @@ )
   3.662 +--
   3.663 +-- 			line = line:gsub(
   3.664 +-- 				"^%s*@@%s+(.+)%s+:%s+(.-)%s+@@%s*$", function(text, id)
   3.665 +-- 				popwhilenot("document")
   3.666 +-- 				id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
   3.667 +-- 				push("node", id, text)
   3.668 +-- 				return ""
   3.669 +-- 			end)
   3.670 +--
   3.671 +-- 			-- node ( @@ ... @@ )
   3.672 +--
   3.673 +-- 			line = line:gsub("^%s*@@%s+(.+)%s+@@%s*$", function(text)
   3.674 +-- 				popwhilenot("document")
   3.675 +-- 				local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
   3.676 +-- 				push("node", id, text)
   3.677 +-- 				return ""
   3.678 +-- 			end)
   3.679 +
   3.680 +			if feature["n"] then
   3.681 +				-- headnode ( ==( id : text )== )
   3.682 +				line = line:gsub("^%s*(=+[%(%[])%s+(.+)%s+:%s+(.+)%s+([%)%]]=+)%s*$",
   3.683 +					function(s1, id, text, s2)
   3.684 +					local l = min(s1:len() - 1, s2:len() - 1, 5)
   3.685 +					popwhilenot("document")
   3.686 +					local id = id:gsub("[^a-zA-Z%_%-%.%:]", "")
   3.687 +					push("headnode", id, text, l, s2:sub(1, 1) == "]")
   3.688 +					return ""
   3.689 +				end)
   3.690 +				-- headnode ( ==( text )== )
   3.691 +				line = line:gsub("^%s*(=+[%(%[])%s+(.*)%s+([%)%]]=+)%s*$",
   3.692 +					function(s1, text, s2)
   3.693 +					local l = min(s1:len(), s2:len(), 5)
   3.694 +					popwhilenot("document")
   3.695 +					local id = text:gsub("[^a-zA-Z%_%-%.%:]", "")
   3.696 +					push("headnode", id, text, l, s2:sub(1, 1) == "]")
   3.697 +					return ""
   3.698 +				end)
   3.699 +			end
   3.700 +
   3.701 +			if feature["s"] then
   3.702 +				-- rule ( ----.. )
   3.703 +				line = line:gsub("^%s*(%-%-%-%-+)%s*$", function(s)
   3.704 +					popwhile("block", "list", "item")
   3.705 +					push("rule")
   3.706 +					pop()
   3.707 +					return ""
   3.708 +				end)
   3.709 +			end
   3.710 +
   3.711 +		end
   3.712 +
   3.713 +		--
   3.714 +
   3.715 +		self.cstack = { }
   3.716 +		self.context = { parentfirstfragment = true }
   3.717 +		pushfragment(line)
   3.718 +		pushcontext()
   3.719 +
   3.720 +		if line == "" then
   3.721 +			popwhile("block")
   3.722 +		end
   3.723 +
   3.724 +		while popcontext() do
   3.725 +			while popfragment() do
   3.726 +				line = self.line
   3.727 +
   3.728 +				if self.preindent then
   3.729 +
   3.730 +					if self.prepend then
   3.731 +						push("preline")
   3.732 +						doout("getpre", "")
   3.733 +						pop()
   3.734 +						self.prepend = nil
   3.735 +					end
   3.736 +
   3.737 +					if line == "" then
   3.738 +						self.prepend = true
   3.739 +					else
   3.740 +						push("preline")
   3.741 +						doout("getpre", (" "):rep(self.indentlevel -
   3.742 +							self.preindent) .. line)
   3.743 +						pop()
   3.744 +					end
   3.745 +
   3.746 +				else
   3.747 +					self.prepend = nil
   3.748 +
   3.749 +					if line ~= "" then
   3.750 +
   3.751 +						if feature["l"] then
   3.752 +							-- list/item ( * ... )
   3.753 +							local _, _, b, a = line:find("^([%*%-])%s+(.*)$")
   3.754 +							if b and a then
   3.755 +								local inlist = checktop("item", "list")
   3.756 +								popwhile("item", "block")
   3.757 +								if not inlist then
   3.758 +									push("list")
   3.759 +								end
   3.760 +								if b == "*" then
   3.761 +									push("item", true)
   3.762 +								else
   3.763 +									push("item")
   3.764 +								end
   3.765 +								pushcontext("item", a)
   3.766 +								break
   3.767 +							end
   3.768 +						end
   3.769 +
   3.770 +						if feature["t"] then
   3.771 +							-- table cells
   3.772 +							local _, pos, a, b =
   3.773 +								line:find("^%s*(.-)%s*||%s*(.*)%s*$")
   3.774 +							if pos then
   3.775 +								if self.context.id == "table" then
   3.776 +									popuntil("cell")
   3.777 +								else
   3.778 +									self.context.id = "table"
   3.779 +									if not self.in_table then
   3.780 +										self.in_table = true
   3.781 +										push("table")
   3.782 +									end
   3.783 +									push("row")
   3.784 +								end
   3.785 +								pushfragment(b)
   3.786 +								push("cell")
   3.787 +								pushcontext("cell", a)
   3.788 +								break
   3.789 +							elseif self.context.id == "table" then
   3.790 +								popuntil("cell")
   3.791 +								push("cell")
   3.792 +								pushcontext("cell", line)
   3.793 +								break
   3.794 +							end
   3.795 +						end
   3.796 +
   3.797 +						if feature["c"] then
   3.798 +							-- code
   3.799 +							local _, _, a, text, b =
   3.800 +								line:find("^(.-){{(.-)}}(.*)%s*$")
   3.801 +							if text then
   3.802 +								if a == "" then
   3.803 +									if not checktop("block", "item", "cell") then
   3.804 +										push("block")
   3.805 +									end
   3.806 +									if self.context.firstfragment then
   3.807 +										doout("gettext", "")
   3.808 +									end
   3.809 +									push("code")
   3.810 +									if b == "" then b = " " end
   3.811 +									pushfragment(b)
   3.812 +									pushcontext("code", text)
   3.813 +								else
   3.814 +									pushfragment("{{", text, "}}", b)
   3.815 +									pushfragment(a)
   3.816 +									pushcontext()
   3.817 +								end
   3.818 +								break
   3.819 +							end
   3.820 +						end
   3.821 +
   3.822 +						if feature["e"] then
   3.823 +							-- emphasis
   3.824 +							local _, _, a, x, text, y, b =
   3.825 +								line:find("^(.-)(''+)(.-)(''+)(.*)$")
   3.826 +							if text then
   3.827 +								if a == "" then
   3.828 +									x, y = x:len(), y:len()
   3.829 +									local len = min(x, y, 4)
   3.830 +									if not checktop("block", "item", "cell") then
   3.831 +										push("block")
   3.832 +									end
   3.833 +									if self.context.firstfragment then
   3.834 +										doout("gettext", "")
   3.835 +									end
   3.836 +									push("emphasis", len - 1)
   3.837 +									if b == "" then b = " " end
   3.838 +									pushfragment(b)
   3.839 +									pushcontext("emphasis", text)
   3.840 +								else
   3.841 +									pushfragment(x, text, y, b)
   3.842 +									pushfragment(a)
   3.843 +									pushcontext()
   3.844 +								end
   3.845 +								break
   3.846 +							end
   3.847 +						end
   3.848 +
   3.849 +						if feature["l"] then
   3.850 +							-- [[link]], [[title][link]], function(), [[link : title]]
   3.851 +							if self.context.id ~= "link"
   3.852 +								and self.context.id ~= "code" then
   3.853 +								local a, title, link, b
   3.854 +								a, link, title, b = -- [[link : title]]
   3.855 +									line:match("^(.-)%[%[(.-)%s+:%s+(.-)%]%](.*)%s*$")
   3.856 +								if not link then
   3.857 +									a, title, link, b = -- [[text][...]]
   3.858 +										line:match("^(.-)%[%[(.-)%]%[(.-)%]%](.*)%s*$")
   3.859 +								end
   3.860 +								if not link then -- [[....]]
   3.861 +									a, link, b = line:match("^(.-)%[%[(.-)%]%](.*)%s*$")
   3.862 +								end
   3.863 +								if not link then -- class:function()
   3.864 +									a, link, b = line:match("^(.-)(%a[%w_:]-%(%))(.*)%s*$")
   3.865 +								end
   3.866 +								if not link then -- prot://foo/bar
   3.867 +									a, link, b = line:match(
   3.868 +										"^(.-)(%a*://[%w_%-%.,:;/%?=~]*)(.*)%s*$")
   3.869 +								end
   3.870 +								if link then
   3.871 +									if a == "" then
   3.872 +										if not checktop("block", "item", "cell") then
   3.873 +											push("block")
   3.874 +										end
   3.875 +										if self.context.firstfragment then
   3.876 +											doout("gettext", "")
   3.877 +										end
   3.878 +										push("link", link)
   3.879 +										if b == "" then b = " " end
   3.880 +										pushfragment(b)
   3.881 +										pushcontext("link", title or link)
   3.882 +									else
   3.883 +										pushfragment("[[", title or link, "][", link, "]]", b)
   3.884 +										pushfragment(a)
   3.885 +										pushcontext()
   3.886 +									end
   3.887 +									break
   3.888 +								end
   3.889 +							end
   3.890 +						end
   3.891 +
   3.892 +						if feature["i"] then
   3.893 +							-- imglink (@@...@@)
   3.894 +							line = line:gsub("@@(.-)@@", function(link)
   3.895 +								push("image", link)
   3.896 +								pop()
   3.897 +								return ""
   3.898 +							end)
   3.899 +						end
   3.900 +
   3.901 +						if feature["h"] then
   3.902 +							-- head ( = ... = )
   3.903 +							line = line:gsub("(=+)%s+(.*)%s+(=+)",
   3.904 +								function(s1, text, s2)
   3.905 +								local l = min(s1:len(), s2:len(), 5)
   3.906 +								popwhile("block", "item", "list")
   3.907 +								push("head", l)
   3.908 +								return text
   3.909 +							end)
   3.910 +						end
   3.911 +
   3.912 +						-- output
   3.913 +						if line ~= "" then
   3.914 +							if not checktop("item", "block", "cell", "pre",
   3.915 +								"head", "emphasis", "link", "code") then
   3.916 +								popwhile("item", "list", "pre", "code")
   3.917 +								push("block")
   3.918 +							end
   3.919 +							if top() == "code" then
   3.920 +								doout("getcode", line, top())
   3.921 +							else
   3.922 +								doout("gettext", line, top())
   3.923 +							end
   3.924 +						end
   3.925 +						popif("emphasis", "head", "link", "code")
   3.926 +					end
   3.927 +				end
   3.928 +			end
   3.929 +		end
   3.930 +
   3.931 +		self.previndent = self.indentlevel
   3.932 +		self.lnr = self.lnr + 1
   3.933 +	end
   3.934 +
   3.935 +	popuntil()
   3.936 +	doout("exit")
   3.937 +
   3.938 +	return self.is_dynamic_content
   3.939 +end
   3.940 +
   3.941 +-------------------------------------------------------------------------------
   3.942 +--
   3.943 +--	parser = Markup:new(args): Creates a new markup parser. {{args}} can be a
   3.944 +--	table of initial attributes that constitute its behavior. Fields supported
   3.945 +--	are:
   3.946 +--
   3.947 +--	args.indentchar || character recognized for indentation, default {{"\t"}}
   3.948 +--	args.input      || filehandle or string, default {{io.stdin}}
   3.949 +--	args.rdfunc     || line reader func, by default reads from {{args.input}}
   3.950 +--	args.wrfunc     || writer func, by default {{io.stdout.write}}
   3.951 +--	args.docname    || name of document, default {{"Manual"}}
   3.952 +--	args.features   || string of feature codes, default {{"hespcadlint"}}
   3.953 +--
   3.954 +--	The parser supports the following features, which can be combined into
   3.955 +--	a string and passed to the constructor in {{args.features}}:
   3.956 +--
   3.957 +--	Code || Description        || rough HTML equivalent
   3.958 +--	h	   || Heading            || <h1>, <h2>, ...
   3.959 +--	e    || Emphasis           || <strong>
   3.960 +--	s    || Separator          || <hr>
   3.961 +--	p    || Preformatted block || <pre>
   3.962 +--	c    || Code               || <code>
   3.963 +--	a    || Anchor, link       || <a href="...">
   3.964 +--	d    || Definition         || <dfn>
   3.965 +--	l    || List               || <ul>, <li>
   3.966 +--	i    || Image              || <img>
   3.967 +--	n    || Node header        || <a name="...">
   3.968 +--	t    || Table              || <table>
   3.969 +--	f    || Function           || (undocumented, internal use only)
   3.970 +--
   3.971 +--	After creation of the parser object, conversion starts by calling the
   3.972 +--	method Markup:run().
   3.973 +--
   3.974 +-------------------------------------------------------------------------------
   3.975 +
   3.976 +function Markup.new(class, self)
   3.977 +	self = self or { }
   3.978 +	self.indentchar = self.indentchar or "\t"
   3.979 +	self.input = self.input or stdin
   3.980 +	self.rdfunc = self.rdfunc or
   3.981 +		type(self.input) == "string" and rd_string or self.input.lines
   3.982 +	self.wrfunc = self.wrfunc or function(s) stdout:write(s) end
   3.983 +	self.features = self.features or "hespcadlint"
   3.984 +	self.docname = self.docname or "Manual"
   3.985 +	return Class.new(class, self)
   3.986 +end