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 = {
|
||||
type = "builtin",
|
||||
modules = {
|
||||
argparse = "src/argparse.lua",
|
||||
["argparse.state"] = "src/state.lua",
|
||||
["argparse.utils"] = "src/utils.lua"
|
||||
argparse = "src/argparse.lua"
|
||||
}
|
||||
}
|
||||
|
@@ -77,16 +77,6 @@ describe("tests related to positional arguments", function()
|
||||
end)
|
||||
|
||||
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()
|
||||
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()
|
||||
local parser = argparse.parser()
|
||||
parser:argument("foo", {
|
||||
parser:argument "foo" {
|
||||
args = "+"
|
||||
})
|
||||
}
|
||||
assert.has_error(curry(parser.parse, parser, {}), "too few arguments")
|
||||
end)
|
||||
|
||||
it("handles too many arguments with multi-argument correctly", function()
|
||||
local parser = argparse.parser()
|
||||
parser:argument("foo", {
|
||||
parser:argument "foo" {
|
||||
args = "2-4"
|
||||
})
|
||||
}
|
||||
assert.has_error(curry(parser.parse, parser, {"foo", "bar", "baz", "qu", "quu"}), "too many arguments")
|
||||
end)
|
||||
|
||||
|
@@ -151,16 +151,6 @@ describe("tests related to options", function()
|
||||
assert.same(args, {exclude = {{"Alice", "Bob"}, {"Emma", "Jacob"}}})
|
||||
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()
|
||||
local parser = argparse.parser()
|
||||
parser:flag("-q", "--quiet", {
|
||||
@@ -191,15 +181,6 @@ describe("tests related to options", function()
|
||||
end)
|
||||
|
||||
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()
|
||||
local parser = argparse.parser()
|
||||
parser:option("-s", "--server")
|
||||
@@ -222,9 +203,17 @@ describe("tests related to options", function()
|
||||
local parser = argparse.parser()
|
||||
parser:flag("-q", "--quiet", {
|
||||
count = 1,
|
||||
no_overwrite = true
|
||||
overwrite = false
|
||||
})
|
||||
assert.has_error(curry(parser.parse, parser, {"-qq"}), "option -q must be used at most 1 times")
|
||||
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)
|
||||
|
@@ -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)
|
429
src/argparse.lua
429
src/argparse.lua
@@ -2,203 +2,10 @@ local argparse = {}
|
||||
|
||||
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 = {}
|
||||
|
||||
function Declarative:__init(...)
|
||||
return self(...)
|
||||
self(...)
|
||||
end
|
||||
|
||||
function Declarative:__call(...)
|
||||
@@ -228,6 +35,7 @@ function Declarative:__call(...)
|
||||
end
|
||||
|
||||
local Parser = class {
|
||||
__name = "Parser",
|
||||
arguments = {},
|
||||
options = {},
|
||||
commands = {},
|
||||
@@ -235,54 +43,72 @@ local Parser = class {
|
||||
}:include(Declarative)
|
||||
|
||||
local Command = Parser:extends {
|
||||
__name = "Command",
|
||||
aliases = {}
|
||||
}:include(Declarative)
|
||||
|
||||
local Argument = class {
|
||||
__name = "Argument",
|
||||
args = 1,
|
||||
count = 1,
|
||||
fields = {"name", "description", "target", "args", "default", "convert"}
|
||||
fields = {
|
||||
"name", "description", "target", "args",
|
||||
"minargs", "maxargs", "default", "convert"
|
||||
}
|
||||
}:include(Declarative)
|
||||
|
||||
local Option = class {
|
||||
__name = "Option",
|
||||
aliases = {},
|
||||
args = 1,
|
||||
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)
|
||||
|
||||
local Flag = Option:extends {
|
||||
__name = "Flag",
|
||||
args = 0
|
||||
}:include(Declarative)
|
||||
|
||||
function Parser:argument(...)
|
||||
local argument = Argument(...)
|
||||
local argument = Argument:new(...)
|
||||
table.insert(self.arguments, argument)
|
||||
return argument
|
||||
end
|
||||
|
||||
function Parser:option(...)
|
||||
local option = Option(...)
|
||||
local option = Option:new(...)
|
||||
table.insert(self.options, option)
|
||||
return option
|
||||
end
|
||||
|
||||
function Parser:flag(...)
|
||||
local flag = Flag(...)
|
||||
local flag = Flag:new(...)
|
||||
table.insert(self.options, flag)
|
||||
return flag
|
||||
end
|
||||
|
||||
function Parser:command(...)
|
||||
local command = Command(...)
|
||||
local command = Command:new(...)
|
||||
table.insert(self.commands, command)
|
||||
return command
|
||||
end
|
||||
|
||||
function Parser:error(fmt, ...)
|
||||
local msg = fmt:format(...)
|
||||
io.stderr:write("Error: " .. msg .. "\n")
|
||||
|
||||
if _TEST then
|
||||
error(msg)
|
||||
else
|
||||
io.stderr:write("Error: " .. msg .. "\r\n")
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
function Parser:assert(assertion, ...)
|
||||
@@ -291,7 +117,7 @@ end
|
||||
|
||||
function Parser:make_charset()
|
||||
if not self.charset then
|
||||
self.charset = {}
|
||||
self.charset = {["-"] = true}
|
||||
|
||||
for _, command in ipairs(self.commands) do
|
||||
command:make_charset()
|
||||
@@ -332,7 +158,49 @@ function Parser:make_targets()
|
||||
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
|
||||
command.name = self.name .. " " .. command.name
|
||||
end
|
||||
@@ -341,6 +209,7 @@ end
|
||||
function Parser:prepare()
|
||||
self:make_charset()
|
||||
self:make_targets()
|
||||
self:make_boundaries()
|
||||
self:make_command_names()
|
||||
return self
|
||||
end
|
||||
@@ -353,9 +222,60 @@ function Parser:parse(args)
|
||||
local charset
|
||||
local options = {}
|
||||
local arguments, commands
|
||||
local opt_context= {}
|
||||
local opt_context = {}
|
||||
local com_context
|
||||
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)
|
||||
parser = p:prepare()
|
||||
@@ -367,9 +287,19 @@ function Parser:parse(args)
|
||||
for _, alias in ipairs(option.aliases) do
|
||||
opt_context[alias] = option
|
||||
end
|
||||
|
||||
result[option.target] = {}
|
||||
end
|
||||
|
||||
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
|
||||
com_context = {}
|
||||
|
||||
@@ -381,13 +311,36 @@ function Parser:parse(args)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local function handle_option(data)
|
||||
|
||||
if cur_option then
|
||||
close(cur_option)
|
||||
end
|
||||
|
||||
cur_option = opt_context[data]
|
||||
invoke(cur_option)
|
||||
end
|
||||
|
||||
local function mainloop()
|
||||
local handle_options = true
|
||||
|
||||
for _, data in ipairs(args) do
|
||||
@@ -396,17 +349,17 @@ function Parser:parse(args)
|
||||
|
||||
if handle_options then
|
||||
first = data:sub(1, 1)
|
||||
if self.charset[first] then
|
||||
if charset[first] then
|
||||
if #data > 1 then
|
||||
if data:sub(2, 2):match "[a-zA-Z]" then
|
||||
plain = false
|
||||
|
||||
for i = 2, #data do
|
||||
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)
|
||||
|
||||
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))
|
||||
break
|
||||
end
|
||||
@@ -421,8 +374,8 @@ function Parser:parse(args)
|
||||
local equal = data:find "="
|
||||
if equal then
|
||||
name = data:sub(1, equal-1)
|
||||
option = self:assert(self.opt_context[name], "unknown option " .. name)
|
||||
self:assert(option.maxargs > 0, "option " .. name .. " doesn't take arguments")
|
||||
option = parser:assert(opt_context[name], "unknown option " .. name)
|
||||
parser:assert(option.maxargs > 0, "option " .. name .. " doesn't take arguments")
|
||||
|
||||
handle_option(data:sub(1, equal-1))
|
||||
handle_argument(data:sub(equal+1))
|
||||
@@ -439,6 +392,62 @@ function Parser:parse(args)
|
||||
handle_argument(data)
|
||||
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
|
||||
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