Skip to content

Commit d6355d3

Browse files
committed
improve pretty printing for values/specs, add ability to disable assertions
1 parent c775f40 commit d6355d3

File tree

2 files changed

+75
-24
lines changed

2 files changed

+75
-24
lines changed

spec.lua

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,56 @@
2222

2323
local M = {}
2424

25+
---Enable/Disable spec.lua assertions
26+
M.assertions_enabled = true
27+
2528
---Internal functions to be overwritten when desired
2629
---@type table<string, function>
27-
M.fn = {}
30+
M.fn = {
31+
---Assert function
32+
---@type fun(assertion: boolean, message: string)
33+
assert = function(assertion, message)
34+
assert(assertion, string.format("%s\n%s", message, debug.traceback()))
35+
end,
2836

29-
---Assert function
30-
---@type fun(assertion: boolean, message: string)
31-
M.fn.assert = function(assertion, message)
32-
assert(assertion, message)
33-
end
37+
---Print error function
38+
---@type fun(reason: string)
39+
error = function(reason)
40+
error(string.format("%s\n%s", reason, debug.traceback()))
41+
end,
3442

35-
---Print error function
36-
---@type fun(reason: string)
37-
M.fn.error = function(reason)
38-
error(reason)
39-
end
43+
---Converts table to simple string representation
44+
---@param t table
45+
---@param indent integer|nil
46+
---@return string
47+
table_to_string = function(t, indent)
48+
indent = indent or 0
49+
local str = string.rep(" ", indent) .. "{\n"
50+
for k, v in pairs(t) do
51+
local key_str = type(k) == "string" and '"' .. k .. '"' or "[" .. tostring(k) .. "]"
52+
if type(v) == "table" then
53+
str = str .. string.rep(" ", indent + 1) .. key_str .. " = " .. M.fn.table_to_string(v, indent + 1)
54+
else
55+
str = str .. string.rep(" ", indent + 1) .. key_str .. " = " .. tostring(v) .. ",\n"
56+
end
57+
end
58+
str = str .. string.rep(" ", indent) .. "}"
59+
return str
60+
end,
4061

41-
---Pretty printer function
42-
---@type fun(object: any): string
43-
M.fn.pretty_print = function(object)
44-
return tostring(object)
45-
end
62+
---Pretty printer function
63+
---@type fun(object: any): string
64+
pretty_print = function(object)
65+
if type(object) == "function" then
66+
local info = debug.getinfo(object, "nSl")
67+
return string.format("fn %s(...) defined at %s:%d", info.name or "anonymous", info.source, info.linedefined)
68+
elseif type(object) == "table" then
69+
return M.fn.table_to_string(object)
70+
end
71+
72+
return tostring(object)
73+
end,
74+
}
4675

4776
-- predicates
4877

@@ -174,7 +203,18 @@ function M.keys(spec_map)
174203
for key, sub_spec in pairs(spec_map) do
175204
local val = value[key]
176205

177-
if val == nil or not (type(sub_spec) == "function" and sub_spec(val)) then
206+
if type(sub_spec) ~= "function" then
207+
M.fn.error(
208+
string.format(
209+
"spec.lua: Spec '%s' for key '%s' must be a function (e.g. spec.keys for tables)",
210+
M.fn.pretty_print(sub_spec),
211+
M.fn.pretty_print(key)
212+
)
213+
)
214+
return false
215+
end
216+
217+
if not sub_spec(val) then
178218
return false
179219
end
180220
end
@@ -217,14 +257,16 @@ end
217257
---@param value T
218258
---@return T
219259
function M.assert(spec, value)
220-
M.fn.assert(
221-
M.valid(spec, value),
222-
string.format(
223-
"spec.lua: Assertion failed because '%s' doesn't conform to spec '%s'",
224-
M.fn.pretty_print(value),
225-
M.fn.pretty_print(spec)
260+
if M.assertions_enabled then
261+
M.fn.assert(
262+
M.valid(spec, value),
263+
string.format(
264+
"spec.lua: Assertion failed because '%s' doesn't conform to spec '%s'",
265+
M.fn.pretty_print(value),
266+
M.fn.pretty_print(spec)
267+
)
226268
)
227-
)
269+
end
228270
return value
229271
end
230272

spec_test.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,15 @@ describe("spec.lua", function()
105105
assert.False(user_spec "not a table")
106106
assert.False(user_spec(nil))
107107
assert.False(user_spec {})
108+
109+
local num_spec = spec.keys {
110+
a = spec.number,
111+
b = spec.optional(spec.number),
112+
}
113+
114+
assert.True(num_spec { a = 1337 })
115+
assert.True(num_spec { a = 1337, b = 42 })
116+
assert.False(num_spec { a = 1337, b = "test" })
108117
end)
109118

110119
it("spec.keys with extra fields", function()

0 commit comments

Comments
 (0)