Skip to content

Commit d3040d7

Browse files
committed
feat: Add SimpleEnum package and interface, to simplify enum usage across Nevermore
1 parent d8aadbf commit d3040d7

File tree

35 files changed

+388
-153
lines changed

35 files changed

+388
-153
lines changed

pnpm-lock.yaml

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/clienttranslator/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
],
3030
"dependencies": {
3131
"@quenty/blend": "workspace:*",
32+
"@quenty/enums": "workspace:*",
3233
"@quenty/instanceutils": "workspace:*",
3334
"@quenty/loader": "workspace:*",
3435
"@quenty/maid": "workspace:*",

src/clienttranslator/src/Shared/Numbers/RoundingBehaviourTypes.lua

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,12 @@
55

66
local require = require(script.Parent.loader).load(script)
77

8-
local Table = require("Table")
8+
local SimpleEnum = require("SimpleEnum")
99

1010
export type RoundingBehaviourType = "roundToClosest" | "truncate" | "None"
1111

12-
export type RoundingBehaviourTypeMap = {
13-
ROUND_TO_CLOSEST: "roundToClosest",
14-
TRUNCATE: "truncate",
15-
NONE: "none",
16-
}
17-
18-
return Table.readonly({
19-
ROUND_TO_CLOSEST = "roundToClosest",
20-
TRUNCATE = "truncate",
21-
NONE = "none",
22-
} :: RoundingBehaviourTypeMap)
12+
return SimpleEnum.new({
13+
ROUND_TO_CLOSEST = "roundToClosest" :: "roundToClosest",
14+
TRUNCATE = "truncate" :: "truncate",
15+
NONE = "none" :: "none",
16+
})

src/enums/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## Enums
2+
3+
<div align="center">
4+
<a href="http://quenty.github.io/NevermoreEngine/">
5+
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/docs.yml/badge.svg" alt="Documentation status" />
6+
</a>
7+
<a href="https://discord.gg/mhtGUS8">
8+
<img src="https://img.shields.io/discord/385151591524597761?color=5865F2&label=discord&logo=discord&logoColor=white" alt="Discord" />
9+
</a>
10+
<a href="https://github.com/Quenty/NevermoreEngine/actions">
11+
<img src="https://github.com/Quenty/NevermoreEngine/actions/workflows/build.yml/badge.svg" alt="Build and release status" />
12+
</a>
13+
</div>
14+
15+
A very simple enum implementation that makes typechecking boilerplate simpler.
16+
17+
<div align="center"><a href="https://quenty.github.io/NevermoreEngine/api/SimpleEnum">View docs →</a></div>
18+
19+
## Installation
20+
21+
```
22+
npm install @quenty/enums --save
23+
```

src/enums/default.project.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "enums",
3+
"globIgnorePaths": [
4+
"**/.package-lock.json",
5+
"**/.pnpm",
6+
"**/.pnpm-workspace-state-v1.json",
7+
"**/.modules.yaml",
8+
"**/.ignored",
9+
"**/.ignored_*"
10+
],
11+
"tree": {
12+
"$path": "src"
13+
}
14+
}

