From 56e223a1f1930fecf5be746f1d63867c2ecbf046 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Sat, 20 Jun 2015 19:21:03 +0300 Subject: [PATCH] Upgrade to argparse 0.4.0 --- src/luacheck/argparse.lua | 412 +++++++++++++++++++------------------- src/luacheck/main.lua | 137 +++++-------- 2 files changed, 260 insertions(+), 289 deletions(-) diff --git a/src/luacheck/argparse.lua b/src/luacheck/argparse.lua index 591a0646..842dcd7d 100644 --- a/src/luacheck/argparse.lua +++ b/src/luacheck/argparse.lua @@ -1,256 +1,261 @@ -local Parser, Command, Argument, Option - --- Create classes with setters -do - local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v +local function deep_update(t1, t2) + for k, v in pairs(t2) do + if type(v) == "table" then + v = deep_update({}, v) end - return t1 + t1[k] = v end - local class_metatable = {} + return t1 +end - function class_metatable.__call(cls, ...) - return setmetatable(deep_update({}, cls.__proto), cls)(...) - end +-- A property is a tuple {name, callback}. +-- properties.args is number of properties that can be set as arguments +-- when calling an object. +local function new_class(prototype, properties, parent) + -- Class is the metatable of its instances. + local class = {} + class.__index = class - function class_metatable.__index(cls, key) - return cls.__parent and cls.__parent[key] + if parent then + class.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) + else + class.__prototype = prototype end - local function class(proto) - local cls = setmetatable({__proto = proto, __parent = {}}, class_metatable) - cls.__index = cls - return cls - end + local names = {} - local function extend(cls, proto) - local new_cls = class(deep_update(deep_update({}, cls.__proto), proto)) - new_cls.__parent = cls - return new_cls - end + -- Create setter methods and fill set of property names. + for _, property in ipairs(properties) do + local name, callback = property[1], property[2] - local function add_setters(cl, fields) - for field, setter in pairs(fields) do - cl[field] = function(self, value) - setter(self, value) - self["_"..field] = value - return self + class[name] = function(self, value) + if not callback(self, value) then + self["_" .. name] = value end - end - - cl.__call = function(self, ...) - local name_or_options - for i=1, select("#", ...) do - name_or_options = select(i, ...) + return self + end - if type(name_or_options) == "string" then - if self._aliases then - table.insert(self._aliases, name_or_options) - end + names[name] = true + end - if not self._aliases or not self._name then - self._name = name_or_options - end - elseif type(name_or_options) == "table" then - for field in pairs(fields) do - if name_or_options[field] ~= nil then - self[field](self, name_or_options[field]) - end - end + function class.__call(self, ...) + -- When calling an object, if the first argument is a table, + -- interpret keys as property names, else delegate arguments + -- to corresponding setters in order. + if type((...)) == "table" then + for name, value in pairs((...)) do + if names[name] then + self[name](self, value) end end + else + local nargs = select("#", ...) - return self - end + for i, property in ipairs(properties) do + if i > nargs or i > properties.args then + break + end - return cl - end + local arg = select(i, ...) - 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 + if arg ~= nil then + self[property[1]](self, arg) end end - - self[type_] = typechecker_factory - return typechecker_factory end - }) - local function aliased_name(self, name) - typecheck.string "name" (self, name) + return self + end - table.insert(self._aliases, name) + -- If indexing class fails, fallback to its parent. + local class_metatable = {} + class_metatable.__index = parent + + function class_metatable.__call(self, ...) + -- Calling a class returns its instance. + -- Arguments are delegated to the instance. + local object = deep_update({}, self.__prototype) + setmetatable(object, self) + return object(...) end - local function aliased_aliases(self, aliases) - typecheck.table "aliases" (self, aliases) + return setmetatable(class, class_metatable) +end - if not self._name then - self._name = aliases[1] +local function typecheck(name, types, value) + for _, type_ in ipairs(types) do + if type(value) == type_ then + return true end end - local function parse_boundaries(boundaries) - if tonumber(boundaries) then - return tonumber(boundaries), tonumber(boundaries) - end + error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) +end - if boundaries == "*" then - return 0, math.huge - end +local function typechecked(name, ...) + local types = {...} + return {name, function(_, value) typecheck(name, types, value) end} +end - if boundaries == "+" then - return 1, math.huge - end +local multiname = {"name", function(self, value) + typecheck("name", {"string"}, value) - if boundaries == "?" then - return 0, 1 - end + for alias in value:gmatch("%S+") do + self._name = self._name or alias + table.insert(self._aliases, alias) + end - if boundaries:match "^%d+%-%d+$" then - local min, max = boundaries:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end + -- Do not set _name as with other properties. + return true +end} - if boundaries:match "^%d+%+$" then - local min = boundaries:match "^(%d+)%+$" - return tonumber(min), math.huge - end +local function parse_boundaries(str) + if tonumber(str) then + return tonumber(str), tonumber(str) end - local function boundaries(field) - return function(self, value) - local min, max = parse_boundaries(value) + if str == "*" then + return 0, math.huge + end - if not min then - error(("bad field '%s'"):format(field)) - end + if str == "+" then + return 1, math.huge + end - self["_min"..field], self["_max"..field] = min, max - end + if str == "?" then + return 0, 1 end - local function convert(_, 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 + if str:match "^%d+%-%d+$" then + local min, max = str:match "^(%d+)%-(%d+)$" + return tonumber(min), tonumber(max) end - local function argname(_, value) - if type(value) ~= "string" then - if type(value) ~= "table" then - error(("bad field 'argname' (string or table expected, got %s)"):format(type(value))) - end - end + if str:match "^%d+%+$" then + local min = str:match "^(%d+)%+$" + return tonumber(min), math.huge end +end + +local function boundaries(name) + return {name, function(self, value) + typecheck(name, {"number", "string"}, value) + + local min, max = parse_boundaries(value) - local function add_help(self, param) - if self._has_help then - table.remove(self._options) - self._has_help = false + if not min then + error(("bad property '%s'"):format(name)) end - if param then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - io.stdout:write(self:get_help() .. "\n") - os.exit(0) - end)(param) + self["_min" .. name], self["_max" .. name] = min, max + end} +end - if not help._name then - help "-h" "--help" - end +local add_help = {"add_help", function(self, value) + typecheck("add_help", {"boolean", "string", "table"}, value) - self._has_help = true - end + if self._has_help then + table.remove(self._options) + self._has_help = false end - Parser = add_setters(class { - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _require_command = true - }, { - name = typecheck.string "name", - description = typecheck.string "description", - epilog = typecheck.string "epilog", - require_command = typecheck.boolean "require_command", - usage = typecheck.string "usage", - help = typecheck.string "help", - add_help = add_help - }) - - Command = add_setters(extend(Parser, { - _aliases = {} - }), { - name = aliased_name, - aliases = aliased_aliases, - 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 = add_help - }) - - Argument = add_setters(class { - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true - }, { - name = typecheck.string "name", - description = typecheck.string "description", - target = typecheck.string "target", - args = boundaries "args", - default = typecheck.string "default", - defmode = typecheck.string "defmode", - convert = convert, - argname = argname, - show_default = typecheck.boolean "show_default" - }) - - Option = add_setters(extend(Argument, { - _aliases = {}, - _mincount = 0, - _overwrite = true - }), { - name = aliased_name, - aliases = aliased_aliases, - description = typecheck.string "description", - target = typecheck.string "target", - args = boundaries "args", - count = boundaries "count", - default = typecheck.string "default", - defmode = typecheck.string "defmode", - convert = convert, - overwrite = typecheck.boolean "overwrite", - action = typecheck["function"] "action", - argname = argname, - show_default = typecheck.boolean "show_default" - }) -end + if value then + local help = self:flag() + :description "Show this help message and exit." + :action(function() + print(self:get_help()) + os.exit(0) + end) + + if value ~= true then + help = help(value) + end + + if not help._name then + help "-h" "--help" + end + + self._has_help = true + end +end} + +local Parser = new_class({ + _arguments = {}, + _options = {}, + _commands = {}, + _mutexes = {}, + _require_command = true, + _handle_options = true +}, { + args = 3, + typechecked("name", "string"), + typechecked("description", "string"), + typechecked("epilog", "string"), + typechecked("usage", "string"), + typechecked("help", "string"), + typechecked("require_command", "boolean"), + typechecked("handle_options", "boolean"), + add_help +}) + +local Command = new_class({ + _aliases = {} +}, { + args = 3, + multiname, + typechecked("description", "string"), + typechecked("epilog", "string"), + typechecked("target", "string"), + typechecked("usage", "string"), + typechecked("help", "string"), + typechecked("require_command", "boolean"), + typechecked("handle_options", "boolean"), + typechecked("action", "function"), + add_help +}, Parser) + +local Argument = new_class({ + _minargs = 1, + _maxargs = 1, + _mincount = 1, + _maxcount = 1, + _defmode = "unused", + _show_default = true +}, { + args = 5, + typechecked("name", "string"), + typechecked("description", "string"), + typechecked("default", "string"), + typechecked("convert", "function", "table"), + boundaries("args"), + typechecked("target", "string"), + typechecked("defmode", "string"), + typechecked("show_default", "boolean"), + typechecked("argname", "string", "table") +}) + +local Option = new_class({ + _aliases = {}, + _mincount = 0, + _overwrite = true +}, { + args = 6, + multiname, + typechecked("description", "string"), + typechecked("default", "string"), + typechecked("convert", "function", "table"), + boundaries("args"), + boundaries("count"), + typechecked("target", "string"), + typechecked("defmode", "string"), + typechecked("show_default", "boolean"), + typechecked("overwrite", "boolean"), + typechecked("argname", "string", "table"), + typechecked("action", "function") +}, Argument) function Argument:_get_argument_list() local buf = {} @@ -679,6 +684,7 @@ function Parser:_parse(args, errhandler) local cur_arg_i = 1 local cur_arg local targets = {} + local handle_options = true local function error_(fmt, ...) return errhandler(parser, fmt:format(...)) @@ -830,6 +836,7 @@ function Parser:_parse(args, errhandler) invoke(argument) end + handle_options = parser._handle_options cur_arg = arguments[cur_arg_i] commands = parser._commands com_context = {} @@ -897,7 +904,6 @@ function Parser:_parse(args, errhandler) end local function mainloop() - local handle_options = true for _, data in ipairs(args) do local plain = true diff --git a/src/luacheck/main.lua b/src/luacheck/main.lua index f89282d0..012d1ef6 100644 --- a/src/luacheck/main.lua +++ b/src/luacheck/main.lua @@ -29,13 +29,11 @@ local function main() local default_cache_path = ".luacheckcache" local function get_parser() - local parser = argparse "luacheck" - :description ("luacheck " .. luacheck._VERSION .. ", a simple static analyzer for Lua.") - :epilog [[ + local parser = argparse("luacheck", "luacheck " .. luacheck._VERSION .. ", a simple static analyzer for Lua.", [[ Links: Luacheck on GitHub: https://github.com/mpeterv/luacheck - Luacheck documentation: http://luacheck.readthedocs.org]] + Luacheck documentation: http://luacheck.readthedocs.org]]) parser:argument "files" :description (fs.has_lfs and [[List of files, directories and rockspecs to check. @@ -44,26 +42,21 @@ Pass "-" to check stdin.]]) :args "+" :argname "" - parser:flag "-g" "--no-global" - :description [[Filter out warnings related to global variables. -Equivalent to --ignore 1.]] - parser:flag "-u" "--no-unused" - :description [[Filter out warnings related to unused variables and values. -Equivalent to --ignore [23].]] - parser:flag "-r" "--no-redefined" - :description [[Filter out warnings related to redefined variables. -Equivalent to --ignore 4.]] - - parser:flag "-a" "--no-unused-args" - :description [[Filter out warnings related to unused arguments and loop variables. -Equivalent to --ignore 21[23].]] - parser:flag "-s" "--no-unused-secondaries" - :description "Filter out warnings related to unused variables set together with used ones." - parser:flag "--no-self" - :description "Filter out warnings related to implicit self argument." - - parser:option "--std" - :description [[Set standard globals. must be one of: + parser:flag("-g --no-global", [[Filter out warnings related to global variables. +Equivalent to --ignore 1.]]) + parser:flag("-u --no-unused", [[Filter out warnings related to unused variables and values. +Equivalent to --ignore [23].]]) + parser:flag("-r --no-redefined", [[Filter out warnings related to redefined variables. +Equivalent to --ignore 4.]]) + + parser:flag("-a --no-unused-args", [[Filter out warnings related to unused arguments and loop variables. +Equivalent to --ignore 21[23].]]) + parser:flag("-s --no-unused-secondaries", + "Filter out warnings related to unused variables set together with used ones.") + parser:flag("--no-self", + "Filter out warnings related to implicit self argument.") + + parser:option("--std", [[Set standard globals. must be one of: _G - globals of the current Lua interpreter (default); lua51 - globals of Lua 5.1; lua52 - globals of Lua 5.2; @@ -73,111 +66,83 @@ Equivalent to --ignore 21[23].]] luajit - globals of LuaJIT 2.0; min - intersection of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.0; max - union of globals of Lua 5.1, Lua 5.2, Lua 5.3 and LuaJIT 2.0; - none - no standard globals.]] - parser:option "--globals" - :description "Add custom globals on top of standard ones." + none - no standard globals.]]) + parser:option("--globals", "Add custom globals on top of standard ones.") :args "*" :count "*" :argname "" - parser:option "--read-globals" - :description "Add read-only globals." + parser:option("--read-globals", "Add read-only globals.") :args "*" :count "*" :argname "" - parser:option "--new-globals" - :description "Set custom globals. Removes custom globals added previously." + parser:option("--new-globals", "Set custom globals. Removes custom globals added previously.") :args "*" :count "*" :argname "" - parser:option "--new-read-globals" - :description "Set read-only globals. Removes read-only globals added previously." + parser:option("--new-read-globals", "Set read-only globals. Removes read-only globals added previously.") :args "*" :count "*" :argname "" - parser:flag "-c" "--compat" - :description "Equivalent to --std max." - parser:flag "-d" "--allow-defined" - :description "Allow defining globals implicitly by setting them." - parser:flag "-t" "--allow-defined-top" - :description "Allow defining globals implicitly by setting them in the top level scope." - parser:flag "-m" "--module" - :description "Limit visibility of implicitly defined globals to their files." - - parser:option "--ignore" "-i" - :description [[Filter out warnings matching these patterns. + parser:flag("-c --compat", "Equivalent to --std max.") + parser:flag("-d --allow-defined", "Allow defining globals implicitly by setting them.") + parser:flag("-t --allow-defined-top", "Allow defining globals implicitly by setting them in the top level scope.") + parser:flag("-m --module", "Limit visibility of implicitly defined globals to their files.") + + parser:option("--ignore -i", [[Filter out warnings matching these patterns. If a pattern contains slash, part before slash matches warning code and part after it matches name of related variable. Otherwise, if the pattern contains letters or underscore, it matches name of related variable. -Otherwise, the pattern matches warning code.]] +Otherwise, the pattern matches warning code.]]) :args "+" :count "*" :argname "" - parser:option "--enable" "-e" - :description "Do not filter out warnings matching these patterns." + parser:option("--enable -e", "Do not filter out warnings matching these patterns.") :args "+" :count "*" :argname "" - parser:option "--only" "-o" - :description "Filter out warnings not matching these patterns." + parser:option("--only -o", "Filter out warnings not matching these patterns.") :args "+" :count "*" :argname "" - parser:flag "--no-inline" - :description "Disable inline options." - - local config_opt = parser:option "--config" - :description ("Path to configuration file. (default: "..default_config..")") + parser:flag("--no-inline", "Disable inline options.") - local no_config_opt = parser:flag "--no-config" - :description "Do not look up configuration file." - - parser:mutex(config_opt, no_config_opt) + parser:mutex( + parser:option("--config", "Path to configuration file. (default: "..default_config..")"), + parser:flag("--no-config", "Do not look up configuration file.") + ) if fs.has_lfs then - local cache_opt = parser:option "--cache" - :description "Path to cache file." - :default (default_cache_path) - :defmode "arg" - - local no_cache_opt = parser:flag "--no-cache" - :description "Do not use cache." - - parser:mutex(cache_opt, no_cache_opt) + parser:mutex( + parser:option("--cache", "Path to cache file.", default_cache_path) + :defmode "arg", + parser:flag("--no-cache", "Do not use cache.") + ) end if multithreading.has_lanes then - parser:option "-j" "--jobs" - :description "Check files in parallel." + parser:option("-j --jobs", "Check files in parallel.") :convert(tonumber) end - parser:option "--formatter" - :description [[Use custom formatter. must be a module name or one of: + parser:option("--formatter" , [[Use custom formatter. must be a module name or one of: TAP - Test Anything Protocol formatter; JUnit - JUnit XML formatter; plain - simple warning-per-line formatter; - default - standard formatter.]] + default - standard formatter.]]) - parser:flag "-q" "--quiet" - :count "0-3" - :description [[Suppress output for files without warnings. + parser:flag("-q --quiet", [[Suppress output for files without warnings. -qq: Suppress output of warnings. - -qqq: Only print total number of warnings and errors.]] + -qqq: Only print total number of warnings and errors.]]) + :count "0-3" - parser:flag "--codes" - :description "Show warning codes." + parser:flag("--codes", "Show warning codes.") - parser:flag "--no-color" - :description "Do not color output." + parser:flag("--no-color", "Do not color output.") - parser:flag "-v" "--version" - :description "Show version info and exit." - :action(function() - print(version.string) - os.exit(0) - end) + parser:flag("-v --version", "Show version info and exit.") + :action(function() print(version.string) os.exit(0) end) return parser end