@weborigami/async-tree 0.0.48 → 0.0.50
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/main.js +12 -6
- package/package.json +4 -4
- package/src/BrowserFileTree.js +3 -2
- package/src/FileTree.js +10 -4
- package/src/Tree.js +19 -6
- package/src/operations/{mergeDeep.js → deepMerge.js} +21 -4
- package/src/operations/deepTake.js +15 -0
- package/src/operations/deepTakeFn.js +37 -0
- package/src/operations/deepValues.js +10 -0
- package/src/operations/group.js +12 -0
- package/src/{transforms/groupBy.js → operations/groupFn.js} +2 -2
- package/src/operations/map.js +14 -0
- package/src/operations/merge.js +9 -4
- package/src/operations/sort.js +18 -0
- package/src/operations/take.js +11 -0
- package/src/transforms/{map.js → mapFn.js} +8 -2
- package/src/transforms/sortFn.js +67 -0
- package/src/transforms/takeFn.js +25 -0
- package/src/utilities.d.ts +1 -2
- package/src/utilities.js +10 -18
- package/test/operations/{mergeDeep.test.js → deepMerge.test.js} +1 -1
- package/test/operations/deepTakeFn.test.js +21 -0
- package/test/operations/deepValues.test.js +22 -0
- package/test/transforms/{groupBy.test.js → groupFn.test.js} +3 -3
- package/test/transforms/keyMapsForExtensions.test.js +1 -1
- package/test/transforms/{map.test.js → mapFn.test.js} +13 -13
- package/test/transforms/sortFn.test.js +49 -0
- package/test/transforms/takeFn.test.js +20 -0
- package/test/utilities.test.js +6 -0
- package/src/transforms/sort.js +0 -25
- package/src/transforms/sortBy.js +0 -33
- package/src/transforms/sortNatural.js +0 -10
- package/test/transforms/sort.test.js +0 -30
- package/test/transforms/sortBy.test.js +0 -22
- package/test/transforms/sortNatural.test.js +0 -21
package/main.js
CHANGED
|
@@ -11,14 +11,20 @@ export { default as SiteTree } from "./src/SiteTree.js";
|
|
|
11
11
|
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
12
12
|
export * as keysJson from "./src/keysJson.js";
|
|
13
13
|
export { default as cache } from "./src/operations/cache.js";
|
|
14
|
+
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
|
15
|
+
export { default as deepTake } from "./src/operations/deepTake.js";
|
|
16
|
+
export { default as deepTakeFn } from "./src/operations/deepTakeFn.js";
|
|
17
|
+
export { default as deepValues } from "./src/operations/deepValues.js";
|
|
18
|
+
export { default as group } from "./src/operations/group.js";
|
|
19
|
+
export { default as groupFn } from "./src/operations/groupFn.js";
|
|
20
|
+
export { default as map } from "./src/operations/map.js";
|
|
14
21
|
export { default as merge } from "./src/operations/merge.js";
|
|
15
|
-
export { default as
|
|
22
|
+
export { default as sort } from "./src/operations/sort.js";
|
|
23
|
+
export { default as take } from "./src/operations/take.js";
|
|
16
24
|
export * as symbols from "./src/symbols.js";
|
|
17
25
|
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
18
|
-
export { default as groupBy } from "./src/transforms/groupBy.js";
|
|
19
26
|
export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
|
|
20
|
-
export { default as
|
|
21
|
-
export { default as
|
|
22
|
-
export { default as
|
|
23
|
-
export { default as sortNatural } from "./src/transforms/sortNatural.js";
|
|
27
|
+
export { default as mapFn } from "./src/transforms/mapFn.js";
|
|
28
|
+
export { default as sortFn } from "./src/transforms/sortFn.js";
|
|
29
|
+
export { default as takeFn } from "./src/transforms/takeFn.js";
|
|
24
30
|
export * from "./src/utilities.js";
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"browser": "./browser.js",
|
|
8
8
|
"types": "./index.ts",
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@types/node": "20.
|
|
11
|
-
"typescript": "5.4.
|
|
10
|
+
"@types/node": "20.12.8",
|
|
11
|
+
"typescript": "5.4.5"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.50"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/BrowserFileTree.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tree } from "./internal.js";
|
|
2
|
-
import { hiddenFileNames, isStringLike,
|
|
2
|
+
import { hiddenFileNames, isStringLike, naturalOrder } from "./utilities.js";
|
|
3
3
|
|
|
4
4
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
5
5
|
|
|
@@ -74,7 +74,8 @@ export default class BrowserFileTree {
|
|
|
74
74
|
}
|
|
75
75
|
// Filter out unhelpful file names.
|
|
76
76
|
keys = keys.filter((key) => !hiddenFileNames.includes(key));
|
|
77
|
-
|
|
77
|
+
keys.sort(naturalOrder);
|
|
78
|
+
|
|
78
79
|
return keys;
|
|
79
80
|
}
|
|
80
81
|
|
package/src/FileTree.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getRealmObjectPrototype,
|
|
7
7
|
hiddenFileNames,
|
|
8
8
|
isPacked,
|
|
9
|
-
|
|
9
|
+
naturalOrder,
|
|
10
10
|
} from "./utilities.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -41,6 +41,11 @@ export default class FileTree {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
async get(key) {
|
|
44
|
+
if (!key) {
|
|
45
|
+
// Undefined key or empty string key is invalid.
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
const filePath = path.resolve(this.dirname, key);
|
|
45
50
|
|
|
46
51
|
let stats;
|
|
@@ -91,7 +96,8 @@ export default class FileTree {
|
|
|
91
96
|
|
|
92
97
|
// Node fs.readdir sort order appears to be unreliable; see, e.g.,
|
|
93
98
|
// https://github.com/nodejs/node/issues/3232.
|
|
94
|
-
|
|
99
|
+
names.sort(naturalOrder);
|
|
100
|
+
|
|
95
101
|
return names;
|
|
96
102
|
}
|
|
97
103
|
|
|
@@ -101,7 +107,7 @@ export default class FileTree {
|
|
|
101
107
|
|
|
102
108
|
async set(key, value) {
|
|
103
109
|
// Where are we going to write this value?
|
|
104
|
-
const stringKey = key ? String(key) : "";
|
|
110
|
+
const stringKey = key != null ? String(key) : "";
|
|
105
111
|
const destPath = path.resolve(this.dirname, stringKey);
|
|
106
112
|
|
|
107
113
|
if (value === undefined) {
|
|
@@ -189,7 +195,7 @@ function isStringLike(obj) {
|
|
|
189
195
|
return true;
|
|
190
196
|
} else if (obj?.toString === undefined) {
|
|
191
197
|
return false;
|
|
192
|
-
} else if (obj.toString === getRealmObjectPrototype(obj)
|
|
198
|
+
} else if (obj.toString === getRealmObjectPrototype(obj)?.toString) {
|
|
193
199
|
// The stupid Object.prototype.toString implementation always returns
|
|
194
200
|
// "[object Object]", so if that's the only toString method the object has,
|
|
195
201
|
// we return false.
|
package/src/Tree.js
CHANGED
|
@@ -3,7 +3,7 @@ import FunctionTree from "./FunctionTree.js";
|
|
|
3
3
|
import MapTree from "./MapTree.js";
|
|
4
4
|
import SetTree from "./SetTree.js";
|
|
5
5
|
import { DeepObjectTree, ObjectTree } from "./internal.js";
|
|
6
|
-
import mapTransform from "./transforms/
|
|
6
|
+
import mapTransform from "./transforms/mapFn.js";
|
|
7
7
|
import * as utilities from "./utilities.js";
|
|
8
8
|
import {
|
|
9
9
|
castArrayLike,
|
|
@@ -388,11 +388,16 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
388
388
|
value = await value.unpack();
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
391
|
+
if (!isTreelike(value)) {
|
|
392
|
+
// Value isn't treelike, so can't be traversed except for a special case:
|
|
393
|
+
// if there's only one key left and it's an empty string, return the
|
|
394
|
+
// non-treelike value.
|
|
395
|
+
if (remainingKeys.length === 1 && remainingKeys[0] === "") {
|
|
396
|
+
return value;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (value instanceof Function) {
|
|
396
401
|
// Value is a function: call it with the remaining keys.
|
|
397
402
|
const fn = value;
|
|
398
403
|
// We'll take as many keys as the function's length, but at least one.
|
|
@@ -401,12 +406,20 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
401
406
|
key = null;
|
|
402
407
|
value = await fn.call(target, ...args);
|
|
403
408
|
} else {
|
|
409
|
+
const originalValue = value;
|
|
410
|
+
|
|
404
411
|
// Value is some other treelike object: cast it to a tree.
|
|
405
412
|
const tree = from(value);
|
|
406
413
|
// Get the next key.
|
|
407
414
|
key = remainingKeys.shift();
|
|
408
415
|
// Get the value for the key.
|
|
409
416
|
value = await tree.get(key);
|
|
417
|
+
|
|
418
|
+
// The empty key as the final key is a special case: if the tree doesn't
|
|
419
|
+
// have a value for the empty key, use the original value.
|
|
420
|
+
if (value === undefined && remainingKeys.length === 0 && key === "") {
|
|
421
|
+
value = originalValue;
|
|
422
|
+
}
|
|
410
423
|
}
|
|
411
424
|
}
|
|
412
425
|
|
|
@@ -4,11 +4,14 @@ import { Tree } from "../internal.js";
|
|
|
4
4
|
* Return a tree that performs a deep merge of the given trees.
|
|
5
5
|
*
|
|
6
6
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
+
* @param {import("../../index.ts").Treelike[]} sources
|
|
7
8
|
* @returns {AsyncTree & { description: string }}
|
|
8
9
|
*/
|
|
9
|
-
export default function
|
|
10
|
+
export default function deepMerge(...sources) {
|
|
11
|
+
let trees = sources.map((treelike) => Tree.from(treelike));
|
|
12
|
+
let mergeParent;
|
|
10
13
|
return {
|
|
11
|
-
description: "
|
|
14
|
+
description: "deepMerge",
|
|
12
15
|
|
|
13
16
|
async get(key) {
|
|
14
17
|
const subtrees = [];
|
|
@@ -22,12 +25,12 @@ export default function mergeDeep(...trees) {
|
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
return subtrees.length > 0 ?
|
|
28
|
+
return subtrees.length > 0 ? deepMerge(...subtrees) : undefined;
|
|
26
29
|
},
|
|
27
30
|
|
|
28
31
|
async isKeyForSubtree(key) {
|
|
29
32
|
for (const tree of trees) {
|
|
30
|
-
if (await
|
|
33
|
+
if (await Tree.isKeyForSubtree(tree, key)) {
|
|
31
34
|
return true;
|
|
32
35
|
}
|
|
33
36
|
}
|
|
@@ -43,5 +46,19 @@ export default function mergeDeep(...trees) {
|
|
|
43
46
|
}
|
|
44
47
|
return keys;
|
|
45
48
|
},
|
|
49
|
+
|
|
50
|
+
get parent() {
|
|
51
|
+
return mergeParent;
|
|
52
|
+
},
|
|
53
|
+
set parent(parent) {
|
|
54
|
+
mergeParent = parent;
|
|
55
|
+
trees = sources.map((treelike) => {
|
|
56
|
+
const tree = Tree.isAsyncTree(treelike)
|
|
57
|
+
? Object.create(treelike)
|
|
58
|
+
: Tree.from(treelike);
|
|
59
|
+
tree.parent = parent;
|
|
60
|
+
return tree;
|
|
61
|
+
});
|
|
62
|
+
},
|
|
46
63
|
};
|
|
47
64
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import deepTakeFn from "./deepTakeFn.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a function that traverses a tree deeply and returns the values of the
|
|
5
|
+
* first `count` keys.
|
|
6
|
+
*
|
|
7
|
+
* This is similar to `deepValues`, but it is more efficient for large trees as
|
|
8
|
+
* stops after `count` values.
|
|
9
|
+
*
|
|
10
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
11
|
+
* @param {number} count
|
|
12
|
+
*/
|
|
13
|
+
export default function deepTake(treelike, count) {
|
|
14
|
+
return deepTakeFn(count)(treelike);
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a function that traverses a tree deeply and returns the values of the
|
|
5
|
+
* first `count` keys.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} count
|
|
8
|
+
*/
|
|
9
|
+
export default function deepTakeFn(count) {
|
|
10
|
+
/**
|
|
11
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
12
|
+
*/
|
|
13
|
+
return async function deepTakeFn(treelike) {
|
|
14
|
+
const tree = await Tree.from(treelike);
|
|
15
|
+
const { values } = await traverse(tree, count);
|
|
16
|
+
return values;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function traverse(tree, count) {
|
|
21
|
+
const values = [];
|
|
22
|
+
for (const key of await tree.keys()) {
|
|
23
|
+
if (count <= 0) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
let value = await tree.get(key);
|
|
27
|
+
if (Tree.isAsyncTree(value)) {
|
|
28
|
+
const traversed = await traverse(value, count);
|
|
29
|
+
values.push(...traversed.values);
|
|
30
|
+
count = traversed.count;
|
|
31
|
+
} else {
|
|
32
|
+
values.push(value);
|
|
33
|
+
count--;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { count, values };
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the in-order exterior values of a tree as a flat array.
|
|
5
|
+
*
|
|
6
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
7
|
+
*/
|
|
8
|
+
export default async function deepValues(treelike) {
|
|
9
|
+
return Tree.mapReduce(treelike, null, async (values) => values.flat());
|
|
10
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import groupFn from "./groupFn.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given a function that returns a grouping key for a value, returns a transform
|
|
5
|
+
* that applies that grouping function to a tree.
|
|
6
|
+
*
|
|
7
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
8
|
+
* @param {import("../../index.ts").ValueKeyFn} groupKeyFn
|
|
9
|
+
*/
|
|
10
|
+
export default function group(treelike, groupKeyFn) {
|
|
11
|
+
return groupFn(groupKeyFn)(treelike);
|
|
12
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Given a function that returns a grouping key for a value, returns a transform
|
|
@@ -36,6 +36,6 @@ export default function createGroupByTransform(groupKeyFn) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
return new
|
|
39
|
+
return new ObjectTree(result);
|
|
40
40
|
};
|
|
41
41
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import mapFn from "../transforms/mapFn.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Transform the keys and/or values of a tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
|
7
|
+
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
8
|
+
*
|
|
9
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
10
|
+
* @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
|
|
11
|
+
*/
|
|
12
|
+
export default function map(treelike, options = {}) {
|
|
13
|
+
return mapFn(options)(treelike);
|
|
14
|
+
}
|
package/src/operations/merge.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Return a tree that performs a shallow merge of the given trees.
|
|
3
5
|
*
|
|
@@ -8,10 +10,11 @@
|
|
|
8
10
|
* return a defined value, the `get` method returns undefined.
|
|
9
11
|
*
|
|
10
12
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
13
|
+
* @param {import("../../index.ts").Treelike[]} sources
|
|
11
14
|
* @returns {AsyncTree & { description: string }}
|
|
12
15
|
*/
|
|
13
16
|
export default function merge(...sources) {
|
|
14
|
-
let trees = sources;
|
|
17
|
+
let trees = sources.map((treelike) => Tree.from(treelike));
|
|
15
18
|
let mergeParent;
|
|
16
19
|
return {
|
|
17
20
|
description: "merge",
|
|
@@ -28,7 +31,7 @@ export default function merge(...sources) {
|
|
|
28
31
|
|
|
29
32
|
async isKeyForSubtree(key) {
|
|
30
33
|
for (const tree of trees) {
|
|
31
|
-
if (await
|
|
34
|
+
if (await Tree.isKeyForSubtree(tree, key)) {
|
|
32
35
|
return true;
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -50,8 +53,10 @@ export default function merge(...sources) {
|
|
|
50
53
|
},
|
|
51
54
|
set parent(parent) {
|
|
52
55
|
mergeParent = parent;
|
|
53
|
-
trees = sources.map((
|
|
54
|
-
const tree =
|
|
56
|
+
trees = sources.map((treelike) => {
|
|
57
|
+
const tree = Tree.isAsyncTree(treelike)
|
|
58
|
+
? Object.create(treelike)
|
|
59
|
+
: Tree.from(treelike);
|
|
55
60
|
tree.parent = parent;
|
|
56
61
|
return tree;
|
|
57
62
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sortFn from "../transforms/sortFn.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a new tree with the original's keys sorted. A comparison function can
|
|
5
|
+
* be provided; by default the keys will be sorted in [natural sort
|
|
6
|
+
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
|
|
10
|
+
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
|
|
11
|
+
* SortOptions
|
|
12
|
+
*
|
|
13
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
14
|
+
* @param {SortOptions} [options]
|
|
15
|
+
*/
|
|
16
|
+
export default function sort(treelike, options) {
|
|
17
|
+
return sortFn(options)(treelike);
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import takeFn from "../transforms/takeFn.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a new tree with the number of keys limited to the indicated count.
|
|
5
|
+
*
|
|
6
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
7
|
+
* @param {number} count
|
|
8
|
+
*/
|
|
9
|
+
export default function take(treelike, count) {
|
|
10
|
+
return takeFn(count)(treelike);
|
|
11
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Tree } from "../internal.js";
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
|
+
import { isPlainObject } from "../utilities.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return a transform function that maps the keys and/or values of a tree.
|
|
@@ -7,6 +8,7 @@ import { Tree } from "../internal.js";
|
|
|
7
8
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
8
9
|
*
|
|
9
10
|
* @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
|
|
11
|
+
* @returns {import("../../index.ts").TreeTransform}
|
|
10
12
|
*/
|
|
11
13
|
export default function createMapTransform(options = {}) {
|
|
12
14
|
let deep;
|
|
@@ -45,7 +47,11 @@ export default function createMapTransform(options = {}) {
|
|
|
45
47
|
* @type {import("../../index.ts").TreeTransform}
|
|
46
48
|
*/
|
|
47
49
|
return function map(treelike) {
|
|
48
|
-
const tree =
|
|
50
|
+
const tree =
|
|
51
|
+
!deep && isPlainObject(treelike) && !Tree.isAsyncTree(treelike)
|
|
52
|
+
? new ObjectTree(treelike)
|
|
53
|
+
: Tree.from(treelike);
|
|
54
|
+
|
|
49
55
|
// The transformed tree is actually an extension of the original tree's
|
|
50
56
|
// prototype chain. This allows the transformed tree to inherit any
|
|
51
57
|
// properties/methods. For example, the `parent` of the transformed tree is
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a transform function that sorts a tree's keys using a comparison
|
|
5
|
+
* function.
|
|
6
|
+
*
|
|
7
|
+
* If the `options` include a `sortKey` function, that will be invoked for each
|
|
8
|
+
* key in the tree to produce a sort key. If no `sortKey` function is provided,
|
|
9
|
+
* the original keys will be used as sort keys.
|
|
10
|
+
*
|
|
11
|
+
* If the `options` include a `compare` function, that will be used to compare
|
|
12
|
+
* sort keys.
|
|
13
|
+
*
|
|
14
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
15
|
+
* @typedef {(key: any, tree: AsyncTree) => any} SortKeyFn
|
|
16
|
+
* @typedef {{ compare?: (a: any, b: any) => number, sortKey?: SortKeyFn }}
|
|
17
|
+
* SortOptions
|
|
18
|
+
*
|
|
19
|
+
* @param {SortOptions} [options]
|
|
20
|
+
*/
|
|
21
|
+
export default function sortFn(options) {
|
|
22
|
+
const sortKey = options?.sortKey;
|
|
23
|
+
let compare = options?.compare;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @type {import("../../index.ts").TreeTransform}
|
|
27
|
+
*/
|
|
28
|
+
return function sort(treelike) {
|
|
29
|
+
const tree = Tree.from(treelike);
|
|
30
|
+
const transformed = Object.create(tree);
|
|
31
|
+
transformed.keys = async () => {
|
|
32
|
+
const keys = Array.from(await tree.keys());
|
|
33
|
+
|
|
34
|
+
if (sortKey) {
|
|
35
|
+
// Invoke the async sortKey function to get sort keys.
|
|
36
|
+
// Create { key, sortKey } tuples.
|
|
37
|
+
const tuples = await Promise.all(
|
|
38
|
+
keys.map(async (key) => {
|
|
39
|
+
const sort = await sortKey(key, tree);
|
|
40
|
+
if (sort === undefined) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`sortKey function returned undefined for key ${key}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return { key, sort };
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Wrap the comparison function so it applies to sort keys.
|
|
50
|
+
const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
51
|
+
const originalCompare = compare ?? defaultCompare;
|
|
52
|
+
// Sort by the sort key.
|
|
53
|
+
const sortedTuples = tuples.toSorted((a, b) =>
|
|
54
|
+
originalCompare(a.sort, b.sort)
|
|
55
|
+
);
|
|
56
|
+
// Map back to the original keys.
|
|
57
|
+
const sorted = sortedTuples.map((pair) => pair.key);
|
|
58
|
+
return sorted;
|
|
59
|
+
} else {
|
|
60
|
+
// Use original keys as sort keys.
|
|
61
|
+
// If compare is undefined, this uses default sort order.
|
|
62
|
+
return keys.toSorted(compare);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return transformed;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Limit the number of keys to the indicated count.
|
|
5
|
+
*
|
|
6
|
+
* @param {number} count
|
|
7
|
+
*/
|
|
8
|
+
export default function take(count) {
|
|
9
|
+
/**
|
|
10
|
+
* @type {import("../../index.ts").TreeTransform}
|
|
11
|
+
*/
|
|
12
|
+
return (treelike) => {
|
|
13
|
+
const tree = Tree.from(treelike);
|
|
14
|
+
return {
|
|
15
|
+
async keys() {
|
|
16
|
+
const keys = Array.from(await tree.keys());
|
|
17
|
+
return keys.slice(0, count);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async get(key) {
|
|
21
|
+
return tree.get(key);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
package/src/utilities.d.ts
CHANGED
|
@@ -8,5 +8,4 @@ export function isPlainObject(object: any): object is PlainObject;
|
|
|
8
8
|
export function isUnpackable(object): object is { unpack: () => any };
|
|
9
9
|
export function isStringLike(obj: any): obj is StringLike;
|
|
10
10
|
export function keysFromPath(path: string): string[];
|
|
11
|
-
export const
|
|
12
|
-
export function sortNatural(array: string[]): void;
|
|
11
|
+
export const naturalOrder: (a: string, b: string) => number;
|
package/src/utilities.js
CHANGED
|
@@ -30,6 +30,10 @@ export function castArrayLike(object) {
|
|
|
30
30
|
* @param {any} object
|
|
31
31
|
*/
|
|
32
32
|
export function getRealmObjectPrototype(object) {
|
|
33
|
+
if (Object.getPrototypeOf(object) === null) {
|
|
34
|
+
// The object has no prototype.
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
33
37
|
let proto = object;
|
|
34
38
|
while (Object.getPrototypeOf(proto) !== null) {
|
|
35
39
|
proto = Object.getPrototypeOf(proto);
|
|
@@ -96,7 +100,7 @@ export function isStringLike(object) {
|
|
|
96
100
|
return true;
|
|
97
101
|
} else if (object?.toString === undefined) {
|
|
98
102
|
return false;
|
|
99
|
-
} else if (object.toString === getRealmObjectPrototype(object)
|
|
103
|
+
} else if (object.toString === getRealmObjectPrototype(object)?.toString) {
|
|
100
104
|
// The stupid Object.prototype.toString implementation always returns
|
|
101
105
|
// "[object Object]", so if that's the only toString method the object has,
|
|
102
106
|
// we return false.
|
|
@@ -131,22 +135,10 @@ export function keysFromPath(pathname) {
|
|
|
131
135
|
return keys;
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
// Used for natural sort order
|
|
135
|
-
export const naturalSortCompareFn = new Intl.Collator(undefined, {
|
|
136
|
-
numeric: true,
|
|
137
|
-
}).compare;
|
|
138
|
-
|
|
139
138
|
/**
|
|
140
|
-
*
|
|
141
|
-
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
142
|
-
* `Array` `sort` function, this operation destructively modifies the array in
|
|
143
|
-
* place.
|
|
144
|
-
*
|
|
145
|
-
* The default sort order for some sources like operating system files can be
|
|
146
|
-
* unpredictable. Since it's quite common for file names to include numbers, it
|
|
147
|
-
* can helpful to use natural sort order instead: ["file1", "file9", "file10"]
|
|
148
|
-
* instead of ["file1", "file10", "file9"].
|
|
139
|
+
* Compare two strings using [natural sort
|
|
140
|
+
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
149
141
|
*/
|
|
150
|
-
export
|
|
151
|
-
|
|
152
|
-
}
|
|
142
|
+
export const naturalOrder = new Intl.Collator(undefined, {
|
|
143
|
+
numeric: true,
|
|
144
|
+
}).compare;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import { DeepObjectTree, Tree } from "../../src/internal.js";
|
|
4
|
-
import mergeDeep from "../../src/operations/
|
|
4
|
+
import mergeDeep from "../../src/operations/deepMerge.js";
|
|
5
5
|
|
|
6
6
|
describe("mergeDeep", () => {
|
|
7
7
|
test("can merge deep", async () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import deepTakeFn from "../../src/operations/deepTakeFn.js";
|
|
4
|
+
|
|
5
|
+
describe("deepTakeFn", () => {
|
|
6
|
+
test("traverses deeply and returns a limited number of items", async () => {
|
|
7
|
+
const tree = {
|
|
8
|
+
a: 1,
|
|
9
|
+
b: {
|
|
10
|
+
c: 2,
|
|
11
|
+
d: {
|
|
12
|
+
e: 3,
|
|
13
|
+
},
|
|
14
|
+
f: 4,
|
|
15
|
+
},
|
|
16
|
+
g: 5,
|
|
17
|
+
};
|
|
18
|
+
const result = await deepTakeFn(4)(tree);
|
|
19
|
+
assert.deepEqual(result, [1, 2, 3, 4]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import deepValues from "../../src/operations/deepValues.js";
|
|
4
|
+
|
|
5
|
+
describe("deepValues", () => {
|
|
6
|
+
test("returns in-order array of a tree's values", async () => {
|
|
7
|
+
const tree = {
|
|
8
|
+
a: 1,
|
|
9
|
+
b: {
|
|
10
|
+
c: 2,
|
|
11
|
+
d: {
|
|
12
|
+
e: 3,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
f: {
|
|
16
|
+
g: 4,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const values = await deepValues(tree);
|
|
20
|
+
assert.deepEqual(values, [1, 2, 3, 4]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import { Tree } from "../../src/internal.js";
|
|
4
|
-
import
|
|
4
|
+
import groupFn from "../../src/operations/groupFn.js";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("groupFn transform", () => {
|
|
7
7
|
test("groups using a group key function", async () => {
|
|
8
8
|
const fonts = [
|
|
9
9
|
{ name: "Aboreto", tags: ["Sans Serif"] },
|
|
@@ -12,7 +12,7 @@ describe("groupBy transform", () => {
|
|
|
12
12
|
{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
|
|
13
13
|
];
|
|
14
14
|
const tree = Tree.from(fonts);
|
|
15
|
-
const grouped = await
|
|
15
|
+
const grouped = await groupFn((value, key, tree) => value.tags)(tree);
|
|
16
16
|
assert.deepEqual(await Tree.plain(grouped), {
|
|
17
17
|
Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
|
|
18
18
|
Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
|
|
@@ -2,7 +2,7 @@ import assert from "node:assert";
|
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import { ObjectTree, Tree } from "../../src/internal.js";
|
|
4
4
|
import keyFunctionsForExtensions from "../../src/transforms/keyFunctionsForExtensions.js";
|
|
5
|
-
import map from "../../src/transforms/
|
|
5
|
+
import map from "../../src/transforms/mapFn.js";
|
|
6
6
|
|
|
7
7
|
describe("keyMapsForExtensions", () => {
|
|
8
8
|
test("returns key functions that pass a matching key through", async () => {
|
|
@@ -2,15 +2,15 @@ import assert from "node:assert";
|
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
3
|
import FunctionTree from "../../src/FunctionTree.js";
|
|
4
4
|
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
|
5
|
-
import
|
|
5
|
+
import mapFn from "../../src/transforms/mapFn.js";
|
|
6
6
|
|
|
7
|
-
describe("
|
|
7
|
+
describe("mapFn", () => {
|
|
8
8
|
test("returns identity graph if no key or value", async () => {
|
|
9
9
|
const tree = {
|
|
10
10
|
a: "letter a",
|
|
11
11
|
b: "letter b",
|
|
12
12
|
};
|
|
13
|
-
const mapped =
|
|
13
|
+
const mapped = mapFn()(tree);
|
|
14
14
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
15
15
|
a: "letter a",
|
|
16
16
|
b: "letter b",
|
|
@@ -23,7 +23,7 @@ describe("map", () => {
|
|
|
23
23
|
b: "letter b",
|
|
24
24
|
c: undefined, // Won't be mapped
|
|
25
25
|
});
|
|
26
|
-
const uppercaseValues =
|
|
26
|
+
const uppercaseValues = mapFn({
|
|
27
27
|
value: (sourceValue, sourceKey, innerTree) => {
|
|
28
28
|
assert(sourceKey === "a" || sourceKey === "b");
|
|
29
29
|
assert.equal(innerTree, tree);
|
|
@@ -43,7 +43,7 @@ describe("map", () => {
|
|
|
43
43
|
a: "letter a",
|
|
44
44
|
b: "letter b",
|
|
45
45
|
};
|
|
46
|
-
const uppercaseValues =
|
|
46
|
+
const uppercaseValues = mapFn((sourceValue, sourceKey, tree) => {
|
|
47
47
|
assert(sourceKey === "a" || sourceKey === "b");
|
|
48
48
|
return sourceValue.toUpperCase();
|
|
49
49
|
});
|
|
@@ -59,7 +59,7 @@ describe("map", () => {
|
|
|
59
59
|
a: "letter a",
|
|
60
60
|
b: "letter b",
|
|
61
61
|
};
|
|
62
|
-
const doubleKeys =
|
|
62
|
+
const doubleKeys = mapFn({
|
|
63
63
|
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
64
64
|
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
65
65
|
});
|
|
@@ -75,7 +75,7 @@ describe("map", () => {
|
|
|
75
75
|
a: "letter a",
|
|
76
76
|
b: "letter b",
|
|
77
77
|
};
|
|
78
|
-
const doubleKeysUppercaseValues =
|
|
78
|
+
const doubleKeysUppercaseValues = mapFn({
|
|
79
79
|
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
80
80
|
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
81
81
|
value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
@@ -94,7 +94,7 @@ describe("map", () => {
|
|
|
94
94
|
b: "letter b",
|
|
95
95
|
},
|
|
96
96
|
};
|
|
97
|
-
const doubleKeys =
|
|
97
|
+
const doubleKeys = mapFn({
|
|
98
98
|
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
99
99
|
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
100
100
|
value: async (sourceValue, sourceKey, tree) => sourceKey,
|
|
@@ -114,7 +114,7 @@ describe("map", () => {
|
|
|
114
114
|
a: "letter a",
|
|
115
115
|
b: "letter b",
|
|
116
116
|
};
|
|
117
|
-
const mapped =
|
|
117
|
+
const mapped = mapFn(uppercase)(tree);
|
|
118
118
|
assert.deepEqual(await Tree.plain(mapped), {
|
|
119
119
|
_a: "LETTER A",
|
|
120
120
|
_b: "LETTER B",
|
|
@@ -128,7 +128,7 @@ describe("map", () => {
|
|
|
128
128
|
b: "letter b",
|
|
129
129
|
},
|
|
130
130
|
});
|
|
131
|
-
const uppercaseValues =
|
|
131
|
+
const uppercaseValues = mapFn({
|
|
132
132
|
deep: true,
|
|
133
133
|
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
134
134
|
});
|
|
@@ -148,7 +148,7 @@ describe("map", () => {
|
|
|
148
148
|
b: "letter b",
|
|
149
149
|
},
|
|
150
150
|
});
|
|
151
|
-
const doubleKeys =
|
|
151
|
+
const doubleKeys = mapFn({
|
|
152
152
|
deep: true,
|
|
153
153
|
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
154
154
|
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
@@ -169,7 +169,7 @@ describe("map", () => {
|
|
|
169
169
|
b: "letter b",
|
|
170
170
|
},
|
|
171
171
|
});
|
|
172
|
-
const doubleKeysUppercaseValues =
|
|
172
|
+
const doubleKeysUppercaseValues = mapFn({
|
|
173
173
|
deep: true,
|
|
174
174
|
key: async (sourceKey, tree) => `_${sourceKey}`,
|
|
175
175
|
inverseKey: async (resultKey, tree) => resultKey.slice(1),
|
|
@@ -189,7 +189,7 @@ describe("map", () => {
|
|
|
189
189
|
const tree = new FunctionTree(() => {
|
|
190
190
|
flag = true;
|
|
191
191
|
}, ["a", "b", "c"]);
|
|
192
|
-
const mapped =
|
|
192
|
+
const mapped = mapFn({
|
|
193
193
|
needsSourceValue: false,
|
|
194
194
|
value: () => "X",
|
|
195
195
|
})(tree);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { Tree } from "../../src/internal.js";
|
|
4
|
+
import sortFn from "../../src/transforms/sortFn.js";
|
|
5
|
+
|
|
6
|
+
describe("sortFn", () => {
|
|
7
|
+
test("sorts keys using default sort order", async () => {
|
|
8
|
+
const tree = Tree.from({
|
|
9
|
+
file10: null,
|
|
10
|
+
file1: null,
|
|
11
|
+
file9: null,
|
|
12
|
+
});
|
|
13
|
+
const sorted = sortFn()(tree);
|
|
14
|
+
assert.deepEqual(Array.from(await sorted.keys()), [
|
|
15
|
+
"file1",
|
|
16
|
+
"file10",
|
|
17
|
+
"file9",
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("invokes a comparison function", async () => {
|
|
22
|
+
const tree = Tree.from({
|
|
23
|
+
b: 2,
|
|
24
|
+
c: 3,
|
|
25
|
+
a: 1,
|
|
26
|
+
});
|
|
27
|
+
// Reverse order
|
|
28
|
+
const compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
|
|
29
|
+
const sorted = sortFn({ compare })(tree);
|
|
30
|
+
assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("invokes a sortKey function", async () => {
|
|
34
|
+
const tree = {
|
|
35
|
+
Alice: { age: 48 },
|
|
36
|
+
Bob: { age: 36 },
|
|
37
|
+
Carol: { age: 42 },
|
|
38
|
+
};
|
|
39
|
+
const transform = await sortFn({
|
|
40
|
+
sortKey: async (key, tree) => Tree.traverse(tree, key, "age"),
|
|
41
|
+
});
|
|
42
|
+
const result = transform(tree);
|
|
43
|
+
assert.deepEqual(Array.from(await result.keys()), [
|
|
44
|
+
"Bob",
|
|
45
|
+
"Carol",
|
|
46
|
+
"Alice",
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { Tree } from "../../src/internal.js";
|
|
4
|
+
import takeFn from "../../src/transforms/takeFn.js";
|
|
5
|
+
|
|
6
|
+
describe("takeFn", () => {
|
|
7
|
+
test("limits the number of keys to the indicated count", async () => {
|
|
8
|
+
const tree = {
|
|
9
|
+
a: 1,
|
|
10
|
+
b: 2,
|
|
11
|
+
c: 3,
|
|
12
|
+
d: 4,
|
|
13
|
+
};
|
|
14
|
+
const result = await takeFn(2)(tree);
|
|
15
|
+
assert.deepEqual(await Tree.plain(result), {
|
|
16
|
+
a: 1,
|
|
17
|
+
b: 2,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
package/test/utilities.test.js
CHANGED
|
@@ -21,4 +21,10 @@ describe("utilities", () => {
|
|
|
21
21
|
assert.deepEqual(utilities.keysFromPath("a/b/c"), ["a", "b", "c"]);
|
|
22
22
|
assert.deepEqual(utilities.keysFromPath("foo/"), ["foo", ""]);
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
test("naturalOrder compares strings in natural order", () => {
|
|
26
|
+
const strings = ["file10", "file1", "file9"];
|
|
27
|
+
strings.sort(utilities.naturalOrder);
|
|
28
|
+
assert.deepEqual(strings, ["file1", "file9", "file10"]);
|
|
29
|
+
});
|
|
24
30
|
});
|
package/src/transforms/sort.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Tree } from "../internal.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Return a transform function that sorts a tree's keys.
|
|
5
|
-
*
|
|
6
|
-
* For sorting, the keys are converted to strings, then sorted according to each
|
|
7
|
-
* character's Unicode code point value.
|
|
8
|
-
*
|
|
9
|
-
* @param {(a: any, b: any) => number} [compareFn]
|
|
10
|
-
*/
|
|
11
|
-
export default function createSortTransform(compareFn) {
|
|
12
|
-
/**
|
|
13
|
-
* @type {import("../../index.ts").TreeTransform}
|
|
14
|
-
*/
|
|
15
|
-
return function sortTransform(treelike) {
|
|
16
|
-
const tree = Tree.from(treelike);
|
|
17
|
-
const transform = Object.create(tree);
|
|
18
|
-
transform.keys = async () => {
|
|
19
|
-
const keys = Array.from(await tree.keys());
|
|
20
|
-
keys.sort(compareFn);
|
|
21
|
-
return keys;
|
|
22
|
-
};
|
|
23
|
-
return transform;
|
|
24
|
-
};
|
|
25
|
-
}
|
package/src/transforms/sortBy.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Tree } from "../internal.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Return a transform function that sorts a tree's keys.
|
|
5
|
-
*
|
|
6
|
-
* The given `sortKeyFn` is invoked for each key/value pair in the tree, and
|
|
7
|
-
* should produce a sort key for that pair. The sort keys are then compared to
|
|
8
|
-
* determine the sort order for the tree's keys.
|
|
9
|
-
*
|
|
10
|
-
* @param {import("./map.js").KeyFn} sortKeyFn
|
|
11
|
-
*/
|
|
12
|
-
export default function createSortByTransform(sortKeyFn) {
|
|
13
|
-
/**
|
|
14
|
-
* @type {import("../../index.ts").TreeTransform}
|
|
15
|
-
*/
|
|
16
|
-
return function sortByTransform(treelike) {
|
|
17
|
-
const tree = Tree.from(treelike);
|
|
18
|
-
const transform = Object.create(tree);
|
|
19
|
-
transform.keys = async () => {
|
|
20
|
-
const keysAndSortKeys = [];
|
|
21
|
-
for (const key of await tree.keys()) {
|
|
22
|
-
const sortKey = await sortKeyFn(key, tree);
|
|
23
|
-
keysAndSortKeys.push({ key, sortKey });
|
|
24
|
-
}
|
|
25
|
-
keysAndSortKeys.sort((a, b) =>
|
|
26
|
-
a.sortKey < b.sortKey ? -1 : a.sortKey > b.sortKey ? 1 : 0
|
|
27
|
-
);
|
|
28
|
-
const sortedKeys = keysAndSortKeys.map(({ key }) => key);
|
|
29
|
-
return sortedKeys;
|
|
30
|
-
};
|
|
31
|
-
return transform;
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { naturalSortCompareFn } from "../utilities.js";
|
|
2
|
-
import sort from "./sort.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Return a transform function that sorts a tree's keys using [natural sort
|
|
6
|
-
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
7
|
-
*/
|
|
8
|
-
export default function sortNaturalTransform() {
|
|
9
|
-
return sort(naturalSortCompareFn);
|
|
10
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { Tree } from "../../src/internal.js";
|
|
4
|
-
import sort from "../../src/transforms/sort.js";
|
|
5
|
-
|
|
6
|
-
describe("sort transform", () => {
|
|
7
|
-
test("sorts keys", async () => {
|
|
8
|
-
const tree = Tree.from({
|
|
9
|
-
b: 2,
|
|
10
|
-
c: 3,
|
|
11
|
-
a: 1,
|
|
12
|
-
});
|
|
13
|
-
const sortTransform = sort();
|
|
14
|
-
const sorted = await sortTransform(tree);
|
|
15
|
-
assert.deepEqual(Array.from(await sorted.keys()), ["a", "b", "c"]);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("sorts keys using a comparison function", async () => {
|
|
19
|
-
const tree = Tree.from({
|
|
20
|
-
b: 2,
|
|
21
|
-
c: 3,
|
|
22
|
-
a: 1,
|
|
23
|
-
});
|
|
24
|
-
// Reverse order
|
|
25
|
-
const compareFn = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
|
|
26
|
-
const sortTransform = sort(compareFn);
|
|
27
|
-
const sorted = await sortTransform(tree);
|
|
28
|
-
assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { Tree } from "../../src/internal.js";
|
|
4
|
-
import sortBy from "../../src/transforms/sortBy.js";
|
|
5
|
-
|
|
6
|
-
describe("sortBy transform", () => {
|
|
7
|
-
test("sorts keys using a provided sort key function", async () => {
|
|
8
|
-
const tree = Tree.from({
|
|
9
|
-
Alice: { age: 48 },
|
|
10
|
-
Bob: { age: 36 },
|
|
11
|
-
Carol: { age: 42 },
|
|
12
|
-
});
|
|
13
|
-
const sorted = await sortBy((key, tree) => Tree.traverse(tree, key, "age"))(
|
|
14
|
-
tree
|
|
15
|
-
);
|
|
16
|
-
assert.deepEqual(Array.from(await sorted.keys()), [
|
|
17
|
-
"Bob",
|
|
18
|
-
"Carol",
|
|
19
|
-
"Alice",
|
|
20
|
-
]);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { Tree } from "../../src/internal.js";
|
|
4
|
-
import sortNatural from "../../src/transforms/sortNatural.js";
|
|
5
|
-
|
|
6
|
-
describe("sortNatural transform", () => {
|
|
7
|
-
test("sorts keys", async () => {
|
|
8
|
-
const tree = Tree.from({
|
|
9
|
-
file10: null,
|
|
10
|
-
file1: null,
|
|
11
|
-
file9: null,
|
|
12
|
-
});
|
|
13
|
-
const sortTransform = sortNatural();
|
|
14
|
-
const sorted = await sortTransform(tree);
|
|
15
|
-
assert.deepEqual(Array.from(await sorted.keys()), [
|
|
16
|
-
"file1",
|
|
17
|
-
"file9",
|
|
18
|
-
"file10",
|
|
19
|
-
]);
|
|
20
|
-
});
|
|
21
|
-
});
|