src/enums/package.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@quenty/enums",
3+
"version": "1.0.0",
4+
"description": "Custom Luau enum implementation",
5+
"keywords": [
6+
"Roblox",
7+
"Nevermore",
8+
"Lua",
9+
"enums"
10+
],
11+
"bugs": {
12+
"url": "https://github.com/Quenty/NevermoreEngine/issues"
13+
},
14+
"repository": {
15+
"type": "git",
16+
"url": "https://github.com/Quenty/NevermoreEngine.git",
17+
"directory": "src/enums/"
18+
},
19+
"funding": {
20+
"type": "patreon",
21+
"url": "https://www.patreon.com/quenty"
22+
},
23+
"license": "MIT",
24+
"scripts": {
25+
"preinstall": "npx only-allow pnpm"
26+
},
27+
"contributors": [
28+
"Quenty"
29+
],
30+
"dependencies": {
31+
"@quenty/loader": "workspace:*",
32+
"@quentystudios/t": "^3.0.0"
33+
},
34+
"publishConfig": {
35+
"access": "public"
36+
}
37+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
--!strict
2+
--[=[
3+
A very simple enum implementation that makes typechecking boilerplate simpler.
4+
5+
```lua
6+
local require = require(script.Parent.loader).load(script)
7+
8+
local SimpleEnum = require("SimpleEnum")
9+
10+
export type MyEnumType = "none" | "always"
11+
12+
return SimpleEnum.new({
13+
NONE = "none" :: "none",
14+
ALWAYS = "always" :: "always",
15+
})
16+
```
17+
18+
@class SimpleEnum
19+
]=]
20+
21+
local require = require(script.Parent.loader).load(script)
22+
23+
local t = require("t")
24+
25+
local SimpleEnum = {}
26+
SimpleEnum.ClassName = "SimpleEnum"
27+
28+
export type EnumValue = any
29+
30+
export type SimpleEnum<EnumMembers> = EnumMembers & {
31+
GetKeys: (self: SimpleEnum<EnumMembers>) -> { string },
32+
GetValues: (self: SimpleEnum<EnumMembers>) -> { EnumValue },
33+
GetMap: (self: SimpleEnum<EnumMembers>) -> EnumMembers,
34+
IsValue: (self: SimpleEnum<EnumMembers>, value: any) -> (boolean, string?),
35+
GetInterface: (self: SimpleEnum<EnumMembers>) -> (value: any) -> (boolean, string?),
36+
}
37+
38+
export type PrivateSimpleEnum<EnumMembers> =
39+
typeof(setmetatable(
40+
{} :: {
41+
_members: { [string]: EnumValue },
42+
},
43+
{} :: typeof({ __index = SimpleEnum })
44+
))
45+
& SimpleEnum<EnumMembers>
46+
47+
--[=[
48+
Creates a new SimpleEnum. This is indexable like a normal table, BUT it is also
49+
type-checkable via `IsValue` and `GetInterface` and more.
50+
]=]
51+
function SimpleEnum.new<EnumMembers>(members: EnumMembers): SimpleEnum<EnumMembers>
52+
assert(type(members) == "table", "Members must be a table")
53+
54+
local self: SimpleEnum<EnumMembers> = setmetatable({
55+
_members = table.freeze(members),
56+
}, SimpleEnum) :: any
57+
58+
for key, value in pairs(members :: any) do
59+
rawset(self, key, value)
60+
end
61+
62+
return self
63+
end
64+
65+
--[=[
66+
Returns the list of enum keys.
67+
]=]
68+
function SimpleEnum.GetKeys<EnumMembers>(self: SimpleEnum<EnumMembers>): { string }
69+
local members: any = rawget(self, "_members")
70+
local keys = {}
71+
for key, _ in pairs(members) do
72+
table.insert(keys, key)
73+
end
74+
return keys
75+
end
76+
77+
--[=[
78+
Returns the list of enum values.
79+
]=]
80+
function SimpleEnum.GetValues<EnumMembers>(self: SimpleEnum<EnumMembers>): { EnumValue }
81+
local members: any = rawget(self, "_members")
82+
local values = {}
83+
for _, value in pairs(members) do
84+
table.insert(values, value)
85+
end
86+
return values
87+
end
88+
89+
--[=[
90+
Returns the map of enum members.
91+
]=]
92+
function SimpleEnum.GetMap<EnumMembers>(self: SimpleEnum<EnumMembers>): EnumMembers
93+
return rawget(self, "_members") :: EnumMembers
94+
end
95+
96+
--[=[
97+
Returns whether the value is a valid enum value.
98+
99+
```lua
100+
local MyEnum = SimpleEnum.new({
101+
FOO = "foo",
102+
BAR = "bar",
103+
})
104+
105+
print(MyEnum:IsValue("foo")) --> true
106+
print(MyEnum:IsValue("baz")) --> false, "Expected one of: foo, bar; got baz"
107+
```
108+
]=]
109+
function SimpleEnum.IsValue<EnumMembers>(self: PrivateSimpleEnum<EnumMembers>, value: any): (boolean, string?)
110+
return self:GetInterface()(value)
111+
end
112+
113+
--[=[
114+
Returns a type-checking function for the enum.
115+
116+
```lua
117+
local MyEnum = SimpleEnum.new({
118+
FOO = "foo",
119+
BAR = "bar",
120+
})
121+
122+
local typeChecker = MyEnum:GetInterface()
123+
124+
print(typeChecker("foo")) --> true
125+
print(typeChecker("baz")) --> false, "Expected one of: foo, bar; got baz"
126+
```
127+
]=]
128+
function SimpleEnum.GetInterface<EnumMembers>(self: PrivateSimpleEnum<EnumMembers>): (value: any) -> (boolean, string?)
129+
local found = rawget(self :: any, "_typeChecker")
130+
if found then
131+
return found
132+
end
133+
134+
local typeChecker: any = t.valueOf(self:GetValues())
135+
rawset(self :: any, "_typeChecker", typeChecker)
136+
return typeChecker
137+
end
138+
139+
SimpleEnum.__index = function<EnumMembers>(self: PrivateSimpleEnum<EnumMembers>, index: string)
140+
if SimpleEnum[index] then
141+
return SimpleEnum[index]
142+
elseif self._members[index] then
143+
return self._members[index]
144+
else
145+
error(`'{tostring(self)}' has no member '{index}'`)
146+
end
147+
end
148+
149+
SimpleEnum.__iter = function<EnumMembers>(self: PrivateSimpleEnum<EnumMembers>)
150+
return pairs(self._members)
151+
end
152+
153+
SimpleEnum.__newindex = function<EnumMembers>(self: PrivateSimpleEnum<EnumMembers>, index: string)
154+
error(`Cannot assign to member '{index}' of enum '{tostring(self)}'`)
155+
end
156+
157+
return SimpleEnum
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "node_modules",
3+
"globIgnorePaths": [ "**/.package-lock.json" ],
4+
"tree": {
5+
"$path": { "optional": "../node_modules" }
6+
}
7+
}

0 commit comments

Comments
 (0)