cortav  Artifact [37b0a0a8ee]

Artifact 37b0a0a8ee6c8a36a3a10629b597abae4afe1f4c589c7bc075b9c731b23bb03a:


local ct = require 'cortav'
local ss = require 'sirsem'

local default_mode = {
	['render:format'] = 'html';
	['html:gen-styles'] = true;
}

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, suggestions, vars)
	local doc = ct.parse(input.stream, input.src, mode)
	input.stream:close()
	if mode['parse:show-tree'] then
		log:write(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
		ct.exns.unimpl('output format ā€œ%sā€ unsupported', mode['render:format']):throw()
	end
	
	local render_opts = kmap(function(k,v)
		return k:sub(2+#mode['render:format'])
	end, kfilter(mode, function(m)
		return ss.str.begins(m, mode['render:format']..':')
	end))

	doc.vars = vars
	
	-- this is kind of gross but the context object belongs to the parser,
	-- not the renderer, so that's not a suitable place for this information
	doc.stage = {
		kind = 'render';
		format = mode['render:format'];
		mode = mode;
		suggestions = suggestions;
	}

	output:write(ct.render[mode['render:format']](doc, render_opts))
	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;
		}
		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';
	}

	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;
	}

	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 ss.str.enc.utf8.each(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(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)
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 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
	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)