Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stevedonovan/Penlight.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThijs Schreijer <thijs@thijsschreijer.nl>2022-01-13 21:06:17 +0300
committerThijs Schreijer <thijs@thijsschreijer.nl>2022-02-13 11:42:34 +0300
commite738aa8b6c87ed8ed89128ad31849f216c575e0d (patch)
tree0c36fff551a71e3f12622b8f619d3d21369b314a
parentc401f83c52461e0cf2e38dfca003f53cef10ce20 (diff)
feat(utils) enum to accept hash tables as well
-rw-r--r--CHANGELOG.md5
-rw-r--r--lua/pl/utils.lua105
-rw-r--r--spec/utils-enum_spec.lua128
3 files changed, 199 insertions, 39 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70fd196..b3385f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,13 @@ deprecation policy.
see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions
## 1.13.0 (unreleased)
+<<<<<<< HEAD
- fix: `compat.warn` raised write guard warning in OpenResty
[#414](https://github.com/lunarmodules/Penlight/pull/414)
+=======
+ - feat: `utils.enum` now accepts hash tables, to enable better error handling
+
+>>>>>>> b38c390 (feat(utils) enum to accept hash tables as well)
## 1.12.0 (2022-Jan-10)
- deprecate: module `pl.text` the contents have moved to `pl.stringx` (removal later)
diff --git a/lua/pl/utils.lua b/lua/pl/utils.lua
index 1cb8a64..d56a6ef 100644
--- a/lua/pl/utils.lua
+++ b/lua/pl/utils.lua
@@ -249,17 +249,20 @@ function utils.assert_arg (n,val,tp,verify,msg,lev)
return val
end
---- creates an Enum table.
+--- creates an Enum or constants lookup table for improved error handling.
-- This helps prevent magic strings in code by throwing errors for accessing
--- non-existing values.
+-- non-existing values, and/or converting strings/identifiers to other values.
--
--- Calling on the object does the same, but returns a soft error; `nil + err`.
+-- Calling on the object does the same, but returns a soft error; `nil + err`, if
+-- the call is succesful (the key exists), it will return the value.
--
--- The values are equal to the keys. The enum object is
--- read-only.
--- @param ... strings that make up the enumeration.
--- @return Enum object
--- @usage -- accessing at runtime
+-- When calling with varargs or an array the values will be equal to the keys.
+-- The enum object is read-only.
+-- @tparam table|vararg ... the input for the Enum. If varargs or an array then the
+-- values in the Enum will be equal to the names (must be strings), if a hash-table
+-- then values remain (any type), and the keys must be strings.
+-- @return Enum object (read-only table/object)
+-- @usage -- Enum access at runtime
-- local obj = {}
-- obj.MOVEMENT = utils.enum("FORWARD", "REVERSE", "LEFT", "RIGHT")
--
@@ -271,21 +274,83 @@ end
-- -- "'REVERES' is not a valid value (expected one of: 'FORWARD', 'REVERSE', 'LEFT', 'RIGHT')"
--
-- end
--- @usage -- validating user-input
--- local parameter = "...some user provided option..."
--- local ok, err = obj.MOVEMENT(parameter) -- calling on the object
--- if not ok then
--- print("bad 'parameter', " .. err)
+-- @usage -- standardized error codes
+-- local obj = {
+-- ERR = utils.enum {
+-- NOT_FOUND = "the item was not found",
+-- OUT_OF_BOUNDS = "the index is outside the allowed range"
+-- },
+--
+-- some_method = function(self)
+-- return self.ERR.OUT_OF_BOUNDS
+-- end,
+-- }
+--
+-- local result, err = obj:some_method()
+-- if not result then
+-- if err == obj.ERR.NOT_FOUND then
+-- -- check on error code, not magic strings
+--
+-- else
+-- -- return the error description, contained in the constant
+-- return nil, "error: "..err -- "error: the index is outside the allowed range"
+-- end
+-- end
+-- @usage -- validating/converting user-input
+-- local color = "purple"
+-- local ansi_colors = utils.enum {
+-- black = 30,
+-- red = 31,
+-- green = 32,
+-- }
+-- local color_code, err = ansi_colors(color) -- calling on the object, returns the value from the enum
+-- if not color_code then
+-- print("bad 'color', " .. err)
+-- -- "bad 'color', 'purple' is not a valid value (expected one of: 'black', 'red', 'green')"
-- os.exit(1)
-- end
function utils.enum(...)
- local lst = utils.pack(...)
- utils.assert_arg(1, lst[1], "string") -- at least 1 string
-
+ local first = select(1, ...)
local enum = {}
- for i, value in ipairs(lst) do
- utils.assert_arg(i, value, "string")
- enum[value] = value
+ local lst
+
+ if type(first) ~= "table" then
+ -- vararg with strings
+ lst = utils.pack(...)
+ for i, value in ipairs(lst) do
+ utils.assert_arg(i, value, "string")
+ enum[value] = value
+ end
+
+ else
+ -- table/array with values
+ utils.assert_arg(1, first, "table")
+ lst = {}
+ -- first add array part
+ for i, value in ipairs(first) do
+ if type(value) ~= "string" then
+ error(("expected 'string' but got '%s' at index %d"):format(type(value), i), 2)
+ end
+ lst[i] = value
+ enum[value] = value
+ end
+ -- add key-ed part
+ for key, value in pairs(first) do
+ if not lst[key] then
+ if type(key) ~= "string" then
+ error(("expected key to be 'string' but got '%s'"):format(type(key)), 2)
+ end
+ if enum[key] then
+ error(("duplicate entry in array and hash part: '%s'"):format(key), 2)
+ end
+ enum[key] = value
+ lst[#lst+1] = key
+ end
+ end
+ end
+
+ if not lst[1] then
+ error("expected at least 1 entry", 2)
end
local valid = "(expected one of: '" .. concat(lst, "', '") .. "')"
@@ -299,7 +364,7 @@ function utils.enum(...)
__call = function(self, key)
if type(key) == "string" then
local v = rawget(self, key)
- if v then
+ if v ~= nil then
return v
end
end
diff --git a/spec/utils-enum_spec.lua b/spec/utils-enum_spec.lua
index 752acb9..21fde62 100644
--- a/spec/utils-enum_spec.lua
+++ b/spec/utils-enum_spec.lua
@@ -5,45 +5,130 @@ describe("pl.utils", function ()
before_each(function()
enum = require("pl.utils").enum
- t = enum("ONE", "two", "THREE")
end)
- it("holds enumerated values", function()
- assert.equal("ONE", t.ONE)
- assert.equal("two", t.two)
- assert.equal("THREE", t.THREE)
- end)
+ describe("creating", function()
+ it("accepts a vararg", function()
+ t = enum("ONE", "two", "THREE")
+ assert.same({
+ ONE = "ONE",
+ two = "two",
+ THREE = "THREE",
+ }, t)
+ end)
- describe("accessing", function()
+ it("vararg entries must be strings", function()
+ assert.has.error(function()
+ t = enum("hello", true, "world")
+ end, "argument 2 expected a 'string', got a 'boolean'")
+ end)
- it("errors on unknown values", function()
+
+ it("vararg requires at least 1 entry", function()
assert.has.error(function()
- print(t.four)
- end, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')")
+ t = enum()
+ end, "expected at least 1 entry")
end)
- it("errors on setting new keys", function()
+ it("accepts an array", function()
+ t = enum { "ONE", "two", "THREE" }
+ assert.same({
+ ONE = "ONE",
+ two = "two",
+ THREE = "THREE",
+ }, t)
+ end)
+
+
+ it("array entries must be strings", function()
assert.has.error(function()
- t.four = "four"
- end, "the Enum object is read-only")
+ t = enum { "ONE", 999, "THREE" }
+ end, "expected 'string' but got 'number' at index 2")
end)
- it("entries must be strings", function()
+ it("array requires at least 1 entry", function()
assert.has.error(function()
- t = enum("hello", true, "world")
- end, "argument 2 expected a 'string', got a 'boolean'")
+ t = enum {}
+ end, "expected at least 1 entry")
+ end)
+
+
+ it("accepts a hash-table", function()
+ t = enum {
+ FILE_NOT_FOUND = "The file was not found in the filesystem",
+ FILE_READ_ONLY = "The file is read-only",
+ }
+ assert.same({
+ FILE_NOT_FOUND = "The file was not found in the filesystem",
+ FILE_READ_ONLY = "The file is read-only",
+ }, t)
end)
- it("requires at least 1 entry", function()
+ it("hash-table keys must be strings", function()
assert.has.error(function()
- t = enum()
- end, "argument 1 expected a 'string', got a 'nil'")
+ t = enum { [{}] = "ONE" }
+ end, "expected key to be 'string' but got 'table'")
+ end)
+
+
+ it("hash-table requires at least 1 entry", function()
+ assert.has.error(function()
+ t = enum {}
+ end, "expected at least 1 entry")
+ end)
+
+
+ it("accepts a combined array/hash-table", function()
+ t = enum {
+ "BAD_FD",
+ FILE_NOT_FOUND = "The file was not found in the filesystem",
+ FILE_READ_ONLY = "The file is read-only",
+ }
+ assert.same({
+ BAD_FD = "BAD_FD",
+ FILE_NOT_FOUND = "The file was not found in the filesystem",
+ FILE_READ_ONLY = "The file is read-only",
+ }, t)
+ end)
+
+
+ it("keys must be unique with combined array/has-table", function()
+ assert.has.error(function()
+ t = enum {
+ "FILE_NOT_FOUND",
+ FILE_NOT_FOUND = "The file was not found in the filesystem",
+ }
+ end, "duplicate entry in array and hash part: 'FILE_NOT_FOUND'")
+ end)
+
+ end)
+
+
+
+ describe("accessing", function()
+
+ before_each(function()
+ t = enum("ONE", "two", "THREE")
+ end)
+
+
+ it("errors on unknown values", function()
+ assert.has.error(function()
+ print(t.four)
+ end, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')")
+ end)
+
+
+ it("errors on setting new keys", function()
+ assert.has.error(function()
+ t.four = "four"
+ end, "the Enum object is read-only")
end)
@@ -60,6 +145,11 @@ describe("pl.utils", function ()
describe("calling", function()
+ before_each(function()
+ t = enum("ONE", "two", "THREE")
+ end)
+
+
it("returns error on unknown values", function()
local ok, err = t("four")
assert.equal(err, "'four' is not a valid value (expected one of: 'ONE', 'two', 'THREE')")