Skip to content

Commit

Permalink
Merge pull request #4497 from vallode/argparse-types
Browse files Browse the repository at this point in the history
Add types to argparse and alt_getopt
  • Loading branch information
myk002 committed May 9, 2024
2 parents 325f510 + a559449 commit a731f1b
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 20 deletions.
27 changes: 27 additions & 0 deletions library/lua/3rdparty/alt_getopt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@

local _ENV = mkmodule('3rdparty.alt_getopt')

---@nodiscard
---@param opts string
---@return table<string, string|integer>
local function get_opt_map(opts)
local i = 1
local len = #opts
Expand All @@ -57,11 +60,16 @@ local function get_opt_map(opts)
return options
end

---@param opt string|integer
local function err_unknown_opt(opt)
qerror(string.format('Unknown option "-%s%s"', #opt > 1 and '-' or '', opt))
end

-- resolve aliases into their canonical forms
---@nodiscard
---@param options table<string, string|integer>
---@param opt string|integer
---@return integer
local function canonicalize(options, opt)
if not options[opt] then
err_unknown_opt(opt)
Expand All @@ -80,14 +88,26 @@ local function canonicalize(options, opt)
'Option "%s" resolves to non-number for has_arg flag', opt))
end

---@type integer
return opt
end

---@nodiscard
---@param options table<string, string|integer>
---@param opt string|integer
---@return boolean
local function has_arg(options, opt)
return options[canonicalize(options, opt)] == 1
end

-- returns vectors for opts, optargs, and nonoptions
---@nodiscard
---@param args string[] e.g, { ... }
---@param sh_opts string e.g., 'ak:hv'
---@param long_opts table<string, string|integer>
---@return string[] opts
---@return string[] optargs
---@return string[] nonoptions
function get_ordered_opts(args, sh_opts, long_opts)
local optind, count, opts, optargs, nonoptions = 1, 1, {}, {}, {}

Expand Down Expand Up @@ -159,7 +179,14 @@ end

-- returns a map of options to their optargs (or 1 if the option doesn't take an
-- argument), and a vector for nonoptions
---@nodiscard
---@param args string[]
---@param sh_opts string
---@param long_opts string[]
---@return table<string, string|integer>
---@return string[]
function get_opts(args, sh_opts, long_opts)
---@type table<string, string|integer>
local ret = {}

local opts,optargs,nonoptions = get_ordered_opts(args, sh_opts, long_opts)
Expand Down
63 changes: 54 additions & 9 deletions library/lua/argparse.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ local _ENV = mkmodule('argparse')

local getopt = require('3rdparty.alt_getopt')

---@nodiscard
---@param args string[] Most commonly `{ ... }`
---@param validArgs table<string, integer> Use `utils.invert`
---@return table<string, string|string[]>
function processArgs(args, validArgs)
local result = {}
local argName
Expand Down Expand Up @@ -58,25 +62,34 @@ function processArgs(args, validArgs)
return result
end

---@class argparse.OptionAction
---@field [1] string|nil Short option (eg. "q")
---@field [2] string|nil Long option (eg. "quiet")
---@field handler fun(optarg?: string)
---@field hasArg boolean|nil

-- See online docs for full usage info.
--
-- Quick example:
--
-- local args = {...}
-- local open_readonly, filename = false, nil -- set defaults
-- local args = {...}
-- local open_readonly, filename = false, nil -- set defaults
--
-- local positionals = argparse.processArgsGetopt(args, {
-- {'r', handler=function() open_readonly = true end},
-- {'f', 'filename', hasArg=true,
-- handler=function(optarg) filename = optarg end}
-- })
-- local positionals = argparse.processArgsGetopt(args, {
-- {'r', handler=function() open_readonly = true end},
-- {'f', 'filename', hasArg=true,
-- handler=function(optarg) filename = optarg end}
-- })
--
-- In this example, if args is {'first', '-rf', 'fname', 'second'} or,
-- equivalently, {'first', '-r', '--filename', 'myfile.txt', 'second'} (note the
-- double dash in front of the long option alias), then:
-- open_readonly == true
-- filename == 'myfile.txt'
-- positionals == {'first', 'second'}.
---@param args string[] Most commonly `{ ... }`
---@param optionActions argparse.OptionAction[]
---@return string[] positionals # Positional arguments
function processArgsGetopt(args, optionActions)
local sh_opts, long_opts = '', {}
local handlers = {}
Expand Down Expand Up @@ -120,6 +133,9 @@ function processArgsGetopt(args, optionActions)
return nonoptions
end

---@param arg_name? string
---@param fmt string
---@param ... any
local function arg_error(arg_name, fmt, ...)
local prefix = ''
if arg_name and #arg_name > 0 then
Expand All @@ -128,6 +144,11 @@ local function arg_error(arg_name, fmt, ...)
qerror(('%s'..fmt):format(prefix, ...))
end

---@nodiscard
---@param arg string
---@param arg_name? string
---@param list_length? integer
---@return string[]
function stringList(arg, arg_name, list_length)
if not list_length then list_length = 0 end
local list = arg and (arg):split(',') or {}
Expand All @@ -141,18 +162,29 @@ function stringList(arg, arg_name, list_length)
return list
end

