@@ -153,9 +153,14 @@ } ct.exnkind = declare { ident = 'exn-kind'; mk = function(desc, report) - return { desc = desc, report = report } + return { + desc = desc; + report = report or function(msg,...) + return string.format(msg,...) + end; + } end; call = function(me, ...) return ct.exn(me, ...) end; @@ -163,9 +168,16 @@ ct.exns = { tx = ct.exnkind('translation error', function(msg,...) return string.format("(%s:%u) "..msg, ...) - end) + end); + io = ct.exnkind('IO error', function(msg, ...) + return string.format("<%s %s> "..msg, ...) + end); + cli = ct.exnkind 'command line parse error'; + mode = ct.exnkind('bad mode', function(msg, ...) + return string.format("mode “%s” "..msg, ...) + end); } ct.ctx = declare { mk = function(src) return {src = src} end; @@ -393,11 +405,11 @@ local function htmlSpan(spans, block, sec) local text = {} for k,v in pairs(spans) do if type(v) == 'string' then - table.insert(text,(v:gsub('[<>&]', + table.insert(text,(v:gsub('[<>&"]', function(x) - return string.format('&#%02u', string.byte(x)) + return string.format('&#%02u;', string.byte(x)) end))) else table.insert(text, span_renderers[v.kind](v, block, sec)) end @@ -406,9 +418,9 @@ end function span_renderers.format(sp) local tags = { strong = 'strong', emph = 'em', strike = 'del', insert = 'ins', literal = 'code' } - if sp.style == 'literal' then + if sp.style == 'literal' and not opts['fossil-uv'] then stylesNeeded.code = true end if sp.style == 'del' or sp.style == 'ins' then stylesNeeded.editors_markup = true @@ -680,14 +692,25 @@ table.insert(styles, (stylesets[k]:gsub('%s+',' '))) end local head = {} + local styletag = '' + if opts['link-css'] then + local css = opts['link-css'] + if type(css) ~= 'string' then ct.exns.mode('must be a string', 'html:link-css'):throw() end + styletag = styletag .. elt('link',{rel='stylesheet',type='text/css',href=opts['link-css']}) + end if next(styles) then - table.insert(head, tag('style',{type='text/css'},table.concat(styles))) + if opts['gen-styles'] then + styletag = styletag .. tag('style',{type='text/css'},table.concat(styles)) + end + table.insert(head, styletag) end - if opts.snippet then - return body + if opts['fossil-uv'] then + return tag('div',{class='fossil-doc',['data-title']=doctitle},styletag .. body) + elseif opts.snippet then + return styletag .. body else return dr.htmlDoc(doctitle, next(head) and table.concat(head), body) end end @@ -932,8 +955,9 @@ align = 'center' elseif a1 ~= ':' and a2 == ':' then align = 'right' end + text = text:match '^%s*(.-)%s*$' table.insert(row, { spans = ct.parse_span(text, c); align = align; header = header; @@ -1103,26 +1127,57 @@ return ctx.doc end local default_mode = { - format = 'html'; + ['render:format'] = 'html'; + ['html:gen-styles'] = true; } + +local function filter(list, fn) + local new = {} + for i, v in ipairs(list) do + if fn(v,i) then table.insert(new, v) end + end + return new +end + +local function kmap(fn, list) + local new = {} + for k, v in pairs(list) do + local nk,nv = fn(k,v) + new[nk or k] = nv or v + end + return new +end +local function kfilter(list, fn) + local new = {} + for k, v in pairs(list) do + if fn(k,v) then new[k] = v end + end + return new +end local function main(input, output, log, mode, vars) local doc = ct.parse(input.stream, input.src) input.stream:close() - if mode['show-tree'] then + if mode['parse:show-tree'] then log:write(dump(doc)) end - if not mode.format then + if not mode['render:format'] then error 'what output format should i translate the input to?' end - if not ct.render[mode.format] then - error(string.format('output format “%s” unsupported', mode.format)) + if not ct.render[mode['render:format']] then + error(string.format('output format “%s” unsupported', mode['render:format'])) end - output:write(ct.render[mode.format](doc, {})) + local render_opts = kmap(function(k,v) + return k:sub(2+#mode['render:format']) + end, kfilter(mode, function(m) + return startswith(m, mode['render:format']..':') + end)) + + output:write(ct.render[mode['render:format']](doc, render_opts)) end local inp,outp,log = io.stdin, io.stdout, io.stderr @@ -1133,13 +1188,108 @@ file = '(stdin)'; } } - if arg[1] and arg[1] ~= '' then + local optnparams = function(o) + local param_opts = { + out = 1; + log = 1; + define = 2; -- key value + ['mode-set'] = 1; + ['mode-clear'] = 1; + mode = 2; + } + return param_opts[o] or 0 + end + + local optmap = { + o = 'out'; + l = 'log'; + d = 'define'; + V = 'version'; + h = 'help'; + y = 'mode-set', n = 'mode-clear'; + m = 'mode'; + } + + local checkmodekey = function(key) + if not key:match '[^:]+:.+' then + ct.exns.cli('invalid mode key %s', key):throw() + end + return key + end + local onswitch = { + out = function(file) + local nf = io.open(file,'wb') + if nf then outp:close() outp = nf else + ct.exns.io('could not open output file for writing', 'open',file):throw() + end + end; + log = function(file) + local nf = io.open(file,'wb') + if nf then log:close() log = nf else + ct.exns.io('could not open log file for writing', 'open',file):throw() + end + end; + define = function(key,value) + -- set context key + end; + mode = function(key,value) mode[checkmodekey(key)] = value end; + ['mode-set'] = function(key) mode[checkmodekey(key)] = true end; + ['mode-clear'] = function(key) mode[checkmodekey(key)] = false end; + } + + local args = {} + local keepParsing = true + do local i = 1 while i <= #arg do local v = arg[i] + local execLongOpt = function(longopt) + if not onswitch[longopt] then + ct.exns.cli('switch --%s unrecognized', longopt):throw() + end + local nargs = optnparams(longopt) + + if nargs > 1 then + if i + nargs > #arg then + ct.exns.cli('not enough arguments for switch --%s (%u expected)', longopt, nargs):throw() + end + local nt = {} + for j = i+1, i+nargs do + table.insert(nt, arg[j]) + end + onswitch[longopt](table.unpack(nt)) + elseif nargs == 1 then + onswitch[longopt](arg[i+1]) + end + i = i + nargs + end + if v == '--' then + keepParsing = false + else + local longopt = v:match '^%-%-(.+)$' + if keepParsing and longopt then + execLongOpt(longopt) + else + if keepParsing and v:sub(1,1) == '-' then + for c,p in eachcode(v:sub(2)) do + if optmap[c] then + execLongOpt(optmap[c]) + else + ct.exns.cli('switch -%i unrecognized', c):throw() + end + end + else + table.insert(args, v) + end + end + + end + i = i + 1 end end + + if args[1] and args[1] ~= '' then local file = io.open(arg[1], "rb") - if not file then error('unable to load file ' .. arg[1]) end + if not file then error('unable to load file ' .. args[1]) end input.stream = file - input.src.file = arg[1] + input.src.file = args[1] end main(input, outp, log, mode, vars) end