Skip to content

Commit afd88a7

Browse files
committed
feat: add is module and typings
1 parent 0f3cea5 commit afd88a7

5 files changed

Lines changed: 408 additions & 1 deletion

File tree

spec/is_spec.lua

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---@diagnostic disable: undefined-field, param-type-mismatch, need-check-nil
2+
3+
local is = require("mods.is")
4+
local fmt = string.format
5+
6+
describe("mods.is", function()
7+
local f = function() end
8+
local co = coroutine.create(f)
9+
local ct = setmetatable({}, { __call = f })
10+
local nct = setmetatable({}, { __call = true })
11+
12+
it("is is callable via __call", function()
13+
assert.is_callable(is)
14+
end)
15+
16+
-- stylua: ignore
17+
local tests = {
18+
-----type----|----ok-----|-not-ok---
19+
{ "Boolean" , false , 123 },
20+
{ "Boolean" , true , nil },
21+
{ "Callable" , ct , nct },
22+
{ "Callable" , f , {} },
23+
{ "False" , false , true },
24+
{ "Falsy" , false , true },
25+
{ "Falsy" , nil , 123 },
26+
{ "Function" , f , "abc" },
27+
{ "Integer" , 123 , 13.4 },
28+
{ "Integer" , 123 , nil },
29+
{ "Nil" , nil , 123 },
30+
{ "Number" , 123 , "123" },
31+
{ "String" , "abc" , true },
32+
{ "Table" , {} , false },
33+
{ "Thread" , co , f },
34+
{ "True" , true , false },
35+
{ "Truthy" , 123 , nil },
36+
{ "Truthy" , true , false },
37+
{ "Userdata" , io.stdout , {} },
38+
}
39+
40+
for i = 1, #tests do
41+
local tp, v1, v2 = unpack(tests[i], 1, 3)
42+
43+
it(fmt("exposes is.%s", tp), function()
44+
assert.is_function(is[tp])
45+
end)
46+
47+
it(fmt("exposes is.%s", tp:lower()), function()
48+
assert.is_function(is[tp:lower()])
49+
end)
50+
51+
it(fmt("is.%s returns true for %s", tp, pretty(v1)), function()
52+
assert.is_true(is[tp](v1))
53+
end)
54+
55+
it(fmt("is.%s returns false for %s", tp, pretty(v2)), function()
56+
assert.is_false(is[tp](v2))
57+
end)
58+
59+
it(fmt("is(%q, %s) returns true", tp, pretty(v1)), function()
60+
assert.is_true(is(v1, tp))
61+
end)
62+
63+
it(fmt("is(%q, %s) returns false", tp, pretty(v2)), function()
64+
assert.is_false(is(v2, tp))
65+
end)
66+
end
67+
end)

src/mods/init.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
local mods = {}
2-
for _, name in ipairs({ "List", "Set", "str", "stringcase", "tbl" }) do
2+
for _, name in ipairs({
3+
"is",
4+
"List",
5+
"Set",
6+
"str",
7+
"stringcase",
8+
"tbl",
9+
}) do
310
mods[name] = "mods." .. name
411
end
512

src/mods/is.lua

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
local type = type
2+
local getmt = getmetatable
3+
local gsub = string.gsub
4+
local upper = string.upper
5+
6+
local lfs
7+
8+
local function require_lfs()
9+
if lfs then
10+
return lfs
11+
end
12+
local ok, mod = pcall(require, "lfs")
13+
if not ok then
14+
error("lfs is required for path-type checks", 2)
15+
end
16+
lfs = mod
17+
return lfs
18+
end
19+
20+
local function attrs(...)
21+
local mod = require_lfs()
22+
attrs = mod.attributes
23+
return attrs(...)
24+
end
25+
26+
local function symlinkattrs(...)
27+
local mod = require_lfs()
28+
symlinkattrs = mod.symlinkattributes
29+
return symlinkattrs(...)
30+
end
31+
32+
---@type mods.is
33+
local M = {}
34+
35+
function M.callable(v)
36+
if type(v) == "function" then
37+
return true
38+
end
39+
local mt = getmt(v)
40+
if mt and type(mt.__call) == "function" then
41+
return true
42+
end
43+
return false
44+
end
45+
46+
function M.device(v)
47+
if type(v) ~= "string" then
48+
return false
49+
end
50+
local mode = attrs(v, "mode")
51+
return mode == "char device" or mode == "block device"
52+
end
53+
54+
55+
-- stylua: ignore start
56+
function M.boolean(v) return type(v) == "boolean" end
57+
function M.Function(v) return type(v) == "function" end
58+
function M.Nil(v) return type(v) == "nil" end
59+
function M.number(v) return type(v) == "number" end
60+
function M.string(v) return type(v) == "string" end
61+
function M.table(v) return type(v) == "table" end
62+
function M.thread(v) return type(v) == "thread" end
63+
function M.userdata(v) return type(v) == "userdata" end
64+
65+
function M.False(v) return v == false end
66+
function M.falsy(v) return not v and true or false end
67+
function M.integer(v) return type(v) == "number" and v % 1 == 0 end
68+
function M.True(v) return v == true end
69+
function M.truthy(v) return v and true or false end
70+
71+
function M.block(v) return type(v) == "string" and attrs(v, "mode") == "block device" end
72+
function M.char(v) return type(v) == "string" and attrs(v, "mode") == "char device" end
73+
function M.dir(v) return type(v) == "string" and attrs(v, "mode") == "directory" end
74+
function M.fifo(v) return type(v) == "string" and attrs(v, "mode") == "named pipe" end
75+
function M.file(v) return type(v) == "string" and attrs(v, "mode") == "file" end
76+
function M.link(v) return type(v) == "string" and symlinkattrs(v, "mode") == "link" end
77+
function M.socket(v) return type(v) == "string" and attrs(v, "mode") == "socket" end
78+
-- stylua: ignore end
79+
80+
local function capitalize(s)
81+
return gsub(s, "^%l", upper)
82+
end
83+
84+
local aliases = {}
85+
for k in pairs(M) do
86+
aliases[#aliases + 1] = k
87+
end
88+
89+
for _, v in ipairs(aliases) do
90+
M[v:lower()] = M[v]
91+
M[capitalize(v)] = M[v]
92+
end
93+
94+
return setmetatable(M, {
95+
__call = function(_, v, tp)
96+
local fn = M[tp]
97+
if fn then
98+
return fn(v)
99+
elseif type(v) == tp then
100+
return true
101+
end
102+
return false
103+
end,
104+
})

0 commit comments

Comments
 (0)