mirror of
https://github.com/TangentFoxy/argparse.git
synced 2025-07-28 02:52:20 +00:00
Finished working on new interface and stuff
This commit is contained in:
@@ -16,8 +16,6 @@ dependencies = {
|
|||||||
build = {
|
build = {
|
||||||
type = "builtin",
|
type = "builtin",
|
||||||
modules = {
|
modules = {
|
||||||
argparse = "src/argparse.lua",
|
argparse = "src/argparse.lua"
|
||||||
["argparse.state"] = "src/state.lua",
|
|
||||||
["argparse.utils"] = "src/utils.lua"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -77,16 +77,6 @@ describe("tests related to positional arguments", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe("passing incorrect arguments", function()
|
describe("passing incorrect arguments", function()
|
||||||
local old_parser = argparse.parser
|
|
||||||
|
|
||||||
setup(function()
|
|
||||||
argparse.parser = old_parser:extends()
|
|
||||||
function argparse.parser:error(fmt, ...)
|
|
||||||
error(fmt:format(...))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
|
|
||||||
it("handles extra arguments with empty parser correctly", function()
|
it("handles extra arguments with empty parser correctly", function()
|
||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
|
|
||||||
@@ -132,17 +122,17 @@ describe("tests related to positional arguments", function()
|
|||||||
|
|
||||||
it("handles too few arguments with multi-argument correctly", function()
|
it("handles too few arguments with multi-argument correctly", function()
|
||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
parser:argument("foo", {
|
parser:argument "foo" {
|
||||||
args = "+"
|
args = "+"
|
||||||
})
|
}
|
||||||
assert.has_error(curry(parser.parse, parser, {}), "too few arguments")
|
assert.has_error(curry(parser.parse, parser, {}), "too few arguments")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("handles too many arguments with multi-argument correctly", function()
|
it("handles too many arguments with multi-argument correctly", function()
|
||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
parser:argument("foo", {
|
parser:argument "foo" {
|
||||||
args = "2-4"
|
args = "2-4"
|
||||||
})
|
}
|
||||||
assert.has_error(curry(parser.parse, parser, {"foo", "bar", "baz", "qu", "quu"}), "too many arguments")
|
assert.has_error(curry(parser.parse, parser, {"foo", "bar", "baz", "qu", "quu"}), "too many arguments")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@@ -151,16 +151,6 @@ describe("tests related to options", function()
|
|||||||
assert.same(args, {exclude = {{"Alice", "Bob"}, {"Emma", "Jacob"}}})
|
assert.same(args, {exclude = {{"Alice", "Bob"}, {"Emma", "Jacob"}}})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("handles multi-count option with optional argument correctly", function()
|
|
||||||
local parser = argparse.parser()
|
|
||||||
parser:option("-w", "--why", "--why-would-someone-use-this", {
|
|
||||||
count = "*",
|
|
||||||
args = "?"
|
|
||||||
})
|
|
||||||
local args = parser:parse({"-w", "-wfoo", "--why=because", "-ww"})
|
|
||||||
assert.same(args, {why = {{}, {"foo"}, {"because"}, {}, {}}})
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles multi-count flag correctly", function()
|
it("handles multi-count flag correctly", function()
|
||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
parser:flag("-q", "--quiet", {
|
parser:flag("-q", "--quiet", {
|
||||||
@@ -191,15 +181,6 @@ describe("tests related to options", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
describe("passing incorrect options", function()
|
describe("passing incorrect options", function()
|
||||||
local old_parser = argparse.parser
|
|
||||||
|
|
||||||
setup(function()
|
|
||||||
argparse.parser = old_parser:extends()
|
|
||||||
function argparse.parser:error(fmt, ...)
|
|
||||||
error(fmt:format(...))
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles lack of required argument correctly", function()
|
it("handles lack of required argument correctly", function()
|
||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
parser:option("-s", "--server")
|
parser:option("-s", "--server")
|
||||||
@@ -222,9 +203,17 @@ describe("tests related to options", function()
|
|||||||
local parser = argparse.parser()
|
local parser = argparse.parser()
|
||||||
parser:flag("-q", "--quiet", {
|
parser:flag("-q", "--quiet", {
|
||||||
count = 1,
|
count = 1,
|
||||||
no_overwrite = true
|
overwrite = false
|
||||||
})
|
})
|
||||||
assert.has_error(curry(parser.parse, parser, {"-qq"}), "option -q must be used at most 1 times")
|
assert.has_error(curry(parser.parse, parser, {"-qq"}), "option -q must be used at most 1 times")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("handles too few invocations correctly", function()
|
||||||
|
local parser = argparse.parser()
|
||||||
|
parser:option("-f", "--foo", {
|
||||||
|
count = "3-4"
|
||||||
|
})
|
||||||
|
assert.has_error(curry(parser.parse, parser, {"-fFOO", "-fBAR"}), "option -f must be used at least 3 times")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@@ -1,39 +0,0 @@
|
|||||||
local utils = require "argparse.utils"
|
|
||||||
|
|
||||||
describe("tests related to utils.parse_boundaries", function()
|
|
||||||
it("handles * correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries("*")
|
|
||||||
assert.equal(min, 0)
|
|
||||||
assert.equal(max, math.huge)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles + correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries("+")
|
|
||||||
assert.equal(min, 1)
|
|
||||||
assert.equal(max, math.huge)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles ? correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries("?")
|
|
||||||
assert.equal(min, 0)
|
|
||||||
assert.equal(max, 1)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles numbers correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries(42)
|
|
||||||
assert.equal(min, 42)
|
|
||||||
assert.equal(max, 42)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles numbers+ correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries("42+")
|
|
||||||
assert.equal(min, 42)
|
|
||||||
assert.equal(max, math.huge)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("handles ranges correctly", function()
|
|
||||||
local min, max = utils.parse_boundaries("42-96")
|
|
||||||
assert.equal(min, 42)
|
|
||||||
assert.equal(max, 96)
|
|
||||||
end)
|
|
||||||
end)
|
|
491
src/argparse.lua
491
src/argparse.lua
@@ -2,203 +2,10 @@ local argparse = {}
|
|||||||
|
|
||||||
local class = require "30log"
|
local class = require "30log"
|
||||||
|
|
||||||
function State:invoke(element)
|
|
||||||
if not self.invocatons then
|
|
||||||
|
|
||||||
|
|
||||||
function State:push(option)
|
|
||||||
if self.top_is_opt then
|
|
||||||
self:pop()
|
|
||||||
end
|
|
||||||
|
|
||||||
self:invoke(option)
|
|
||||||
|
|
||||||
if option.maxargs ~= 0 then
|
|
||||||
table.insert(self.stack, option)
|
|
||||||
self.top_is_opt = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:pop()
|
|
||||||
if self.top_is_opt
|
|
||||||
|
|
||||||
|
|
||||||
function State:handle_option(name)
|
|
||||||
local option = self:_assert(self.context[name], "unknown option %s", name)
|
|
||||||
|
|
||||||
self:_open(option)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:handle_argument(data)
|
|
||||||
if self._current then
|
|
||||||
if self:_can_pass(self._current) then
|
|
||||||
self:_pass(self._current, data)
|
|
||||||
return
|
|
||||||
else
|
|
||||||
self._current = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local argument = self._arguments[self._next_arg_i]
|
|
||||||
if argument then
|
|
||||||
self:_open(argument)
|
|
||||||
self:_pass(argument, data)
|
|
||||||
else
|
|
||||||
local command = self.context[data]
|
|
||||||
if command and command.type == "command" then
|
|
||||||
self._result[command.target] = {{}}
|
|
||||||
self:_switch(command)
|
|
||||||
else
|
|
||||||
if #self._commands > 0 then
|
|
||||||
self:_error("unknown command %s", data)
|
|
||||||
else
|
|
||||||
self:_error("too many arguments")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:get_result()
|
|
||||||
self:_check()
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
|
|
||||||
local invocations
|
|
||||||
for _, element in ipairs(self._all_elements) do
|
|
||||||
invocations = self._result[element.target]
|
|
||||||
|
|
||||||
if element.maxcount == 1 then
|
|
||||||
if element.maxargs == 0 then
|
|
||||||
if #invocations > 0 then
|
|
||||||
result[element.target] = true
|
|
||||||
end
|
|
||||||
elseif element.maxargs == 1 and element.minargs == 1 then
|
|
||||||
if #invocations > 0 then
|
|
||||||
result[element.target] = invocations[1][1]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result[element.target] = invocations[1]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if element.maxargs == 0 then
|
|
||||||
result[element.target] = #invocations
|
|
||||||
elseif element.maxargs == 1 and element.minargs == 1 then
|
|
||||||
local new_result = {}
|
|
||||||
for i, passed in ipairs(invocations) do
|
|
||||||
new_result[i] = passed[1]
|
|
||||||
end
|
|
||||||
result[element.target] = new_result
|
|
||||||
else
|
|
||||||
result[element.target] = invocations
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_check()
|
|
||||||
self:_assert(not self._parser.must_command, "a command is required")
|
|
||||||
|
|
||||||
local invocations
|
|
||||||
for _, element in ipairs(self._all_elements) do
|
|
||||||
invocations = self._result[element.target] or {}
|
|
||||||
|
|
||||||
if element.type == "argument" and #invocations == 0 then
|
|
||||||
invocations[1] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
if #invocations > element.maxcount then
|
|
||||||
if element.no_overwrite then
|
|
||||||
self:_error("option %s must be used at most %d times", element.name, element.maxcount)
|
|
||||||
else
|
|
||||||
local new_invocations = {}
|
|
||||||
for i = 1, element.maxcount do
|
|
||||||
new_invocations[i] = invocations[#invocations-element.maxcount+i]
|
|
||||||
end
|
|
||||||
invocations = new_invocations
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:_assert(#invocations >= element.mincount, "option %s must be used at least %d times", element.name, element.mincount)
|
|
||||||
|
|
||||||
for _, passed in ipairs(invocations) do
|
|
||||||
self:_assert(#passed <= element.maxargs, "too many arguments")
|
|
||||||
if #passed < element.minargs then
|
|
||||||
if element.default then
|
|
||||||
for i = 1, element.minargs-#passed do
|
|
||||||
table.insert(passed, element.default)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:_error("too few arguments")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self._result[element.target] = invocations
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, group in ipairs(self._all_groups) do
|
|
||||||
local invoked
|
|
||||||
for _, element in ipairs(group.elements) do
|
|
||||||
if #self._result[element.target] > 0 then
|
|
||||||
if invoked then
|
|
||||||
self:_error("%s can not be used together with %s", invoked.name, element.name)
|
|
||||||
else
|
|
||||||
invoked = element
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if group.required then
|
|
||||||
self:_assert(invoked, "WIP(required mutually exclusive group)")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_open(element)
|
|
||||||
if not self._result[element.target] then
|
|
||||||
self._result[element.target] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(self._result[element.target], {})
|
|
||||||
|
|
||||||
if element.type == "argument" then
|
|
||||||
self._next_arg_i = self._next_arg_i+1
|
|
||||||
end
|
|
||||||
|
|
||||||
self._current = element
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_can_pass(element)
|
|
||||||
local invocations = self._result[element.target]
|
|
||||||
local passed = invocations[#invocations]
|
|
||||||
|
|
||||||
return #passed < element.maxargs
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_pass(element, data)
|
|
||||||
local invocations = self._result[element.target]
|
|
||||||
local passed = invocations[#invocations]
|
|
||||||
|
|
||||||
table.insert(passed, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_error(...)
|
|
||||||
return self._parser:error(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_assert(...)
|
|
||||||
return self._parser:assert(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local utils = require "argparse.utils"
|
|
||||||
|
|
||||||
local Declarative = {}
|
local Declarative = {}
|
||||||
|
|
||||||
function Declarative:__init(...)
|
function Declarative:__init(...)
|
||||||
return self(...)
|
self(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Declarative:__call(...)
|
function Declarative:__call(...)
|
||||||
@@ -228,6 +35,7 @@ function Declarative:__call(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local Parser = class {
|
local Parser = class {
|
||||||
|
__name = "Parser",
|
||||||
arguments = {},
|
arguments = {},
|
||||||
options = {},
|
options = {},
|
||||||
commands = {},
|
commands = {},
|
||||||
@@ -235,54 +43,72 @@ local Parser = class {
|
|||||||
}:include(Declarative)
|
}:include(Declarative)
|
||||||
|
|
||||||
local Command = Parser:extends {
|
local Command = Parser:extends {
|
||||||
|
__name = "Command",
|
||||||
aliases = {}
|
aliases = {}
|
||||||
}:include(Declarative)
|
}:include(Declarative)
|
||||||
|
|
||||||
local Argument = class {
|
local Argument = class {
|
||||||
|
__name = "Argument",
|
||||||
args = 1,
|
args = 1,
|
||||||
count = 1,
|
count = 1,
|
||||||
fields = {"name", "description", "target", "args", "default", "convert"}
|
fields = {
|
||||||
|
"name", "description", "target", "args",
|
||||||
|
"minargs", "maxargs", "default", "convert"
|
||||||
|
}
|
||||||
}:include(Declarative)
|
}:include(Declarative)
|
||||||
|
|
||||||
local Option = class {
|
local Option = class {
|
||||||
|
__name = "Option",
|
||||||
aliases = {},
|
aliases = {},
|
||||||
args = 1,
|
args = 1,
|
||||||
count = "?",
|
count = "?",
|
||||||
fields = {"name", "aliases", "description", "target", "args", "count", "default", "convert"}
|
overwrite = true,
|
||||||
|
fields = {
|
||||||
|
"name", "aliases", "description", "target",
|
||||||
|
"args", "minargs", "maxargs", "count",
|
||||||
|
"mincount", "maxcount", "default", "convert",
|
||||||
|
"overwrite"
|
||||||
|
}
|
||||||
}:include(Declarative)
|
}:include(Declarative)
|
||||||
|
|
||||||
local Flag = Option:extends {
|
local Flag = Option:extends {
|
||||||
|
__name = "Flag",
|
||||||
args = 0
|
args = 0
|
||||||
}:include(Declarative)
|
}:include(Declarative)
|
||||||
|
|
||||||
function Parser:argument(...)
|
function Parser:argument(...)
|
||||||
local argument = Argument(...)
|
local argument = Argument:new(...)
|
||||||
table.insert(self.arguments, argument)
|
table.insert(self.arguments, argument)
|
||||||
return argument
|
return argument
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:option(...)
|
function Parser:option(...)
|
||||||
local option = Option(...)
|
local option = Option:new(...)
|
||||||
table.insert(self.options, option)
|
table.insert(self.options, option)
|
||||||
return option
|
return option
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:flag(...)
|
function Parser:flag(...)
|
||||||
local flag = Flag(...)
|
local flag = Flag:new(...)
|
||||||
table.insert(self.options, flag)
|
table.insert(self.options, flag)
|
||||||
return flag
|
return flag
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:command(...)
|
function Parser:command(...)
|
||||||
local command = Command(...)
|
local command = Command:new(...)
|
||||||
table.insert(self.commands, command)
|
table.insert(self.commands, command)
|
||||||
return command
|
return command
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:error(fmt, ...)
|
function Parser:error(fmt, ...)
|
||||||
local msg = fmt:format(...)
|
local msg = fmt:format(...)
|
||||||
io.stderr:write("Error: " .. msg .. "\n")
|
|
||||||
os.exit(1)
|
if _TEST then
|
||||||
|
error(msg)
|
||||||
|
else
|
||||||
|
io.stderr:write("Error: " .. msg .. "\r\n")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:assert(assertion, ...)
|
function Parser:assert(assertion, ...)
|
||||||
@@ -291,7 +117,7 @@ end
|
|||||||
|
|
||||||
function Parser:make_charset()
|
function Parser:make_charset()
|
||||||
if not self.charset then
|
if not self.charset then
|
||||||
self.charset = {}
|
self.charset = {["-"] = true}
|
||||||
|
|
||||||
for _, command in ipairs(self.commands) do
|
for _, command in ipairs(self.commands) do
|
||||||
command:make_charset()
|
command:make_charset()
|
||||||
@@ -332,7 +158,49 @@ function Parser:make_targets()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function self:make_command_names()
|
local function parse_boundaries(boundaries)
|
||||||
|
if tonumber(boundaries) then
|
||||||
|
return tonumber(boundaries), tonumber(boundaries)
|
||||||
|
end
|
||||||
|
|
||||||
|
if boundaries == "*" then
|
||||||
|
return 0, math.huge
|
||||||
|
end
|
||||||
|
|
||||||
|
if boundaries == "+" then
|
||||||
|
return 1, math.huge
|
||||||
|
end
|
||||||
|
|
||||||
|
if boundaries == "?" then
|
||||||
|
return 0, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if boundaries:match "^%d+%-%d+$" then
|
||||||
|
local min, max = boundaries:match "^(%d+)%-(%d+)$"
|
||||||
|
return tonumber(min), tonumber(max)
|
||||||
|
end
|
||||||
|
|
||||||
|
if boundaries:match "^%d+%+$" then
|
||||||
|
local min = boundaries:match "^(%d+)%+$"
|
||||||
|
return tonumber(min), math.huge
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:make_boundaries()
|
||||||
|
for _, elements in ipairs{self.arguments, self.options} do
|
||||||
|
for _, element in ipairs(elements) do
|
||||||
|
if not element.minargs or not element.maxargs then
|
||||||
|
element.minargs, element.maxargs = parse_boundaries(element.args)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not element.mincount or not element.maxcount then
|
||||||
|
element.mincount, element.maxcount = parse_boundaries(element.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:make_command_names()
|
||||||
for _, command in ipairs(self.commands) do
|
for _, command in ipairs(self.commands) do
|
||||||
command.name = self.name .. " " .. command.name
|
command.name = self.name .. " " .. command.name
|
||||||
end
|
end
|
||||||
@@ -341,6 +209,7 @@ end
|
|||||||
function Parser:prepare()
|
function Parser:prepare()
|
||||||
self:make_charset()
|
self:make_charset()
|
||||||
self:make_targets()
|
self:make_targets()
|
||||||
|
self:make_boundaries()
|
||||||
self:make_command_names()
|
self:make_command_names()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -353,9 +222,60 @@ function Parser:parse(args)
|
|||||||
local charset
|
local charset
|
||||||
local options = {}
|
local options = {}
|
||||||
local arguments, commands
|
local arguments, commands
|
||||||
local opt_context= {}
|
local opt_context = {}
|
||||||
local com_context
|
local com_context
|
||||||
local result = {}
|
local result = {}
|
||||||
|
local cur_option, cur_arg_i, cur_arg
|
||||||
|
|
||||||
|
local function close(element)
|
||||||
|
local invocations = result[element.target]
|
||||||
|
local passed = invocations[#invocations]
|
||||||
|
|
||||||
|
if #passed < element.minargs then
|
||||||
|
if element.default then
|
||||||
|
while #passed < element.minargs do
|
||||||
|
table.insert(passed, element.default)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
parser:error("too few arguments")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
local function invoke(element)
|
||||||
|
local invocations = result[element.target]
|
||||||
|
|
||||||
|
if #invocations == element.maxcount then
|
||||||
|
if element.overwrite then
|
||||||
|
table.remove(invocations, 1)
|
||||||
|
else
|
||||||
|
parser:error("option %s must be used at most %d times", element.name, element.maxcount)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(result[element.target], {})
|
||||||
|
|
||||||
|
if element.maxargs == 0 then
|
||||||
|
close(element)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pass(element, data)
|
||||||
|
local invocations = result[element.target]
|
||||||
|
local passed = invocations[#invocations]
|
||||||
|
table.insert(passed, data)
|
||||||
|
|
||||||
|
if #passed == element.maxargs then
|
||||||
|
close(element)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function switch(p)
|
local function switch(p)
|
||||||
parser = p:prepare()
|
parser = p:prepare()
|
||||||
@@ -367,9 +287,19 @@ function Parser:parse(args)
|
|||||||
for _, alias in ipairs(option.aliases) do
|
for _, alias in ipairs(option.aliases) do
|
||||||
opt_context[alias] = option
|
opt_context[alias] = option
|
||||||
end
|
end
|
||||||
|
|
||||||
|
result[option.target] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
arguments = p.arguments
|
arguments = p.arguments
|
||||||
|
cur_arg_i = 1
|
||||||
|
cur_arg = arguments[cur_arg_i]
|
||||||
|
|
||||||
|
for _, argument in ipairs(arguments) do
|
||||||
|
result[argument.target] = {}
|
||||||
|
invoke(argument)
|
||||||
|
end
|
||||||
|
|
||||||
commands = p.commands
|
commands = p.commands
|
||||||
com_context = {}
|
com_context = {}
|
||||||
|
|
||||||
@@ -381,65 +311,144 @@ function Parser:parse(args)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function handle_argument(data)
|
local function handle_argument(data)
|
||||||
|
if cur_option then
|
||||||
|
pass(cur_option, data)
|
||||||
|
elseif cur_arg then
|
||||||
|
pass(cur_arg, data)
|
||||||
|
else
|
||||||
|
local com = com_context[data]
|
||||||
|
|
||||||
|
if not com then
|
||||||
|
if #commands > 0 then
|
||||||
|
parser:error("wrong command") -- add lev-based guessing here
|
||||||
|
else
|
||||||
|
parser:error("too many arguments")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
result[com.target] = true
|
||||||
|
switch(com)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function handle_option(data)
|
local function handle_option(data)
|
||||||
|
if cur_option then
|
||||||
|
close(cur_option)
|
||||||
|
end
|
||||||
|
|
||||||
|
cur_option = opt_context[data]
|
||||||
|
invoke(cur_option)
|
||||||
end
|
end
|
||||||
|
|
||||||
local handle_options = true
|
local function mainloop()
|
||||||
|
local handle_options = true
|
||||||
|
|
||||||
for _, data in ipairs(args) do
|
for _, data in ipairs(args) do
|
||||||
local plain = true
|
local plain = true
|
||||||
local first, name, option
|
local first, name, option
|
||||||
|
|
||||||
if handle_options then
|
if handle_options then
|
||||||
first = data:sub(1, 1)
|
first = data:sub(1, 1)
|
||||||
if self.charset[first] then
|
if charset[first] then
|
||||||
if #data > 1 then
|
if #data > 1 then
|
||||||
if data:sub(2, 2):match "[a-zA-Z]" then
|
if data:sub(2, 2):match "[a-zA-Z]" then
|
||||||
plain = false
|
plain = false
|
||||||
|
|
||||||
for i = 2, #data do
|
for i = 2, #data do
|
||||||
name = first .. data:sub(i, i)
|
name = first .. data:sub(i, i)
|
||||||
option = self:assert(self.opt_context[name], "unknown option " .. name)
|
option = parser:assert(opt_context[name], "unknown option " .. name)
|
||||||
handle_option(name)
|
handle_option(name)
|
||||||
|
|
||||||
if i ~= #data and not (options.minargs == 0 and self.opt_context[first .. data:sub(i+1, i+1)]) then
|
if i ~= #data and option.minargs > 0 then
|
||||||
handle_argument(data:sub(i+1))
|
handle_argument(data:sub(i+1))
|
||||||
break
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
elseif data:sub(2, 2) == first then
|
||||||
elseif data:sub(2, 2) == first then
|
if #data == 2 then
|
||||||
if #data == 2 then
|
plain = false
|
||||||
plain = false
|
handle_options = false
|
||||||
handle_options = false
|
elseif data:sub(3, 3):match "[a-zA-Z]" then
|
||||||
elseif data:sub(3, 3):match "[a-zA-Z]" then
|
plain = false
|
||||||
plain = false
|
|
||||||
|
|
||||||
local equal = data:find "="
|
local equal = data:find "="
|
||||||
if equal then
|
if equal then
|
||||||
name = data:sub(1, equal-1)
|
name = data:sub(1, equal-1)
|
||||||
option = self:assert(self.opt_context[name], "unknown option " .. name)
|
option = parser:assert(opt_context[name], "unknown option " .. name)
|
||||||
self:assert(option.maxargs > 0, "option " .. name .. " doesn't take arguments")
|
parser:assert(option.maxargs > 0, "option " .. name .. " doesn't take arguments")
|
||||||
|
|
||||||
handle_option(data:sub(1, equal-1))
|
handle_option(data:sub(1, equal-1))
|
||||||
handle_argument(data:sub(equal+1))
|
handle_argument(data:sub(equal+1))
|
||||||
else
|
else
|
||||||
handle_option(data)
|
handle_option(data)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
if plain then
|
if plain then
|
||||||
handle_argument(data)
|
handle_argument(data)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function format()
|
||||||
|
local new_result = {}
|
||||||
|
local invocations
|
||||||
|
|
||||||
|
for _, elements in ipairs{options, arguments} do
|
||||||
|
for _, element in ipairs(elements) do
|
||||||
|
invocations = result[element.target]
|
||||||
|
|
||||||
|
parser:assert(#invocations >= element.mincount,
|
||||||
|
"option %s must be used at least %d times", element.name, element.mincount)
|
||||||
|
|
||||||
|
if element.maxcount == 1 then
|
||||||
|
if element.maxargs == 0 then
|
||||||
|
if #invocations > 0 then
|
||||||
|
new_result[element.target] = true
|
||||||
|
end
|
||||||
|
elseif element.maxargs == 1 and element.minargs == 1 then
|
||||||
|
if #invocations > 0 then
|
||||||
|
new_result[element.target] = invocations[1][1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
new_result[element.target] = invocations[1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if element.maxargs == 0 then
|
||||||
|
new_result[element.target] = #invocations
|
||||||
|
elseif element.maxargs == 1 and element.minargs == 1 then
|
||||||
|
new_result[element.target] = {}
|
||||||
|
|
||||||
|
for _, passed in ipairs(invocations) do
|
||||||
|
table.insert(new_result[element.target], passed[1])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
new_result[element.target] = invocations
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result = new_result
|
||||||
|
end
|
||||||
|
|
||||||
|
switch(self)
|
||||||
|
mainloop()
|
||||||
|
|
||||||
|
if cur_option then
|
||||||
|
close(cur_option)
|
||||||
|
end
|
||||||
|
|
||||||
|
while cur_arg do
|
||||||
|
close(cur_arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
format()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -1,101 +0,0 @@
|
|||||||
-- new, awesome declarative interface implementation
|
|
||||||
|
|
||||||
local class = require "30log"
|
|
||||||
|
|
||||||
local Declarative = class()
|
|
||||||
|
|
||||||
function Declarative:__init(...)
|
|
||||||
return self(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Declarative:__call(...)
|
|
||||||
local name_or_options
|
|
||||||
|
|
||||||
for i=1, select("#", ...) do
|
|
||||||
name_or_options = select(i, ...)
|
|
||||||
|
|
||||||
if type(name_or_options) == "string" then
|
|
||||||
self:set_name(name_or_options)
|
|
||||||
elseif type(name_or_options) == "table" then
|
|
||||||
for _, field in ipairs(self.fields) do
|
|
||||||
if name_or_options[field] ~= nil then
|
|
||||||
self[field] = name_or_options[field]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
local Aliased = {}
|
|
||||||
|
|
||||||
function Aliased:set_name(name)
|
|
||||||
table.insert(self.aliases, name)
|
|
||||||
|
|
||||||
if not self.name then
|
|
||||||
self.name = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Named = {}
|
|
||||||
|
|
||||||
function Named:set_name(name)
|
|
||||||
self.name = name
|
|
||||||
end
|
|
||||||
|
|
||||||
local Parser = class {
|
|
||||||
arguments = {},
|
|
||||||
options = {},
|
|
||||||
commands = {},
|
|
||||||
groups = {},
|
|
||||||
mutex_groups = {},
|
|
||||||
fields = {"name", "description", "target"}
|
|
||||||
}:include(Declarative):include(Named)
|
|
||||||
|
|
||||||
local Command = Parser:extends {
|
|
||||||
aliases = {}
|
|
||||||
}:include(Declarative):include(Aliased)
|
|
||||||
|
|
||||||
local Argument = class {
|
|
||||||
args = 1,
|
|
||||||
count = 1,
|
|
||||||
fields = {"name", "description", "target", "args", "default", "convert"}
|
|
||||||
}:include(Declarative):include(Named)
|
|
||||||
|
|
||||||
local Option = class {
|
|
||||||
aliases = {},
|
|
||||||
args = 1,
|
|
||||||
count = "?",
|
|
||||||
fields = {"name", "aliases", "description", "target", "args", "count", "default", "convert"}
|
|
||||||
}:include(Declarative):include(Aliased)
|
|
||||||
|
|
||||||
local Flag = Option:extends {
|
|
||||||
args = 0
|
|
||||||
}:include(Declarative):include(Aliased)
|
|
||||||
|
|
||||||
function Parser:argument(...)
|
|
||||||
local argument = Argument(...)
|
|
||||||
table.insert(self.arguments, argument)
|
|
||||||
return argument
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:option(...)
|
|
||||||
local option = Option(...)
|
|
||||||
table.insert(self.options, option)
|
|
||||||
return option
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:flag(...)
|
|
||||||
local flag = Flag(...)
|
|
||||||
table.insert(self.options, flag)
|
|
||||||
return flag
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:command(...)
|
|
||||||
local command = Command(...)
|
|
||||||
table.insert(self.commands, command)
|
|
||||||
return command
|
|
||||||
end
|
|
||||||
|
|
||||||
return Parser
|
|
@@ -1,39 +0,0 @@
|
|||||||
--Just some testing
|
|
||||||
|
|
||||||
local MetaParser = require "interface"
|
|
||||||
|
|
||||||
local parser = MetaParser "luarocks" {
|
|
||||||
description = "a module deployment system for Lua"
|
|
||||||
}
|
|
||||||
|
|
||||||
parser:option "--server" "-s" {
|
|
||||||
description = "Fetch rocks/rockspecs from this server"
|
|
||||||
}
|
|
||||||
|
|
||||||
parser:flag "--local" "-l" {
|
|
||||||
description = "Use the tree in the user's home directory."
|
|
||||||
}
|
|
||||||
|
|
||||||
local install = parser:command "install" "i"
|
|
||||||
|
|
||||||
install:argument "rock"
|
|
||||||
|
|
||||||
install:argument "version" {
|
|
||||||
args = "?"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(parser.description == "a module deployment system for Lua")
|
|
||||||
assert(parser.options[1].name == "--server")
|
|
||||||
assert(parser.options[1].aliases[1] == "--server")
|
|
||||||
assert(parser.options[1].aliases[2] == "-s")
|
|
||||||
assert(parser.options[1].description == "Fetch rocks/rockspecs from this server")
|
|
||||||
assert(parser.options[1].args == 1)
|
|
||||||
assert(parser.options[1].count == "?")
|
|
||||||
assert(parser.options[2].name == "--local")
|
|
||||||
assert(parser.options[2].args == 0)
|
|
||||||
assert(parser.commands[1] == install)
|
|
||||||
assert(install.arguments[1].name == "rock")
|
|
||||||
assert(install.aliases[2] == "i")
|
|
||||||
assert(install.arguments[2].name == "version")
|
|
||||||
assert(install.arguments[2].count == 1)
|
|
||||||
assert(install.arguments[2].args == "?")
|
|
202
src/state.lua
202
src/state.lua
@@ -1,202 +0,0 @@
|
|||||||
local class = require "30log"
|
|
||||||
|
|
||||||
local State = class {
|
|
||||||
context = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function State:__init(parser)
|
|
||||||
self.context = {}
|
|
||||||
self._all_elements = {}
|
|
||||||
self._all_groups = {}
|
|
||||||
self:_switch(parser)
|
|
||||||
|
|
||||||
self._result = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:handle_option(name)
|
|
||||||
local option = self:_assert(self.context[name], "unknown option %s", name)
|
|
||||||
|
|
||||||
self:_open(option)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:handle_argument(data)
|
|
||||||
if self._current then
|
|
||||||
if self:_can_pass(self._current) then
|
|
||||||
self:_pass(self._current, data)
|
|
||||||
return
|
|
||||||
else
|
|
||||||
self._current = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local argument = self._arguments[self._next_arg_i]
|
|
||||||
if argument then
|
|
||||||
self:_open(argument)
|
|
||||||
self:_pass(argument, data)
|
|
||||||
else
|
|
||||||
local command = self.context[data]
|
|
||||||
if command and command.type == "command" then
|
|
||||||
self._result[command.target] = {{}}
|
|
||||||
self:_switch(command)
|
|
||||||
else
|
|
||||||
if #self._commands > 0 then
|
|
||||||
self:_error("unknown command %s", data)
|
|
||||||
else
|
|
||||||
self:_error("too many arguments")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:get_result()
|
|
||||||
self:_check()
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
|
|
||||||
local invocations
|
|
||||||
for _, element in ipairs(self._all_elements) do
|
|
||||||
invocations = self._result[element.target]
|
|
||||||
|
|
||||||
if element.maxcount == 1 then
|
|
||||||
if element.maxargs == 0 then
|
|
||||||
if #invocations > 0 then
|
|
||||||
result[element.target] = true
|
|
||||||
end
|
|
||||||
elseif element.maxargs == 1 and element.minargs == 1 then
|
|
||||||
if #invocations > 0 then
|
|
||||||
result[element.target] = invocations[1][1]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result[element.target] = invocations[1]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if element.maxargs == 0 then
|
|
||||||
result[element.target] = #invocations
|
|
||||||
elseif element.maxargs == 1 and element.minargs == 1 then
|
|
||||||
local new_result = {}
|
|
||||||
for i, passed in ipairs(invocations) do
|
|
||||||
new_result[i] = passed[1]
|
|
||||||
end
|
|
||||||
result[element.target] = new_result
|
|
||||||
else
|
|
||||||
result[element.target] = invocations
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_check()
|
|
||||||
self:_assert(not self._parser.must_command, "a command is required")
|
|
||||||
|
|
||||||
local invocations
|
|
||||||
for _, element in ipairs(self._all_elements) do
|
|
||||||
invocations = self._result[element.target] or {}
|
|
||||||
|
|
||||||
if element.type == "argument" and #invocations == 0 then
|
|
||||||
invocations[1] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
if #invocations > element.maxcount then
|
|
||||||
if element.no_overwrite then
|
|
||||||
self:_error("option %s must be used at most %d times", element.name, element.maxcount)
|
|
||||||
else
|
|
||||||
local new_invocations = {}
|
|
||||||
for i = 1, element.maxcount do
|
|
||||||
new_invocations[i] = invocations[#invocations-element.maxcount+i]
|
|
||||||
end
|
|
||||||
invocations = new_invocations
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:_assert(#invocations >= element.mincount, "option %s must be used at least %d times", element.name, element.mincount)
|
|
||||||
|
|
||||||
for _, passed in ipairs(invocations) do
|
|
||||||
self:_assert(#passed <= element.maxargs, "too many arguments")
|
|
||||||
if #passed < element.minargs then
|
|
||||||
if element.default then
|
|
||||||
for i = 1, element.minargs-#passed do
|
|
||||||
table.insert(passed, element.default)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:_error("too few arguments")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self._result[element.target] = invocations
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, group in ipairs(self._all_groups) do
|
|
||||||
local invoked
|
|
||||||
for _, element in ipairs(group.elements) do
|
|
||||||
if #self._result[element.target] > 0 then
|
|
||||||
if invoked then
|
|
||||||
self:_error("%s can not be used together with %s", invoked.name, element.name)
|
|
||||||
else
|
|
||||||
invoked = element
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if group.required then
|
|
||||||
self:_assert(invoked, "WIP(required mutually exclusive group)")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_open(element)
|
|
||||||
if not self._result[element.target] then
|
|
||||||
self._result[element.target] = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(self._result[element.target], {})
|
|
||||||
|
|
||||||
if element.type == "argument" then
|
|
||||||
self._next_arg_i = self._next_arg_i+1
|
|
||||||
end
|
|
||||||
|
|
||||||
self._current = element
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_can_pass(element)
|
|
||||||
local invocations = self._result[element.target]
|
|
||||||
local passed = invocations[#invocations]
|
|
||||||
|
|
||||||
return #passed < element.maxargs
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_pass(element, data)
|
|
||||||
local invocations = self._result[element.target]
|
|
||||||
local passed = invocations[#invocations]
|
|
||||||
|
|
||||||
table.insert(passed, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_switch(command)
|
|
||||||
self._parser = command
|
|
||||||
self._arguments = command.arguments
|
|
||||||
self._commands = command.commands
|
|
||||||
|
|
||||||
for _, element in ipairs(command.elements) do
|
|
||||||
table.insert(self._all_elements, element)
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, group in ipairs(command.groups) do
|
|
||||||
table.insert(self._all_groups, group)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.context = setmetatable(command.context, {__index = self.context})
|
|
||||||
self._next_arg_i = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_error(...)
|
|
||||||
return self._parser:error(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
function State:_assert(...)
|
|
||||||
return self._parser:assert(...)
|
|
||||||
end
|
|
||||||
|
|
||||||
return State
|
|
@@ -1,31 +0,0 @@
|
|||||||
local utils = {}
|
|
||||||
|
|
||||||
function utils.parse_boundaries(boundaries)
|
|
||||||
if tonumber(boundaries) then
|
|
||||||
return tonumber(boundaries), tonumber(boundaries)
|
|
||||||
end
|
|
||||||
|
|
||||||
if boundaries == "*" then
|
|
||||||
return 0, math.huge
|
|
||||||
end
|
|
||||||
|
|
||||||
if boundaries == "+" then
|
|
||||||
return 1, math.huge
|
|
||||||
end
|
|
||||||
|
|
||||||
if boundaries == "?" then
|
|
||||||
return 0, 1
|
|
||||||
end
|
|
||||||
|
|
||||||
if boundaries:match "^%d+%-%d+$" then
|
|
||||||
local min, max = boundaries:match "^(%d+)%-(%d+)$"
|
|
||||||
return tonumber(min), tonumber(max)
|
|
||||||
end
|
|
||||||
|
|
||||||
if boundaries:match "^%d+%+$" then
|
|
||||||
local min = boundaries:match "^(%d+)%+$"
|
|
||||||
return tonumber(min), math.huge
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return utils
|
|
Reference in New Issue
Block a user