From aa09e064c48be491ab279aa4c0dc9ed2785a345a Mon Sep 17 00:00:00 2001 From: HowManySmall Date: Fri, 9 Apr 2021 16:53:15 -0600 Subject: [PATCH 1/2] Updated 2.0.0 --- .styluaignore | 1 + CHANGELOG.md | 28 ++++++ rotriever.toml | 4 +- src/enumerator/init.lua | 79 +++++++-------- src/enumerator/init.spec.lua | 183 +++++++++++++++++++++++++++++------ src/enumerator/t.lua | 9 +- 6 files changed, 234 insertions(+), 70 deletions(-) create mode 100644 .styluaignore create mode 100644 CHANGELOG.md diff --git a/.styluaignore b/.styluaignore new file mode 100644 index 0000000..e831038 --- /dev/null +++ b/.styluaignore @@ -0,0 +1 @@ +src \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..85beca9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.1.0] - 2021-04-09 +### Added +- **[BREAKING]** Added `Enumerator.cast` to the enumerator function list, replacing `castToEnumerator`. +- Added all the missing tests for the module. +- Added `EnumeratorItem.type` and `EnumeratorItem.rawType()`, which return the `Enumerator` they belong to. +- Added this CHANGELOG. + +### Changed +- Minor formatting updates. + +### Removed +- Removed `castToEnumerator`, please use `Enumerator.cast` instead. + +## [1.1.0] - 2021-03-18 +### Added +- Added `EnumeratorItem.name` and `EnumeratorItem.rawName()`, which simply return the string version of the EnumeratorItem. + +## [1.0.0] - ????-??-?? +### Added +- All the code. + +[1.0.0]: https://github.com/howmanysmall/enumerator/releases/tag/1.0.0 \ No newline at end of file diff --git a/rotriever.toml b/rotriever.toml index 024424f..d64a946 100644 --- a/rotriever.toml +++ b/rotriever.toml @@ -1,8 +1,8 @@ [package] name = "howmanysmall/enumerator" -version = "1.0.0" +version = "2.0.0" author = "howmanysmall" -dependencies_root="./src/enumerator/" +dependencies_root = "./src/enumerator/" content_root = "./src/enumerator" license = "MIT" diff --git a/src/enumerator/init.lua b/src/enumerator/init.lua index d7a4dfe..0ebd2b5 100644 --- a/src/enumerator/init.lua +++ b/src/enumerator/init.lua @@ -38,12 +38,13 @@ end --[[** Creates a new enumeration. @param [t:string] enumName The unique name of the enumeration. - @param [t:union(t:array, t:keys)] enumValues The values of the enumeration. + @param [t:union, t:keys>] enumValues The values of the enumeration. @returns [t:userdata] a new enumeration **--]] local function enumerator(enumName, enumValues) assert(enumeratorTuple(enumName, enumValues)) + local enum = newproxy(true) local internal = {} local rawValues = {} @@ -65,10 +66,34 @@ local function enumerator(enumName, enumValues) return false end + --[[** + This function will cast values to the appropriate enumerator. This behaves like a type checker from t, except it returns the value if it was found. + @param [t:any] value The value you want to cast. + @returns [t:tuple, enumerator>, t:optional>] Either returns the appropriate enumeration if found or false and an error message if it couldn't find it. + **--]] + function internal.cast(value) + if internal.isEnumValue(value) then + return value + end + + local foundEnumerator = rawValues[value] + if foundEnumerator ~= nil then + return foundEnumerator + else + return false, string.format( + INVALID_VALUE_ERROR, + tostring(value), + typeof(value), + tostring(enum) + ) + end + end + if enumValues[1] then for _, valueName in ipairs(enumValues) do assert(valueName ~= "fromRawValue", "Cannot use 'fromRawValue' as a value") assert(valueName ~= "isEnumValue", "Cannot use 'isEnumValue' as a value") + assert(valueName ~= "cast", "Cannot use 'cast' as a value") assert(internal[valueName] == nil, string.format(ALREADY_USED_NAME_ERROR, valueName, enumName)) assert(rawValues[valueName] == nil, string.format(ALREADY_USED_VALUE_ERROR, valueName, enumName)) @@ -76,17 +101,22 @@ local function enumerator(enumName, enumValues) local metatable = getmetatable(value) local valueString = string.format("%s.%s", enumName, valueName) - function metatable.__tostring() + function metatable:__tostring() return valueString end metatable.__index = lockTable({ name = valueName, + type = enum, value = valueName, rawName = function() return valueName end, + rawType = function() + return enum + end, + rawValue = function() return valueName end, @@ -99,6 +129,7 @@ local function enumerator(enumName, enumValues) for valueName, rawValue in pairs(enumValues) do assert(valueName ~= "fromRawValue", "Cannot use 'fromRawValue' as a value") assert(valueName ~= "isEnumValue", "Cannot use 'isEnumValue' as a value") + assert(valueName ~= "cast", "Cannot use 'cast' as a value") assert(internal[valueName] == nil, string.format(ALREADY_USED_NAME_ERROR, valueName, enumName)) assert(rawValues[valueName] == nil, string.format(ALREADY_USED_VALUE_ERROR, valueName, enumName)) @@ -106,17 +137,22 @@ local function enumerator(enumName, enumValues) local metatable = getmetatable(value) local valueString = string.format("%s.%s", enumName, valueName) - function metatable.__tostring() + function metatable:__tostring() return valueString end metatable.__index = lockTable({ name = valueName, + type = enum, value = rawValue, rawName = function() return valueName end, + rawType = function() + return enum + end, + rawValue = function() return rawValue end, @@ -127,47 +163,14 @@ local function enumerator(enumName, enumValues) end end - local enum = newproxy(true) local metatable = getmetatable(enum) metatable.__index = lockTable(internal, enumName) - function metatable.__tostring() + function metatable:__tostring() return enumName end return enum end ---[[** - Creates a function for the passed enumerator that will cast values to the appropriate enumerator. This behaves like a type checker from t, except it returns the value if it was found. - @param [enumerator] castFromEnumerator The enumerator you are casting from. - @returns [t:callback] castingFunction This is a function that takes a value and returns the appropriate enumeration if found or false and an error message if it couldn't find it. -**--]] -local function castEnumerator(castFromEnumerator) - return function(value) - if castFromEnumerator.isEnumValue(value) then - return value - end - - local foundEnumerator = castFromEnumerator.fromRawValue(value) - if foundEnumerator ~= nil then - return foundEnumerator - else - return false, string.format( - INVALID_VALUE_ERROR, - tostring(value), - typeof(value), - tostring(castFromEnumerator) - ) - end - end -end - -return setmetatable({ - castEnumerator = castEnumerator, - enumerator = enumerator, -}, { - __call = function(_, enumName, enumValues) - return enumerator(enumName, enumValues) - end, -}) \ No newline at end of file +return enumerator \ No newline at end of file diff --git a/src/enumerator/init.spec.lua b/src/enumerator/init.spec.lua index 6c1f443..b534c77 100644 --- a/src/enumerator/init.spec.lua +++ b/src/enumerator/init.spec.lua @@ -1,6 +1,48 @@ +type Dictionary = {[string]: Value} +type DescribeFunction = (Dictionary?) -> nil + +type Expectation = { + never: { + to: { + be: { + a: (string) -> Expectation, + an: (string) -> Expectation, + near: (number, number?) -> Expectation, + ok: () -> Expectation, + }, + + equal: (any) -> Expectation, + throw: (string?) -> Expectation, + }, + }, + + to: { + be: { + a: (string) -> Expectation, + an: (string) -> Expectation, + near: (number, number?) -> Expectation, + ok: () -> Expectation, + }, + + equal: (any) -> Expectation, + throw: (string?) -> Expectation, + }, +} + +type describe = (string, DescribeFunction) -> nil +type expect = (any) -> Expectation +type it = describe +type describeSKIP = (string) -> nil + return function() local enumerator = require(script.Parent) + -- describe = describe :: describe + -- expect = expect :: expect + -- it = it :: it + -- describeSKIP = describeSKIP :: describeSKIP + -- itSKIP = itSKIP :: it + describe("Enum creation", function() local MyEnum = enumerator("MyEnum", {"ValueOne", "ValueTwo", "ValueThree"}) @@ -9,7 +51,7 @@ return function() end) it("should return the correct name when called with tostring", function() - expect(tostring(MyEnum)).to.be.equal("MyEnum") + expect(tostring(MyEnum)).to.equal("MyEnum") end) it("should be locked", function() @@ -31,72 +73,159 @@ return function() it("should use userdata for entries", function() expect(typeof(MyEnum.ValueOne)).to.equal("userdata") + expect(typeof(MyEnum.ValueTwo)).to.equal("userdata") end) - it("should have values with correct rawValue types", function() - expect(typeof(MyEnum.ValueOne.rawValue())).to.equal("string") + it("should have values return the correct name", function() + expect(tostring(MyEnum.ValueOne)).to.equal("MyEnum.ValueOne") end) + end) - it("should have values with correct value types", function() - expect(typeof(MyEnum.ValueOne.value)).to.equal("string") + describe("Enum rawValue/value", function() + local MyEnum = enumerator("MyValueEnum", {"ValueOne", "ValueTwo"}) + + -- Checking `rawValue` type + it("should have values with correct rawValue types", function() + expect(type(MyEnum.ValueOne.rawValue())).to.equal("string") + expect(type(MyEnum.ValueTwo.rawValue())).to.equal("string") end) - it("should have values return the correct name", function() - expect(tostring(MyEnum.ValueOne)).to.equal("MyEnum.ValueOne") + -- Checking `value` type + it("should have values with correct value types", function() + expect(type(MyEnum.ValueOne.value)).to.equal("string") + expect(type(MyEnum.ValueTwo.value)).to.equal("string") end) + -- Checking `rawValue` it("should have values with correct rawValues", function() expect(MyEnum.ValueOne.rawValue()).to.equal("ValueOne") + expect(MyEnum.ValueTwo.rawValue()).to.equal("ValueTwo") end) + -- Checking `value` it("should have values with correct value", function() expect(MyEnum.ValueOne.value).to.equal("ValueOne") + expect(MyEnum.ValueTwo.value).to.equal("ValueTwo") end) + end) - it("should return the correct value from a rawValue", function() - expect(MyEnum.fromRawValue("ValueOne")).to.equal(MyEnum.ValueOne) + describe("Enum rawType/type", function() + local MyEnum = enumerator("MyTypeEnum", {"ValueOne", "ValueTwo"}) + + -- Checking `rawType` type + it("should have values with correct rawType types", function() + expect(typeof(MyEnum.ValueOne.rawType())).to.equal("userdata") + expect(typeof(MyEnum.ValueTwo.rawType())).to.equal("userdata") end) - it("should detect whether a value is an enum value", function() - expect(MyEnum.isEnumValue(MyEnum.ValueOne)).to.equal(true) - expect(MyEnum.isEnumValue("ValueOne")).to.equal(false) + -- Checking `type` type + it("should have values with correct type types", function() + expect(typeof(MyEnum.ValueOne.type)).to.equal("userdata") + expect(typeof(MyEnum.ValueTwo.type)).to.equal("userdata") + end) + + -- Checking `rawType` + it("should have values with correct rawType types", function() + expect(MyEnum.ValueOne.rawType()).to.equal(MyEnum) + expect(MyEnum.ValueTwo.rawType()).to.equal(MyEnum) + end) + + -- Checking `type` + it("should have values with correct type types", function() + expect(MyEnum.ValueOne.type).to.equal(MyEnum) + expect(MyEnum.ValueTwo.type).to.equal(MyEnum) end) end) - it("should error when creating an enum with a non-string name", function() - expect(function() - enumerator(1, {"ValueOne"}) - end).to.throw() + describe("Enum rawName/name", function() + local MyEnum = enumerator("MyNameEnum", {"ValueOne", "ValueTwo"}) + + -- Checking `rawName` type + it("should have values with correct rawName types", function() + expect(type(MyEnum.ValueOne.rawName())).to.equal("string") + expect(type(MyEnum.ValueTwo.rawName())).to.equal("string") + end) + + -- Checking `name` type + it("should have values with correct name types", function() + expect(type(MyEnum.ValueOne.name)).to.equal("string") + expect(type(MyEnum.ValueTwo.name)).to.equal("string") + end) + + -- Checking `rawName` + it("should have values with correct rawName types", function() + expect(MyEnum.ValueOne.rawName()).to.equal("ValueOne") + expect(MyEnum.ValueTwo.rawName()).to.equal("ValueTwo") + end) + + -- Checking `name` + it("should have values with correct name types", function() + expect(MyEnum.ValueOne.name).to.equal("ValueOne") + expect(MyEnum.ValueTwo.name).to.equal("ValueTwo") + end) end) - it("should error when creating an enum with duplicate values", function() - expect(function() - enumerator("MyCoolEnum", {"ValueOne", "ValueOne"}) - end).to.throw() + describe("Enum.fromRawValue", function() + local MyEnum = enumerator("MyRawValueEnum", {"ValueOne", "ValueTwo"}) + + it("should return the correct value from a rawValue", function() + expect(MyEnum.fromRawValue("ValueOne")).to.equal(MyEnum.ValueOne) + expect(MyEnum.fromRawValue("ValueTwo")).to.equal(MyEnum.ValueTwo) + end) end) - describe("castEnumerator", function() - local MyAwesomeEnum = enumerator("MyAwesomeEnum", {"ValueOne"}) - local castToMyAwesomeEnum = enumerator.castEnumerator(MyAwesomeEnum) + describe("Enum.isEnumValue", function() + local MyEnum = enumerator("MyBooleanEnum", {"ValueOne", "ValueTwo"}) + + it("should detect whether a value is an enum value", function() + expect(MyEnum.isEnumValue(MyEnum.ValueOne)).to.equal(true) + expect(MyEnum.isEnumValue("ValueOne")).to.equal(false) + + expect(MyEnum.isEnumValue(MyEnum.ValueTwo)).to.equal(true) + expect(MyEnum.isEnumValue("ValueTwo")).to.equal(false) + end) + end) + describe("Enum.cast", function() + local MyCastEnum = enumerator("MyCastEnum", {"ValueOne", "ValueTwo"}) it("should return the enumeration if found from a value", function() - expect(castToMyAwesomeEnum("ValueOne")).to.equal(MyAwesomeEnum.ValueOne) + expect(MyCastEnum.cast("ValueOne")).to.equal(MyCastEnum.ValueOne) + expect(MyCastEnum.cast("ValueTwo")).to.equal(MyCastEnum.ValueTwo) end) it("should return the enumeration if found from an enumeration", function() - expect(castToMyAwesomeEnum(MyAwesomeEnum.ValueOne)).to.equal(MyAwesomeEnum.ValueOne) + expect(MyCastEnum.cast(MyCastEnum.ValueOne)).to.equal(MyCastEnum.ValueOne) + expect(MyCastEnum.cast(MyCastEnum.ValueTwo)).to.equal(MyCastEnum.ValueTwo) end) it("should return false and an error message if not found", function() - local value, errorMessage = castToMyAwesomeEnum("ValueTwo") + local value, errorMessage = MyCastEnum.cast("ValueThree") expect(value).to.equal(false) expect(errorMessage).to.be.a("string") end) it("should error if put into an assert if not found", function() expect(function() - assert(castToMyAwesomeEnum("ValueTwo")) + assert(MyCastEnum.cast("ValueThree")) end).to.throw() end) end) + + it("should error when creating an enum with a non-string name", function() + expect(function() + enumerator(1, {"ValueOne"}) + end).to.throw() + end) + + it("should error when creating an enum with duplicate values", function() + expect(function() + enumerator("MyCoolEnum", {"ValueOne", "ValueOne"}) + end).to.throw() + end) + + it("should error when creating an enum with disallowed values", function() + expect(function() + enumerator("MyUncoolEnum", {"cast", "Cast"}) + end).to.throw() + end) end \ No newline at end of file diff --git a/src/enumerator/t.lua b/src/enumerator/t.lua index 8cb560b..f5984a3 100644 --- a/src/enumerator/t.lua +++ b/src/enumerator/t.lua @@ -600,7 +600,8 @@ t.numberNegative = t.numberMaxExclusive(0) @returns A function that will return true iff the condition is passed **--]] function t.numberConstrained(min, max) - assert(t.number(min) and t.number(max)) + assert(t.number(min)) + assert(t.number(max)) local minCheck = t.numberMin(min) local maxCheck = t.numberMax(max) @@ -628,7 +629,8 @@ end @returns A function that will return true iff the condition is passed **--]] function t.numberConstrainedExclusive(min, max) - assert(t.number(min) and t.number(max)) + assert(t.number(min)) + assert(t.number(max)) local minCheck = t.numberMinExclusive(min) local maxCheck = t.numberMaxExclusive(max) @@ -776,7 +778,8 @@ end @returns A function that will return true iff the condition is passed **--]] function t.map(keyCheck, valueCheck) - assert(t.callback(keyCheck), t.callback(valueCheck)) + assert(t.callback(keyCheck)) + assert(t.callback(valueCheck)) local keyChecker = t.keys(keyCheck) local valueChecker = t.values(valueCheck) From 12ea28d9a53f0f6ec1c2206e1b424a7a86c15344 Mon Sep 17 00:00:00 2001 From: HowManySmall Date: Fri, 9 Apr 2021 16:57:02 -0600 Subject: [PATCH 2/2] Updated settings. --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b033023..f272372 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "Lua.runtime.version": "Luau" + "Lua.runtime.version": "Luau", + "rojo.buildOutputPath": "enumerator" } \ No newline at end of file