add even more sugar

This commit is contained in:
mpeterv
2014-01-23 20:02:11 +04:00
parent ba095896ac
commit c763e19b40
2 changed files with 146 additions and 135 deletions

View File

@@ -49,7 +49,7 @@ describe("tests related to commands", function()
local args = parser:parse{}
assert.same({}, args)
parser.require_command = true
parser:require_command(true)
assert.has_error(function() parser:parse{} end, "command is required")
end)

View File

@@ -5,6 +5,17 @@ local class = require "30log"
local Declarative = {}
function Declarative:__init(...)
local cl = getmetatable(self)
for _, field in ipairs(self._fields) do
if not cl[field] then
cl[field] = function(s, value)
s["_" .. field] = value
return s
end
end
end
self(...)
end
@@ -15,17 +26,17 @@ function Declarative:__call(...)
name_or_options = select(i, ...)
if type(name_or_options) == "string" then
if self.aliases then
table.insert(self.aliases, 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
if not self._name then
self._name = name_or_options
end
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
self[field] = name_or_options[field]
self["_" .. field] = name_or_options[field]
end
end
end
@@ -36,11 +47,11 @@ end
local Parser = class {
__name = "Parser",
arguments = {},
options = {},
commands = {},
require_command = false,
fields = {
_arguments = {},
_options = {},
_commands = {},
_require_command = false,
_fields = {
"name", "description", "target", "require_command",
"action", "usage"
}
@@ -48,14 +59,14 @@ local Parser = class {
local Command = Parser:extends {
__name = "Command",
aliases = {}
_aliases = {}
}
local Argument = class {
__name = "Argument",
args = 1,
count = 1,
fields = {
_args = 1,
_count = 1,
_fields = {
"name", "description", "target", "args",
"minargs", "maxargs", "default", "convert",
"action", "usage", "argname"
@@ -64,10 +75,10 @@ local Argument = class {
local Option = Argument:extends {
__name = "Option",
aliases = {},
count = "?",
overwrite = true,
fields = {
_aliases = {},
_count = "?",
_overwrite = true,
_fields = {
"name", "aliases", "description", "target",
"args", "minargs", "maxargs", "count",
"mincount", "maxcount", "default", "convert",
@@ -77,29 +88,29 @@ local Option = Argument:extends {
local Flag = Option:extends {
__name = "Flag",
args = 0
_args = 0
}
function Argument:get_arg_usage(argname)
argname = self.argname or argname
argname = self._argname or argname
local buf = {}
local i = 1
while i <= math.min(self.minargs, 3) do
while i <= math.min(self._minargs, 3) do
table.insert(buf, argname)
i = i+1
end
while i <= math.min(self.maxargs, 3) do
while i <= math.min(self._maxargs, 3) do
table.insert(buf, "[" .. argname .. "]")
i = i+1
if self.maxargs == math.huge then
if self._maxargs == math.huge then
break
end
end
if i < self.maxargs then
if i < self._maxargs then
table.insert(buf, "...")
end
@@ -107,48 +118,48 @@ function Argument:get_arg_usage(argname)
end
function Argument:get_usage()
if not self.usage then
self.usage = table.concat(self:get_arg_usage("<" .. self.name .. ">"), " ")
if not self._usage then
self._usage = table.concat(self:get_arg_usage("<" .. self._name .. ">"), " ")
end
return self.usage
return self._usage
end
function Option:get_usage()
if not self.usage then
self.usage = self:get_arg_usage("<" .. self.target .. ">")
table.insert(self.usage, 1, self.name)
self.usage = table.concat(self.usage, " ")
if not self._usage then
self._usage = self:get_arg_usage("<" .. self._target .. ">")
table.insert(self._usage, 1, self._name)
self._usage = table.concat(self._usage, " ")
if self.mincount == 0 then
self.usage = "[" .. self.usage .. "]"
if self._mincount == 0 then
self._usage = "[" .. self._usage .. "]"
end
end
return self.usage
return self._usage
end
function Parser:argument(...)
local argument = Argument:new(...)
table.insert(self.arguments, argument)
table.insert(self._arguments, argument)
return argument
end
function Parser:option(...)
local option = Option:new(...)
table.insert(self.options, option)
table.insert(self._options, option)
return option
end
function Parser:flag(...)
local flag = Flag:new(...)
table.insert(self.options, flag)
table.insert(self._options, flag)
return flag
end
function Parser:command(...)
local command = Command:new(...)
table.insert(self.commands, command)
table.insert(self._commands, command)
return command
end
@@ -168,45 +179,45 @@ function Parser:assert(assertion, ...)
end
function Parser:make_charset()
if not self.charset then
self.charset = {["-"] = true}
if not self._charset then
self._charset = {["-"] = true}
for _, command in ipairs(self.commands) do
for _, command in ipairs(self._commands) do
command:make_charset()
for char in pairs(command.charset) do
self.charset[char] = true
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
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
function Parser:make_targets()
for _, option in ipairs(self.options) do
if not option.target then
for _, alias in ipairs(option.aliases) do
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)
option._target = alias:sub(3)
break
end
end
end
option.target = option.target or option.aliases[1]:sub(2)
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
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
for _, command in ipairs(self._commands) do
command._target = command._target or command._name
end
end
@@ -239,43 +250,43 @@ local function parse_boundaries(boundaries)
end
function Parser:make_boundaries()
for _, elements in ipairs{self.arguments, self.options} do
for _, elements in ipairs{self._arguments, self._options} do
for _, element in ipairs(elements) do
if not element.minargs or not element.maxargs then
element.minargs, element.maxargs = parse_boundaries(element.args)
if not element._minargs or not element._maxargs then
element._minargs, element._maxargs = parse_boundaries(element._args)
end
if not element.mincount or not element.maxcount then
element.mincount, element.maxcount = parse_boundaries(element.count)
if not element._mincount or not element._maxcount then
element._mincount, element._maxcount = parse_boundaries(element._count)
end
end
end
end
function Parser:make_command_names()
for _, command in ipairs(self.commands) do
command.name = self.name .. " " .. command.name
for _, command in ipairs(self._commands) do
command._name = self._name .. " " .. command._name
end
end
function Parser:make_types()
for _, elements in ipairs{self.arguments, self.options} do
for _, elements in ipairs{self._arguments, self._options} do
for _, element in ipairs(elements) do
if element.maxcount == 1 then
if element.maxargs == 0 then
element.type = "flag"
elseif element.maxargs == 1 and element.minargs == 1 then
element.type = "arg"
if element._maxcount == 1 then
if element._maxargs == 0 then
element._type = "flag"
elseif element._maxargs == 1 and element._minargs == 1 then
element._type = "arg"
else
element.type = "multi-arg"
element._type = "multi-arg"
end
else
if element.maxargs == 0 then
element.type = "counter"
elseif element.maxargs == 1 and element.minargs == 1 then
element.type = "multi-count"
if element._maxargs == 0 then
element._type = "counter"
elseif element._maxargs == 1 and element._minargs == 1 then
element._type = "multi-count"
else
element.type = "multi-count multi-arg"
element._type = "multi-count multi-arg"
end
end
end
@@ -292,17 +303,17 @@ function Parser:prepare()
end
function Parser:get_usage()
if not self.usage then
local buf = {"Usage:", self.name}
if not self._usage then
local buf = {"Usage:", self._name}
for _, elements in ipairs{self.options, self.arguments} do
for _, elements in ipairs{self._options, self._arguments} do
for _, element in ipairs(elements) do
table.insert(buf, element:get_usage())
end
end
if #self.commands > 0 then
if self.require_command then
if #self._commands > 0 then
if self._require_command then
table.insert(buf, "<command>")
else
table.insert(buf, "[<command>]")
@@ -312,15 +323,15 @@ function Parser:get_usage()
end
-- TODO: prettify
self.usage = table.concat(buf, " ")
self._usage = table.concat(buf, " ")
end
return self.usage
return self._usage
end
function Parser:parse(args)
args = args or arg
self.name = self.name or args[0]
self._name = self._name or args[0]
local parser
local charset
@@ -338,8 +349,8 @@ function Parser:parse(args)
local cur_arg
local function convert(element, data)
if element.convert then
local ok, err = element.convert(data)
if element._convert then
local ok, err = element._convert(data)
return parser:assert(ok, "%s", err or "malformed argument " .. data)
else
@@ -352,11 +363,11 @@ function Parser:parse(args)
function invoke(element)
local overwrite = false
if invocations[element] == element.maxcount then
if element.overwrite then
if invocations[element] == element._maxcount then
if element._overwrite then
overwrite = true
else
parser:error("option %s must be used at most %d times", element.name, element.maxcount)
parser:error("option %s must be used at most %d times", element._name, element._maxcount)
end
else
invocations[element] = invocations[element]+1
@@ -364,27 +375,27 @@ function Parser:parse(args)
passed[element] = 0
if element.type == "flag" then
result[element.target] = true
elseif element.type == "multi-arg" then
result[element.target] = {}
elseif element.type == "counter" then
if element._type == "flag" then
result[element._target] = true
elseif element._type == "multi-arg" then
result[element._target] = {}
elseif element._type == "counter" then
if not overwrite then
result[element.target] = result[element.target]+1
result[element._target] = result[element._target]+1
end
elseif element.type == "multi-count" then
elseif element._type == "multi-count" then
if overwrite then
table.remove(result[element.target], 1)
table.remove(result[element._target], 1)
end
elseif element.type == "multi-count multi-arg" then
table.insert(result[element.target], {})
elseif element._type == "multi-count multi-arg" then
table.insert(result[element._target], {})
if overwrite then
table.remove(result[element.target], 1)
table.remove(result[element._target], 1)
end
end
if element.maxargs == 0 then
if element._maxargs == 0 then
close(element)
end
end
@@ -393,24 +404,24 @@ function Parser:parse(args)
passed[element] = passed[element]+1
data = convert(element, data)
if element.type == "arg" then
result[element.target] = data
elseif element.type == "multi-arg" or element.type == "multi-count" then
table.insert(result[element.target], data)
elseif element.type == "multi-count multi-arg" then
table.insert(result[element.target][#result[element.target]], data)
if element._type == "arg" then
result[element._target] = data
elseif element._type == "multi-arg" or element._type == "multi-count" then
table.insert(result[element._target], data)
elseif element._type == "multi-count multi-arg" then
table.insert(result[element._target][#result[element._target]], data)
end
if passed[element] == element.maxargs then
if passed[element] == element._maxargs then
close(element)
end
end
function close(element)
if passed[element] < element.minargs then
if element.default then
while passed[element] < element.minargs do
pass(element, element.default)
if passed[element] < element._minargs then
if element._default then
while passed[element] < element._minargs do
pass(element, element._default)
end
else
parser:error("too few arguments")
@@ -423,11 +434,11 @@ function Parser:parse(args)
cur_arg = arguments[cur_arg_i]
end
if element.action then
if element.type == "multi-count" or element.type == "multi-count multi-arg" then
element.action(result[element.target][#result[element.target]])
if element._action then
if element._type == "multi-count" or element._type == "multi-count multi-arg" then
element._action(result[element._target][#result[element._target]])
else
element.action(result[element.target])
element._action(result[element._target])
end
end
end
@@ -435,40 +446,40 @@ function Parser:parse(args)
local function switch(p)
parser = p:prepare()
charset = p.charset
charset = parser._charset
if p.action then
table.insert(com_callbacks, p.action)
if parser._action then
table.insert(com_callbacks, parser._action)
end
for _, option in ipairs(p.options) do
for _, option in ipairs(parser._options) do
table.insert(options, option)
for _, alias in ipairs(option.aliases) do
for _, alias in ipairs(option._aliases) do
opt_context[alias] = option
end
if option.type == "counter" then
result[option.target] = 0
elseif option.type == "multi-count" or option.type == "multi-count multi-arg" then
result[option.target] = {}
if option._type == "counter" then
result[option._target] = 0
elseif option._type == "multi-count" or option._type == "multi-count multi-arg" then
result[option._target] = {}
end
invocations[option] = 0
end
for _, argument in ipairs(p.arguments) do
for _, argument in ipairs(parser._arguments) do
table.insert(arguments, argument)
invocations[argument] = 0
invoke(argument)
end
cur_arg = arguments[cur_arg_i]
commands = p.commands
commands = parser._commands
com_context = {}
for _, command in ipairs(p.commands) do
for _, alias in ipairs(command.aliases) do
for _, command in ipairs(commands) do
for _, alias in ipairs(command._aliases) do
com_context[alias] = command
end
end
@@ -489,7 +500,7 @@ function Parser:parse(args)
parser:error("too many arguments")
end
else
result[com.target] = true
result[com._target] = true
switch(com)
end
end
@@ -523,7 +534,7 @@ function Parser:parse(args)
option = parser:assert(opt_context[name], "unknown option %s", name)
handle_option(name)
if i ~= #data and option.minargs > 0 then
if i ~= #data and option._minargs > 0 then
handle_argument(data:sub(i+1))
break
end
@@ -539,7 +550,7 @@ function Parser:parse(args)
if equal then
name = data:sub(1, equal-1)
option = parser:assert(opt_context[name], "unknown option %s", name)
parser:assert(option.maxargs > 0, "option %s doesn't take arguments", name)
parser:assert(option._maxargs > 0, "option %s doesn't take arguments", name)
handle_option(data:sub(1, equal-1))
handle_argument(data:sub(equal+1))
@@ -570,13 +581,13 @@ function Parser:parse(args)
close(cur_arg)
end
if parser.require_command and #commands > 0 then
if parser._require_command and #commands > 0 then
parser:error("command is required")
end
for _, option in ipairs(options) do
parser:assert(invocations[option] >= option.mincount,
"option %s must be used at least %d times", option.name, option.mincount
parser:assert(invocations[option] >= option._mincount,
"option %s must be used at least %d times", option._name, option._mincount
)
end