mirror of
https://github.com/TangentFoxy/argparse.git
synced 2025-07-28 02:52:20 +00:00
declarative interface
This commit is contained in:
442
src/argparse.lua
442
src/argparse.lua
@@ -2,34 +2,25 @@ local argparse = {}
|
|||||||
|
|
||||||
local class = require "30log"
|
local class = require "30log"
|
||||||
|
|
||||||
local State = require "argparse.state"
|
local State = class {
|
||||||
local utils = require "argparse.utils"
|
context = {}, -- {alias -> element}
|
||||||
|
result = {}
|
||||||
|
}
|
||||||
|
|
||||||
local Parser = class()
|
function State:__init(parser)
|
||||||
|
self:switch(parser)
|
||||||
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
|
end
|
||||||
|
|
||||||
function Parser:add_alias(element, alias)
|
function State:switch(parser)
|
||||||
table.insert(element.aliases, alias)
|
self.parser = parser
|
||||||
self.context[alias] = element
|
self.parser:make_targets()
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:apply_options(element, options)
|
for _, option in ipairs(parser.options) do
|
||||||
for k, v in pairs(options) do
|
table.insert(self.options, option)
|
||||||
element[k] = v -- fixme
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self.arguments = parser.arguments
|
||||||
|
self.commands = parser.commands
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:make_target(element)
|
function Parser:make_target(element)
|
||||||
@@ -45,151 +36,297 @@ function Parser:make_target(element)
|
|||||||
end
|
end
|
||||||
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
|
function State:parse(args)
|
||||||
for i = 1, select('#', ...) do
|
args = args or arg
|
||||||
argument = select(i, ...)
|
|
||||||
|
|
||||||
if type(argument) == "string" then
|
|
||||||
self:add_alias(element, argument)
|
|
||||||
|
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
|
else
|
||||||
self:apply_options(element, argument)
|
self._current = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self:make_target(element)
|
local argument = self._arguments[self._next_arg_i]
|
||||||
|
if argument then
|
||||||
element.mincount, element.maxcount = utils.parse_boundaries(element.count)
|
self:_open(argument)
|
||||||
element.minargs, element.maxargs = utils.parse_boundaries(element.args)
|
self:_pass(argument, data)
|
||||||
|
else
|
||||||
table.insert(self.arguments, element)
|
local command = self.context[data]
|
||||||
table.insert(self.elements, element)
|
if command and command.type == "command" then
|
||||||
|
self._result[command.target] = {{}}
|
||||||
return element
|
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
|
end
|
||||||
|
|
||||||
function Parser:option(name, ...)
|
function State:get_result()
|
||||||
local element = {
|
self:_check()
|
||||||
name = name,
|
|
||||||
aliases = {},
|
|
||||||
count = "0-1",
|
|
||||||
args = 1,
|
|
||||||
type = "option"
|
|
||||||
}
|
|
||||||
|
|
||||||
self:add_alias(element, name)
|
local result = {}
|
||||||
|
|
||||||
local argument
|
local invocations
|
||||||
for i = 1, select('#', ...) do
|
for _, element in ipairs(self._all_elements) do
|
||||||
argument = select(i, ...)
|
invocations = self._result[element.target]
|
||||||
|
|
||||||
if type(argument) == "string" then
|
if element.maxcount == 1 then
|
||||||
self:add_alias(element, argument)
|
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
|
else
|
||||||
self:apply_options(element, argument)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
self:make_target(element)
|
return result
|
||||||
|
|
||||||
element.mincount, element.maxcount = utils.parse_boundaries(element.count)
|
|
||||||
element.minargs, element.maxargs = utils.parse_boundaries(element.args)
|
|
||||||
|
|
||||||
table.insert(self.elements, element)
|
|
||||||
|
|
||||||
return element
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- DRY?
|
function State:_check()
|
||||||
|
self:_assert(not self._parser.must_command, "a command is required")
|
||||||
|
|
||||||
function Parser:flag(name, ...)
|
local invocations
|
||||||
local element = {
|
for _, element in ipairs(self._all_elements) do
|
||||||
name = name,
|
invocations = self._result[element.target] or {}
|
||||||
aliases = {},
|
|
||||||
count = "0-1",
|
|
||||||
args = 0,
|
|
||||||
type = "option"
|
|
||||||
}
|
|
||||||
|
|
||||||
self:add_alias(element, name)
|
if element.type == "argument" and #invocations == 0 then
|
||||||
|
invocations[1] = {}
|
||||||
|
end
|
||||||
|
|
||||||
local argument
|
if #invocations > element.maxcount then
|
||||||
for i = 1, select('#', ...) do
|
if element.no_overwrite then
|
||||||
argument = select(i, ...)
|
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
|
||||||
|
|
||||||
if type(argument) == "string" then
|
self:_assert(#invocations >= element.mincount, "option %s must be used at least %d times", element.name, element.mincount)
|
||||||
self:add_alias(element, argument)
|
|
||||||
else
|
for _, passed in ipairs(invocations) do
|
||||||
self:apply_options(element, argument)
|
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
|
||||||
|
|
||||||
|
|
||||||
|
local utils = require "argparse.utils"
|
||||||
|
|
||||||
|
local Declarative = {}
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
self:make_target(element)
|
return self
|
||||||
|
|
||||||
element.mincount, element.maxcount = utils.parse_boundaries(element.count)
|
|
||||||
element.minargs, element.maxargs = utils.parse_boundaries(element.args)
|
|
||||||
|
|
||||||
table.insert(self.elements, element)
|
|
||||||
|
|
||||||
return element
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:command(name, ...)
|
local Aliased = {}
|
||||||
local element = {
|
|
||||||
name = name,
|
|
||||||
aliases = {},
|
|
||||||
count = "0-1",
|
|
||||||
args = 0,
|
|
||||||
type = "command"
|
|
||||||
}
|
|
||||||
|
|
||||||
local command = Parser() -- fixme
|
function Aliased:set_name(name)
|
||||||
for k, v in pairs(element) do
|
table.insert(self.aliases, name)
|
||||||
command[k] = v
|
|
||||||
|
if not self.name then
|
||||||
|
self.name = name
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self:add_alias(command, name)
|
local Named = {}
|
||||||
|
|
||||||
local argument
|
function Named:set_name(name)
|
||||||
for i = 1, select('#', ...) do
|
self.name = name
|
||||||
argument = select(i, ...)
|
end
|
||||||
self:add_alias(command, argument)
|
|
||||||
end
|
|
||||||
|
|
||||||
self:make_target(command)
|
local Parser = class {
|
||||||
|
arguments = {},
|
||||||
|
options = {},
|
||||||
|
commands = {},
|
||||||
|
charset = {"-"},
|
||||||
|
fields = {"name", "description", "target"}
|
||||||
|
}:include(Declarative):include(Named)
|
||||||
|
|
||||||
command.mincount, command.maxcount = utils.parse_boundaries(command.count)
|
local Command = Parser:extends {
|
||||||
command.minargs, command.maxargs = utils.parse_boundaries(command.args)
|
aliases = {}
|
||||||
|
}:include(Declarative):include(Aliased)
|
||||||
|
|
||||||
table.insert(self.elements, command)
|
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
|
return command
|
||||||
end
|
end
|
||||||
|
|
||||||
function Parser:group(...)
|
|
||||||
--
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:mutually_exclusive(...)
|
|
||||||
local group = {
|
|
||||||
elements = {...}
|
|
||||||
}
|
|
||||||
|
|
||||||
table.insert(self.groups, group)
|
|
||||||
return group
|
|
||||||
end
|
|
||||||
|
|
||||||
function Parser:error(fmt, ...)
|
function Parser:error(fmt, ...)
|
||||||
local msg = fmt:format(...)
|
local msg = fmt:format(...)
|
||||||
io.stderr:write("Error: " .. msg .. "\n")
|
io.stderr:write("Error: " .. msg .. "\n")
|
||||||
@@ -200,6 +337,51 @@ function Parser:assert(assertion, ...)
|
|||||||
return assertion or self:error(...)
|
return assertion or self:error(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Parser:get_charset()
|
||||||
|
for _, command in ipairs(self.commands) do
|
||||||
|
for char in command:get_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
|
||||||
|
|
||||||
|
return self.charset
|
||||||
|
end
|
||||||
|
|
||||||
|
-- to be called from State
|
||||||
|
function Parser:make_targets()
|
||||||
|
for _, option in ipairs(self.options) do
|
||||||
|
if not option.target then
|
||||||
|
for _, alias in ipairs(option.aliases) do
|
||||||
|
if alias:sub(1, 1) == alias:sub(2, 2) then
|
||||||
|
option.target = alias:sub(3)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
option.target = option.target or option.aliases[1]:sub(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, argument in ipairs(self.arguments) do
|
||||||
|
argument.target = argument.target or argument.name
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, command in ipairs(self.commands) do
|
||||||
|
command.target = command.target or command.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Parser:parse(args)
|
||||||
|
self:get_charset()
|
||||||
|
return State(self):parse(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
function Parser:parse(args)
|
function Parser:parse(args)
|
||||||
args = args or arg
|
args = args or arg
|
||||||
|
101
src/interface.lua
Normal file
101
src/interface.lua
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
-- 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
|
39
src/interface_test.lua
Normal file
39
src/interface_test.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
--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 == "?")
|
@@ -1,6 +1,8 @@
|
|||||||
local class = require "30log"
|
local class = require "30log"
|
||||||
|
|
||||||
local State = class()
|
local State = class {
|
||||||
|
context = {}
|
||||||
|
}
|
||||||
|
|
||||||
function State:__init(parser)
|
function State:__init(parser)
|
||||||
self.context = {}
|
self.context = {}
|
||||||
|
Reference in New Issue
Block a user