-- [Ź] cli.lua
-- ~ lexi hale <lexi@hale.su>
-- šÆ AGPLv3
-- ? simple command line driver for the cortav library
local ct = require 'cortav'
local ss = require 'sirsem'
local native = _G.native
local default_mode = {
['render:format'] = 'html';
['html:gen-styles'] = true;
['groff:color'] = true;
}
local function
main(input, output, log, mode, suggestions, vars, extrule)
local doc = ct.parse(input.stream, input.src, mode, function(c)
c.doc.ext = extrule
end)
input.stream:close()
if mode['parse:show-tree'] then
log:write(ss.dump(doc))
end
-- the document has now had a chance to give its say; if it hasn't specified
-- any modes of its own, we now merge in the 'weak modes' (suggestions)
for k,v in pairs(suggestions) do
if not mode[k] then mode[k] = v end
end
if not mode['render:format'] then
error 'what output format should i translate the input to?'
end
if mode['render:format'] == 'none' then return 0 end
if not ct.render[mode['render:format']] then
if (not ct.render.html) and not _G.native then
-- we may be running uncompiled; otherwise something is seriously broken
require('render.' .. mode['render:format'])
else
ct.exns.unimpl('output format ā%sā unsupported', mode['render:format']):throw()
end
end
local render_opts = ss.kmap(function(k,v)
return k:sub(2+#mode['render:format'])
end, ss.kfilter(mode, function(m)
return ss.str.begins(m, mode['render:format']..':')
end))
doc.vars = vars
output:write(ct.render[mode['render:format']](
doc, render_opts, function(stage)
stage.mode = mode
end))
return 0
end
local inp,outp,log = io.stdin, io.stdout, io.stderr
local function entry_cli()
local suggestions, vars, input = default_mode, {}, {
stream = inp;
src = {
file = '(stdin)';
}
}
local mode = {}
local optnparams = function(o)
local param_opts = {
out = 1;
log = 1;
define = 2; -- key value
['mode-set'] = 1;
['mode-clear'] = 1;
mode = 2;
['mode-set-weak'] = 1;
['mode-clear-weak'] = 1;
['mode-weak'] = 2;
['use'] = 1;
['inhibit'] = 1;
['need'] = 1;
['load'] = 1;
['enc'] = 1;
}
return param_opts[o] or 0
end
local optmap = {
o = 'out';
l = 'log';
d = 'define';
V = 'version';
h = 'help';
y = 'mode-set', Y = 'mode-set-weak';
n = 'mode-clear', N = 'mode-clear-weak';
m = 'mode', M = 'mode-weak';
L = 'load',
u = 'use', i = 'inhibit', r = 'require';
e = 'enc';
}
local extrule = {use={},inhibit={},need={}}
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)
if ss.str.begins(key, 'cortav.') or ss.str.begins(key, 'env.') then
ct.exns.cli 'cannot define variable in restricted namespace':throw()
end
vars[key] = value
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;
['mode-weak'] = function(key,value) suggestions[checkmodekey(key)] = value end;
['mode-set-weak'] = function(key) suggestions[checkmodekey(key)] = true end;
['mode-clear-weak'] = function(key) suggestions[checkmodekey(key)] = false end;
['use' ] = function(ext) extrule.use [ext] = true end;
['inhibit'] = function(ext) extrule.inhibit[ext] = true end;
['require'] = function(ext) extrule.need [ext] = true end;
['load'] = function(extpath) end;
['enc'] = function(enc) end;
['version'] = function()
outp:write(ct.info:about())
if next(ct.ext.loaded) then
outp:write('\nactive extensions:\n')
for k,v in pairs(ct.ext.loaded) do
outp:write(string.format(' * %s', v.id ..
(v.version and (' ' .. v.version:string()) or '')))
if v.desc then
outp:write(string.format(': %s', v.desc))
if v.contributors then
outp:write(string.format(' [%s]', table.concat(
ss.map(function(ctr)
return ctr.name or ctr.handle
end, v.contributors), ', ')))
end
else
outp:write'\n'
end
end
end
os.exit(0)
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 (%s 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])
else
onswitch[longopt]()
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 ss.str.each(ss.str.enc.utf8, v:sub(2)) do
if optmap[c] then
execLongOpt(optmap[c])
else
ct.exns.cli('switch -%s 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(args[1], "rb")
if not file then error('unable to load file ' .. args[1]) end
input.stream = file
input.src.file = args[1]
end
return main(input, outp, log, mode, suggestions, vars, extrule)
end
-- local ok, e = pcall(entry_cli)
local ok, e = true, entry_cli()
if not ok then
local str = 'translation failure'
if ss.exn.is(e) then
str = e.kind.desc
end
local color = false
if native then
if native.posix.isatty(log) then
color = true
end
else
if log:seek() == nil then
-- this is not a very reliable heuristic for detecting
-- attachment to a tty but it's better than nothing
if os.getenv('COLORTERM') then
color = true
else
local term = os.getenv('TERM')
if term:find 'color' then color = true end
end
end
end
if color then
str = string.format('\27[1;31m%s\27[m', str)
end
log:write(string.format('%s: %s\n', str, e))
os.exit(1)
end
os.exit(e)