@gesslar/toolkit 3.9.0 → 3.13.0

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.
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "gesslar",
6
6
  "url": "https://gesslar.dev"
7
7
  },
8
- "version": "3.9.0",
8
+ "version": "3.13.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -53,12 +53,11 @@
53
53
  "dependencies": {
54
54
  "@gesslar/colours": "^0.7.1",
55
55
  "ajv": "^8.17.1",
56
- "globby": "^16.1.0",
57
56
  "json5": "^2.2.3",
58
57
  "yaml": "^2.8.2"
59
58
  },
60
59
  "devDependencies": {
61
- "@gesslar/uglier": "^0.5.1",
60
+ "@gesslar/uglier": "^0.6.0",
62
61
  "eslint": "^9.39.2",
63
62
  "happy-dom": "^20.0.11",
64
63
  "typescript": "^5.9.3"
@@ -70,6 +69,7 @@
70
69
  "submit": "pnpm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
71
70
  "update": "pnpm up --latest --recursive",
72
71
  "test": "node --test tests/**/*.test.js",
72
+ "test:coverage": "node --experimental-config-file=node.config.json --experimental-test-coverage --test-timeout=3000 --test tests/**/*.test.js",
73
73
  "test:node": "node --test tests/node/*.test.js",
74
74
  "test:browser": "node --test tests/browser/*.test.js",
75
75
  "pr": "gt submit -p --ai",
@@ -75,7 +75,7 @@ export default class Data {
75
75
  *
76
76
  * @type {Array<string>}
77
77
  */
78
- static emptyableTypes = Object.freeze(["String", "Array", "Object"])
78
+ static emptyableTypes = Object.freeze(["String", "Array", "Object", "Map", "Set"])
79
79
 
80
80
  /**
81
81
  * Appends a string to another string if it does not already end with it.
@@ -84,8 +84,10 @@ export default class Data {
84
84
  * @param {string} append - The string to append
85
85
  * @returns {string} The appended string
86
86
  */
87
- static appendString(string, append) {
88
- return string.endsWith(append) ? string : `${string}${append}`
87
+ static append(string, append) {
88
+ return string.endsWith(append)
89
+ ? string :
90
+ `${string}${append}`
89
91
  }
90
92
 
91
93
  /**
@@ -95,8 +97,87 @@ export default class Data {
95
97
  * @param {string} prepend - The string to prepend
96
98
  * @returns {string} The prepended string
97
99
  */
98
- static prependString(string, prepend) {
99
- return string.startsWith(prepend) ? string : `${prepend}${string}`
100
+ static prepend(string, prepend) {
101
+ return string.startsWith(prepend)
102
+ ? string
103
+ : `${prepend}${string}`
104
+ }
105
+
106
+ /**
107
+ * Remove a suffix from the end of a string if present.
108
+ *
109
+ * @param {string} string - The string to process
110
+ * @param {string} toChop - The suffix to remove from the end
111
+ * @param {boolean} [caseInsensitive=false] - Whether to perform case-insensitive matching
112
+ * @returns {string} The string with suffix removed, or original if suffix not found
113
+ * @example
114
+ * Data.chopRight("hello.txt", ".txt") // "hello"
115
+ * Data.chopRight("Hello", "o") // "Hell"
116
+ * Data.chopRight("HELLO", "lo", true) // "HEL"
117
+ */
118
+ static chopRight(string, toChop, caseInsensitive=false) {
119
+ const escaped = toChop.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
120
+ const regex = new RegExp(`${escaped}$`, caseInsensitive === true ? "i" : "")
121
+
122
+ return string.replace(regex, "")
123
+ }
124
+
125
+ /**
126
+ * Remove a prefix from the beginning of a string if present.
127
+ *
128
+ * @param {string} string - The string to process
129
+ * @param {string} toChop - The prefix to remove from the beginning
130
+ * @param {boolean} [caseInsensitive=false] - Whether to perform case-insensitive matching
131
+ * @returns {string} The string with prefix removed, or original if prefix not found
132
+ * @example
133
+ * Data.chopLeft("hello.txt", "hello") // ".txt"
134
+ * Data.chopLeft("Hello", "H") // "ello"
135
+ * Data.chopLeft("HELLO", "he", true) // "LLO"
136
+ */
137
+ static chopLeft(string, toChop, caseInsensitive=false) {
138
+ const escaped = toChop.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
139
+ const regex = new RegExp(`^${escaped}`, caseInsensitive === true ? "i" : "")
140
+
141
+ return string.replace(regex, "")
142
+ }
143
+
144
+ /**
145
+ * Chop a string after the first occurence of another string.
146
+ *
147
+ * @param {string} string - The string to search
148
+ * @param {string} needle - The bit to chop after
149
+ * @param {boolean} caseInsensitive - Whether to search insensitive to case
150
+ * @returns {string} The remaining string
151
+ */
152
+ static chopAfter(string, needle, caseInsensitive=false) {
153
+ const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
154
+ const regex = new RegExp(`${escaped}`, caseInsensitive === true ? "i" : "")
155
+ const index = string.search(regex)
156
+
157
+ if(index === -1)
158
+ return string
159
+
160
+ return string.slice(0, index)
161
+ }
162
+
163
+ /**
164
+ * Chop a string before the first occurrence of another string.
165
+ *
166
+ * @param {string} string - The string to search
167
+ * @param {string} needle - The bit to chop before
168
+ * @param {boolean} caseInsensitive - Whether to search insensitive to case
169
+ * @returns {string} The remaining string
170
+ */
171
+ static chopBefore(string, needle, caseInsensitive=false) {
172
+ const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
173
+ const regex = new RegExp(`${escaped}`, caseInsensitive === true ? "i" : "")
174
+ const length = needle.length
175
+ const index = string.search(regex)
176
+
177
+ if(index === -1)
178
+ return string
179
+
180
+ return string.slice(index + length)
100
181
  }
101
182
 
102
183
  /**
@@ -236,6 +317,9 @@ export default class Data {
236
317
  return Object.keys(value).length === 0
237
318
  case "String":
238
319
  return value.trim().length === 0
320
+ case "Map":
321
+ case "Set":
322
+ return value.size === 0
239
323
  default:
240
324
  return false
241
325
  }
@@ -144,7 +144,6 @@ export default class TypeSpec {
144
144
 
145
145
  match(value, options) {
146
146
  const allowEmpty = options?.allowEmpty ?? true
147
- const empty = Data.isEmpty(value)
148
147
 
149
148
  // If we have a list of types, because the string was validly parsed, we
150
149
  // need to ensure that all of the types that were parsed are valid types in
@@ -161,6 +160,9 @@ export default class TypeSpec {
161
160
  // types in an array, if it is an array and an array is allowed.
162
161
  const matchingTypeSpec = this.filter(spec => {
163
162
  const {typeName: allowedType, array: allowedArray} = spec
163
+ const empty =
164
+ Data.emptyableTypes.includes(allowedType) &&
165
+ Data.isEmpty(value)
164
166
 
165
167
  // Handle non-array values
166
168
  if(!isArray && !allowedArray) {