diff --git a/spec/actions_spec.lua b/spec/actions_spec.lua
index 8f365e1..b750a66 100644
--- a/spec/actions_spec.lua
+++ b/spec/actions_spec.lua
@@ -3,8 +3,14 @@ getmetatable(Parser()).error = function(_, msg) error(msg) end
describe("tests related to actions", function()
it("calls actions for options", function()
- local action1 = spy.new(function() end)
- local action2 = spy.new(function() end)
+ local action1 = spy.new(function(_, _, arg)
+ assert.equal("nowhere", arg)
+ end)
+ local expected_args = {"Alice", "Bob"}
+ local action2 = spy.new(function(_, _, args)
+ assert.same(expected_args, args)
+ expected_args = {"Emma", "John"}
+ end)
local parser = Parser()
parser:option "-f" "--from" {
@@ -16,8 +22,7 @@ describe("tests related to actions", function()
args = 2
}
- local args = parser:parse{"-fnowhere", "--pair", "Alice", "Bob", "-p", "Emma", "John"}
- assert.same({from = "nowhere", pair = {{"Alice", "Bob"}, {"Emma", "John"}}}, args)
+ parser:parse{"-fnowhere", "--pair", "Alice", "Bob", "-p", "Emma", "John"}
assert.spy(action1).called(1)
assert.spy(action2).called(2)
end)
@@ -39,27 +44,9 @@ describe("tests related to actions", function()
action = function(...) return action3(...) end
}
- local args = parser:parse{"-vv", "--quiet"}
- assert.same({verbose = 2, quiet = true}, args)
+ parser:parse{"-vv", "--quiet"}
assert.spy(action1).called(2)
assert.spy(action2).called(1)
assert.spy(action3).called(0)
end)
-
- it("calls actions for commands", function()
- local action = spy.new(function() end)
-
- local parser = Parser "name"
- parser:flag "-v" "--verbose" {
- count = "0-3"
- }
- local add = parser:command "add" {
- action = function(...) return action(...) end
- }
- add:argument "something"
-
- local args = parser:parse{"add", "me"}
- assert.same({add = true, verbose = 0, something = "me"}, args)
- assert.spy(action).called(1)
- end)
end)
diff --git a/spec/arguments_spec.lua b/spec/arguments_spec.lua
index bc38eac..6185d8d 100644
--- a/spec/arguments_spec.lua
+++ b/spec/arguments_spec.lua
@@ -97,7 +97,7 @@ describe("tests related to positional arguments", function()
local parser = Parser()
parser:argument "foo"
- assert.has_error(function() parser:parse{} end, "too few arguments")
+ assert.has_error(function() parser:parse{} end, "argument 'foo' is required")
end)
it("handles extra arguments with several arguments correctly", function()
@@ -113,7 +113,7 @@ describe("tests related to positional arguments", function()
parser:argument "foo1"
parser:argument "foo2"
- assert.has_error(function() parser:parse{"bar"} end, "too few arguments")
+ assert.has_error(function() parser:parse{"bar"} end, "argument 'foo2' is required")
end)
it("handles too few arguments with multi-argument correctly", function()
@@ -121,7 +121,7 @@ describe("tests related to positional arguments", function()
parser:argument "foo" {
args = "+"
}
- assert.has_error(function() parser:parse{} end, "too few arguments")
+ assert.has_error(function() parser:parse{} end, "argument 'foo' is required")
end)
it("handles too many arguments with multi-argument correctly", function()
@@ -137,7 +137,7 @@ describe("tests related to positional arguments", function()
parser:argument "foo" {
args = "2-4"
}
- assert.has_error(function() parser:parse{"foo"} end, "too few arguments")
+ assert.has_error(function() parser:parse{"foo"} end, "argument 'foo' requires at least 2 arguments")
end)
it("handles too many arguments with several multi-arguments correctly", function()
@@ -159,7 +159,7 @@ describe("tests related to positional arguments", function()
parser:argument "foo2" {
args = "*"
}
- assert.has_error(function() parser:parse{} end, "too few arguments")
+ assert.has_error(function() parser:parse{} end, "argument 'foo1' is required")
end)
end)
end)
diff --git a/spec/default_spec.lua b/spec/default_spec.lua
index 3e91c5e..40094ac 100644
--- a/spec/default_spec.lua
+++ b/spec/default_spec.lua
@@ -55,7 +55,7 @@ describe("tests related to default values", function()
assert.same({output = "a.out"}, args)
args = parser:parse{"--output", "foo.txt"}
assert.same({output = "foo.txt"}, args)
- assert.has_error(function() parser:parse{"-o"} end, "too few arguments")
+ assert.has_error(function() parser:parse{"-o"} end, "option '-o' requires an argument")
end)
it("handles option with default value for multi-argument option correctly", function()
diff --git a/spec/integrity_spec.lua b/spec/integrity_spec.lua
index c81abbf..d473ac9 100644
--- a/spec/integrity_spec.lua
+++ b/spec/integrity_spec.lua
@@ -20,7 +20,7 @@ describe("tests related to CLI behaviour #unsafe", function()
assert.equal([[
Usage: ]]..script..[[ [-v] [-h] [] ...
-Error: too few arguments
+Error: argument 'input' is required
]], get_output(""))
end)
@@ -62,7 +62,7 @@ Did you mean 'install'?
assert.equal([[
Usage: ]]..script..[[ install [-f ] [-h] []
-Error: too few arguments
+Error: argument 'rock' is required
]], get_output("foo install"))
end)
diff --git a/spec/options_spec.lua b/spec/options_spec.lua
index 9e0aed7..3ebfdff 100644
--- a/spec/options_spec.lua
+++ b/spec/options_spec.lua
@@ -258,7 +258,8 @@ describe("tests related to options", function()
it("handles lack of required argument correctly", function()
local parser = Parser()
parser:option "-s" "--server"
- assert.has_error(function() parser:parse{"--server"} end, "too few arguments")
+ assert.has_error(function() parser:parse{"--server"} end, "option '--server' requires an argument")
+ assert.has_error(function() parser:parse{"-s"} end, "option '-s' requires an argument")
end)
it("handles unknown options correctly", function()
@@ -289,7 +290,7 @@ describe("tests related to options", function()
count = 1,
overwrite = false
}
- assert.has_error(function() parser:parse{"-qq"} end, "option '-q' must be used at most 1 time")
+ assert.has_error(function() parser:parse{"-qq"} end, "option '-q' must be used 1 time")
end)
it("handles too few invocations correctly", function()
diff --git a/spec/pparse_spec.lua b/spec/pparse_spec.lua
index a37d308..b72abd6 100644
--- a/spec/pparse_spec.lua
+++ b/spec/pparse_spec.lua
@@ -15,13 +15,13 @@ describe("tests related to :pparse()", function()
parser:argument "foo"
local ok, errmsg = parser:pparse{}
assert.is_false(ok)
- assert.equal("too few arguments", errmsg)
+ assert.equal("argument 'foo' is required", errmsg)
end)
- it("still raises an error if it is caused by misconfiguration", function()
+ it("rethrows errors from callbacks", function()
local parser = Parser()
parser:flag "--foo"
- :action(error)
- assert.has_error(function() parser:pparse{"--foo"} end)
+ :action(function() error("some error message") end)
+ assert.error_matches(function() parser:pparse{"--foo"} end, "some error message")
end)
end)
diff --git a/src/argparse.lua b/src/argparse.lua
index 08ac352..8c652c4 100644
--- a/src/argparse.lua
+++ b/src/argparse.lua
@@ -19,7 +19,6 @@
-- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
local function deep_update(t1, t2)
for k, v in pairs(t2) do
if type(v) == "table" then
@@ -35,61 +34,63 @@ end
-- A property is a tuple {name, callback}.
-- properties.args is number of properties that can be set as arguments
-- when calling an object.
-local function new_class(prototype, properties, parent)
+local function class(prototype, properties, parent)
-- Class is the metatable of its instances.
- local class = {}
- class.__index = class
+ local cl = {}
+ cl.__index = cl
if parent then
- class.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
+ cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype)
else
- class.__prototype = prototype
+ cl.__prototype = prototype
end
- local names = {}
+ if properties then
+ local names = {}
- -- Create setter methods and fill set of property names.
- for _, property in ipairs(properties) do
- local name, callback = property[1], property[2]
+ -- Create setter methods and fill set of property names.
+ for _, property in ipairs(properties) do
+ local name, callback = property[1], property[2]
- class[name] = function(self, value)
- if not callback(self, value) then
- self["_" .. name] = value
+ cl[name] = function(self, value)
+ if not callback(self, value) then
+ self["_" .. name] = value
+ end
+
+ return self
+ end
+
+ names[name] = true
+ end
+
+ function cl.__call(self, ...)
+ -- When calling an object, if the first argument is a table,
+ -- interpret keys as property names, else delegate arguments
+ -- to corresponding setters in order.
+ if type((...)) == "table" then
+ for name, value in pairs((...)) do
+ if names[name] then
+ self[name](self, value)
+ end
+ end
+ else
+ local nargs = select("#", ...)
+
+ for i, property in ipairs(properties) do
+ if i > nargs or i > properties.args then
+ break
+ end
+
+ local arg = select(i, ...)
+
+ if arg ~= nil then
+ self[property[1]](self, arg)
+ end
+ end
end
return self
end
-
- names[name] = true
- end
-
- function class.__call(self, ...)
- -- When calling an object, if the first argument is a table,
- -- interpret keys as property names, else delegate arguments
- -- to corresponding setters in order.
- if type((...)) == "table" then
- for name, value in pairs((...)) do
- if names[name] then
- self[name](self, value)
- end
- end
- else
- local nargs = select("#", ...)
-
- for i, property in ipairs(properties) do
- if i > nargs or i > properties.args then
- break
- end
-
- local arg = select(i, ...)
-
- if arg ~= nil then
- self[property[1]](self, arg)
- end
- end
- end
-
- return self
end
-- If indexing class fails, fallback to its parent.
@@ -104,7 +105,7 @@ local function new_class(prototype, properties, parent)
return object(...)
end
- return setmetatable(class, class_metatable)
+ return setmetatable(cl, class_metatable)
end
local function typecheck(name, types, value)
@@ -204,7 +205,7 @@ local add_help = {"add_help", function(self, value)
end
end}
-local Parser = new_class({
+local Parser = class({
_arguments = {},
_options = {},
_commands = {},
@@ -223,7 +224,7 @@ local Parser = new_class({
add_help
})
-local Command = new_class({
+local Command = class({
_aliases = {}
}, {
args = 3,
@@ -235,11 +236,10 @@ local Command = new_class({
typechecked("help", "string"),
typechecked("require_command", "boolean"),
typechecked("handle_options", "boolean"),
- typechecked("action", "function"),
add_help
}, Parser)
-local Argument = new_class({
+local Argument = class({
_minargs = 1,
_maxargs = 1,
_mincount = 1,
@@ -259,7 +259,7 @@ local Argument = new_class({
typechecked("argname", "string", "table")
})
-local Option = new_class({
+local Option = class({
_aliases = {},
_mincount = 0,
_overwrite = true
@@ -322,22 +322,42 @@ function Argument:_get_usage()
return usage
end
-function Argument:_get_type()
+local actions = {}
+
+function actions.store_true(result, target)
+ result[target] = true
+end
+
+function actions.store(result, target, argument)
+ result[target] = argument
+end
+
+function actions.count(result, target, _, overwrite)
+ if not overwrite then
+ result[target] = result[target] + 1
+ end
+end
+
+function actions.append(result, target, argument, overwrite)
+ table.insert(result[target], argument)
+
+ if overwrite then
+ table.remove(result[target], 1)
+ end
+end
+
+function Argument:_get_action()
if self._maxcount == 1 then
if self._maxargs == 0 then
- return "flag"
- elseif self._maxargs == 1 and (self._minargs == 1 or self._mincount == 1) then
- return "arg"
+ return self._action or actions.store_true, nil
else
- return "multiarg"
+ return self._action or actions.store, nil
end
else
if self._maxargs == 0 then
- return "counter"
- elseif self._maxargs == 1 and self._minargs == 1 then
- return "multicount"
+ return self._action or actions.count, 0
else
- return "twodimensional"
+ return self._action or actions.append, {}
end
end
end
@@ -411,6 +431,10 @@ function Option:_get_usage()
return usage
end
+function Argument:_get_default_target()
+ return self._name
+end
+
function Option:_get_default_target()
local res
@@ -635,19 +659,21 @@ local function get_tip(context, wrong_name)
local possible_names = {}
for name in pairs(context) do
- for i=1, #name do
- possible_name = name:sub(1, i-1) .. name:sub(i+1)
+ if type(name) == "string" then
+ for i = 1, #name do
+ possible_name = name:sub(1, i - 1) .. name:sub(i + 1)
- if not context_pool[possible_name] then
- context_pool[possible_name] = {}
+ if not context_pool[possible_name] then
+ context_pool[possible_name] = {}
+ end
+
+ table.insert(context_pool[possible_name], name)
end
-
- table.insert(context_pool[possible_name], name)
end
end
- for i=1, #wrong_name+1 do
- possible_name = wrong_name:sub(1, i-1) .. wrong_name:sub(i+1)
+ for i = 1, #wrong_name + 1 do
+ possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1)
if context[possible_name] then
possible_names[possible_name] = true
@@ -659,6 +685,7 @@ local function get_tip(context, wrong_name)
end
local first = next(possible_names)
+
if first then
if next(possible_names, first) then
local possible_names_arr = {}
@@ -677,350 +704,360 @@ local function get_tip(context, wrong_name)
end
end
-local function plural(x)
- if x == 1 then
- return ""
- end
+local ElementState = class({
+ invocations = 0
+})
- return "s"
+function ElementState:__call(state, element)
+ self.state = state
+ self.result = state.result
+ self.element = element
+ self.target = element._target or element:_get_default_target()
+ self.action, self.result[self.target] = element:_get_action()
+ return self
end
--- Compatibility with strict.lua and other checkers:
-local default_cmdline = rawget(_G, "arg") or {}
+function ElementState:error(fmt, ...)
+ self.state:error(fmt, ...)
+end
-function Parser:_parse(args, errhandler)
- args = args or default_cmdline
- local parser
- local charset
- local options = {}
- local arguments = {}
- local commands
- local option_mutexes = {}
- local used_mutexes = {}
- local opt_context = {}
- local com_context
- local result = {}
- local invocations = {}
- local passed = {}
- local cur_option
- local cur_arg_i = 1
- local cur_arg
- local targets = {}
- local handle_options = true
+function ElementState:convert(argument)
+ local converter = self.element._convert
- local function error_(fmt, ...)
- return errhandler(parser, fmt:format(...))
- end
+ if converter then
+ local ok, err
- local function assert_(assertion, ...)
- return assertion or error_(...)
- end
-
- local function convert(element, data)
- if element._convert then
- local ok, err
-
- if type(element._convert) == "function" then
- ok, err = element._convert(data)
- else
- ok = element._convert[data]
- end
-
- assert_(ok ~= nil, "%s", err or "malformed argument '" .. data .. "'")
- data = ok
- end
-
- return data
- end
-
- local invoke, pass, close
-
- function invoke(element)
- local overwrite = false
-
- if invocations[element] == element._maxcount then
- if element._overwrite then
- overwrite = true
- else
- error_("option '%s' must be used at most %d time%s", element._name, element._maxcount, plural(element._maxcount))
- end
+ if type(converter) == "function" then
+ ok, err = converter(argument)
else
- invocations[element] = invocations[element]+1
+ ok = converter[argument]
end
- passed[element] = 0
- local type_ = element:_get_type()
- local target = targets[element]
-
- if type_ == "flag" then
- result[target] = true
- elseif type_ == "multiarg" then
- result[target] = {}
- elseif type_ == "counter" then
- if not overwrite then
- result[target] = result[target]+1
- end
- elseif type_ == "multicount" then
- if overwrite then
- table.remove(result[target], 1)
- end
- elseif type_ == "twodimensional" then
- table.insert(result[target], {})
-
- if overwrite then
- table.remove(result[target], 1)
- end
+ if ok == nil then
+ self:error(err and "%s" or "malformed argument '%s'", err or argument)
end
- if element._maxargs == 0 then
- close(element)
- end
+ argument = ok
end
- function pass(element, data)
- passed[element] = passed[element]+1
- data = convert(element, data)
- local type_ = element:_get_type()
- local target = targets[element]
+ return argument
+end
- if type_ == "arg" then
- result[target] = data
- elseif type_ == "multiarg" or type_ == "multicount" then
- table.insert(result[target], data)
- elseif type_ == "twodimensional" then
- table.insert(result[target][#result[target]], data)
- end
+function ElementState:default(mode)
+ return self.element._defmode:find(mode) and self.element._default
+end
- if passed[element] == element._maxargs then
- close(element)
- end
+local function bound(noun, min, max, is_max)
+ local res = ""
+
+ if min ~= max then
+ res = "at " .. (is_max and "most" or "least") .. " "
end
- local function complete_invocation(element)
- while passed[element] < element._minargs do
- pass(element, element._default)
- end
- end
+ local number = is_max and max or min
+ return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s")
+end
- function close(element)
- if passed[element] < element._minargs then
- if element._default and element._defmode:find "a" then
- complete_invocation(element)
- else
- error_("too few arguments")
- end
+function ElementState:invoke(alias)
+ self.open = true
+ self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name)
+ self.overwrite = false
+
+ if self.invocations >= self.element._maxcount then
+ if self.element._overwrite then
+ self.overwrite = true
else
- if element == cur_option then
- cur_option = nil
- elseif element == cur_arg then
- cur_arg_i = cur_arg_i+1
- cur_arg = arguments[cur_arg_i]
- end
+ self:error("%s must be used %s", self.name, bound("time", self.element._mincount, self.element._maxcount, true))
end
+ else
+ self.invocations = self.invocations + 1
end
- local function switch(p)
- parser = p
+ self.args = {}
- for _, option in ipairs(parser._options) do
- table.insert(options, option)
+ if self.element._maxargs <= 0 then
+ self:close()
+ end
- for _, alias in ipairs(option._aliases) do
- opt_context[alias] = option
- end
+ return self.open
+end
- local type_ = option:_get_type()
- targets[option] = option._target or option:_get_default_target()
+function ElementState:pass(argument)
+ argument = self:convert(argument)
+ table.insert(self.args, argument)
- if type_ == "counter" then
- result[targets[option]] = 0
- elseif type_ == "multicount" or type_ == "twodimensional" then
- result[targets[option]] = {}
- end
+ if #self.args >= self.element._maxargs then
+ self:close()
+ end
- invocations[option] = 0
- end
+ return self.open
+end
- for _, mutex in ipairs(parser._mutexes) do
- for _, option in ipairs(mutex) do
- if not option_mutexes[option] then
- option_mutexes[option] = {mutex}
- else
- table.insert(option_mutexes[option], mutex)
+function ElementState:complete_invocation()
+ while #self.args < self.element._minargs do
+ self:pass(self.element._default)
+ end
+end
+
+function ElementState:close()
+ if self.open then
+ self.open = false
+
+ if #self.args < self.element._minargs then
+ if self:default("a") then
+ self:complete_invocation()
+ else
+ if #self.args == 0 then
+ if getmetatable(self.element) == Argument then
+ self:error("%s is required", self.name)
+ elseif self.element._maxargs == 1 then
+ self:error("%s requires an argument", self.name)
+ end
end
+
+ self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs))
end
end
- for _, argument in ipairs(parser._arguments) do
- table.insert(arguments, argument)
- invocations[argument] = 0
- targets[argument] = argument._target or argument._name
- invoke(argument)
+ local args = self.args
+
+ if self.element._maxargs <= 1 then
+ args = args[1]
end
- handle_options = parser._handle_options
- cur_arg = arguments[cur_arg_i]
- commands = parser._commands
- com_context = {}
+ if self.element._maxargs == 1 and self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then
+ args = self.args
+ end
- for _, command in ipairs(commands) do
- targets[command] = command._target or command._name
+ self.action(self.result, self.target, args, self.overwrite)
+ end
+end
- for _, alias in ipairs(command._aliases) do
- com_context[alias] = command
+local ParseState = class({
+ result = {},
+ options = {},
+ arguments = {},
+ argument_i = 1,
+ element_to_mutexes = {},
+ mutex_to_used_option = {}
+})
+
+function ParseState:__call(parser, error_handler)
+ self.parser = parser
+ self.error_handler = error_handler
+ self.charset = parser:_update_charset()
+ self:switch(parser)
+ return self
+end
+
+function ParseState:error(fmt, ...)
+ self.error_handler(self.parser, fmt:format(...))
+end
+
+function ParseState:switch(parser)
+ self.parser = parser
+
+ for _, option in ipairs(parser._options) do
+ option = ElementState(self, option)
+ table.insert(self.options, option)
+
+ for _, alias in ipairs(option.element._aliases) do
+ self.options[alias] = option
+ end
+ end
+
+ for _, mutex in ipairs(parser._mutexes) do
+ for _, option in ipairs(mutex) do
+ if not self.element_to_mutexes[option] then
+ self.element_to_mutexes[option] = {}
end
+
+ table.insert(self.element_to_mutexes[option], mutex)
end
end
- local function get_option(name)
- return assert_(opt_context[name], "unknown option '%s'%s", name, get_tip(opt_context, name))
+ for _, argument in ipairs(parser._arguments) do
+ argument = ElementState(self, argument)
+ table.insert(self.arguments, argument)
+ argument:invoke()
end
- local function do_action(element)
- if element._action then
- element._action()
+ self.handle_options = parser._handle_options
+ self.argument = self.arguments[self.argument_i]
+ self.commands = parser._commands
+
+ for _, command in ipairs(self.commands) do
+ for _, alias in ipairs(command._aliases) do
+ self.commands[alias] = command
end
end
+end
- local function handle_argument(data)
- if cur_option then
- pass(cur_option, data)
- elseif cur_arg then
- pass(cur_arg, data)
+function ParseState:get_option(name)
+ local option = self.options[name]
+
+ if not option then
+ self:error("unknown option '%s'%s", name, get_tip(self.options, name))
+ else
+ return option
+ end
+end
+
+function ParseState:get_command(name)
+ local command = self.commands[name]
+
+ if not command then
+ if #self.commands > 0 then
+ self:error("unknown command '%s'%s", name, get_tip(self.commands, name))
else
- local com = com_context[data]
+ self:error("too many arguments")
+ end
+ else
+ return command
+ end
+end
- if not com then
- if #commands > 0 then
- error_("unknown command '%s'%s", data, get_tip(com_context, data))
- else
- error_("too many arguments")
+function ParseState:invoke(option, name)
+ self:close()
+
+ if self.element_to_mutexes[option.element] then
+ for _, mutex in ipairs(self.element_to_mutexes[option.element]) do
+ local used_option = self.mutex_to_used_option[mutex]
+
+ if used_option and used_option ~= option then
+ self:error("option '%s' can not be used together with %s", name, used_option.name)
+ else
+ self.mutex_to_used_option[mutex] = option
+ end
+ end
+ end
+
+ if option:invoke(name) then
+ self.option = option
+ end
+end
+
+function ParseState:pass(arg)
+ if self.option then
+ if not self.option:pass(arg) then
+ self.option = nil
+ end
+ elseif self.argument then
+ if not self.argument:pass(arg) then
+ self.argument_i = self.argument_i + 1
+ self.argument = self.arguments[self.argument_i]
+ end
+ else
+ local command = self:get_command(arg)
+ self.result[command._target or command._name] = true
+ self:switch(command)
+ end
+end
+
+function ParseState:close()
+ if self.option then
+ self.option:close()
+ self.option = nil
+ end
+end
+
+function ParseState:finalize()
+ self:close()
+
+ for i = self.argument_i, #self.arguments do
+ local argument = self.arguments[i]
+ if #argument.args == 0 and argument:default("u") then
+ argument:complete_invocation()
+ else
+ argument:close()
+ end
+ end
+
+ if self.parser._require_command and #self.commands > 0 then
+ self:error("a command is required")
+ end
+
+ for _, option in ipairs(self.options) do
+ local name = option.name or ("option '%s'"):format(option.element._name)
+
+ if option.invocations == 0 then
+ if option:default("u") then
+ option:invoke(name)
+ option:complete_invocation()
+ option:close()
+ end
+ end
+
+ local mincount = option.element._mincount
+
+ if option.invocations < mincount then
+ if option:default("a") then
+ while option.invocations < mincount do
+ option:invoke(name)
+ option:close()
end
else
- result[targets[com]] = true
- do_action(com)
- switch(com)
+ self:error("%s must be used %s", name, bound("time", mincount, option.element._maxcount))
end
end
end
+end
- local function handle_option(data)
- if cur_option then
- close(cur_option)
- end
+function ParseState:parse(args)
+ for _, arg in ipairs(args) do
+ local plain = true
- cur_option = opt_context[data]
+ if self.handle_options then
+ local first = arg:sub(1, 1)
- if option_mutexes[cur_option] then
- for _, mutex in ipairs(option_mutexes[cur_option]) do
- if used_mutexes[mutex] and used_mutexes[mutex] ~= cur_option then
- error_("option '%s' can not be used together with option '%s'", data, used_mutexes[mutex]._name)
- else
- used_mutexes[mutex] = cur_option
- end
- end
- end
+ if self.charset[first] then
+ if #arg > 1 then
+ plain = false
- do_action(cur_option)
- invoke(cur_option)
- end
-
- local function mainloop()
-
- for _, data in ipairs(args) do
- local plain = true
- local first, name, option
-
- if handle_options then
- first = data:sub(1, 1)
- if charset[first] then
- if #data > 1 then
- plain = false
- if data:sub(2, 2) == first then
- if #data == 2 then
- if cur_option then
- close(cur_option)
- end
-
- handle_options = false
- else
- local equal = data:find "="
- if equal then
- name = data:sub(1, equal-1)
- option = get_option(name)
- assert_(option._maxargs > 0, "option '%s' does not take arguments", name)
-
- handle_option(data:sub(1, equal-1))
- handle_argument(data:sub(equal+1))
- else
- get_option(data)
- handle_option(data)
- end
- end
+ if arg:sub(2, 2) == first then
+ if #arg == 2 then
+ self:close()
+ self.handle_options = false
else
- for i = 2, #data do
- name = first .. data:sub(i, i)
- option = get_option(name)
- handle_option(name)
+ local equals = arg:find "="
+ if equals then
+ local name = arg:sub(1, equals - 1)
+ local option = self:get_option(name)
- if i ~= #data and option._minargs > 0 then
- handle_argument(data:sub(i+1))
- break
+ if option.element._maxargs <= 0 then
+ self:error("option '%s' does not take arguments", name)
end
+
+ self:invoke(option, name)
+ self:pass(arg:sub(equals + 1))
+ else
+ local option = self:get_option(arg)
+ self:invoke(option, arg)
+ end
+ end
+ else
+ for i = 2, #arg do
+ local name = first .. arg:sub(i, i)
+ local option = self:get_option(name)
+ self:invoke(option, name)
+
+ if i ~= #arg and option.element._minargs > 0 and option.element._maxargs > 0 then
+ self:pass(arg:sub(i + 1))
+ break
end
end
end
end
end
+ end
- if plain then
- handle_argument(data)
- end
+ if plain then
+ self:pass(arg)
end
end
- switch(self)
- charset = parser:_update_charset()
- mainloop()
-
- if cur_option then
- close(cur_option)
- end
-
- while cur_arg do
- if passed[cur_arg] == 0 and cur_arg._default and cur_arg._defmode:find "u" then
- complete_invocation(cur_arg)
- else
- close(cur_arg)
- end
- end
-
- if parser._require_command and #commands > 0 then
- error_("a command is required")
- end
-
- for _, option in ipairs(options) do
- if invocations[option] == 0 then
- if option._default and option._defmode:find "u" then
- invoke(option)
- complete_invocation(option)
- close(option)
- end
- end
-
- if invocations[option] < option._mincount then
- if option._default and option._defmode:find "a" then
- while invocations[option] < option._mincount do
- invoke(option)
- close(option)
- end
- else
- error_("option '%s' must be used at least %d time%s", option._name, option._mincount, plural(option._mincount))
- end
- end
- end
-
- return result
+ self:finalize()
+ return self.result
end
function Parser:error(msg)
@@ -1028,24 +1065,37 @@ function Parser:error(msg)
os.exit(1)
end
+-- Compatibility with strict.lua and other checkers:
+local default_cmdline = rawget(_G, "arg") or {}
+
+function Parser:_parse(args, error_handler)
+ return ParseState(self, error_handler):parse(args or default_cmdline)
+end
+
function Parser:parse(args)
- return self:_parse(args, Parser.error)
+ return self:_parse(args, self.error)
+end
+
+local function xpcall_error_handler(err)
+ return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2)
end
function Parser:pparse(args)
- local errmsg
- local ok, result = pcall(function()
+ local parse_error
+
+ local ok, result = xpcall(function()
return self:_parse(args, function(_, err)
- errmsg = err
- return error()
+ parse_error = err
+ error(err, 0)
end)
- end)
+ end, xpcall_error_handler)
if ok then
return true, result
+ elseif not parse_error then
+ error(result, 0)
else
- assert(errmsg, result)
- return false, errmsg
+ return false, parse_error
end
end