diff options
author | Daniel Kolesa <d.kolesa@samsung.com> | 2015-01-05 10:48:46 +0000 |
---|---|---|
committer | Daniel Kolesa <d.kolesa@samsung.com> | 2015-01-05 16:51:01 +0000 |
commit | c78fc161cfd0b952c978781b4410c8266149185e (patch) | |
tree | 4be9c8c786b6a00cebcdabeb1ef657503d48f181 | |
parent | 3be1c0b7cf2fc38b456505dc690fb4542df836db (diff) | |
download | efl-c78fc161cfd0b952c978781b4410c8266149185e.tar.gz |
elua: more getopt.lua features
Argument count limit is now supported. The source has been cleaned up and
unified so that less code repeating is done. The module is now environment
safe, not depending on specific metatable being set on strings. More callbacks
have been added and error messages are now more descriptive.
-rw-r--r-- | src/scripts/elua/modules/getopt.lua | 196 |
1 files changed, 109 insertions, 87 deletions
diff --git a/src/scripts/elua/modules/getopt.lua b/src/scripts/elua/modules/getopt.lua index 3a04ed643a..b5261a2c19 100644 --- a/src/scripts/elua/modules/getopt.lua +++ b/src/scripts/elua/modules/getopt.lua @@ -4,8 +4,7 @@ documentation. TODO: - - arguments that can only be specified once (for now you can check - that manually by going over array values of opts) + - mutually exclusive groups - i18n support Copyright (c) 2014 Daniel "q66" Kolesa <quaker66@gmail.com> @@ -29,10 +28,20 @@ DEALINGS IN THE SOFTWARE. ]] +local arg = _G.arg -- Capture global 'arg' + local M = {} local prefixes = { "-", "--" } +local ssub, sfind, sgsub, sformat, smatch = string.sub, string.find, + string.gsub, string.format, + string.match + +local slower, supper, srep = string.lower, string.upper, string.rep + +local unpack = table.unpack or unpack + local ac_process_name = function(np, nm) if not nm or #nm < 2 then return @@ -44,7 +53,7 @@ local ac_process_name = function(np, nm) np[nm] = { nm } end for i = 1, #nm do - local pnm = nm:sub(1, i - 1) .. nm:sub(i + 1) + local pnm = ssub(nm, 1, i - 1) .. ssub(nm, i + 1) local t = np[pnm] if not t then t = {} @@ -67,7 +76,7 @@ local get_autocorrect = function(descs, wrong, vi) end for i = 1, #wrong do - local nm = wrong:sub(1, i - 1) .. wrong:sub(i + 1) + local nm = ssub(wrong, 1, i - 1) .. ssub(wrong, i + 1) local inp = np[nm] if inp then if inp == true then @@ -114,34 +123,24 @@ local is_arg = function(opt, j, descs) return false end -local parse_l = function(opts, opt, descs, args, parser) - local optval - local i = opt:find("=") - if i then - opt, optval = opt:sub(1, i - 1), opt:sub(i + 1) - end - - local desc = get_desc(opt, 2, descs) - local argr = desc[3] - if argr or argr == nil then - if not optval then - if #args == 0 then - if argr then - error("option --" .. opt .. " requires an argument", 0) - end - elseif argr or not is_arg(args[1], 2, descs) then - optval = table.remove(args, 1) - end - end - elseif optval then - error("option --" .. opt .. " cannot have an argument", 0) - end +local write_arg = function(desc, j, opts, opt, optval, parser, argcounts) local rets if desc.callback then rets = { desc:callback(parser, optval, opts) } end if not rets or #rets == 0 then rets = { optval } end local optn = desc.alias or desc[1] or desc[2] + local cnt = desc.max_count or (desc.list and -1 or 1) + local acnt = argcounts[optn] + if acnt then + if cnt >= 0 and acnt >= cnt then + error("option " .. prefixes[j] .. opt + .. " can be specified at most " .. cnt .. " times", 0) + end + argcounts[optn] = acnt + 1 + else + argcounts[optn] = 1 + end opts[#opts + 1] = { optn, short = desc[1], long = desc[2], alias = desc.alias, val = optval, unpack(rets) } local optret = #rets > 1 and rets or rets[1] @@ -160,62 +159,67 @@ local parse_l = function(opts, opt, descs, args, parser) end end -local parse_s = function(opts, optstr, descs, args, parser) - while optstr ~= "" do - local optval - local opt = optstr:sub(1, 1) - optstr = optstr:sub(2) - local desc = get_desc(opt, 1, descs) - local argr = desc[3] - if argr or argr == nil then - if optstr == "" then - optstr = nil - if #args == 0 then - if argr then - error("option -" .. opt .. " requires an argument", 0) - end - elseif argr or not is_arg(args[1], 1, descs) then - optstr = table.remove(args, 1) +local parse_l = function(opts, opt, descs, args, parser, argcounts) + local optval + local i = sfind(opt, "=") + if i then + opt, optval = ssub(opt, 1, i - 1), ssub(opt, i + 1) + end + + local desc = get_desc(opt, 2, descs) + local argr = desc[3] + if argr or argr == nil then + if not optval then + if #args == 0 then + if argr then + error("option --" .. opt .. " requires an argument", 0) end + elseif argr or not is_arg(args[1], 2, descs) then + optval = table.remove(args, 1) end - optval, optstr = optstr, "" - end - local rets - if desc.callback then - rets = { desc:callback(parser, optval, opts) } - end - if not rets or #rets == 0 then rets = { optval } end - local optn = desc.alias or desc[1] or desc[2] - opts[#opts + 1] = { optn, short = desc[1], long = desc[2], - alias = desc.alias, val = optval, unpack(rets) } - local optret = #rets > 1 and rets or rets[1] - if desc.list then - desc.list[#desc.list + 1] = optret - opts[optn] = desc.list - elseif optret ~= nil then - opts[optn] = optret - else - opts[optn] = true end - local dopts = desc.opts - if dopts then - dopts[#dopts + 1] = opts[#opts] - dopts[optn] = opts[optn ] + elseif optval then + error("option --" .. opt .. " cannot have an argument", 0) + end + write_arg(desc, 2, opts, opt, optval, parser, argcounts) +end + +local parse_s = function(opts, optstr, descs, args, parser, argcounts) + local opt = ssub(optstr, 1, 1) + local optval = ssub(optstr, 2) + local desc = get_desc(opt, 1, descs) + local argr = desc[3] + if argr or argr == nil then + if optval == "" then + optval = nil + if #args == 0 then + if argr then + error("option -" .. opt .. " requires an argument", 0) + end + elseif argr or not is_arg(args[1], 1, descs) then + optval = table.remove(args, 1) + end end + elseif optval ~= "" then + error("option -" .. opt .. " cannot have an argument", 0) + else + optval = nil end + write_arg(desc, 1, opts, opt, optval, parser, argcounts) end local getopt_u = function(parser) - local args = { unpack(parser.args) } + local argcounts = {} + local args = { unpack(parser.args or arg) } local descs = parser.descs local opts = {} - while #args > 0 and args[1]:sub(1, 1) == "-" and args[1] ~= "-" do + while #args > 0 and ssub(args[1], 1, 1) == "-" and args[1] ~= "-" do local v = table.remove(args, 1) if v == "--" then break end - if v:sub(1, 2) == "--" then - parse_l(opts, v:sub(3), descs, args, parser) + if ssub(v, 1, 2) == "--" then + parse_l(opts, ssub(v, 3), descs, args, parser, argcounts) else - parse_s(opts, v:sub(2), descs, args, parser) + parse_s(opts, ssub(v, 2), descs, args, parser, argcounts) end end return opts, args @@ -293,7 +297,7 @@ end A description is represented by a table. The table has this layout: { shortn, longn, optional, help = helpmsg, metavar = metavar, - alias = alias, callback = retcb, list = list + alias = alias, callback = retcb, list = list, max_count = max_count } "shortn" refers to the short name. For example if you want your argument @@ -324,6 +328,10 @@ end times, the list will contain all the values provided. The mapping opts[n] will refer to the list rather than the last value given like without list. + The field "max_count" can be used to specify a limit on how many arguments + can be provided. Its implicit value is 1, unless a list is provided, + in which case it's -1 (which is a value for infinity here). + A description can also be used to specify a category, purely for help listing purposes: @@ -349,7 +357,7 @@ end local parse = M.parse local repl_prog = function(str, progn) - return (str:gsub("%f[%%]%%prog", progn):gsub("%%%%prog", "%%prog")) + return (sgsub(sgsub(str, "%f[%%]%%prog", progn), "%%%%prog", "%%prog")) end local buf_write = function(self, ...) @@ -360,7 +368,7 @@ end local get_metavar = function(desc) local mv = desc.metavar if not mv and (desc[3] or desc[3] == nil) then - mv = desc[2] and desc[2]:upper() or "VAL" + mv = desc[2] and supper(desc[2]) or "VAL" elseif desc[3] == false then mv = nil end @@ -369,11 +377,11 @@ end local help = function(parser, f, category) local usage = parser.usage - local progn = parser.prog or parser.args[0] or "program" + local progn = parser.prog or (parser.args or arg)[0] or "program" if usage then usage = repl_prog(usage, progn) else - usage = ("Usage: %s [OPTIONS]"):format(progn) + usage = sformat("Usage: %s [OPTIONS]", progn) end local buf = { write = buf_write } buf:write(usage, "\n") @@ -418,12 +426,12 @@ local help = function(parser, f, category) local sdf = lls - sln if desc[2] then ln[#ln + 1] = ", " end if sdf > 0 then - ln[#ln + 1] = (" "):rep(sdf) + ln[#ln + 1] = srep(" ", sdf) end elseif not desc[2] and mv then ln[#ln + 1] = mv else - ln[#ln + 1] = (" "):rep(lls + 2) + ln[#ln + 1] = srep(" ", lls + 2) end if desc[2] then ln[#ln + 1] = "--" .. desc[2] @@ -434,10 +442,10 @@ local help = function(parser, f, category) lln = math.max(lln, #ln) lns[#lns + 1] = { ln, desc.help } elseif nign and desc.category then - local lcat = category and category:lower() or nil - local alias = desc.alias and desc.alias:lower() or nil + local lcat = category and slower(category) or nil + local alias = desc.alias and slower(desc.alias) or nil iscat = (not category) or (alias == lcat) - or (desc.category:lower() == lcat) + or (slower(desc.category) == lcat) if iscat then wascat = true lns[#lns + 1] = { false, desc.category } @@ -514,7 +522,7 @@ end The header is printed only when given as part of the parser. - The "The following optins are supported:" line is only printed when there + The "The following options are supported:" line is only printed when there are options to print. Same goes for the footer as for the header. @@ -534,33 +542,47 @@ M.help = function(parser, category, f) return pcall(help, parser, f or io.stderr, category) end +-- A utility callback to parse a number +-- If a 'base' field is present in the description, uses that. +M.number_cb = function(desc, parser, v) + local n = tonumber(v, desc.base) + if not n then + error("bad number value: " .. v, 0) + end + return n +end + -- A utility callback for geometry parsing (--foo=x:y:w:h). M.geometry_parse_cb = function(desc, parser, v) - local x, y, w, h = v:match("^(%d+):(%d+):(%d+):(%d+)$") + local x, y, w, h = smatch(v, "^(%d+):(%d+):(%d+):(%d+)$") if not x then - error("bad geometry value: " .. v, 0) + error("bad geometry value (X:Y:W:H expected): " .. v, 0) end - return x, y, w, h + return tonumber(x), tonumber(y), tonumber(w), tonumber(h) end -- A utility callback for size parsing (--foo=WxH). M.size_parse_cb = function(desc, parser, v) - local w, h = v:match("^(%d+)x(%d+)$") + local w, h = smatch(v, "^(%d+)x(%d+)$") if not w then - error("bad size value: " .. v, 0) + error("bad size value (WxH expected): " .. v, 0) end - return w, h + return tonumber(w), tonumber(h) end -- A utility callback generator for help. Returns a utility callback when -- called with file stream as an argument (optional, defaults to stderr). +-- If the second argument is true, exits the program with successful exit code. -- For help args that take a value, the value will be used as a category name. -M.help_cb = function(fstream) +M.help_cb = function(fstream, exit) return function(desc, parser, v) local succ, err = M.help(parser, v, fstream) if not succ then error(err, 0) end + if exit then + os.exit(0, true) -- need 0 for lua 5.1 + end end end |