added some code

This commit is contained in:
mpeterv
2013-12-30 18:32:29 +04:00
parent f7953baf45
commit 77ee49649b
5 changed files with 550 additions and 0 deletions

View 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"
}
}

View 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
View 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
View 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
View 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))