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