Index: cli.lua ================================================================== --- cli.lua +++ cli.lua @@ -4,29 +4,10 @@ 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 @@ -45,13 +26,13 @@ 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) + local render_opts = ss.kmap(function(k,v) return k:sub(2+#mode['render:format']) - end, kfilter(mode, function(m) + end, ss.kfilter(mode, function(m) return ss.str.begins(m, mode['render:format']..':') end)) doc.vars = vars @@ -138,10 +119,33 @@ ['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; + + ['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] @@ -151,19 +155,21 @@ 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() + 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)) + print('onsw') elseif nargs == 1 then onswitch[longopt](arg[i+1]) + else + onswitch[longopt]() end i = i + nargs end if v == '--' then keepParsing = false @@ -175,11 +181,11 @@ 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() + ct.exns.cli('switch -%s unrecognized', c):throw() end end else table.insert(args, v) end Index: cortav.lua ================================================================== --- cortav.lua +++ cortav.lua @@ -10,10 +10,68 @@ local dump = ss.dump local declare = ss.declare -- make this module available to require() when linked into a lua bytecode program with luac local ct = ss.namespace 'cortav' +ct.info = { + version = ss.version {0,1; 'devel'}; + package_name = 'cortav'; + contributors = { + { name = 'lexi hale', handle = 'velartrill'; + mail = 'lexi@hale.su', homepage = 'https://hale.su' }; + }; + ident_string = function(i) + return string.format('%s %s', i.package_name, i.version) + end; + credits = function(i) + local all = ss.copy(i.contributors) + for i,who in pairs(all) do + who.role = who.role or 'core functionality' + end + for name,ext in pairs(ct.ext.loaded) do + if ext.contributors then + for _,c in pairs(ext.contributors) do + local ofs, ref = ss.find(all, function(a) + return a.handle == c.handle + end) + if ofs then + ref.role = string.format('%s; %s extension', ref.role, name) + else + local c = ss.clone(ext.author) + c.role = name .. ' extension' + end + end + end + end + return all + end; + credits_ascii = function(contributors) + local body = '' + for _, c in pairs(contributors) do + local str + if c.handle then + str = string.format('%s “%s” <%s>', c.name, c.handle, c.mail) + else + str = string.format('%s <%s>', c.name, c.mail) + end + if c.homepage then + str = string.format('%s (%s)', str, c.homepage) + end + if c.role then + str = string.format('%s: %s', str, c.role) + end + body = body .. string.format(' ~ %s\n', str) + end + return body + end; + about = function(i) + return i:ident_string() .. '\n' .. + i.credits_ascii(i:credits()) + end; +} + + ct.render = {} ct.exns = { tx = ss.exnkind('translation error', function(msg,...) return string.format("(%s:%u) "..msg, ...) Index: ext/toc.lua ================================================================== --- ext/toc.lua +++ ext/toc.lua @@ -2,9 +2,11 @@ local ss = require 'sirsem' ct.ext.install { id = 'toc'; desc = 'provides a table of contents for HTML renderer plus generic fallback'; + version = ss.version {0,1; 'devel'}; + contributors = {{name='lexi hale', handle='velartrill', mail='lexi@hale.su', homepage='https://hale.su'}}; directive = function(words) end; } Index: sirsem.lua ================================================================== --- sirsem.lua +++ sirsem.lua @@ -37,10 +37,67 @@ for i, v in ipairs(list) do if fn(v,i) then table.insert(new, v) end end return new end +function ss.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 + +function ss.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 + +function ss.find(tbl, pred, ...) + pred = pred or function(a,b) + return a == b + end + for k,v in pairs(tbl) do + if pred(v,k,...) then return k,v end + end + return nil +end + +function ss.clone(tbl) -- performs a shallow copy + if tbl.clone then return tbl:clone() end + local new = {} + for k,v in pairs(tbl) do + new[k] = v + end + return new +end + +function ss.copy(tbl) -- performs a deep copy + local new = {} + for k,v in pairs(tbl) do + if type(v) == 'table' then + if v.clone then + new[k] = v:clone() -- this may or may not do a deep copy! + else + new[k] = ss.copy(v) + end + else + new[k] = v + end + end + return new +end + +function ss.delegate(tbl,tpl) -- returns a table that looks up keys it lacks from + -- tbl (lightweight alternative to shallow copies) + tpl = tpl or {} + return setmetatable({}, {__index=tbl}) +end ss.str = {} function ss.str.begins(str, pfx) return string.sub(str, 1, #pfx) == pfx @@ -270,5 +327,44 @@ end end ss.str.exn('token “%s” expected before end of line', stop):throw() end + +ss.version = ss.declare { + name = 'version'; + mk = function(tbl) return tbl end; + fns = { + pre = function(self,other) end; + post = function(self,other) end; + string = function(self) return tostring(self) end; + }; + cast = { + string = function(vers) + if not(next(vers)) then return '0.0' end + local str = '' + for _,v in pairs(vers) do + if type(v) == 'string' then + if str ~= '' then str = str .. '-' end + str = str .. v + else + if str ~= '' then str = str .. '.' end + str = str .. tostring(v) + end + end + return str + end + }; +} + +function ss.classinstance(o) + local g = getmetatable(o) + if not o then return nil end + local mm = getmetatable(g) + if not o then return nil end + if mm.__name == 'class' then + return g + else + return nil + end +end +