Merge #11307 'Lua: vim.validate()'
This commit is contained in:
commit
0190de9aab
3
.luacov
3
.luacov
|
@ -14,6 +14,9 @@ return {
|
||||||
-- Relative (non-hidden) paths.
|
-- Relative (non-hidden) paths.
|
||||||
'^[^/\\.]',
|
'^[^/\\.]',
|
||||||
},
|
},
|
||||||
|
modules = {
|
||||||
|
['vim'] = 'runtime/lua/vim/shared.lua'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
-- vim: ft=lua tw=80 sw=2 et
|
-- vim: ft=lua tw=80 sw=2 et
|
||||||
|
|
|
@ -847,4 +847,63 @@ pesc({s}) *vim.pesc()*
|
||||||
See also: ~
|
See also: ~
|
||||||
https://github.com/rxi/lume
|
https://github.com/rxi/lume
|
||||||
|
|
||||||
|
validate({opt}) *vim.validate()*
|
||||||
|
Validates a parameter specification (types and values).
|
||||||
|
|
||||||
|
Usage example: >
|
||||||
|
|
||||||
|
function user.new(name, age, hobbies)
|
||||||
|
vim.validate{
|
||||||
|
name={name, 'string'},
|
||||||
|
age={age, 'number'},
|
||||||
|
hobbies={hobbies, 'table'},
|
||||||
|
}
|
||||||
|
...
|
||||||
|
end
|
||||||
|
<
|
||||||
|
|
||||||
|
Examples with explicit argument values (can be run directly): >
|
||||||
|
|
||||||
|
vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
|
||||||
|
=> NOP (success)
|
||||||
|
<
|
||||||
|
>
|
||||||
|
vim.validate{arg1={1, 'table'}}
|
||||||
|
=> error('arg1: expected table, got number')
|
||||||
|
<
|
||||||
|
>
|
||||||
|
vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
|
||||||
|
=> error('arg1: expected even number, got 3')
|
||||||
|
<
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{opt} Map of parameter names to validations. Each key is
|
||||||
|
a parameter name; each value is a tuple in one of
|
||||||
|
these forms:
|
||||||
|
1. (arg_value, type_name, optional)
|
||||||
|
• arg_value: argument value
|
||||||
|
• type_name: string type name, one of: ("table",
|
||||||
|
"t", "string", "s", "number", "n", "boolean",
|
||||||
|
"b", "function", "f", "nil", "thread",
|
||||||
|
"userdata")
|
||||||
|
• optional: (optional) boolean, if true, `nil`
|
||||||
|
is valid
|
||||||
|
|
||||||
|
2. (arg_value, fn, msg)
|
||||||
|
• arg_value: argument value
|
||||||
|
• fn: any function accepting one argument,
|
||||||
|
returns true if and only if the argument is
|
||||||
|
valid
|
||||||
|
• msg: (optional) error string if validation
|
||||||
|
fails
|
||||||
|
|
||||||
|
is_callable({f}) *vim.is_callable()*
|
||||||
|
Returns true if object `f` can be called as a function.
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{f} Any object
|
||||||
|
|
||||||
|
Return: ~
|
||||||
|
true if `f` is callable, else false
|
||||||
|
|
||||||
vim:tw=78:ts=8:ft=help:norl:
|
vim:tw=78:ts=8:ft=help:norl:
|
||||||
|
|
|
@ -47,9 +47,7 @@ end)()
|
||||||
--@param plain If `true` use `sep` literally (passed to String.find)
|
--@param plain If `true` use `sep` literally (passed to String.find)
|
||||||
--@returns Iterator over the split components
|
--@returns Iterator over the split components
|
||||||
function vim.gsplit(s, sep, plain)
|
function vim.gsplit(s, sep, plain)
|
||||||
assert(type(s) == "string", string.format("Expected string, got %s", type(s)))
|
vim.validate{s={s,'s'},sep={sep,'s'},plain={plain,'b',true}}
|
||||||
assert(type(sep) == "string", string.format("Expected string, got %s", type(sep)))
|
|
||||||
assert(type(plain) == "boolean" or type(plain) == "nil", string.format("Expected boolean or nil, got %s", type(plain)))
|
|
||||||
|
|
||||||
local start = 1
|
local start = 1
|
||||||
local done = false
|
local done = false
|
||||||
|
@ -106,7 +104,7 @@ end
|
||||||
--@param value Value to compare
|
--@param value Value to compare
|
||||||
--@returns true if `t` contains `value`
|
--@returns true if `t` contains `value`
|
||||||
function vim.tbl_contains(t, value)
|
function vim.tbl_contains(t, value)
|
||||||
assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
|
vim.validate{t={t,'t'}}
|
||||||
|
|
||||||
for _,v in ipairs(t) do
|
for _,v in ipairs(t) do
|
||||||
if v == value then
|
if v == value then
|
||||||
|
@ -176,7 +174,7 @@ end
|
||||||
--@param s String to trim
|
--@param s String to trim
|
||||||
--@returns String with whitespace removed from its beginning and end
|
--@returns String with whitespace removed from its beginning and end
|
||||||
function vim.trim(s)
|
function vim.trim(s)
|
||||||
assert(type(s) == 'string', string.format("Expected string, got %s", type(s)))
|
vim.validate{s={s,'s'}}
|
||||||
return s:match('^%s*(.*%S)') or ''
|
return s:match('^%s*(.*%S)') or ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -186,8 +184,98 @@ end
|
||||||
--@param s String to escape
|
--@param s String to escape
|
||||||
--@returns %-escaped pattern string
|
--@returns %-escaped pattern string
|
||||||
function vim.pesc(s)
|
function vim.pesc(s)
|
||||||
assert(type(s) == 'string', string.format("Expected string, got %s", type(s)))
|
vim.validate{s={s,'s'}}
|
||||||
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
|
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Validates a parameter specification (types and values).
|
||||||
|
---
|
||||||
|
--- Usage example:
|
||||||
|
--- <pre>
|
||||||
|
--- function user.new(name, age, hobbies)
|
||||||
|
--- vim.validate{
|
||||||
|
--- name={name, 'string'},
|
||||||
|
--- age={age, 'number'},
|
||||||
|
--- hobbies={hobbies, 'table'},
|
||||||
|
--- }
|
||||||
|
--- ...
|
||||||
|
--- end
|
||||||
|
--- </pre>
|
||||||
|
---
|
||||||
|
--- Examples with explicit argument values (can be run directly):
|
||||||
|
--- <pre>
|
||||||
|
--- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
|
||||||
|
--- => NOP (success)
|
||||||
|
---
|
||||||
|
--- vim.validate{arg1={1, 'table'}}
|
||||||
|
--- => error('arg1: expected table, got number')
|
||||||
|
---
|
||||||
|
--- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
|
||||||
|
--- => error('arg1: expected even number, got 3')
|
||||||
|
--- </pre>
|
||||||
|
---
|
||||||
|
--@param opt Map of parameter names to validations. Each key is a parameter
|
||||||
|
--- name; each value is a tuple in one of these forms:
|
||||||
|
--- 1. (arg_value, type_name, optional)
|
||||||
|
--- - arg_value: argument value
|
||||||
|
--- - type_name: string type name, one of: ("table", "t", "string",
|
||||||
|
--- "s", "number", "n", "boolean", "b", "function", "f", "nil",
|
||||||
|
--- "thread", "userdata")
|
||||||
|
--- - optional: (optional) boolean, if true, `nil` is valid
|
||||||
|
--- 2. (arg_value, fn, msg)
|
||||||
|
--- - arg_value: argument value
|
||||||
|
--- - fn: any function accepting one argument, returns true if and
|
||||||
|
--- only if the argument is valid
|
||||||
|
--- - msg: (optional) error string if validation fails
|
||||||
|
function vim.validate(opt) end -- luacheck: no unused
|
||||||
|
vim.validate = (function()
|
||||||
|
local type_names = {
|
||||||
|
t='table', s='string', n='number', b='boolean', f='function', c='callable',
|
||||||
|
['table']='table', ['string']='string', ['number']='number',
|
||||||
|
['boolean']='boolean', ['function']='function', ['callable']='callable',
|
||||||
|
['nil']='nil', ['thread']='thread', ['userdata']='userdata',
|
||||||
|
}
|
||||||
|
local function _type_name(t)
|
||||||
|
local tname = type_names[t]
|
||||||
|
if tname == nil then
|
||||||
|
error(string.format('invalid type name: %s', tostring(t)))
|
||||||
|
end
|
||||||
|
return tname
|
||||||
|
end
|
||||||
|
local function _is_type(val, t)
|
||||||
|
return t == 'callable' and vim.is_callable(val) or type(val) == t
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(opt)
|
||||||
|
assert(type(opt) == 'table', string.format('opt: expected table, got %s', type(opt)))
|
||||||
|
for param_name, spec in pairs(opt) do
|
||||||
|
assert(type(spec) == 'table', string.format('%s: expected table, got %s', param_name, type(spec)))
|
||||||
|
|
||||||
|
local val = spec[1] -- Argument value.
|
||||||
|
local t = spec[2] -- Type name, or callable.
|
||||||
|
local optional = (true == spec[3])
|
||||||
|
|
||||||
|
if not vim.is_callable(t) then -- Check type name.
|
||||||
|
if (not optional or val ~= nil) and not _is_type(val, _type_name(t)) then
|
||||||
|
error(string.format("%s: expected %s, got %s", param_name, _type_name(t), type(val)))
|
||||||
|
end
|
||||||
|
elseif not t(val) then -- Check user-provided validation function.
|
||||||
|
error(string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end)()
|
||||||
|
|
||||||
|
--- Returns true if object `f` can be called as a function.
|
||||||
|
---
|
||||||
|
--@param f Any object
|
||||||
|
--@return true if `f` is callable, else false
|
||||||
|
function vim.is_callable(f)
|
||||||
|
if type(f) == 'function' then return true end
|
||||||
|
local m = getmetatable(f)
|
||||||
|
if m == nil then return false end
|
||||||
|
return type(m.__call) == 'function'
|
||||||
|
end
|
||||||
|
|
||||||
return vim
|
return vim
|
||||||
|
|
|
@ -261,14 +261,14 @@ end
|
||||||
|
|
||||||
|
|
||||||
-- vim.fn.{func}(...)
|
-- vim.fn.{func}(...)
|
||||||
local function fn_index(t, key)
|
local function _fn_index(t, key)
|
||||||
local function func(...)
|
local function _fn(...)
|
||||||
return vim.call(key, ...)
|
return vim.call(key, ...)
|
||||||
end
|
end
|
||||||
t[key] = func
|
t[key] = _fn
|
||||||
return func
|
return _fn
|
||||||
end
|
end
|
||||||
local fn = setmetatable({}, {__index=fn_index})
|
local fn = setmetatable({}, {__index=_fn_index})
|
||||||
|
|
||||||
local module = {
|
local module = {
|
||||||
_update_package_paths = _update_package_paths,
|
_update_package_paths = _update_package_paths,
|
||||||
|
|
|
@ -230,12 +230,12 @@ describe('lua stdlib', function()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Validates args.
|
-- Validates args.
|
||||||
eq(true, pcall(split, 'string', 'string', nil))
|
eq(true, pcall(split, 'string', 'string'))
|
||||||
eq('Error executing lua: .../shared.lua: Expected string, got number',
|
eq('Error executing lua: .../shared.lua: s: expected string, got number',
|
||||||
pcall_err(split, 1, 'string', nil))
|
pcall_err(split, 1, 'string'))
|
||||||
eq('Error executing lua: .../shared.lua: Expected string, got number',
|
eq('Error executing lua: .../shared.lua: sep: expected string, got number',
|
||||||
pcall_err(split, 'string', 1, nil))
|
pcall_err(split, 'string', 1))
|
||||||
eq('Error executing lua: .../shared.lua: Expected boolean or nil, got number',
|
eq('Error executing lua: .../shared.lua: plain: expected boolean, got number',
|
||||||
pcall_err(split, 'string', 'string', 1))
|
pcall_err(split, 'string', 'string', 1))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -256,7 +256,7 @@ describe('lua stdlib', function()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Validates args.
|
-- Validates args.
|
||||||
eq('Error executing lua: .../shared.lua: Expected string, got number',
|
eq('Error executing lua: .../shared.lua: s: expected string, got number',
|
||||||
pcall_err(trim, 2))
|
pcall_err(trim, 2))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -301,7 +301,7 @@ describe('lua stdlib', function()
|
||||||
eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
|
eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]]))
|
||||||
|
|
||||||
-- Validates args.
|
-- Validates args.
|
||||||
eq("Error executing lua: .../shared.lua: Expected string, got number",
|
eq('Error executing lua: .../shared.lua: s: expected string, got number',
|
||||||
pcall_err(exec_lua, [[return vim.pesc(2)]]))
|
pcall_err(exec_lua, [[return vim.pesc(2)]]))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -402,4 +402,79 @@ describe('lua stdlib', function()
|
||||||
feed('<cr>')
|
feed('<cr>')
|
||||||
eq({3, NIL}, meths.get_var('yy'))
|
eq({3, NIL}, meths.get_var('yy'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('vim.validate', function()
|
||||||
|
exec_lua("vim.validate{arg1={{}, 'table' }}")
|
||||||
|
exec_lua("vim.validate{arg1={{}, 't' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 't', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={{ foo='foo' }, 't' }}")
|
||||||
|
exec_lua("vim.validate{arg1={{ 'foo' }, 't' }}")
|
||||||
|
exec_lua("vim.validate{arg1={'foo', 'string' }}")
|
||||||
|
exec_lua("vim.validate{arg1={'foo', 's' }}")
|
||||||
|
exec_lua("vim.validate{arg1={'', 's' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 's', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={1, 'number' }}")
|
||||||
|
exec_lua("vim.validate{arg1={1, 'n' }}")
|
||||||
|
exec_lua("vim.validate{arg1={0, 'n' }}")
|
||||||
|
exec_lua("vim.validate{arg1={0.1, 'n' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'n', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={true, 'boolean' }}")
|
||||||
|
exec_lua("vim.validate{arg1={true, 'b' }}")
|
||||||
|
exec_lua("vim.validate{arg1={false, 'b' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'b', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={function()end, 'function' }}")
|
||||||
|
exec_lua("vim.validate{arg1={function()end, 'f' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'f', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'nil' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'nil', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={coroutine.create(function()end), 'thread' }}")
|
||||||
|
exec_lua("vim.validate{arg1={nil, 'thread', true }}")
|
||||||
|
exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}")
|
||||||
|
exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}")
|
||||||
|
|
||||||
|
eq("Error executing lua: .../shared.lua: 1: expected table, got number",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ 1, 'x' }"))
|
||||||
|
eq("Error executing lua: .../shared.lua: invalid type name: x",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: invalid type name: 1",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: invalid type name: nil",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}"))
|
||||||
|
|
||||||
|
-- Validated parameters are required by default.
|
||||||
|
eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}"))
|
||||||
|
-- Explicitly required.
|
||||||
|
eq("Error executing lua: .../shared.lua: arg1: expected string, got nil",
|
||||||
|
pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}"))
|
||||||
|
|
||||||
|
eq("Error executing lua: .../shared.lua: arg1: expected table, got number",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: arg2: expected string, got number",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: arg2: expected string, got nil",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}"))
|
||||||
|
eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3",
|
||||||
|
pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('vim.is_callable', function()
|
||||||
|
eq(true, exec_lua("return vim.is_callable(function()end)"))
|
||||||
|
eq(true, exec_lua([[
|
||||||
|
local meta = { __call = function()end }
|
||||||
|
local function new_callable()
|
||||||
|
return setmetatable({}, meta)
|
||||||
|
end
|
||||||
|
local callable = new_callable()
|
||||||
|
return vim.is_callable(callable)
|
||||||
|
]]))
|
||||||
|
|
||||||
|
eq(false, exec_lua("return vim.is_callable(1)"))
|
||||||
|
eq(false, exec_lua("return vim.is_callable('foo')"))
|
||||||
|
eq(false, exec_lua("return vim.is_callable({})"))
|
||||||
|
end)
|
||||||
end)
|
end)
|
Loading…
Reference in New Issue