fixed too many mixins

This commit is contained in:
mpeterv
2014-01-17 18:38:56 +04:00
parent a7f7edbb3e
commit 8f1ce0a30f

View File

@@ -3,8 +3,11 @@ local argparse = {}
local class = require "30log" local class = require "30log"
local State = class { local State = class {
context = {}, -- {alias -> element} opt_context = {},
result = {} result = {},
stack = {},
invocations = {},
top_is_opt = false
} }
function State:__init(parser) function State:__init(parser)
@@ -12,34 +15,114 @@ function State:__init(parser)
end end
function State:switch(parser) function State:switch(parser)
self.parser = parser self.parser = parser:prepare()
self.parser:make_targets() self.charset = parser.charset
for _, option in ipairs(parser.options) do for _, option in ipairs(parser.options) do
table.insert(self.options, option) table.insert(self.options, option)
for _, alias in ipairs(option.aliases) do
self.opt_context[alias] = option
end
end end
self.arguments = parser.arguments self.arguments = parser.arguments
self.commands = parser.commands self.commands = parser.commands
end self.com_context = {}
function Parser:make_target(element) for _, command in ipairs(parser.commands) do
if not element.target then for _, alias in ipairs(command.aliases) do
for _, alias in ipairs(element.aliases) do self.com_context[][alias] = command
if alias:match "^%-%-" then
element.target = alias:sub(3)
return
end
end end
element.target = element.aliases[1]:match "^%-*(.*)"
end end
end end
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:iterargs(args)
return coroutine.wrap(function()
local handle_options = true
for _, data in ipairs(args) do
local plain = true
local first, name, option
if handle_options then
first = data:sub(1, 1)
if self.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)
coroutine.yield(nil, name)
if i ~= #data and not (options.minargs == 0 and self.opt_context[first .. data:sub(i+1, i+1)]) then
coroutine.yield(data:sub(i+1), nil)
break
end
end
elseif data:sub(2, 2) == first 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
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")
coroutine.yield(nil, data:sub(1, equal-1))
coroutine.yield(data:sub(equal+1), nil)
else
coroutine.yield(nil, data)
end
end
end
end
end
end
if plain then
coroutine.yield(data, nil)
end
end
end)
end
function State:parse(args) function State:parse(args)
args = args or arg for arg, opt in self:iterargs(args) do
if arg then
elseif opt then
end
end
end
@@ -204,23 +287,6 @@ function State:_pass(element, data)
table.insert(passed, data) table.insert(passed, data)
end 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(...) function State:_error(...)
return self._parser:error(...) return self._parser:error(...)
end end
@@ -245,7 +311,13 @@ function Declarative:__call(...)
name_or_options = select(i, ...) name_or_options = select(i, ...)
if type(name_or_options) == "string" then if type(name_or_options) == "string" then
self:set_name(name_or_options) if self.aliases then
table.insert(self.aliases, name_or_options)
end
if not self.name then
self.name = name_or_options
end
elseif type(name_or_options) == "table" then elseif type(name_or_options) == "table" then
for _, field in ipairs(self.fields) do for _, field in ipairs(self.fields) do
if name_or_options[field] ~= nil then if name_or_options[field] ~= nil then
@@ -258,50 +330,33 @@ function Declarative:__call(...)
return self return self
end 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 { local Parser = class {
arguments = {}, arguments = {},
options = {}, options = {},
commands = {}, commands = {},
charset = {"-"},
fields = {"name", "description", "target"} fields = {"name", "description", "target"}
}:include(Declarative):include(Named) }:include(Declarative)
local Command = Parser:extends { local Command = Parser:extends {
aliases = {} aliases = {}
}:include(Declarative):include(Aliased) }:include(Declarative)
local Argument = class { local Argument = class {
args = 1, args = 1,
count = 1, count = 1,
fields = {"name", "description", "target", "args", "default", "convert"} fields = {"name", "description", "target", "args", "default", "convert"}
}:include(Declarative):include(Named) }:include(Declarative)
local Option = class { local Option = class {
aliases = {}, aliases = {},
args = 1, args = 1,
count = "?", count = "?",
fields = {"name", "aliases", "description", "target", "args", "count", "default", "convert"} fields = {"name", "aliases", "description", "target", "args", "count", "default", "convert"}
}:include(Declarative):include(Aliased) }:include(Declarative)
local Flag = Option:extends { local Flag = Option:extends {
args = 0 args = 0
}:include(Declarative):include(Aliased) }:include(Declarative)
function Parser:argument(...) function Parser:argument(...)
local argument = Argument(...) local argument = Argument(...)
@@ -337,23 +392,26 @@ function Parser:assert(assertion, ...)
return assertion or self:error(...) return assertion or self:error(...)
end end
function Parser:get_charset() function Parser:make_charset()
for _, command in ipairs(self.commands) do if not self.charset then
for char in command:get_charset() do self.charset = {}
self.charset[char] = true
for _, command in ipairs(self.commands) do
command:make_charset()
for char in pairs(command.charset) do
self.charset[char] = true
end
end
for _, option in ipairs(self.options) do
for _, alias in ipairs(option.aliases) do
self.charset[alias:sub(1, 1)] = true
end
end end
end end
for _, option in ipairs(self.options) do
for _, alias in ipairs(option.aliases) do
self.charset[alias:sub(1, 1)] = true
end
end
return self.charset
end end
-- to be called from State
function Parser:make_targets() function Parser:make_targets()
for _, option in ipairs(self.options) do for _, option in ipairs(self.options) do
if not option.target then if not option.target then
@@ -377,71 +435,23 @@ function Parser:make_targets()
end end
end end
function Parser:parse(args) function self:make_command_names()
self:get_charset() for _, command in ipairs(self.commands) do
return State(self):parse(args) command.name = self.name .. " " .. command.name
end
end end
function Parser:prepare()
self:make_charset()
self:make_targets()
self:make_command_names()
return self
end
function Parser:parse(args) function Parser:parse(args)
args = args or arg args = args or arg
self.name = self.name or args[0] self.name = self.name or args[0]
return State(self):parse(args)
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.minargs == 0 and state.context["-" .. data:sub(i+1, i+1)]) 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 end
argparse.parser = Parser argparse.parser = Parser