mirror of
https://github.com/TangentFoxy/argparse.git
synced 2025-10-02 15:12:30 +00:00
Refactoring: added sanity checks in setters
This commit is contained in:
@@ -7,10 +7,10 @@ describe("tests related to actions", function()
|
|||||||
|
|
||||||
local parser = Parser()
|
local parser = Parser()
|
||||||
parser:option "-f" "--from" {
|
parser:option "-f" "--from" {
|
||||||
action = action1
|
action = function(...) return action1(...) end
|
||||||
}
|
}
|
||||||
parser:option "-p" "--pair" {
|
parser:option "-p" "--pair" {
|
||||||
action = action2,
|
action = function(...) return action2(...) end,
|
||||||
count = "*",
|
count = "*",
|
||||||
args = 2
|
args = 2
|
||||||
}
|
}
|
||||||
@@ -28,14 +28,14 @@ describe("tests related to actions", function()
|
|||||||
|
|
||||||
local parser = Parser()
|
local parser = Parser()
|
||||||
parser:flag "-v" "--verbose" {
|
parser:flag "-v" "--verbose" {
|
||||||
action = action1,
|
action = function(...) return action1(...) end,
|
||||||
count = "0-3"
|
count = "0-3"
|
||||||
}
|
}
|
||||||
parser:flag "-q" "--quiet" {
|
parser:flag "-q" "--quiet" {
|
||||||
action = action2
|
action = function(...) return action2(...) end
|
||||||
}
|
}
|
||||||
parser:flag "-a" "--another-flag" {
|
parser:flag "-a" "--another-flag" {
|
||||||
action = action3
|
action = function(...) return action3(...) end
|
||||||
}
|
}
|
||||||
|
|
||||||
local args = parser:parse{"-vv", "--quiet"}
|
local args = parser:parse{"-vv", "--quiet"}
|
||||||
@@ -53,7 +53,7 @@ describe("tests related to actions", function()
|
|||||||
count = "0-3"
|
count = "0-3"
|
||||||
}
|
}
|
||||||
local add = parser:command "add" {
|
local add = parser:command "add" {
|
||||||
action = action
|
action = function(...) return action(...) end
|
||||||
}
|
}
|
||||||
add:argument "something"
|
add:argument "something"
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ describe("tests related to :pparse()", function()
|
|||||||
it("still raises an error if it is caused by misconfiguration", function()
|
it("still raises an error if it is caused by misconfiguration", function()
|
||||||
local parser = Parser()
|
local parser = Parser()
|
||||||
parser:option "--foo"
|
parser:option "--foo"
|
||||||
:count "a lot"
|
:aliases {1, 2, 3}
|
||||||
assert.has_error(function() parser:pparse{} end)
|
assert.has_error(function() parser:pparse{} end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
291
src/argparse.lua
291
src/argparse.lua
@@ -1,101 +1,201 @@
|
|||||||
local class = require "30log"
|
local class = require "30log"
|
||||||
|
|
||||||
local Declarative = {}
|
local function parse_boundaries(boundaries)
|
||||||
|
if tonumber(boundaries) then
|
||||||
|
return tonumber(boundaries), tonumber(boundaries)
|
||||||
|
end
|
||||||
|
|
||||||
function Declarative:__init(...)
|
if boundaries == "*" then
|
||||||
local cl = getmetatable(self)
|
return 0, math.huge
|
||||||
|
end
|
||||||
|
|
||||||
for _, field in ipairs(self._fields) do
|
if boundaries == "+" then
|
||||||
if not cl[field] then
|
return 1, math.huge
|
||||||
cl[field] = function(s, value)
|
end
|
||||||
s["_" .. field] = value
|
|
||||||
return s
|
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
|
||||||
|
|
||||||
|
local function add_setters(cl, fields)
|
||||||
|
for field, setter in pairs(fields) do
|
||||||
|
cl[field] = function(self, value)
|
||||||
|
if setter then
|
||||||
|
setter(self, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self["_"..field] = value
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self(...)
|
cl.__init = function(self, ...)
|
||||||
end
|
return self(...)
|
||||||
|
end
|
||||||
|
|
||||||
function Declarative:__call(...)
|
cl.__call = function(self, ...)
|
||||||
local name_or_options
|
local name_or_options
|
||||||
|
|
||||||
for i=1, select("#", ...) do
|
for i=1, select("#", ...) do
|
||||||
name_or_options = select(i, ...)
|
name_or_options = select(i, ...)
|
||||||
|
|
||||||
if type(name_or_options) == "string" then
|
if type(name_or_options) == "string" then
|
||||||
if self._aliases then
|
if self._aliases then
|
||||||
table.insert(self._aliases, name_or_options)
|
table.insert(self._aliases, name_or_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self._name then
|
if not self._name then
|
||||||
self._name = name_or_options
|
self._name = name_or_options
|
||||||
end
|
end
|
||||||
elseif type(name_or_options) == "table" then
|
elseif type(name_or_options) == "table" then
|
||||||
for _, field in ipairs(self._fields) do
|
for field, setter in pairs(fields) do
|
||||||
if name_or_options[field] ~= nil then
|
if name_or_options[field] ~= nil then
|
||||||
self["_" .. field] = name_or_options[field]
|
self[field](self, name_or_options[field])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return cl
|
||||||
end
|
end
|
||||||
|
|
||||||
local Parser = class {
|
local typecheck = setmetatable({}, {
|
||||||
|
__index = function(self, type_)
|
||||||
|
local typechecker_factory = function(field)
|
||||||
|
return function(_, value)
|
||||||
|
if type(value) ~= type_ then
|
||||||
|
error(("bad field '%s' (%s expected, got %s)"):format(field, type_, type(value)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self[type_] = typechecker_factory
|
||||||
|
return typechecker_factory
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
local noop = false
|
||||||
|
|
||||||
|
local function aliased_name(self, name)
|
||||||
|
typecheck.string "name" (self, name)
|
||||||
|
|
||||||
|
table.insert(self._aliases, name)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function aliased_aliases(self, aliases)
|
||||||
|
typecheck.table "aliases" (self, aliases)
|
||||||
|
|
||||||
|
if not self._name then
|
||||||
|
self._name = aliases[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function boundaries(field)
|
||||||
|
return function(self, value)
|
||||||
|
local min, max = parse_boundaries(value)
|
||||||
|
|
||||||
|
if not min then
|
||||||
|
error(("bad field '%s'"):format(field))
|
||||||
|
end
|
||||||
|
|
||||||
|
self["_min"..field], self["_max"..field] = min, max
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function convert(self, value)
|
||||||
|
if type(value) ~= "function" then
|
||||||
|
if type(value) ~= "table" then
|
||||||
|
error(("bad field 'convert' (function or table expected, got %s)"):format(type(value)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local Parser = add_setters(class {
|
||||||
__name = "Parser",
|
__name = "Parser",
|
||||||
_arguments = {},
|
_arguments = {},
|
||||||
_options = {},
|
_options = {},
|
||||||
_commands = {},
|
_commands = {},
|
||||||
_require_command = true,
|
_require_command = true,
|
||||||
_add_help = true,
|
_add_help = true
|
||||||
_fields = {
|
}, {
|
||||||
"name", "description", "epilog", "require_command",
|
name = typecheck.string "name",
|
||||||
"usage", "help", "add_help"
|
description = typecheck.string "description",
|
||||||
}
|
epilog = typecheck.string "epilog",
|
||||||
}:include(Declarative)
|
require_command = typecheck.boolean "require_command",
|
||||||
|
usage = typecheck.string "usage",
|
||||||
|
help = typecheck.string "help",
|
||||||
|
add_help = noop
|
||||||
|
})
|
||||||
|
|
||||||
local Command = Parser:extends {
|
local Command = add_setters(Parser:extends {
|
||||||
__name = "Command",
|
__name = "Command",
|
||||||
_aliases = {},
|
_aliases = {}
|
||||||
_fields = {
|
}, {
|
||||||
"name", "aliases", "description", "epilog",
|
name = aliased_name,
|
||||||
"target", "require_command", "action", "usage",
|
aliases = aliased_aliases,
|
||||||
"help", "add_help"
|
description = typecheck.string "description",
|
||||||
}
|
epilog = typecheck.string "epilog",
|
||||||
}
|
target = typecheck.string "target",
|
||||||
|
require_command = typecheck.boolean "require_command",
|
||||||
|
action = typecheck["function"] "action",
|
||||||
|
usage = typecheck.string "usage",
|
||||||
|
help = typecheck.string "help",
|
||||||
|
add_help = noop
|
||||||
|
})
|
||||||
|
|
||||||
local Argument = class {
|
local Argument = add_setters(class {
|
||||||
__name = "Argument",
|
__name = "Argument",
|
||||||
_args = 1,
|
_minargs = 1,
|
||||||
_count = 1,
|
_maxargs = 1,
|
||||||
_defmode = "unused",
|
_mincount = 1,
|
||||||
_fields = {
|
_maxcount = 1,
|
||||||
"name", "description", "target", "args",
|
_defmode = "unused"
|
||||||
"minargs", "maxargs", "default", "defmode",
|
}, {
|
||||||
"convert", "usage", "argname"
|
name = typecheck.string "name",
|
||||||
}
|
description = typecheck.string "description",
|
||||||
}:include(Declarative)
|
target = typecheck.string "target",
|
||||||
|
args = boundaries "args",
|
||||||
|
default = typecheck.string "default",
|
||||||
|
defmode = typecheck.string "defmode",
|
||||||
|
convert = convert,
|
||||||
|
usage = typecheck.string "usage",
|
||||||
|
argname = typecheck.string "argname"
|
||||||
|
})
|
||||||
|
|
||||||
local Option = Argument:extends {
|
local Option = add_setters(Argument:extends {
|
||||||
__name = "Option",
|
__name = "Option",
|
||||||
_aliases = {},
|
_aliases = {},
|
||||||
_count = "?",
|
_mincount = 0,
|
||||||
_overwrite = true,
|
_overwrite = true
|
||||||
_fields = {
|
}, {
|
||||||
"name", "aliases", "description", "target",
|
name = aliased_name,
|
||||||
"args", "minargs", "maxargs", "count",
|
aliases = aliased_aliases,
|
||||||
"mincount", "maxcount", "default", "defmode",
|
description = typecheck.string "description",
|
||||||
"convert", "overwrite", "action", "usage",
|
target = typecheck.string "target",
|
||||||
"argname"
|
args = boundaries "args",
|
||||||
}
|
count = boundaries "count",
|
||||||
}
|
default = typecheck.string "default",
|
||||||
|
defmode = typecheck.string "defmode",
|
||||||
local Flag = Option:extends {
|
convert = convert,
|
||||||
__name = "Flag",
|
overwrite = typecheck.boolean "overwrite",
|
||||||
_args = 0
|
action = typecheck["function"] "action",
|
||||||
}
|
usage = typecheck.string "usage",
|
||||||
|
argname = typecheck.string "argname"
|
||||||
|
})
|
||||||
|
|
||||||
function Argument:get_arg_usage(argname)
|
function Argument:get_arg_usage(argname)
|
||||||
argname = self._argname or argname
|
argname = self._argname or argname
|
||||||
@@ -169,47 +269,8 @@ function Argument:make_type()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function 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
|
|
||||||
|
|
||||||
function Argument:make_boundaries()
|
|
||||||
if not self._minargs or not self._maxargs then
|
|
||||||
self._minargs, self._maxargs = parse_boundaries(self._args)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self._mincount or not self._maxcount then
|
|
||||||
self._mincount, self._maxcount = parse_boundaries(self._count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Argument:prepare()
|
function Argument:prepare()
|
||||||
self:make_target()
|
self:make_target()
|
||||||
self:make_boundaries()
|
|
||||||
self:make_type()
|
self:make_type()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -255,7 +316,7 @@ function Parser:option(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Parser:flag(...)
|
function Parser:flag(...)
|
||||||
local flag = Flag:new(...)
|
local flag = Option:new():args(0)(...)
|
||||||
table.insert(self._options, flag)
|
table.insert(self._options, flag)
|
||||||
return flag
|
return flag
|
||||||
end
|
end
|
||||||
@@ -270,22 +331,16 @@ function Parser:prepare()
|
|||||||
self._fullname = self._fullname or self._name
|
self._fullname = self._fullname or self._name
|
||||||
|
|
||||||
if self._add_help and not self._help_option then
|
if self._add_help and not self._help_option then
|
||||||
self._help_option = self:flag(self._add_help)
|
self._help_option = self:flag()
|
||||||
|
:description "Show this help message and exit. "
|
||||||
:action(function()
|
:action(function()
|
||||||
io.stdout:write(self:get_help() .. "\r\n")
|
io.stdout:write(self:get_help() .. "\r\n")
|
||||||
os.exit(0)
|
os.exit(0)
|
||||||
end)
|
end)
|
||||||
|
(self._add_help)
|
||||||
|
|
||||||
if #self._help_option._aliases == 0 then
|
if not self._help_option._name then
|
||||||
if self._help_option._name then
|
self._help_option "-h" "--help"
|
||||||
self._help_option._aliases[1] = self._help_option._name
|
|
||||||
else
|
|
||||||
self._help_option "-h" "--help"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not self._help_option._description then
|
|
||||||
self._help_option:description "Show this help message and exit. "
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user