@stonyx/utils 0.1.1 → 0.2.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/README.md +208 -16
- package/package.json +4 -4
- package/src/object.js +28 -6
- package/src/plurarize.js +105 -0
- package/src/string.js +3 -5
package/README.md
CHANGED
|
@@ -1,33 +1,225 @@
|
|
|
1
1
|
# stonyx-utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Utilities module for the Stonyx Framework. Provides helpers for files, objects, strings, dates, and promises.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Quick Reference Table
|
|
8
|
+
|
|
9
|
+
| Category | Function | Description |
|
|
10
|
+
| ----------- | ----------------------- | -------------------------------------------------------- |
|
|
11
|
+
| **File** | `createFile` | Create a file (supports JSON serialization). |
|
|
12
|
+
| | `updateFile` | Atomically update a file. |
|
|
13
|
+
| | `copyFile` | Copy a file, with optional overwrite. |
|
|
14
|
+
| | `readFile` | Read a file (JSON optional), with missing file callback. |
|
|
15
|
+
| | `deleteFile` | Delete a file (ignore missing optional). |
|
|
16
|
+
| | `deleteDirectory` | Recursively delete a directory. |
|
|
17
|
+
| | `createDirectory` | Recursively create a directory. |
|
|
18
|
+
| | `forEachFileImport` | Dynamically import all JS files in a directory. |
|
|
19
|
+
| | `fileExists` | Check if a file exists. |
|
|
20
|
+
| **Object** | `deepCopy` | Deep clone an object or array. |
|
|
21
|
+
| | `objToJson` | Convert object to formatted JSON string. |
|
|
22
|
+
| | `makeArray` | Wrap a value in an array. |
|
|
23
|
+
| | `mergeObject` | Deep merge two objects (ignore new keys optional). |
|
|
24
|
+
| | `get` | Get nested property safely via dot path. |
|
|
25
|
+
| | `getOrSet` | Get or set value in a Map. |
|
|
26
|
+
| **String** | `kebabCaseToCamelCase` | Convert kebab-case to camelCase. |
|
|
27
|
+
| | `kebabCaseToPascalCase` | Convert kebab-case to PascalCase. |
|
|
28
|
+
| | `generateRandomString` | Generate a random alphanumeric string. |
|
|
29
|
+
| | `pluralize` | Return plural form of English nouns. |
|
|
30
|
+
| **Date** | `getTimestamp` | Return current UNIX timestamp in seconds. |
|
|
31
|
+
| **Promise** | `sleep` | Async delay for a given number of seconds. |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Table of Contents
|
|
36
|
+
|
|
37
|
+
* [Installation](#installation)
|
|
38
|
+
* [Running the Test Suite](#running-the-test-suite)
|
|
39
|
+
* [File Utils](#file-utils)
|
|
40
|
+
* [Object Utils](#object-utils)
|
|
41
|
+
* [String Utils](#string-utils)
|
|
42
|
+
* [Date Utils](#date-utils)
|
|
43
|
+
* [Promise Utils](#promise-utils)
|
|
44
|
+
* [License](#license)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @stonyx/utils
|
|
6
52
|
```
|
|
7
|
-
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## File Utils
|
|
57
|
+
|
|
58
|
+
The File utils wrap the `path` and `fs` libraries to manipulate the local file system asynchronously with full async/await support. Includes creation, reading, updating, copying, deleting files and directories, checking existence, and dynamic importing via `forEachFileImport`.
|
|
59
|
+
|
|
60
|
+
### Functions
|
|
61
|
+
|
|
62
|
+
#### `createFile(filePath, data, options={})`
|
|
63
|
+
|
|
64
|
+
Creates a file at the given path.
|
|
65
|
+
|
|
66
|
+
* `options.json` — boolean, if true, data will be serialized to JSON.
|
|
67
|
+
|
|
68
|
+
#### `updateFile(filePath, data, options={})`
|
|
69
|
+
|
|
70
|
+
Updates a file atomically by writing to a temporary file first.
|
|
71
|
+
|
|
72
|
+
* `options.json` — boolean, serialize as JSON.
|
|
73
|
+
|
|
74
|
+
#### `copyFile(sourcePath, targetPath, options={})`
|
|
75
|
+
|
|
76
|
+
Copies a file from source to target.
|
|
77
|
+
|
|
78
|
+
* `options.overwrite` — boolean, default false.
|
|
79
|
+
|
|
80
|
+
#### `readFile(filePath, options={})`
|
|
81
|
+
|
|
82
|
+
Reads a file, optionally parsing JSON.
|
|
83
|
+
|
|
84
|
+
* `options.json` — boolean
|
|
85
|
+
* `options.missingFileCallback` — function called if file doesn’t exist.
|
|
86
|
+
|
|
87
|
+
#### `deleteFile(filePath, options={})`
|
|
88
|
+
|
|
89
|
+
Deletes a file.
|
|
90
|
+
|
|
91
|
+
* `options.ignoreAccessFailure` — boolean, ignores errors if file missing.
|
|
92
|
+
|
|
93
|
+
#### `deleteDirectory(dir)`
|
|
94
|
+
|
|
95
|
+
Recursively deletes a directory.
|
|
96
|
+
|
|
97
|
+
#### `createDirectory(dir)`
|
|
98
|
+
|
|
99
|
+
Recursively creates a directory.
|
|
100
|
+
|
|
101
|
+
#### `forEachFileImport(dir, callback, options={})`
|
|
102
|
+
|
|
103
|
+
Dynamically imports all `.js` files in a directory and calls `callback(exports, details)`.
|
|
104
|
+
|
|
105
|
+
| Option | Type | Default | Description |
|
|
106
|
+
| :-------------------: | :-----: | :-----: | :-------------------------------------------------------- |
|
|
107
|
+
| `fullExport` | Boolean | false | If true, callback receives all exports, not just default. |
|
|
108
|
+
| `rawName` | Boolean | false | If true, the file name is not converted to camelCase. |
|
|
109
|
+
| `ignoreAccessFailure` | Boolean | false | If true, directory access errors are ignored. |
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
import { forEachFileImport } from '@stonyx/utils/file';
|
|
115
|
+
|
|
116
|
+
await forEachFileImport('./utils', (exports, details) => {
|
|
117
|
+
console.log(details.name, exports);
|
|
118
|
+
}, { fullExport: true });
|
|
8
119
|
```
|
|
9
120
|
|
|
10
|
-
|
|
121
|
+
#### `fileExists(filePath)`
|
|
122
|
+
|
|
123
|
+
Returns `true` if file exists, else `false`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Object Utils
|
|
128
|
+
|
|
129
|
+
Helpers for working with objects and arrays.
|
|
11
130
|
|
|
12
|
-
|
|
131
|
+
### Functions
|
|
13
132
|
|
|
14
|
-
|
|
133
|
+
#### `deepCopy(obj)`
|
|
15
134
|
|
|
16
|
-
|
|
135
|
+
Returns a deep copy of an object or array.
|
|
136
|
+
|
|
137
|
+
#### `objToJson(obj, format='\t')`
|
|
138
|
+
|
|
139
|
+
Returns a formatted JSON string.
|
|
140
|
+
|
|
141
|
+
#### `makeArray(obj)`
|
|
142
|
+
|
|
143
|
+
Wraps a value in an array if it isn’t already one.
|
|
144
|
+
|
|
145
|
+
#### `mergeObject(obj1, obj2, options={})`
|
|
146
|
+
|
|
147
|
+
Deep merges two objects.
|
|
148
|
+
|
|
149
|
+
* `options.ignoreNewKeys` — boolean, if true, keys not in `obj1` are ignored.
|
|
150
|
+
|
|
151
|
+
#### `get(obj, path)`
|
|
152
|
+
|
|
153
|
+
Safely gets a nested property using dot notation. Returns `null` if path not found.
|
|
154
|
+
|
|
155
|
+
#### `getOrSet(map, key, defaultValue)`
|
|
156
|
+
|
|
157
|
+
Gets the value for a key in a `Map`, or sets it to `defaultValue` if missing.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## String Utils
|
|
162
|
+
|
|
163
|
+
### Functions
|
|
164
|
+
|
|
165
|
+
#### `kebabCaseToCamelCase(str)`
|
|
166
|
+
|
|
167
|
+
Converts `'my-string'` → `'myString'`.
|
|
168
|
+
|
|
169
|
+
#### `kebabCaseToPascalCase(str)`
|
|
170
|
+
|
|
171
|
+
Converts `'my-string'` → `'MyString'`.
|
|
172
|
+
|
|
173
|
+
#### `generateRandomString(length=8)`
|
|
174
|
+
|
|
175
|
+
Generates a random alphanumeric string.
|
|
176
|
+
|
|
177
|
+
#### `pluralize(word)`
|
|
178
|
+
|
|
179
|
+
Returns the plural form of an English noun, handling irregulars and uncountable nouns.
|
|
180
|
+
|
|
181
|
+
Example:
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { pluralize } from '@stonyx/utils/string';
|
|
185
|
+
|
|
186
|
+
console.log(pluralize('person')); // people
|
|
187
|
+
console.log(pluralize('box')); // boxes
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Date Utils
|
|
193
|
+
|
|
194
|
+
### Functions
|
|
195
|
+
|
|
196
|
+
#### `getTimestamp()`
|
|
197
|
+
|
|
198
|
+
Returns the current UNIX timestamp (seconds since epoch).
|
|
17
199
|
|
|
18
200
|
```js
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
201
|
+
import { getTimestamp } from '@stonyx/utils/date';
|
|
202
|
+
|
|
203
|
+
console.log(getTimestamp()); // e.g., 1693564800
|
|
22
204
|
```
|
|
23
205
|
|
|
24
|
-
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Promise Utils
|
|
209
|
+
|
|
210
|
+
### Functions
|
|
211
|
+
|
|
212
|
+
#### `sleep(seconds)`
|
|
213
|
+
|
|
214
|
+
Delays execution for the given number of seconds.
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
import { sleep } from '@stonyx/utils/promise';
|
|
218
|
+
|
|
219
|
+
await sleep(2); // waits 2 seconds
|
|
220
|
+
```
|
|
25
221
|
|
|
26
|
-
|
|
27
|
-
| :---: | :---: | :---: | :--- |
|
|
28
|
-
| `fullExport` | **Boolean** | *false* | When set to true, The `exports` parameter will be all exports, and not just the default one. |
|
|
29
|
-
| `rawName` | **Boolean** | *false* | When set to true, `forEachFileImport` will not convert the file name to be camelCase and leave it raw instead |
|
|
30
|
-
| `ignoreAccessFailure` | **Boolean** | *false* | When set to true, failure to load directory will be ignored |
|
|
222
|
+
---
|
|
31
223
|
|
|
32
224
|
## License
|
|
33
225
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"keywords": [
|
|
4
4
|
"stonyx-module"
|
|
5
5
|
],
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.2.0",
|
|
7
7
|
"description": "Utils module for Stonyx Framework",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"fs": "^0.0.1-security",
|
|
26
|
-
"qunit": "^2.24.1"
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
"qunit": "^2.24.1",
|
|
27
|
+
"sinon": "^21.0.0"
|
|
28
|
+
}
|
|
29
29
|
}
|
package/src/object.js
CHANGED
|
@@ -2,14 +2,20 @@ export function deepCopy(obj) {
|
|
|
2
2
|
return JSON.parse(JSON.stringify(obj));
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
export function objToJson(obj) {
|
|
6
|
-
return JSON.stringify(obj);
|
|
5
|
+
export function objToJson(obj, format='\t') {
|
|
6
|
+
return JSON.stringify(obj, null, format);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function makeArray(obj) {
|
|
10
10
|
return Array.isArray(obj) ? obj : [obj];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function cloneShallow(value) {
|
|
14
|
+
if (Array.isArray(value)) return value.slice();
|
|
15
|
+
if (value && typeof value === 'object') return { ...value };
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export function mergeObject(obj1, obj2, options={}) {
|
|
14
20
|
if (Array.isArray(obj1) || Array.isArray(obj2)) throw new Error('Cannot merge arrays.');
|
|
15
21
|
|
|
@@ -32,8 +38,24 @@ export function mergeObject(obj1, obj2, options={}) {
|
|
|
32
38
|
return result;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
function
|
|
36
|
-
if (
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
41
|
+
export function get(obj, path) {
|
|
42
|
+
if (arguments.length !== 2) return console.error('Get must be called with two arguments; an object and a property key.');
|
|
43
|
+
if (!obj) return console.error(`Cannot call get with '${path}' on an undefined object.`);
|
|
44
|
+
if (typeof path !== 'string') return console.error('The path provided to get must be a string.');
|
|
45
|
+
|
|
46
|
+
for (const key of path.split('.')) {
|
|
47
|
+
if (obj[key] === undefined) return null;
|
|
48
|
+
|
|
49
|
+
obj = obj[key];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return obj;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getOrSet(map, key, defaultValue) {
|
|
56
|
+
if (!(map instanceof Map)) throw new Error('First argument to getOrSet must be a Map.');
|
|
57
|
+
|
|
58
|
+
if (!map.has(key)) map.set(key, typeof defaultValue === "function" ? defaultValue() : defaultValue);
|
|
59
|
+
|
|
60
|
+
return map.get(key);
|
|
39
61
|
}
|
package/src/plurarize.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// --- Irregular nouns ---
|
|
2
|
+
const irregular = {
|
|
3
|
+
person: 'people',
|
|
4
|
+
man: 'men',
|
|
5
|
+
woman: 'women',
|
|
6
|
+
child: 'children',
|
|
7
|
+
tooth: 'teeth',
|
|
8
|
+
foot: 'feet',
|
|
9
|
+
mouse: 'mice',
|
|
10
|
+
goose: 'geese',
|
|
11
|
+
ox: 'oxen',
|
|
12
|
+
cactus: 'cacti',
|
|
13
|
+
nucleus: 'nuclei',
|
|
14
|
+
syllabus: 'syllabi',
|
|
15
|
+
focus: 'foci',
|
|
16
|
+
fungus: 'fungi',
|
|
17
|
+
appendix: 'appendices',
|
|
18
|
+
index: 'indices',
|
|
19
|
+
criterion: 'criteria',
|
|
20
|
+
phenomenon: 'phenomena',
|
|
21
|
+
die: 'dice',
|
|
22
|
+
thesis: 'theses',
|
|
23
|
+
analysis: 'analyses',
|
|
24
|
+
crisis: 'crises',
|
|
25
|
+
radius: 'radii',
|
|
26
|
+
corpus: 'corpora',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// --- Uncountables ---
|
|
30
|
+
const uncountable = new Set([
|
|
31
|
+
'sheep', 'fish', 'deer', 'series', 'species', 'news', 'information',
|
|
32
|
+
'rice', 'moose', 'bison', 'salmon', 'aircraft', 'offspring'
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// --- Exceptions ---
|
|
36
|
+
const fExceptions = new Set(['chief', 'roof', 'belief', 'chef', 'cliff', 'reef', 'proof', 'brief']);
|
|
37
|
+
|
|
38
|
+
// Keep only true irregular -o exceptions (consonant + o but take just "s")
|
|
39
|
+
const oExceptions = new Set(['piano', 'photo', 'halo', 'canto', 'solo']);
|
|
40
|
+
|
|
41
|
+
// --- Utility to preserve casing ---
|
|
42
|
+
function applyCasing(original, plural) {
|
|
43
|
+
if (original === original.toUpperCase()) return plural.toUpperCase();
|
|
44
|
+
if (original === original.toLowerCase()) return plural.toLowerCase();
|
|
45
|
+
if (original[0] === original[0].toUpperCase()) {
|
|
46
|
+
return plural.charAt(0).toUpperCase() + plural.slice(1);
|
|
47
|
+
}
|
|
48
|
+
return plural;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --- Rule-based pluralization ---
|
|
52
|
+
const rules = [
|
|
53
|
+
// quiz → quizzes, waltz → waltzes, topaz → topazes
|
|
54
|
+
[/z$/i, w => (/iz$/i.test(w) ? w + 'zes' : w + 'es')],
|
|
55
|
+
|
|
56
|
+
// bus → buses, box → boxes, church → churches, but stomach → stomachs (exclude -ach)
|
|
57
|
+
[/(s|x|ch|sh)$/i, w => (/ach$/i.test(w) ? w + 's' : w + 'es')],
|
|
58
|
+
|
|
59
|
+
// vowel + y → +s (key → keys)
|
|
60
|
+
[/[aeiou]y$/i, w => w + 's'],
|
|
61
|
+
|
|
62
|
+
// consonant + y → -ies (city → cities)
|
|
63
|
+
[/y$/i, w => w.slice(0, -1) + 'ies'],
|
|
64
|
+
|
|
65
|
+
// -fe → -ves (knife → knives), but not chief/roof/etc
|
|
66
|
+
[/fe$/i, w => (fExceptions.has(w) ? w + 's' : w.slice(0, -2) + 'ves')],
|
|
67
|
+
|
|
68
|
+
// -f → -ves (wolf → wolves), but not cliff/etc
|
|
69
|
+
[/f$/i, w => (fExceptions.has(w) ? w + 's' : w.slice(0, -1) + 'ves')],
|
|
70
|
+
|
|
71
|
+
// -sis → -ses (analysis → analyses, thesis → theses)
|
|
72
|
+
[/sis$/i, w => w.slice(0, -2) + 'ses'],
|
|
73
|
+
|
|
74
|
+
// vowel + o → +s (zoo → zoos, video → videos, patio → patios)
|
|
75
|
+
[/[aeiou]o$/i, w => w + 's'],
|
|
76
|
+
|
|
77
|
+
// consonant + o → usually +es, unless in oExceptions
|
|
78
|
+
[/o$/i, w => (oExceptions.has(w) ? w + 's' : w + 'es')],
|
|
79
|
+
|
|
80
|
+
// default: just +s
|
|
81
|
+
[/$/i, w => w + 's']
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
// --- Exported pluralizer ---
|
|
85
|
+
export default function pluralize(word) {
|
|
86
|
+
if (typeof word !== 'string' || !/^[a-zA-Z]+$/.test(word)) {
|
|
87
|
+
throw new Error('Input must be a single word containing only letters.');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lower = word.toLowerCase();
|
|
91
|
+
|
|
92
|
+
if (uncountable.has(lower)) return word;
|
|
93
|
+
|
|
94
|
+
if (irregular[lower]) {
|
|
95
|
+
return applyCasing(word, irregular[lower]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const [pattern, transform] of rules) {
|
|
99
|
+
if (pattern.test(lower)) {
|
|
100
|
+
return applyCasing(word, transform(lower));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return word; // fallback (shouldn't hit)
|
|
105
|
+
}
|
package/src/string.js
CHANGED
|
@@ -23,11 +23,9 @@ export function kebabCaseToPascalCase(str) {
|
|
|
23
23
|
return kebabToCase(str, true);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function pluralize(str) {
|
|
27
|
-
return `${str}s`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
export function generateRandomString(length=8) {
|
|
31
27
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
32
28
|
return Array(length).fill('').map(() => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
|
|
33
|
-
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { default as pluralize } from './plurarize.js';
|