@weborigami/async-tree 0.0.49 → 0.0.51
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 +2 -2
- package/src/BrowserFileTree.js +3 -2
- package/src/FileTree.js +9 -3
- package/src/Tree.js +1 -1
- package/src/operations/{mergeDeep.js → deepMerge.js} +3 -3
- 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/operations/map.js +14 -0
- package/src/operations/sort.js +18 -0
- package/src/operations/take.js +11 -0
- package/src/transforms/{map.js → mapFn.js} +1 -0
- 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 +5 -17
- 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/src/{transforms/groupBy.js → operations/groupFn.js} +0 -0
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.51",
|
|
4
4
|
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"typescript": "5.4.5"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.
|
|
14
|
+
"@weborigami/types": "0.0.51"
|
|
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) {
|
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,
|
|
@@ -7,11 +7,11 @@ import { Tree } from "../internal.js";
|
|
|
7
7
|
* @param {import("../../index.ts").Treelike[]} sources
|
|
8
8
|
* @returns {AsyncTree & { description: string }}
|
|
9
9
|
*/
|
|
10
|
-
export default function
|
|
10
|
+
export default function deepMerge(...sources) {
|
|
11
11
|
let trees = sources.map((treelike) => Tree.from(treelike));
|
|
12
12
|
let mergeParent;
|
|
13
13
|
return {
|
|
14
|
-
description: "
|
|
14
|
+
description: "deepMerge",
|
|
15
15
|
|
|
16
16
|
async get(key) {
|
|
17
17
|
const subtrees = [];
|
|
@@ -25,7 +25,7 @@ export default function mergeDeep(...sources) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
return subtrees.length > 0 ?
|
|
28
|
+
return subtrees.length > 0 ? deepMerge(...subtrees) : undefined;
|
|
29
29
|
},
|
|
30
30
|
|
|
31
31
|
async isKeyForSubtree(key) {
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -8,6 +8,7 @@ import { isPlainObject } from "../utilities.js";
|
|
|
8
8
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
9
9
|
*
|
|
10
10
|
* @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
|
|
11
|
+
* @returns {import("../../index.ts").TreeTransform}
|
|
11
12
|
*/
|
|
12
13
|
export default function createMapTransform(options = {}) {
|
|
13
14
|
let deep;
|
|
@@ -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
|
@@ -135,22 +135,10 @@ export function keysFromPath(pathname) {
|
|
|
135
135
|
return keys;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// Used for natural sort order
|
|
139
|
-
export const naturalSortCompareFn = new Intl.Collator(undefined, {
|
|
140
|
-
numeric: true,
|
|
141
|
-
}).compare;
|
|
142
|
-
|
|
143
138
|
/**
|
|
144
|
-
*
|
|
145
|
-
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
146
|
-
* `Array` `sort` function, this operation destructively modifies the array in
|
|
147
|
-
* place.
|
|
148
|
-
*
|
|
149
|
-
* The default sort order for some sources like operating system files can be
|
|
150
|
-
* unpredictable. Since it's quite common for file names to include numbers, it
|
|
151
|
-
* can helpful to use natural sort order instead: ["file1", "file9", "file10"]
|
|
152
|
-
* instead of ["file1", "file10", "file9"].
|
|
139
|
+
* Compare two strings using [natural sort
|
|
140
|
+
* order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
|
153
141
|
*/
|
|
154
|
-
export
|
|
155
|
-
|
|
156
|
-
}
|
|
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
|
-
});
|
|
File without changes
|