mirror of
https://github.com/TangentFoxy/argparse.git
synced 2025-07-28 11:02:20 +00:00
added some code
This commit is contained in:
22
rockspecs/largparse-0.1-1.rockspec~
Normal file
22
rockspecs/largparse-0.1-1.rockspec~
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package = "largparse"
|
||||||
|
version = "git-1"
|
||||||
|
source = {
|
||||||
|
url = "src"
|
||||||
|
}
|
||||||
|
description = {
|
||||||
|
summary = "*** please specify description summary ***",
|
||||||
|
detailed = "*** please enter a detailed description ***",
|
||||||
|
homepage = "*** please enter a project homepage ***",
|
||||||
|
license = "MIT/X11"
|
||||||
|
}
|
||||||
|
dependencies = {
|
||||||
|
"lua >= 5.1, < 5.3",
|
||||||
|
"30log >= 0.6"
|
||||||
|
}
|
||||||
|
build = {
|
||||||
|
type = "builtin",
|
||||||
|
modules = {
|
||||||
|
largparse = "src/largparse.lua",
|
||||||
|
["largparse.state"] = "src/state.lua"
|
||||||
|
}
|
||||||
|
}
|
22
rockspecs/largparse-wip-1.rockspec
Normal file
22
rockspecs/largparse-wip-1.rockspec
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package = "largparse"
|
||||||
|
version = "wip-1"
|
||||||
|
source = {
|
||||||
|
url = "src"
|
||||||
|
}
|
||||||
|
description = {
|
||||||
|
summary = "*** please specify description summary ***",
|
||||||
|
detailed = "*** please enter a detailed description ***",
|
||||||
|
homepage = "*** please enter a project homepage ***",
|
||||||
|
license = "MIT/X11"
|
||||||
|
}
|
||||||
|
dependencies = {
|
||||||
|
"lua >= 5.1, < 5.3",
|
||||||
|
"30log >= 0.6"
|
||||||
|
}
|
||||||
|
build = {
|
||||||
|
type = "builtin",
|
||||||
|
modules = {
|
||||||
|
largparse = "src/largparse.lua",
|
||||||
|
["largparse.state"] = "src/state.lua"
|
||||||
|
}
|
||||||
|
}
|
297
src/largparse.lua
Normal file
297
src/largparse.lua
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
local largparse = {}
|
||||||
|
|
||||||
|
local class = require "30log"
|
||||||
|
|
||||||
|
local State = require "largparse.state"
|
||||||
|
|
||||||
|
local Parser = class()
|
||||||
|
|
||||||
|
function Parser:__init(options)
|
||||||
|
options = options or {}
|
||||||
|
self.description = options.description
|
||||||
|
self.name = options.name
|
||||||
|
self.no_help = options.no_help
|
||||||
|
self.must_command = options.must_command
|
||||||
|
|
||||||
|
self.arguments = {}
|
||||||
|
self.elements = {}
|
||||||
|
self.groups = {}
|
||||||
|
self.commands = {}
|
||||||
|
self.context = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:add_alias(element, alias)
|
||||||
|
table.insert(element.aliases, alias)
|
||||||
|
self.context[alias] = element
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:apply_options(element, options)
|
||||||
|
for k, v in pairs(options) do
|
||||||
|
element[k] = v -- fixme
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:make_target(element)
|
||||||
|
if not element.target then
|
||||||
|
for _, alias in ipairs(element.aliases) do
|
||||||
|
if alias:match "^%-%-" then
|
||||||
|
element.target = alias:sub(3)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
element.target = element.aliases[1]:match "^%-*(.*)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser: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
|
||||||
|
|
||||||
|
-- TODO: make it declarative as it was
|
||||||
|
function Parser:argument(name, ...)
|
||||||
|
local element = {
|
||||||
|
name = name,
|
||||||
|
aliases = {},
|
||||||
|
count = 1,
|
||||||
|
args = 1,
|
||||||
|
type = "argument"
|
||||||
|
}
|
||||||
|
|
||||||
|
self:add_alias(element, name)
|
||||||
|
|
||||||
|
local argument
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
argument = select(i, ...)
|
||||||
|
|
||||||
|
if type(argument) == "string" then
|
||||||
|
self:add_alias(element, argument)
|
||||||
|
else
|
||||||
|
self:apply_options(element, argument)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:make_target(element)
|
||||||
|
|
||||||
|
element.mincount, element.maxcount = self:parse_boundaries(element.count)
|
||||||
|
element.minargs, element.maxargs = self:parse_boundaries(element.args)
|
||||||
|
if element.minargs == 0 then
|
||||||
|
element.mincount = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(self.arguments, element)
|
||||||
|
table.insert(self.elements, element)
|
||||||
|
|
||||||
|
return element
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:option(name, ...)
|
||||||
|
local element = {
|
||||||
|
name = name,
|
||||||
|
aliases = {},
|
||||||
|
count = "0-1",
|
||||||
|
args = 1,
|
||||||
|
type = "option"
|
||||||
|
}
|
||||||
|
|
||||||
|
self:add_alias(element, name)
|
||||||
|
|
||||||
|
local argument
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
argument = select(i, ...)
|
||||||
|
|
||||||
|
if type(argument) == "string" then
|
||||||
|
self:add_alias(element, argument)
|
||||||
|
else
|
||||||
|
self:apply_options(element, argument)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:make_target(element)
|
||||||
|
|
||||||
|
element.mincount, element.maxcount = self:parse_boundaries(element.count)
|
||||||
|
element.minargs, element.maxargs = self:parse_boundaries(element.args)
|
||||||
|
|
||||||
|
table.insert(self.elements, element)
|
||||||
|
|
||||||
|
return element
|
||||||
|
end
|
||||||
|
|
||||||
|
-- DRY?
|
||||||
|
|
||||||
|
function Parser:flag(name, ...)
|
||||||
|
local element = {
|
||||||
|
name = name,
|
||||||
|
aliases = {},
|
||||||
|
count = "0-1",
|
||||||
|
args = 0,
|
||||||
|
type = "option"
|
||||||
|
}
|
||||||
|
|
||||||
|
self:add_alias(element, name)
|
||||||
|
|
||||||
|
local argument
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
argument = select(i, ...)
|
||||||
|
|
||||||
|
if type(argument) == "string" then
|
||||||
|
self:add_alias(element, argument)
|
||||||
|
else
|
||||||
|
self:apply_options(element, argument)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:make_target(element)
|
||||||
|
|
||||||
|
element.mincount, element.maxcount = self:parse_boundaries(element.count)
|
||||||
|
element.minargs, element.maxargs = self:parse_boundaries(element.args)
|
||||||
|
|
||||||
|
table.insert(self.elements, element)
|
||||||
|
|
||||||
|
return element
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:command(name, ...)
|
||||||
|
local element = {
|
||||||
|
name = name,
|
||||||
|
aliases = {},
|
||||||
|
count = "0-1",
|
||||||
|
args = 0,
|
||||||
|
type = "command"
|
||||||
|
}
|
||||||
|
|
||||||
|
local command = Parser() -- fixme
|
||||||
|
for k, v in pairs(element) do
|
||||||
|
command[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
self:add_alias(command, name)
|
||||||
|
|
||||||
|
local argument
|
||||||
|
for i = 1, select('#', ...) do
|
||||||
|
argument = select(i, ...)
|
||||||
|
self:add_alias(command, argument)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:make_target(command)
|
||||||
|
|
||||||
|
command.mincount, command.maxcount = self:parse_boundaries(command.count)
|
||||||
|
command.minargs, command.maxargs = self:parse_boundaries(command.args)
|
||||||
|
|
||||||
|
table.insert(self.elements, command)
|
||||||
|
|
||||||
|
return command
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:group(...)
|
||||||
|
--
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:mutually_exclusive(...)
|
||||||
|
local group = {
|
||||||
|
elements = {...}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(self.groups, group)
|
||||||
|
return group
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:error(fmt, ...)
|
||||||
|
local msg = fmt:format(...)
|
||||||
|
io.stderr:write("Error: " .. msg .. "\n")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:assert(assertion, ...)
|
||||||
|
return assertion or self:error(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function Parser:parse(args)
|
||||||
|
args = args or arg
|
||||||
|
self.name = self.name or args[0]
|
||||||
|
|
||||||
|
local state = State(self)
|
||||||
|
|
||||||
|
local handle_options = true
|
||||||
|
for _, data in ipairs(args) do
|
||||||
|
local plain = true
|
||||||
|
|
||||||
|
if handle_options then
|
||||||
|
if data:sub(1, 1) == "-" then
|
||||||
|
if #data > 1 then
|
||||||
|
if data:sub(2, 2):match "[a-zA-Z]" then
|
||||||
|
plain = false
|
||||||
|
|
||||||
|
local name, element
|
||||||
|
for i = 2, #data do
|
||||||
|
name = "-" .. data:sub(i, i)
|
||||||
|
element = self:assert(state.context[name], "unknown option " .. name)
|
||||||
|
state:handle_option(name)
|
||||||
|
|
||||||
|
if i ~= #data and not (element:can_take(0) and data:sub(i+1, i+1):match "[a-zA-Z]") then
|
||||||
|
state:handle_argument(data:sub(i+1))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif data:sub(2, 2) == "-" then
|
||||||
|
if #data == 2 then
|
||||||
|
plain = false
|
||||||
|
handle_options = false
|
||||||
|
elseif data:sub(3, 3):match "[a-zA-Z]" then
|
||||||
|
plain = false
|
||||||
|
|
||||||
|
local equal = data:find "="
|
||||||
|
if equal then
|
||||||
|
local name = data:sub(1, equal-1)
|
||||||
|
local element = self:assert(state.context[name], "unknown option " .. name)
|
||||||
|
self:assert(element.maxargs > 0, "option " .. name .. " doesn't take arguments")
|
||||||
|
|
||||||
|
state:handle_option(data:sub(1, equal-1))
|
||||||
|
state:handle_argument(data:sub(equal+1))
|
||||||
|
else
|
||||||
|
state:handle_option(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if plain then
|
||||||
|
state:handle_argument(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = state:get_result()
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
largparse.parser = Parser
|
||||||
|
|
||||||
|
return largparse
|
185
src/state.lua
Normal file
185
src/state.lua
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
local class = require "30log"
|
||||||
|
|
||||||
|
local State = class()
|
||||||
|
|
||||||
|
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 #invocations > 0 then
|
||||||
|
if element.maxargs == 0 then
|
||||||
|
result[element.target] = true
|
||||||
|
elseif element.maxargs == 1 and element.minargs == 1 then
|
||||||
|
result[element.target] = invocations[1][1]
|
||||||
|
else
|
||||||
|
result[element.target] = invocations[1]
|
||||||
|
end
|
||||||
|
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 #invocations > element.maxcount then
|
||||||
|
if element.no_overwrite then
|
||||||
|
self:_error("option %s can only be used %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, "%s takes at most %d arguments", element.name, element.maxargs)
|
||||||
|
self:_assert(#passed >= element.minargs, "%s takes at least %d arguments", element.name, element.minargs)
|
||||||
|
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
|
24
test/test.lua
Normal file
24
test/test.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
local largparse = require "largparse"
|
||||||
|
local serpent = require "serpent"
|
||||||
|
|
||||||
|
local parser = largparse.parser()
|
||||||
|
|
||||||
|
parser:argument("input", {
|
||||||
|
args = 2
|
||||||
|
})
|
||||||
|
|
||||||
|
parser:flag("-q", "--quiet")
|
||||||
|
parser:option("-s", "--server")
|
||||||
|
|
||||||
|
parser:mutually_exclusive(
|
||||||
|
parser:flag("-q", "--quiet"),
|
||||||
|
parser:option("-s", "--server")
|
||||||
|
)
|
||||||
|
|
||||||
|
local run = parser:command "run"
|
||||||
|
|
||||||
|
run:flag("-f", "--fast")
|
||||||
|
|
||||||
|
local args = parser:parse()
|
||||||
|
|
||||||
|
print(serpent.block(args))
|
Reference in New Issue
Block a user