@gesslar/toolkit 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.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @gesslar/toolkit
2
+
3
+ This package is intended to be a collection of useful utilities for any
4
+ project's consumption. Not the kind that gives you bleeding, hacking coughs,
5
+ but the kind that says "yumyum."
6
+
7
+ There are file and directory abstractions, uhmm, there's also some terminal
8
+ things and validity checkers, lots of data functions.
9
+
10
+ Basically, if you want it, it is most definitely here, and working 100% and
11
+ absolutely none of that is true. There are only a few classes here, but they're
12
+ pretty. And if you bug-shame them, I will _come for you like_ ...
13
+
14
+ nah. Just don't be a dick, okay? Play nice, share, lick a veggie and gentlemen,
15
+ spend less than 5 minutes washing your pits, chest, and downstairs and maybe
16
+ give some time to the other parts. Like the parts that walk on things, sit
17
+ on things. Some things that enjoy being sat upon do not enjoy being sat upon
18
+ by gross sitter-upon-things.
19
+
20
+ Also,
package/UNLICENSE.txt ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@gesslar/toolkit",
3
+ "version": "0.0.1",
4
+ "description": "Get in, bitches, we're going toolkitting.",
5
+ "main": "./src/index.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/types/index.d.ts",
10
+ "default": "./src/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "src/",
15
+ "README.md",
16
+ "UNLICENSE.txt"
17
+ ],
18
+ "sideEffects": false,
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "lint": "eslint src/",
24
+ "submit": "npm publish --access public",
25
+ "update": "npx npm-check-updates -u && npm install",
26
+ "test": "node examples/FileSystem/index.js"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/gesslar/toolkit.git"
31
+ },
32
+ "keywords": [
33
+ "toolkit",
34
+ "utilities",
35
+ "file",
36
+ "directory",
37
+ "data",
38
+ "flavaflav",
39
+ "chuck",
40
+ "norris",
41
+ "validation",
42
+ "typescript",
43
+ "nodejs",
44
+ "iwasbornonapirateship"
45
+ ],
46
+ "author": "gesslar",
47
+ "license": "Unlicense",
48
+ "homepage": "https://github.com/gesslar/toolkit#readme",
49
+ "dependencies": {
50
+ "globby": "^14.0.2",
51
+ "json5": "^2.2.3",
52
+ "yaml": "^2.4.5"
53
+ },
54
+ "devDependencies": {
55
+ "@stylistic/eslint-plugin": "^4.4.0",
56
+ "@types/node": "^24.2.1",
57
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
58
+ "@typescript-eslint/parser": "^8.33.0",
59
+ "eslint": "^9.28.0",
60
+ "eslint-plugin-jsdoc": "^50.6.17"
61
+ }
62
+ }
package/src/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // Core file system abstractions
2
+ export {default as FileObject} from "./lib/FileObject.js"
3
+ export {default as DirectoryObject} from "./lib/DirectoryObject.js"
4
+ export {default as File} from "./lib/File.js"
5
+
6
+ // Utility classes
7
+ export {default as Data} from "./lib/Data.js"
8
+ export {default as Sass} from "./lib/Sass.js"
9
+ export {default as Term} from "./lib/Term.js"
10
+ export {default as Type} from "./lib/Type.js"
11
+ export {default as Valid} from "./lib/Valid.js"
@@ -0,0 +1,533 @@
1
+ /**
2
+ * @file Data utility functions for type checking, object manipulation, and array operations.
3
+ * Provides comprehensive utilities for working with JavaScript data types and structures.
4
+ */
5
+
6
+ import TypeSpec from "./Type.js"
7
+ import Sass from "./Sass.js"
8
+ import Valid from "./Valid.js"
9
+
10
+ export default class Data {
11
+ /**
12
+ * Array of JavaScript primitive type names.
13
+ * Includes basic types and object categories from the typeof operator.
14
+ *
15
+ * @type {string[]}
16
+ */
17
+ static primitives = Object.freeze([
18
+ // Primitives
19
+ "undefined",
20
+ "boolean",
21
+ "number",
22
+ "bigint",
23
+ "string",
24
+ "symbol",
25
+
26
+ // Object Categories from typeof
27
+ "object",
28
+ "function",
29
+ ])
30
+
31
+ /**
32
+ * Array of JavaScript constructor names for built-in objects.
33
+ * Includes common object types and typed arrays.
34
+ *
35
+ * @type {string[]}
36
+ */
37
+ static constructors = Object.freeze([
38
+ // Object Constructors
39
+ "Object",
40
+ "Array",
41
+ "Function",
42
+ "Date",
43
+ "RegExp",
44
+ "Error",
45
+ "Map",
46
+ "Set",
47
+ "WeakMap",
48
+ "WeakSet",
49
+ "Promise",
50
+ "Int8Array",
51
+ "Uint8Array",
52
+ "Float32Array",
53
+ "Float64Array",
54
+ ])
55
+
56
+ /**
57
+ * Combined array of all supported data types (primitives and constructors in lowercase).
58
+ * Used for type validation throughout the utility functions.
59
+ *
60
+ * @type {string[]}
61
+ */
62
+ static dataTypes = Object.freeze([
63
+ ...Data.primitives,
64
+ ...Data.constructors.map(c => c.toLowerCase())
65
+ ])
66
+
67
+ /**
68
+ * Array of type names that can be checked for emptiness.
69
+ * These types have meaningful empty states that can be tested.
70
+ *
71
+ * @type {string[]}
72
+ */
73
+ static emptyableTypes = Object.freeze(["string", "array", "object"])
74
+
75
+ /**
76
+ * Appends a string to another string if it does not already end with it.
77
+ *
78
+ * @param {string} string - The string to append to
79
+ * @param {string} append - The string to append
80
+ * @returns {string} The appended string
81
+ */
82
+ static appendString(string, append) {
83
+ return string.endsWith(append) ? string : `${string}${append}`
84
+ }
85
+
86
+ /**
87
+ * Prepends a string to another string if it does not already start with it.
88
+ *
89
+ * @param {string} string - The string to prepend to
90
+ * @param {string} prepend - The string to prepend
91
+ * @returns {string} The prepended string
92
+ */
93
+ static prependString(string, prepend) {
94
+ return string.startsWith(prepend) ? string : `${prepend}${string}`
95
+ }
96
+
97
+ /**
98
+ * Checks if all elements in an array are of a specified type
99
+ *
100
+ * @param {Array} arr - The array to check
101
+ * @param {string} type - The type to check for (optional, defaults to the
102
+ * type of the first element)
103
+ * @returns {boolean} Whether all elements are of the specified type
104
+ */
105
+ static isArrayUniform(arr, type) {
106
+ return arr.every(
107
+ (item, _index, arr) => typeof item === (type || typeof arr[0]),
108
+ )
109
+ }
110
+
111
+ /**
112
+ * Checks if an array is unique
113
+ *
114
+ * @param {Array} arr - The array of which to remove duplicates
115
+ * @returns {Array} The unique elements of the array
116
+ */
117
+ static isArrayUnique(arr) {
118
+ return arr.filter((item, index, self) => self.indexOf(item) === index)
119
+ }
120
+
121
+ /**
122
+ * Returns the intersection of two arrays.
123
+ *
124
+ * @param {Array} arr1 - The first array.
125
+ * @param {Array} arr2 - The second array.
126
+ * @returns {Array} The intersection of the two arrays.
127
+ */
128
+ static arrayIntersection(arr1, arr2) {
129
+ const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
130
+
131
+ return short.filter(value => long.includes(value))
132
+ }
133
+
134
+ /**
135
+ * Checks whether two arrays have any elements in common.
136
+ *
137
+ * This function returns `true` if at least one element from `arr1` exists in
138
+ * `arr2`, and `false` otherwise. It optimizes by iterating over the shorter
139
+ * array for efficiency.
140
+ *
141
+ * Example:
142
+ * arrayIntersects([1, 2, 3], [3, 4, 5]) // returns true
143
+ * arrayIntersects(["a", "b"], ["c", "d"]) // returns false
144
+ *
145
+ * @param {Array} arr1 - The first array to check for intersection.
146
+ * @param {Array} arr2 - The second array to check for intersection.
147
+ * @returns {boolean} True if any element is shared between the arrays, false otherwise.
148
+ */
149
+ static arrayIntersects(arr1, arr2) {
150
+ const [short,long] = [arr1,arr2].sort((a,b) => a.length - b.length)
151
+
152
+ return !!short.find(value => long.includes(value))
153
+ }
154
+
155
+ /**
156
+ * Pads an array to a specified length with a value. This operation
157
+ * occurs in-place.
158
+ *
159
+ * @param {Array} arr - The array to pad.
160
+ * @param {number} length - The length to pad the array to.
161
+ * @param {unknown} value - The value to pad the array with.
162
+ * @param {number} position - The position to pad the array at.
163
+ * @returns {Array} The padded array.
164
+ */
165
+ static arrayPad(arr, length, value, position = 0) {
166
+ const diff = length - arr.length
167
+
168
+ if(diff <= 0)
169
+ return arr
170
+
171
+ const padding = Array(diff).fill(value)
172
+
173
+ if(position === 0)
174
+ // prepend - default
175
+ return padding.concat(arr)
176
+ else if(position === -1)
177
+ // append
178
+ return arr.concat(padding) // somewhere in the middle - THAT IS ILLEGAL
179
+ else
180
+ throw Sass.new("Invalid position")
181
+ }
182
+
183
+ /**
184
+ * Clones an object
185
+ *
186
+ * @param {object} obj - The object to clone
187
+ * @param {boolean} freeze - Whether to freeze the cloned object
188
+ * @returns {object} The cloned object
189
+ */
190
+ static cloneObject(obj, freeze = false) {
191
+ const result = {}
192
+
193
+ for(const [key, value] of Object.entries(obj)) {
194
+ if(Data.isType(value, "object"))
195
+ result[key] = Data.cloneObject(value)
196
+ else
197
+ result[key] = value
198
+ }
199
+
200
+ return freeze ? Object.freeze(result) : result
201
+ }
202
+
203
+ /**
204
+ * Allocates an object from a source array and a spec array or function.
205
+ *
206
+ * @param {unknown} source The source array
207
+ * @param {Array|function(unknown): unknown} spec The spec array or function
208
+ * @returns {Promise<object>} The allocated object
209
+ */
210
+ static async allocateObject(source, spec) {
211
+ // Data
212
+ const workSource = [],
213
+ workSpec = [],
214
+ result = {}
215
+
216
+ if(!Data.isType(source, "array", {allowEmpty: false}))
217
+ throw Sass.new("Source must be an array.")
218
+
219
+ workSource.push(...source)
220
+
221
+ if(
222
+ !Data.isType(spec, "array", {allowEmpty: false}) &&
223
+ !Data.isType(spec, "function")
224
+ )
225
+ throw Sass.new("Spec must be an array or a function.")
226
+
227
+ if(Data.isType(spec, "function")) {
228
+ const specResult = await spec(workSource)
229
+
230
+ if(!Data.isType(specResult, "array"))
231
+ throw Sass.new("Spec resulting from function must be an array.")
232
+
233
+ workSpec.push(...specResult)
234
+ } else if(Data.isType(spec, "array", {allowEmpty: false})) {
235
+ workSpec.push(...spec)
236
+ }
237
+
238
+ if(workSource.length !== workSpec.length)
239
+ throw Sass.new("Source and spec must have the same number of elements.")
240
+
241
+ // Objects must always be indexed by strings.
242
+ workSource.map((element, index, arr) => (arr[index] = String(element)))
243
+
244
+ // Check that all keys are strings
245
+ if(!Data.isArrayUniform(workSource, "string"))
246
+ throw Sass.new("Indices of an Object must be of type string.")
247
+
248
+ workSource.forEach((element, index) => (result[element] = workSpec[index]))
249
+
250
+ return result
251
+ }
252
+
253
+ /**
254
+ * Maps an object using a transformer function
255
+ *
256
+ * @param {object} original The original object
257
+ * @param {function(unknown): unknown} transformer The transformer function
258
+ * @param {boolean} mutate Whether to mutate the original object
259
+ * @returns {Promise<object>} The mapped object
260
+ */
261
+ static async mapObject(original, transformer, mutate = false) {
262
+ Valid.validType(original, "object", {allowEmpty: true})
263
+ Valid.validType(transformer, "function")
264
+ Valid.validType(mutate, "boolean")
265
+
266
+ const result = mutate ? original : {}
267
+
268
+ for(const [key, value] of Object.entries(original))
269
+ result[key] = Data.isType(value, "object")
270
+ ? await Data.mapObject(value, transformer, mutate)
271
+ : (result[key] = await transformer(key, value))
272
+
273
+ return result
274
+ }
275
+
276
+ /**
277
+ * Checks if an object is empty
278
+ *
279
+ * @param {object} obj - The object to check
280
+ * @returns {boolean} Whether the object is empty
281
+ */
282
+ static isObjectEmpty(obj) {
283
+ return Object.keys(obj).length === 0
284
+ }
285
+
286
+ /**
287
+ * Creates a type spec from a string. A type spec is an array of objects
288
+ * defining the type of a value and whether an array is expected.
289
+ *
290
+ * @param {string} string - The string to parse into a type spec.
291
+ * @param {object} options - Additional options for parsing.
292
+ * @returns {object[]} An array of type specs.
293
+ */
294
+ static newTypeSpec(string, options) {
295
+ return new TypeSpec(string, options)
296
+ }
297
+
298
+ /**
299
+ * Checks if a value is of a specified type
300
+ *
301
+ * @param {unknown} value The value to check
302
+ * @param {string|TypeSpec} type The type to check for
303
+ * @param {object} options Additional options for checking
304
+ * @returns {boolean} Whether the value is of the specified type
305
+ */
306
+ static isType(value, type, options = {}) {
307
+ const typeSpec = type instanceof TypeSpec
308
+ ? type
309
+ : Data.newTypeSpec(type, options)
310
+
311
+ return typeSpec.match(value, options)
312
+ }
313
+
314
+ /**
315
+ * Checks if a type is valid
316
+ *
317
+ * @param {string} type - The type to check
318
+ * @returns {boolean} Whether the type is valid
319
+ */
320
+ static isValidType(type) {
321
+ return Data.dataTypes.includes(type)
322
+ }
323
+
324
+ /**
325
+ * Checks if a value is of a specified type. Unlike the type function, this
326
+ * function does not parse the type string, and only checks for primitive
327
+ * or constructor types.
328
+ *
329
+ * @param {unknown} value - The value to check
330
+ * @param {string} type - The type to check for
331
+ * @returns {boolean} Whether the value is of the specified type
332
+ */
333
+ static isBaseType(value, type) {
334
+ if(!Data.isValidType(type))
335
+ return false
336
+
337
+ const valueType = Data.typeOf(value)
338
+
339
+ switch(type.toLowerCase()) {
340
+ case "array":
341
+ return Array.isArray(value) // Native array check
342
+ case "string":
343
+ return valueType === "string"
344
+ case "boolean":
345
+ return valueType === "boolean"
346
+ case "number":
347
+ return valueType === "number" && !isNaN(value) // Excludes NaN
348
+ case "object":
349
+ return value !== null && valueType === "object" && !Array.isArray(value) // Excludes arrays and null
350
+ case "function":
351
+ return valueType === "function"
352
+ case "symbol":
353
+ return valueType === "symbol" // ES6 Symbol type
354
+ case "bigint":
355
+ return valueType === "bigint" // BigInt support
356
+ case "null":
357
+ return value === null // Explicit null check
358
+ case "undefined":
359
+ return valueType === "undefined" // Explicit undefined check
360
+ default:
361
+ return false // Unknown type
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Returns the type of a value, whether it be a primitive, object, or function.
367
+ *
368
+ * @param {unknown} value - The value to check
369
+ * @returns {string} The type of the value
370
+ */
371
+ static typeOf(value) {
372
+ return Array.isArray(value) ? "array" : typeof value
373
+ }
374
+
375
+ /**
376
+ * Checks a value is undefined or null.
377
+ *
378
+ * @param {unknown} value The value to check
379
+ * @returns {boolean} Whether the value is undefined or null
380
+ */
381
+ static isNothing(value) {
382
+ return value === undefined || value === null
383
+ }
384
+
385
+ /**
386
+ * Checks if a value is empty. This function is used to check if an object,
387
+ * array, or string is empty. Null and undefined values are considered empty.
388
+ *
389
+ * @param {unknown} value The value to check
390
+ * @param {boolean} checkForNothing Whether to check for null or undefined
391
+ * values
392
+ * @returns {boolean} Whether the value is empty
393
+ */
394
+ static isEmpty(value, checkForNothing = true) {
395
+ const type = Data.typeOf(value)
396
+
397
+ if(checkForNothing && Data.isNothing(value))
398
+ return true
399
+
400
+ if(!Data.emptyableTypes.includes(type))
401
+ return false
402
+
403
+ switch(type) {
404
+ case "array":
405
+ return value.length === 0
406
+ case "object":
407
+ return Object.keys(value).length === 0
408
+ case "string":
409
+ return value.trim().length === 0
410
+ default:
411
+ return false
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Freezes an object and all of its properties recursively.
417
+ *
418
+ * @param {object} obj The object to freeze.
419
+ * @returns {object} The frozen object.
420
+ */
421
+ static deepFreezeObject(obj) {
422
+ if(obj === null || typeof obj !== "object")
423
+ return obj // Skip null and non-objects
424
+
425
+ // Retrieve and freeze properties
426
+ const propNames = Object.getOwnPropertyNames(obj)
427
+
428
+ for(const name of propNames) {
429
+ const value = obj[name]
430
+
431
+ // Recursively freeze nested objects
432
+ if(typeof value === "object" && value !== null)
433
+ Data.deepFreezeObject(value)
434
+ }
435
+
436
+ // Freeze the object itself
437
+ return Object.freeze(obj)
438
+ }
439
+
440
+ /**
441
+ * Ensures that a nested path of objects exists within the given object.
442
+ * Creates empty objects along the path if they don't exist.
443
+ *
444
+ * @param {object} obj - The object to check/modify
445
+ * @param {Array<string>} keys - Array of keys representing the path to ensure
446
+ * @returns {object} Reference to the deepest nested object in the path
447
+ */
448
+ static assureObjectPath(obj, keys) {
449
+ let current = obj // a moving reference to internal objects within obj
450
+ const len = keys.length
451
+
452
+ for(let i = 0; i < len; i++) {
453
+ const elem = keys[i]
454
+
455
+ if(!current[elem])
456
+ current[elem] = {}
457
+
458
+ current = current[elem]
459
+ }
460
+
461
+ // Return the current pointer
462
+ return current
463
+ }
464
+
465
+ /**
466
+ * Sets a value in a nested object structure using an array of keys; creating
467
+ * the structure if it does not exist.
468
+ *
469
+ * @param {object} obj - The target object to set the value in
470
+ * @param {Array<string>} keys - Array of keys representing the path to the target property
471
+ * @param {unknown} value - The value to set at the target location
472
+ */
473
+ static setNestedValue(obj, keys, value) {
474
+ const nested = Data.assureObjectPath(obj, keys.slice(0, -1))
475
+
476
+ nested[keys[keys.length-1]] = value
477
+ }
478
+
479
+ /**
480
+ * Deeply merges two or more objects. Arrays are replaced, not merged.
481
+ *
482
+ * @param {...object} sources - Objects to merge (left to right)
483
+ * @returns {object} The merged object
484
+ */
485
+ static mergeObject(...sources) {
486
+ const isObject = obj => typeof obj === "object" && obj !== null && !Array.isArray(obj)
487
+
488
+ return sources.reduce((acc, obj) => {
489
+ if(!isObject(obj))
490
+ return acc
491
+
492
+ Object.keys(obj).forEach(key => {
493
+ const accVal = acc[key]
494
+ const objVal = obj[key]
495
+
496
+ if(isObject(accVal) && isObject(objVal))
497
+ acc[key] = Data.mergeObject(accVal, objVal)
498
+ else
499
+ acc[key] = objVal
500
+ })
501
+
502
+ return acc
503
+ }, {})
504
+ }
505
+
506
+ /**
507
+ * Checks if all elements in an array are strings.
508
+ *
509
+ * @param {Array} arr - The array to check.
510
+ * @returns {boolean} Returns true if all elements are strings, false otherwise.
511
+ * @example
512
+ * uniformStringArray(['a', 'b', 'c']) // returns true
513
+ * uniformStringArray(['a', 1, 'c']) // returns false
514
+ */
515
+ static uniformStringArray(arr) {
516
+ return Array.isArray(arr) && arr.every(item => typeof item === "string")
517
+ }
518
+
519
+ /**
520
+ * Filters an array asynchronously using a predicate function.
521
+ * Applies the predicate to all items in parallel and returns filtered results.
522
+ *
523
+ * @param {Array} arr - The array to filter
524
+ * @param {function(unknown): Promise<boolean>} predicate - Async predicate function that returns a promise resolving to boolean
525
+ * @returns {Promise<Array>} Promise resolving to the filtered array
526
+ */
527
+ static async asyncFilter(arr, predicate) {
528
+ const results = await Promise.all(arr.map(predicate))
529
+
530
+ return arr.filter((_, index) => results[index])
531
+ }
532
+
533
+ }