-- TNT, the Lua highlighter -- -- copyright 2004 by Rici Lake. Permission is granted to use this code under -- the same terms and conditions as found in the Lua copyright notice at -- http://www.lua.org/license.html. -- -- I'd appreciate a credit if you use this code, but I don't insist. -- -- This is prerelease software, no interfaces are guaranteed. -- Use at your own risk, etc. -- TODO -- Separate command line and CGI versions and clean things up. if not import then error "import not loaded; read the INSTALL document" end local VERSION = "TNT/0.5" local COPYRIGHT = "Copyright (C) 2004 Rici Lake" _COPYRIGHT = _COPYRIGHT or "Copyright (C) 1994-2003 Tecgraf, PUC-Rio" local defaultCSSFile = "tnt.css" local globals = {} for k in _G do globals[k] = true end local string = import "string" local strfind, gfind, gsub, format, strlen = string.find, string.gfind, string.gsub, string.format, string.len local lua2htmlFactory = new "lua2html" local lparse = import "lparse" local lexer = new "blex"("", "") local query = import "libquery" local header = [[ <html><head><title>%s</title><style type="text/css">%s</style></head><body style="background-color:#fff7ef"><h1>%s</h1> ]] -- sample style stuff local legend = -- don't muck with the spaces here ! [[<div style="border: 3px double black; padding:6px; margin-left:10%%; margin-right:10%%"><pre>]] .. [[<span class="preprocessor">#!/she/bang or #preprocessor</span> ]] .. [[<span class="comment">--[[Comment]] -- Comment</span> ]].. [[<span class="skip"><span class="skipped">Unparseable text</span></span> ]] .. [[<span class="missing">something missing</span> ]] .. [[<span class='comp'>=</span><span class='missing'>=</span> = instead of == ]].. [[<span class="badGlobal">undefined global</span> ]] .. [[<span class="var unused">unused variable</span> ]] .. [[<span class="vclosure var">mutable upvalue</span> </pre></div> ]] local footer = "<hr/><p>Produced by TNT, the Lua-linter. " .. VERSION .. " " .. COPYRIGHT .. "</p></body></html>\n" -- We could, of course, insist that they tell us :) local function getncolour(style) local ncolour = 1 for i in gfind(style, "%.depth(%d+)") do local n = tonumber(i) + 1 if n > ncolour then ncolour = n end end return ncolour end local stylesheet = new "defaultStyle" () local titlepat, outpat local logf local htmlify local function carp(err) io.stderr:write(err, "\n") os.exit(1) end local function inform(msg) io.stderr:write(msg, "\n") os.exit(0) end local function check(ok, err) if not ok then carp(err) end return ok end local function openfile(fname, mode) return check(io.open(fname, mode or "r")) end local function readAndClose(file) local rv = file:read "*a" file:close() return rv end local function readAll(fname) return readAndClose(openfile(fname)) end -- Try to load the default style sheet local function checkstyle() stylesheet = stylesheet or readAll(defaultCSSFile) return stylesheet end -- Very basic, this is. But it could be improved sometime. local function subst(pat, fname, default) if pat then return (gsub(pat, "%?", fname)) else return default end end -- conditional formatted logging (only one loglevel for now) local function nologger() end local function logger(...) io.stderr:write(format(unpack(arg))) end -- fname is mostly only used for logging local function tnt(inf, outf, title, fname) htmlify = htmlify or lua2htmlFactory(getncolour(checkstyle())) title = query.entify(title) -- This read had an assert around it, but that bombs on /dev/null -- which is pseudo-legit. local now = os.clock() local buf = readAndClose(inf) or "" lexer.reuse(buf, fname) outf:write(query.xhtml10strict) outf:write(format(header, title, stylesheet, title)) local e, w, n = htmlify(outf, lparse.parse(lexer, globals)) outf:write(footer) outf:close() local took = os.clock() - now logf("%s: \t%i/%i chars/tokens, %9.3f seconds %s\n", fname, strlen(buf), n, took, e and ": Errors" or w and ": Warnings" or "") end local function tntfile(fname) local inf if fname == "-" then inf, fname = io.stdin, "STDIN" else inf = openfile(fname) outpat = outpat or "?.html" end local ofname = subst(outpat, fname, "-") local outf = ofname == "-" and io.stdout or openfile(ofname, "w") tnt(inf, outf, subst(titlepat, fname, fname), fname) end local function putlegend() local ofname = subst(outpat, "legend", "-") local outf = ofname == "-" and io.stdout or openfile(ofname, "w") outf:write(query.xhtml10strict) local title = subst(titlepat, "legend", "TNT legend") outf:write(format(header, title, checkstyle(), title)) outf:write(legend) outf:write(footer) outf:close() end ---- CGI handling ---- local function cgitnt(buf, title) htmlify = htmlify or lua2htmlFactory(getncolour(checkstyle())) title = query.entify(title) lexer.reuse(buf, title) io.stdout:write "Content-Type: text/html\r\n\r\n" io.stdout:write(query.xhtml10strict) io.stdout:write(format(header, title, stylesheet, title)) htmlify(io.stdout, lparse.parse(lexer, globals)) io.stdout:write(footer) end local function cgilegend() io.stdout:write "Content-Type: text/html\r\n\r\n" io.stdout:write(query.xhtml10strict) io.stdout:write(format(header, "TNT legend", checkstyle(), "TNT legend")) io.stdout:write(footer) end local function docgi() local fname, buf for k, v in query.qpairs(query.cgiGetQuery()) do if k == "fname" then fname = gsub(v, "[^-._/ %w]", "?") elseif k == "lua" then buf = gsub(v, "\r", "") end end if buf and buf ~= "" then cgitnt(buf, fname) else io.stdout:write "Content-Type: text/html\r\n\r\n" io.stdout:write(query.xhtml10strict) io.stdout:write( [[ <html><head><title>TNT colourises it for you!</title></head> <body><h1>Put your Lua code here</h1> <form action="]]..os.getenv "SCRIPT_NAME"..[[" method="post"> <p>Module name: <input type="text" name="fname"></input> <textarea name="lua" rows="30" cols="120" style="width: 100%; height: 80%;">-- Paste it here!</textarea> <input type="submit" value="send"></input></p> </form> </body> </html> ]]) end end ---- Option handling ---- local nextarg, putback do local argno = 0 function nextarg(r) if r and r ~= "" then return r else argno = argno + 1 return arg[argno] end end -- yuk function putback(r) if r ~= "" then arg[argno] = "-" .. r argno = argno - 1 end end end local function putusage(func) func(arg[0] .. [[ (option | file)... OPTIONS -t title Set title -o file Output filename. Default is ?.html (stdout if input is stdin) -f file Process FILE. Only necessary if filename starts with a - -s file File containing stylesheet -v Verbose (not very) -l Output the style legend -V Output version to stderr and quit -? Output this help and quit - use stdin (default if no files specified) ]]) end local function usage(x) if x then return x else putusage(carp) end end local processed = 0 local optfuncs = { t = function(r) titlepat = usage(nextarg(r)) end, o = function(r) outpat = usage(nextarg(r)) end, f = function(r) tntfile(usage(nextarg(r))) end, s = function(r) stylesheet = readAll(usage(nextarg(r))) htmlify = nil -- force a new htmlifier end, l = function(r) putback(r); processed = processed + 1; putlegend() end, v = function(r) putback(r); logf = logger end, V = function(r) inform(VERSION.."\t"..COPYRIGHT.. "\n" .._VERSION.."\t".._COPYRIGHT) end, ['?'] = function() putusage(inform) end } ---- Main function --- local function handler() logf = nologger for thisarg in nextarg do local _, _, shortoption, rest = strfind(thisarg, "^%-(%w)(.*)") if shortoption then usage(optfuncs[shortoption])(rest) else processed = processed + 1 tntfile(thisarg) end end if processed == 0 then tntfile "-" end end if os.getenv "GATEWAY_INTERFACE" then local ok, err = pcall(docgi) if not ok then local _, _, status = strfind(err, ":%d+: (%d%d%d) ") if status then io.stdout:write ("Status: "..status.."\r\n\r\n") else io.stdout:write "Status: 500\r\nContent-Type: text/plain\r\n\r\n" io.stdout:write(err) end end else -- For now, we just die on errors. It would be nicer to skip the file and -- go on to the next one, maybe. local ok, err = pcall(handler) if not ok then carp(err) end end
Produced by TNT, the Lua-linter. TNT/0.5 Copyright (C) 2004 Rici Lake