@gesslar/muddy 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ import {Sass, Util, Valid} from "@gesslar/toolkit"
2
+ import {fragment} from "xmlbuilder2"
3
+
4
+ /**
5
+ * Base class for all Mudlet module types (scripts, triggers, aliases, etc.).
6
+ */
7
+ export default class MudletModule {
8
+ #meta = new Map()
9
+
10
+ /**
11
+ * Creates a new Mudlet module.
12
+ *
13
+ * @param {object} [object] - Configuration object
14
+ * @param {string} object.name - Module name
15
+ * @param {"yes" | "no"} object.isFolder - Whether this is a folder
16
+ * @param {"yes" | "no"} object.isActive - Whether this module is active
17
+ * @param {string} [object.packageName] - Package name
18
+ * @param {string} [object.script] - Lua script content
19
+ */
20
+ constructor(object={}) {
21
+ const {name, isFolder, isActive, packageName="", script=""} = (object ?? {})
22
+
23
+ Valid.type(name, "String", {allowEmpty: false})
24
+ Valid.assert(isFolder === "yes" || isFolder === "no", "isFolder must be 'yes' or 'no'.")
25
+ Valid.assert(isActive === "yes" || isActive === "no", "isActive must be 'yes' or 'no'.")
26
+ Valid.type(packageName, "String")
27
+ Valid.type(script, "String")
28
+
29
+ this.#meta.set("name", name)
30
+ this.#meta.set("isFolder", isFolder)
31
+ this.#meta.set("isActive", isActive)
32
+ this.#meta.set("script", script)
33
+ this.#meta.set("packageName", packageName) // i have no idea what this is used for
34
+ this.#meta.set("parent", null)
35
+ this.#meta.set("children", new Set())
36
+ this.#meta.set("id", Symbol(name))
37
+ }
38
+
39
+ /**
40
+ * Gets the module name.
41
+ *
42
+ * @returns {string}
43
+ */
44
+ get name() {
45
+ return this.#meta.get("name")
46
+ }
47
+
48
+ /**
49
+ * Gets the module's unique symbol ID.
50
+ *
51
+ * @returns {symbol}
52
+ */
53
+ get id() {
54
+ return this.#meta.get("id")
55
+ }
56
+
57
+ /**
58
+ * Gets whether this module is a folder.
59
+ *
60
+ * @returns {"yes" | "no"}
61
+ */
62
+ get isFolder() {
63
+ return this.#meta.get("isFolder")
64
+ }
65
+
66
+ /**
67
+ * Gets whether this module is active.
68
+ *
69
+ * @returns {"yes" | "no"}
70
+ */
71
+ get isActive() {
72
+ return this.#meta.get("isActive")
73
+ }
74
+
75
+ /**
76
+ * Gets the Lua script content.
77
+ *
78
+ * @returns {string}
79
+ */
80
+ get script() {
81
+ return this.#meta.get("script")
82
+ }
83
+
84
+ /**
85
+ * Gets the package name.
86
+ *
87
+ * @returns {string}
88
+ */
89
+ get packageName() {
90
+ return this.#meta.get("packageName")
91
+ }
92
+
93
+ /**
94
+ * Sets the parent module.
95
+ *
96
+ * @param {MudletModule | null} parent - Parent module
97
+ */
98
+ set parent(parent) {
99
+ if(this.parent)
100
+ throw Sass.new("Parent already set.")
101
+
102
+ this.#meta.set("parent", parent)
103
+ }
104
+
105
+ /**
106
+ * Gets the parent module.
107
+ *
108
+ * @returns {MudletModule | null}
109
+ */
110
+ get parent() {
111
+ return this.#meta.get("parent")
112
+ }
113
+
114
+ /**
115
+ * Adds a child module to this module.
116
+ *
117
+ * @param {MudletModule} child - Child module to add
118
+ * @returns {this}
119
+ */
120
+ addChild(child) {
121
+ if(this.#meta.get("children").has(child))
122
+ throw Sass.new(`Child '${child}' is already present.`)
123
+
124
+ this.#meta.get("children").add(child)
125
+ child.parent = this
126
+
127
+ this.#meta.set("isFolder", "yes")
128
+
129
+ return this
130
+ }
131
+
132
+ /**
133
+ * Gets the set of child modules.
134
+ *
135
+ * @returns {Set<MudletModule>}
136
+ */
137
+ get children() {
138
+ return this.#meta.get("children")
139
+ }
140
+
141
+ /**
142
+ * Iterates through all parent modules up the hierarchy.
143
+ *
144
+ * @yields {MudletModule}
145
+ */
146
+ *parents() {
147
+ let current = this.parent
148
+ while(current !== null) {
149
+ yield current
150
+ current = current.parent
151
+ }
152
+ }
153
+
154
+ toJSON() {
155
+ return Object.fromEntries(this.#meta)
156
+ }
157
+
158
+ toString() {
159
+ return `[${this.constructor.name}: ${this.name}]`
160
+ }
161
+
162
+ /**
163
+ * Converts this module to an XML fragment for Mudlet package format.
164
+ *
165
+ * @returns {import("xmlbuilder2").XMLBuilder}
166
+ */
167
+ toXMLFragment() {
168
+ const kind = this.constructor.name.toLowerCase()
169
+ const baseName = Util.capitalize(kind)
170
+ const tag = this.isFolder === "yes" ? `${baseName}Group` : baseName
171
+ const frag = fragment()
172
+ const children = this.children
173
+
174
+ const root = frag
175
+ .ele(tag, {isActive: this.isActive, isFolder: this.isFolder})
176
+ .ele({name: this.name}).up()
177
+ .ele({script: this.script}).up()
178
+ .ele({packageName: this.packageName}).up()
179
+
180
+ // If this is a folder, serialize its children as nested modules.
181
+ if(this.isFolder === "yes" && children && children.size > 0) {
182
+ for(const child of children)
183
+ root.import(child.toXMLFragment())
184
+ }
185
+
186
+ return frag
187
+ }
188
+ }
@@ -0,0 +1,59 @@
1
+ import {Collection, Valid} from "@gesslar/toolkit"
2
+
3
+ import MudletModule from "./MudletModule.js"
4
+
5
+ /**
6
+ * Script module - represents a Lua script in Mudlet with optional event handlers.
7
+ */
8
+ export default class Script extends MudletModule {
9
+ #meta = new Map()
10
+
11
+ /**
12
+ * Creates a new Script module.
13
+ *
14
+ * @param {object} [object] - Configuration object
15
+ * @param {Array<string>} [object.eventHandlerList] - List of event handler names
16
+ */
17
+ constructor(object={}) {
18
+ super(object)
19
+
20
+ const {eventHandlerList=[]} = object
21
+
22
+ Valid.type(eventHandlerList, "Array")
23
+ Valid.assert(
24
+ eventHandlerList.length === 0 ||
25
+ Collection.isArrayUniform(eventHandlerList, "String"),
26
+ "eventHandlerList must be an empty or String array"
27
+ )
28
+
29
+ this.#meta.set("eventHandlerList", eventHandlerList)
30
+ }
31
+
32
+ /**
33
+ * Gets the list of event handler names.
34
+ *
35
+ * @returns {Array<string>} List of event handler names
36
+ */
37
+ get eventHandlerList() {
38
+ return this.#meta.get("eventHandlerList")
39
+ }
40
+
41
+ toJSON() {
42
+ return Object.assign(
43
+ super.toJSON(),
44
+ {...Object.fromEntries(this.#meta)}
45
+ )
46
+ }
47
+
48
+ toXMLFragment() {
49
+ const frag = super.toXMLFragment()
50
+
51
+ // Add eventHandlerList as a container with string children
52
+ const handlerList = frag.last().ele("eventHandlerList")
53
+ this.eventHandlerList.forEach(handler => handlerList.ele({string: handler}))
54
+
55
+ handlerList.up()
56
+
57
+ return frag
58
+ }
59
+ }
@@ -0,0 +1,76 @@
1
+ import {Valid} from "@gesslar/toolkit"
2
+
3
+ import MudletModule from "./MudletModule.js"
4
+
5
+ export default class Timer extends MudletModule {
6
+ #meta = new Map()
7
+
8
+ constructor(object={}) {
9
+ super(object)
10
+
11
+ const {
12
+ command="",
13
+ time="00:00:00.000",
14
+ isTempTimer="no",
15
+ isOffsetTimer="no"
16
+ } = object
17
+
18
+ // Validate string fields
19
+ Valid.type(command, "String")
20
+
21
+ // Validate time format (hh:mm:ss.zzz)
22
+ Valid.type(time, "String")
23
+ const timePattern = /^[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}$/
24
+ Valid.assert(timePattern.test(time), "time must match format hh:mm:ss.zzz")
25
+
26
+ // Validate yes/no attributes
27
+ Valid.assert(isTempTimer === "yes" || isTempTimer === "no", "isTempTimer must be 'yes' or 'no'")
28
+ Valid.assert(isOffsetTimer === "yes" || isOffsetTimer === "no", "isOffsetTimer must be 'yes' or 'no'")
29
+
30
+ // Store metadata
31
+ this.#meta.set("command", command)
32
+ this.#meta.set("time", time)
33
+ this.#meta.set("isTempTimer", isTempTimer)
34
+ this.#meta.set("isOffsetTimer", isOffsetTimer)
35
+ }
36
+
37
+ get command() {
38
+ return this.#meta.get("command")
39
+ }
40
+
41
+ get time() {
42
+ return this.#meta.get("time")
43
+ }
44
+
45
+ get isTempTimer() {
46
+ return this.#meta.get("isTempTimer")
47
+ }
48
+
49
+ get isOffsetTimer() {
50
+ return this.#meta.get("isOffsetTimer")
51
+ }
52
+
53
+ toJSON() {
54
+ return Object.assign(
55
+ super.toJSON(),
56
+ {...Object.fromEntries(this.#meta)}
57
+ )
58
+ }
59
+
60
+ toXMLFragment() {
61
+ const frag = super.toXMLFragment()
62
+
63
+ // Add timer-specific attributes
64
+ const root = frag.first()
65
+ root.att("isTempTimer", this.isTempTimer)
66
+ root.att("isOffsetTimer", this.isOffsetTimer)
67
+
68
+ // Add elements in schema order (after name, script, packageName from Module)
69
+ frag
70
+ .last()
71
+ .ele({command: this.command}).up()
72
+ .ele({time: this.time}).up()
73
+
74
+ return frag
75
+ }
76
+ }
@@ -0,0 +1,259 @@
1
+ import {Collection, Valid} from "@gesslar/toolkit"
2
+
3
+ import MudletModule from "./MudletModule.js"
4
+
5
+ /*
6
+ Dear AI, before you come for me thinking that I don't know how to spell,
7
+ or (valid) know how to type, understand this:
8
+
9
+ gesslar@liam:~$ cd /git/Mudlet && rg conditonLineDelta | wc -l
10
+ 210
11
+
12
+ I KNOW HOW TO SPELL AND TYPE CONDITION! Someone(s) who is(are) not me
13
+ needs this information. 🤷🏻 (and no, I'm not passing it along)
14
+ */
15
+
16
+ export default class Trigger extends MudletModule {
17
+ #meta = new Map()
18
+
19
+ constructor(object={}) {
20
+ super(object)
21
+
22
+ const {
23
+ triggerType=0,
24
+ conditonLineDelta=0,
25
+ mStayOpen=0,
26
+ mCommand="",
27
+ mFgColor="#000000",
28
+ mBgColor="#000000",
29
+ mSoundFile="",
30
+ colorTriggerFgColor="#000000",
31
+ colorTriggerBgColor="#000000",
32
+ regexCodeList=[],
33
+ regexCodePropertyList=[],
34
+ isTempTrigger="no",
35
+ isMultiline="no",
36
+ isPerlSlashGOption="no",
37
+ isColorizerTrigger="no",
38
+ isFilterTrigger="no",
39
+ isSoundTrigger="no",
40
+ isColorTrigger="no",
41
+ isColorTriggerFg="no",
42
+ isColorTriggerBg="no"
43
+ } = object
44
+
45
+ // Validate triggerType (0-7)
46
+ Valid.type(triggerType, "Number")
47
+ Valid.assert(triggerType >= 0 && triggerType <= 7, "triggerType must be between 0 and 7")
48
+
49
+ // Validate integer fields
50
+ Valid.type(conditonLineDelta, "Number")
51
+ Valid.type(mStayOpen, "Number")
52
+ Valid.assert(mStayOpen >= 0, "mStayOpen must be non-negative")
53
+
54
+ // Validate string fields
55
+ Valid.type(mCommand, "String")
56
+ Valid.type(mSoundFile, "String")
57
+
58
+ // Validate color fields (hex or "transparent")
59
+ const colorPattern = /^(#[0-9A-Fa-f]{6}|transparent)$/
60
+ Valid.assert(colorPattern.test(mFgColor), "mFgColor must be hex color or 'transparent'")
61
+ Valid.assert(colorPattern.test(mBgColor), "mBgColor must be hex color or 'transparent'")
62
+ Valid.assert(colorPattern.test(colorTriggerFgColor), "colorTriggerFgColor must be hex color or 'transparent'")
63
+ Valid.assert(colorPattern.test(colorTriggerBgColor), "colorTriggerBgColor must be hex color or 'transparent'")
64
+
65
+ // Validate lists
66
+ Valid.type(regexCodeList, "Array")
67
+ Valid.assert(
68
+ regexCodeList.length === 0 || Collection.isArrayUniform(regexCodeList, "String"),
69
+ "regexCodeList must be an empty or String array"
70
+ )
71
+
72
+ Valid.type(regexCodePropertyList, "Array")
73
+ Valid.assert(
74
+ regexCodePropertyList.length === 0 || Collection.isArrayUniform(regexCodePropertyList, "Number"),
75
+ "regexCodePropertyList must be an empty or Number array"
76
+ )
77
+ regexCodePropertyList.forEach(val => {
78
+ Valid.assert(val >= 0 && val <= 7, "regexCodePropertyList values must be between 0 and 7")
79
+ })
80
+
81
+ // Validate yes/no attributes
82
+ const yesNoFields = [
83
+ "isTempTrigger", "isMultiline", "isPerlSlashGOption", "isColorizerTrigger",
84
+ "isFilterTrigger", "isSoundTrigger", "isColorTrigger", "isColorTriggerFg", "isColorTriggerBg"
85
+ ]
86
+ const yesNoValues = {
87
+ isTempTrigger,
88
+ isMultiline,
89
+ isPerlSlashGOption,
90
+ isColorizerTrigger,
91
+ isFilterTrigger,
92
+ isSoundTrigger,
93
+ isColorTrigger,
94
+ isColorTriggerFg,
95
+ isColorTriggerBg
96
+ }
97
+
98
+ yesNoFields.forEach(field => {
99
+ Valid.assert(
100
+ yesNoValues[field] === "yes" || yesNoValues[field] === "no",
101
+ `${field} must be 'yes' or 'no'`
102
+ )
103
+ })
104
+
105
+ // Store all metadata
106
+ this.#meta.set("triggerType", triggerType)
107
+ this.#meta.set("conditonLineDelta", conditonLineDelta)
108
+ this.#meta.set("mStayOpen", mStayOpen)
109
+ this.#meta.set("mCommand", mCommand)
110
+ this.#meta.set("mFgColor", mFgColor)
111
+ this.#meta.set("mBgColor", mBgColor)
112
+ this.#meta.set("mSoundFile", mSoundFile)
113
+ this.#meta.set("colorTriggerFgColor", colorTriggerFgColor)
114
+ this.#meta.set("colorTriggerBgColor", colorTriggerBgColor)
115
+ this.#meta.set("regexCodeList", regexCodeList)
116
+ this.#meta.set("regexCodePropertyList", regexCodePropertyList)
117
+ this.#meta.set("isTempTrigger", isTempTrigger)
118
+ this.#meta.set("isMultiline", isMultiline)
119
+ this.#meta.set("isPerlSlashGOption", isPerlSlashGOption)
120
+ this.#meta.set("isColorizerTrigger", isColorizerTrigger)
121
+ this.#meta.set("isFilterTrigger", isFilterTrigger)
122
+ this.#meta.set("isSoundTrigger", isSoundTrigger)
123
+ this.#meta.set("isColorTrigger", isColorTrigger)
124
+ this.#meta.set("isColorTriggerFg", isColorTriggerFg)
125
+ this.#meta.set("isColorTriggerBg", isColorTriggerBg)
126
+ }
127
+
128
+ get triggerType() {
129
+ return this.#meta.get("triggerType")
130
+ }
131
+
132
+ get conditonLineDelta() {
133
+ return this.#meta.get("conditonLineDelta")
134
+ }
135
+
136
+ get mStayOpen() {
137
+ return this.#meta.get("mStayOpen")
138
+ }
139
+
140
+ get mCommand() {
141
+ return this.#meta.get("mCommand")
142
+ }
143
+
144
+ get mFgColor() {
145
+ return this.#meta.get("mFgColor")
146
+ }
147
+
148
+ get mBgColor() {
149
+ return this.#meta.get("mBgColor")
150
+ }
151
+
152
+ get mSoundFile() {
153
+ return this.#meta.get("mSoundFile")
154
+ }
155
+
156
+ get colorTriggerFgColor() {
157
+ return this.#meta.get("colorTriggerFgColor")
158
+ }
159
+
160
+ get colorTriggerBgColor() {
161
+ return this.#meta.get("colorTriggerBgColor")
162
+ }
163
+
164
+ get regexCodeList() {
165
+ return this.#meta.get("regexCodeList")
166
+ }
167
+
168
+ get regexCodePropertyList() {
169
+ return this.#meta.get("regexCodePropertyList")
170
+ }
171
+
172
+ get isTempTrigger() {
173
+ return this.#meta.get("isTempTrigger")
174
+ }
175
+
176
+ get isMultiline() {
177
+ return this.#meta.get("isMultiline")
178
+ }
179
+
180
+ get isPerlSlashGOption() {
181
+ return this.#meta.get("isPerlSlashGOption")
182
+ }
183
+
184
+ get isColorizerTrigger() {
185
+ return this.#meta.get("isColorizerTrigger")
186
+ }
187
+
188
+ get isFilterTrigger() {
189
+ return this.#meta.get("isFilterTrigger")
190
+ }
191
+
192
+ get isSoundTrigger() {
193
+ return this.#meta.get("isSoundTrigger")
194
+ }
195
+
196
+ get isColorTrigger() {
197
+ return this.#meta.get("isColorTrigger")
198
+ }
199
+
200
+ get isColorTriggerFg() {
201
+ return this.#meta.get("isColorTriggerFg")
202
+ }
203
+
204
+ get isColorTriggerBg() {
205
+ return this.#meta.get("isColorTriggerBg")
206
+ }
207
+
208
+ toJSON() {
209
+ return Object.assign(
210
+ super.toJSON(),
211
+ {...Object.fromEntries(this.#meta)}
212
+ )
213
+ }
214
+
215
+ toXMLFragment() {
216
+ const frag = super.toXMLFragment()
217
+
218
+ // Add trigger-specific attributes
219
+ const root = frag.first()
220
+ root.att("isTempTrigger", this.isTempTrigger)
221
+ root.att("isMultiline", this.isMultiline)
222
+ root.att("isPerlSlashGOption", this.isPerlSlashGOption)
223
+ root.att("isColorizerTrigger", this.isColorizerTrigger)
224
+ root.att("isFilterTrigger", this.isFilterTrigger)
225
+ root.att("isSoundTrigger", this.isSoundTrigger)
226
+ root.att("isColorTrigger", this.isColorTrigger)
227
+ root.att("isColorTriggerFg", this.isColorTriggerFg)
228
+ root.att("isColorTriggerBg", this.isColorTriggerBg)
229
+
230
+ // Add elements in schema order
231
+ frag
232
+ .last()
233
+ .ele({triggerType: this.triggerType}).up()
234
+ .ele({conditonLineDelta: this.conditonLineDelta}).up()
235
+ .ele({mStayOpen: this.mStayOpen}).up()
236
+ .ele({mCommand: this.mCommand}).up()
237
+ .ele({mFgColor: this.mFgColor}).up()
238
+ .ele({mBgColor: this.mBgColor}).up()
239
+ .ele({mSoundFile: this.mSoundFile}).up()
240
+ .ele({colorTriggerFgColor: this.colorTriggerFgColor}).up()
241
+ .ele({colorTriggerBgColor: this.colorTriggerBgColor}).up()
242
+
243
+ // Add regexCodeList
244
+ const regexList = frag.last().ele("regexCodeList")
245
+ this.regexCodeList.forEach(code => {
246
+ regexList.ele({string: code})
247
+ })
248
+ regexList.up()
249
+
250
+ // Add regexCodePropertyList
251
+ const propertyList = frag.last().ele("regexCodePropertyList")
252
+ this.regexCodePropertyList.forEach(prop => {
253
+ propertyList.ele({integer: prop})
254
+ })
255
+ propertyList.up()
256
+
257
+ return frag
258
+ }
259
+ }
@@ -0,0 +1,82 @@
1
+ import {Valid} from "@gesslar/toolkit"
2
+ import {fragment} from "xmlbuilder2"
3
+
4
+ export default class Variable {
5
+ #meta = new Map()
6
+
7
+ constructor(object={}) {
8
+ const {
9
+ name,
10
+ keyType=0,
11
+ value="",
12
+ valueType=0,
13
+ isActive="yes",
14
+ isFolder="no"
15
+ } = object
16
+
17
+ // Validate required fields
18
+ Valid.type(name, "String", {allowEmpty: false})
19
+ Valid.type(value, "String")
20
+
21
+ // Validate integer fields
22
+ Valid.type(keyType, "Number")
23
+ Valid.type(valueType, "Number")
24
+
25
+ // Validate yes/no attributes
26
+ Valid.assert(isActive === "yes" || isActive === "no", "isActive must be 'yes' or 'no'")
27
+ Valid.assert(isFolder === "yes" || isFolder === "no", "isFolder must be 'yes' or 'no'")
28
+
29
+ // Store metadata
30
+ this.#meta.set("name", name)
31
+ this.#meta.set("keyType", keyType)
32
+ this.#meta.set("value", value)
33
+ this.#meta.set("valueType", valueType)
34
+ this.#meta.set("isActive", isActive)
35
+ this.#meta.set("isFolder", isFolder)
36
+ }
37
+
38
+ get name() {
39
+ return this.#meta.get("name")
40
+ }
41
+
42
+ get keyType() {
43
+ return this.#meta.get("keyType")
44
+ }
45
+
46
+ get value() {
47
+ return this.#meta.get("value")
48
+ }
49
+
50
+ get valueType() {
51
+ return this.#meta.get("valueType")
52
+ }
53
+
54
+ get isActive() {
55
+ return this.#meta.get("isActive")
56
+ }
57
+
58
+ get isFolder() {
59
+ return this.#meta.get("isFolder")
60
+ }
61
+
62
+ toJSON() {
63
+ return Object.fromEntries(this.#meta)
64
+ }
65
+
66
+ toString() {
67
+ return `[Variable]`
68
+ }
69
+
70
+ toXMLFragment() {
71
+ const frag = fragment()
72
+
73
+ frag
74
+ .ele("VariableGroup", {isActive: this.isActive, isFolder: this.isFolder})
75
+ .ele({name: this.name}).up()
76
+ .ele({keyType: this.keyType}).up()
77
+ .ele({value: this.value}).up()
78
+ .ele({valueType: this.valueType}).up()
79
+
80
+ return frag
81
+ }
82
+ }