---@nodiscard
---@param arg string
---@param arg_name? string
---@param list_length? integer
---@return integer[]
function numberList(arg, arg_name, list_length)
local strings = stringList(arg, arg_name, list_length)
---@type integer[]
local numbers = {}
for i,str in ipairs(strings) do
local num = tonumber(str)
if not num then
arg_error(arg_name, 'invalid number: "%s"', str)
end
strings[i] = num
numbers[i] = num
end
return strings
return numbers
end

---@nodiscard
---@param arg string|integer
---@param arg_name? string
---@return integer
function positiveInt(arg, arg_name)
local val = tonumber(arg)
if not val or val <= 0 or val ~= math.floor(val) then
Expand All @@ -162,6 +194,10 @@ function positiveInt(arg, arg_name)
return val
end

---@nodiscard
---@param arg string|integer
---@param arg_name? string
---@return integer
function nonnegativeInt(arg, arg_name)
local val = tonumber(arg)
if not val or val < 0 or val ~= math.floor(val) then
Expand All @@ -171,6 +207,11 @@ function nonnegativeInt(arg, arg_name)
return val
end

---@nodiscard
---@param arg string|'here'
---@param arg_name? string
---@param skip_validation? boolean
---@return df.coord
function coords(arg, arg_name, skip_validation)
if arg == 'here' then
local guidm = require('gui.dwarfmode') -- globals may not be available yet
Expand Down Expand Up @@ -198,6 +239,10 @@ end

local toBool={["true"]=true,["yes"]=true,["y"]=true,["on"]=true,["1"]=true,
["false"]=false,["no"]=false,["n"]=false,["off"]=false,["0"]=false}
---@nodiscard
---@param arg string
---@param arg_name? string
---@return boolean
function boolean(arg, arg_name)
local arg_lower = string.lower(arg)
if toBool[arg_lower] == nil then
Expand Down
22 changes: 11 additions & 11 deletions library/lua/dfhack.lua
Original file line number Diff line number Diff line change
Expand Up @@ -376,15 +376,15 @@ function printall_recurse(value, seen)
end

---@generic T
---@param table `T`
---@param table T
---@return T
function copyall(table)
local rv = {}
for k,v in pairs(table) do rv[k] = v end
return rv
end

---@param pos coord
---@param pos df.coord
---@return number? x
---@return number? y
---@return number? z
Expand All @@ -401,7 +401,7 @@ end
---@param x number
---@param y number
---@param z number
---@return coord
---@return df.coord
function xyz2pos(x,y,z)
if x then
return {x=x,y=y,z=z}
Expand All @@ -411,15 +411,15 @@ function xyz2pos(x,y,z)
end

---@nodiscard
---@param a coord
---@param b coord
---@param a df.coord
---@param b df.coord
---@return boolean
function same_xyz(a,b)
return a and b and a.x == b.x and a.y == b.y and a.z == b.z
end

---@nodiscard
---@param path coord_path
---@param path df.coord_path
---@param i number
---@return number x
---@return number y
Expand All @@ -429,7 +429,7 @@ function get_path_xyz(path,i)
end

---@nodiscard
---@param pos coord|coord2d
---@param pos df.coord|df.coord2d
---@return number? x
---@return number? y
function pos2xy(pos)
Expand All @@ -444,7 +444,7 @@ end
---@nodiscard
---@param x number
---@param y number
---@return coord2d
---@return df.coord2d
function xy2pos(x,y)
if x then
return {x=x,y=y}
Expand All @@ -454,15 +454,15 @@ function xy2pos(x,y)
end

---@nodiscard
---@param a coord|coord2d
---@param b coord|coord2d
---@param a df.coord|df.coord2d
---@param b df.coord|df.coord2d
---@return boolean
function same_xy(a,b)
return a and b and a.x == b.x and a.y == b.y
end

---@nodiscard
---@param path coord_path|coord2d_path
---@param path df.coord_path|df.coord2d_path
---@param i number
---@return integer x
---@return integer y
Expand Down
1 change: 1 addition & 0 deletions library/lua/gui/dwarfmode.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function getPanelLayout()
}
end

---@return df.coord|nil
function getCursorPos()
if g_cursor.x >= 0 then
return copyall(g_cursor)
Expand Down
10 changes: 10 additions & 0 deletions library/lua/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,9 @@ function normalizePath(path)
return path:gsub(PLATFORM_SLASH, '/'):gsub('/+', '/')
end
---@nodiscard
---@param tab table
---@return table<string, integer>
function invert(tab)
local result = {}
for k,v in pairs(tab) do
Expand All @@ -595,9 +598,16 @@ end
-- processArgs() and processArgsGetopt() have been moved to argparse.lua.
-- The 'require' statements are within the functions to avoid adding hard
-- dependencies to utils.lua (which could lead to circular dependency issues).
---@nodiscard
---@param args string[] Most commonly `{ ... }`
---@param validArgs table<string, integer> Use `utils.invert`
---@return table<string, string|string[]|nil>
function processArgs(args, validArgs)
return require('argparse').processArgs(args, validArgs)
end
---@param args string[] Most commonly `{ ... }`
---@param optionActions argparse.OptionAction[]
---@return nil
function processArgsGetopt(args, optionActions)
return require('argparse').processArgsGetopt(args, optionActions)
end
Expand Down

0 comments on commit a731f1b

Please sign in to comment.