2014-04-23 07:24:45 -07:00
|
|
|
-- Elua getopt module
|
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
2014-04-29 02:41:39 -07:00
|
|
|
local prefixes = { "-", "--" }
|
|
|
|
|
2014-04-24 08:04:37 -07:00
|
|
|
local get_desc = function(opt, j, descs)
|
|
|
|
for i, v in ipairs(descs) do
|
|
|
|
if v[j] == opt then
|
|
|
|
return v
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
end
|
2014-04-29 02:41:39 -07:00
|
|
|
error("option " .. prefixes[j] .. opt .. " not recognized", 4)
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
|
2014-04-28 02:10:10 -07:00
|
|
|
local is_arg = function(opt, j, descs)
|
|
|
|
if opt == "--" then return true end
|
|
|
|
for i, v in ipairs(descs) do
|
|
|
|
if v[j] and opt == (prefixes[j] .. v[j]) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2014-04-29 02:28:00 -07:00
|
|
|
local parse_l = function(opts, opt, descs, args, parser)
|
2014-04-23 07:24:45 -07:00
|
|
|
local optval
|
|
|
|
local i = opt:find("=")
|
|
|
|
if i then
|
|
|
|
opt, optval = opt:sub(1, i - 1), opt:sub(i + 1)
|
|
|
|
end
|
|
|
|
|
2014-04-24 08:04:37 -07:00
|
|
|
local desc = get_desc(opt, 2, descs)
|
2014-04-28 02:10:10 -07:00
|
|
|
local argr = desc[3]
|
|
|
|
if argr or argr == nil then
|
2014-04-23 07:24:45 -07:00
|
|
|
if not optval then
|
|
|
|
if #args == 0 then
|
2014-04-28 02:10:10 -07:00
|
|
|
if argr then
|
|
|
|
error("option --" .. opt .. " requires an argument", 3)
|
|
|
|
end
|
|
|
|
elseif argr or not is_arg(args[1], 2, descs) then
|
|
|
|
optval, args = args[1], { unpack(args, 2) }
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
elseif optval then
|
|
|
|
error("option --" .. opt .. " cannot have an argument", 3)
|
|
|
|
end
|
2014-04-29 02:28:00 -07:00
|
|
|
local rets
|
|
|
|
if desc.callback then rets = { desc:callback(parser, optval) } end
|
|
|
|
if not rets or #rets == 0 then rets = { optval } end
|
|
|
|
opts[#opts + 1] = { desc.alias or desc[1] or desc[2], unpack(rets) }
|
2014-04-23 07:24:45 -07:00
|
|
|
return opts, args
|
|
|
|
end
|
|
|
|
|
2014-04-29 02:28:00 -07:00
|
|
|
local parse_s = function(opts, optstr, descs, args, parser)
|
2014-04-23 07:24:45 -07:00
|
|
|
while optstr ~= "" do
|
|
|
|
local optval
|
|
|
|
local opt = optstr:sub(1, 1)
|
|
|
|
optstr = optstr:sub(2)
|
2014-04-24 08:04:37 -07:00
|
|
|
local desc = get_desc(opt, 1, descs)
|
2014-04-28 02:10:10 -07:00
|
|
|
local argr = desc[3]
|
|
|
|
if argr or argr == nil then
|
2014-04-23 07:24:45 -07:00
|
|
|
if optstr == "" then
|
|
|
|
if #args == 0 then
|
2014-04-28 02:10:10 -07:00
|
|
|
if argr then
|
|
|
|
error("option -" .. opt .. " requires an argument", 3)
|
|
|
|
end
|
|
|
|
elseif argr or not is_arg(args[1], 1, descs) then
|
|
|
|
optstr, args = args[1], { unpack(args, 2) }
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
optval, optstr = optstr, ""
|
|
|
|
end
|
2014-04-29 02:28:00 -07:00
|
|
|
local rets
|
|
|
|
if desc.callback then rets = { desc:callback(parser, optval) } end
|
|
|
|
if not rets or #rets == 0 then rets = { optval } end
|
|
|
|
opts[#opts + 1] = { desc.alias or desc[1] or desc[2], unpack(rets) }
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
return opts, args
|
|
|
|
end
|
|
|
|
|
2014-04-24 08:04:37 -07:00
|
|
|
local getopt_u = function(parser)
|
|
|
|
local args = parser.args
|
|
|
|
local descs = parser.descs
|
|
|
|
local opts = {}
|
2014-04-23 07:24:45 -07:00
|
|
|
while args and #args > 0 and args[1]:sub(1, 1) == "-" and args[1] ~= "-" do
|
|
|
|
if args[1] == "--" then
|
|
|
|
args = { unpack(args, 2) }
|
|
|
|
break
|
|
|
|
end
|
|
|
|
if args[1]:sub(1, 2) == "--" then
|
2014-04-24 08:04:37 -07:00
|
|
|
opts, args = parse_l(opts, args[1]:sub(3), descs,
|
2014-04-29 02:28:00 -07:00
|
|
|
{ unpack(args, 2) }, parser)
|
2014-04-23 07:24:45 -07:00
|
|
|
else
|
2014-04-24 08:04:37 -07:00
|
|
|
opts, args = parse_s(opts, args[1]:sub(2), descs,
|
2014-04-29 02:28:00 -07:00
|
|
|
{ unpack(args, 2) }, parser)
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return opts, args
|
|
|
|
end
|
|
|
|
|
2014-04-24 08:04:37 -07:00
|
|
|
M.parse = function(parser)
|
|
|
|
local ret, opts, args = pcall(getopt_u, parser)
|
2014-04-23 07:24:45 -07:00
|
|
|
if not ret then
|
2014-04-29 02:41:39 -07:00
|
|
|
if parser.error_cb then
|
|
|
|
parser:error_cb(opts)
|
|
|
|
end
|
2014-04-23 07:24:45 -07:00
|
|
|
return nil, opts
|
|
|
|
end
|
2014-04-29 02:50:12 -07:00
|
|
|
if parser.done_cb then
|
|
|
|
parser:done_cb(opts, args)
|
|
|
|
end
|
2014-04-29 02:03:40 -07:00
|
|
|
return opts, args, parser
|
2014-04-23 07:24:45 -07:00
|
|
|
end
|
2014-04-24 07:27:33 -07:00
|
|
|
local parse = M.parse
|
2014-04-24 02:05:59 -07:00
|
|
|
|
2014-04-29 02:14:45 -07:00
|
|
|
local repl_prog = function(str, progn)
|
|
|
|
return (str:gsub("%f[%%]%%prog", progn):gsub("%%%%prog", "%%prog"))
|
|
|
|
end
|
|
|
|
|
2014-04-29 08:59:16 -07:00
|
|
|
local buf_write = function(self, ...)
|
|
|
|
local vs = { ... }
|
|
|
|
for i, v in ipairs(vs) do self[#self + 1] = v end
|
|
|
|
end
|
|
|
|
|
|
|
|
local help = function(parser, f, category)
|
2014-04-24 02:37:42 -07:00
|
|
|
local usage = parser.usage
|
|
|
|
local progn = parser.prog or parser.args[0] or "program"
|
|
|
|
if usage then
|
2014-04-29 02:14:45 -07:00
|
|
|
usage = repl_prog(usage, progn)
|
2014-04-24 02:37:42 -07:00
|
|
|
else
|
|
|
|
usage = ("Usage: %s [OPTIONS]"):format(progn)
|
|
|
|
end
|
2014-04-29 08:59:16 -07:00
|
|
|
local buf = { write = buf_write }
|
|
|
|
buf:write(usage, "\n")
|
2014-04-24 02:37:42 -07:00
|
|
|
if parser.header then
|
2014-04-29 08:59:16 -07:00
|
|
|
buf:write("\n", repl_prog(parser.header, progn), "\n")
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
if #parser.descs > 0 then
|
|
|
|
local ohdr = parser.optheader
|
2014-04-29 08:59:16 -07:00
|
|
|
buf:write("\n", ohdr and repl_prog(ohdr, progn)
|
2014-04-24 02:37:42 -07:00
|
|
|
or "The following options are supported:", "\n\n")
|
|
|
|
local lns = {}
|
|
|
|
local lln = 0
|
2014-04-29 08:59:16 -07:00
|
|
|
local iscat = false
|
|
|
|
local wascat = false
|
2014-04-24 02:37:42 -07:00
|
|
|
for i, desc in ipairs(parser.descs) do
|
2014-04-29 08:59:16 -07:00
|
|
|
if (not category or iscat) and (desc[1] or desc[2]) then
|
2014-04-24 02:37:42 -07:00
|
|
|
local mv = desc.metavar
|
2014-04-28 02:10:10 -07:00
|
|
|
if not mv and (desc[3] or desc[3] == nil) then
|
2014-04-24 02:37:42 -07:00
|
|
|
mv = desc[2] and desc[2]:upper() or "VAL"
|
2014-04-28 02:10:10 -07:00
|
|
|
elseif desc[3] == false then
|
|
|
|
mv = nil
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
local ln = {}
|
|
|
|
ln[#ln + 1] = " "
|
|
|
|
if desc[1] then
|
|
|
|
ln[#ln + 1] = "-" .. desc[1]
|
2014-04-28 02:10:10 -07:00
|
|
|
if mv then ln[#ln + 1] = (desc[3] and "[" or "[?")
|
|
|
|
.. mv .. "]" end
|
2014-04-24 02:37:42 -07:00
|
|
|
if desc[2] then ln[#ln + 1] = ", " end
|
|
|
|
end
|
|
|
|
if desc[2] then
|
|
|
|
ln[#ln + 1] = "--" .. desc[2]
|
2014-04-28 02:10:10 -07:00
|
|
|
if mv then ln[#ln + 1] = (desc[3] and "=[" or "=[?")
|
|
|
|
.. mv .. "]" end
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
ln = table.concat(ln)
|
|
|
|
lln = math.max(lln, #ln)
|
|
|
|
lns[#lns + 1] = { ln, desc.help }
|
2014-04-29 08:59:16 -07:00
|
|
|
elseif desc.category then
|
|
|
|
iscat = (not category) or (desc.alias == category)
|
|
|
|
or (desc.category == category)
|
|
|
|
if iscat then
|
|
|
|
wascat = true
|
|
|
|
lns[#lns + 1] = { false, desc.category }
|
|
|
|
end
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
end
|
2014-04-29 08:59:16 -07:00
|
|
|
if category and not wascat then
|
|
|
|
error("no such category: '" .. category .. "'", 0)
|
|
|
|
end
|
|
|
|
local fcat = true
|
2014-04-24 02:37:42 -07:00
|
|
|
for i, lnt in ipairs(lns) do
|
|
|
|
local ln = lnt[1]
|
|
|
|
local hp = lnt[2]
|
2014-04-29 08:59:16 -07:00
|
|
|
if ln == false then
|
|
|
|
if not fcat then
|
|
|
|
buf:write("\n")
|
|
|
|
end
|
|
|
|
buf:write(hp, ":\n")
|
|
|
|
fcat = false
|
|
|
|
else
|
2014-04-30 02:23:56 -07:00
|
|
|
fcat = false
|
2014-04-29 08:59:16 -07:00
|
|
|
buf:write(ln)
|
|
|
|
if hp then buf:write((" "):rep(lln - #ln), " ", hp) end
|
|
|
|
buf:write("\n")
|
|
|
|
end
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if parser.footer then
|
2014-04-29 08:59:16 -07:00
|
|
|
buf:write("\n", repl_prog(parser.footer, progn), "\n")
|
|
|
|
end
|
|
|
|
f:write(table.concat(buf))
|
|
|
|
end
|
|
|
|
|
|
|
|
M.help = function(parser, category, f)
|
|
|
|
if category and type(category) ~= "string" then
|
|
|
|
f, category = category, f
|
|
|
|
end
|
|
|
|
f = f or io.stderr
|
|
|
|
local ret, err = pcall(help, parser, f, category)
|
|
|
|
if not ret then
|
|
|
|
f:write(err, "\n\n")
|
|
|
|
help(parser, f)
|
2014-04-29 09:01:39 -07:00
|
|
|
return false, err
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
2014-04-29 09:01:39 -07:00
|
|
|
return true
|
2014-04-24 02:37:42 -07:00
|
|
|
end
|
|
|
|
|
2014-04-29 02:28:00 -07:00
|
|
|
M.geometry_parse_cb = function(desc, parser, v)
|
2014-04-28 02:47:01 -07:00
|
|
|
return v:match("^(%d+):(%d+):(%d+):(%d+)$")
|
|
|
|
end
|
|
|
|
|
2014-04-29 02:28:00 -07:00
|
|
|
M.size_parse_cb = function(desc, parser, v)
|
2014-04-28 02:47:01 -07:00
|
|
|
return v:match("^(%d+)x(%d+)$")
|
|
|
|
end
|
|
|
|
|
2014-04-29 02:28:00 -07:00
|
|
|
M.help_cb = function(fstream)
|
|
|
|
return function(desc, parser, v)
|
2014-04-30 02:23:56 -07:00
|
|
|
M.help(parser, v, fstream)
|
2014-04-29 02:28:00 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-23 07:24:45 -07:00
|
|
|
return M
|