@weborigami/async-tree 0.5.4 → 0.5.6
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/index.ts +16 -6
- package/package.json +2 -2
- package/shared.js +20 -29
- package/src/Tree.js +59 -513
- package/src/constants.js +2 -0
- package/src/drivers/BrowserFileTree.js +9 -10
- package/src/drivers/DeepMapTree.js +3 -3
- package/src/drivers/DeepObjectTree.js +4 -5
- package/src/drivers/DeferredTree.js +2 -2
- package/src/drivers/FileTree.js +11 -33
- package/src/drivers/FunctionTree.js +3 -3
- package/src/drivers/MapTree.js +6 -6
- package/src/drivers/ObjectTree.js +6 -8
- package/src/drivers/SetTree.js +1 -1
- package/src/drivers/SiteTree.js +1 -1
- package/src/drivers/constantTree.js +1 -1
- package/src/extension.js +5 -3
- package/src/jsonKeys.js +5 -7
- package/src/operations/addNextPrevious.js +10 -9
- package/src/operations/assign.js +40 -0
- package/src/operations/cache.js +18 -12
- package/src/operations/cachedKeyFunctions.js +15 -4
- package/src/operations/clear.js +20 -0
- package/src/operations/deepMap.js +25 -0
- package/src/operations/deepMerge.js +11 -25
- package/src/operations/deepReverse.js +6 -7
- package/src/operations/deepTake.js +6 -7
- package/src/operations/deepText.js +4 -4
- package/src/operations/deepValuesIterator.js +8 -6
- package/src/operations/delete.js +20 -0
- package/src/operations/entries.js +16 -0
- package/src/operations/extensionKeyFunctions.js +1 -1
- package/src/operations/filter.js +7 -8
- package/src/operations/first.js +18 -0
- package/src/operations/forEach.js +20 -0
- package/src/operations/from.js +77 -0
- package/src/operations/globKeys.js +8 -8
- package/src/operations/group.js +3 -46
- package/src/operations/groupBy.js +51 -0
- package/src/operations/has.js +16 -0
- package/src/operations/indent.js +4 -2
- package/src/operations/inners.js +29 -0
- package/src/operations/invokeFunctions.js +5 -4
- package/src/operations/isAsyncMutableTree.js +15 -0
- package/src/operations/isAsyncTree.js +21 -0
- package/src/operations/isTraversable.js +15 -0
- package/src/operations/isTreelike.js +33 -0
- package/src/operations/json.js +4 -3
- package/src/operations/keys.js +14 -0
- package/src/operations/length.js +15 -0
- package/src/operations/map.js +156 -95
- package/src/operations/mapExtension.js +78 -0
- package/src/operations/mapReduce.js +44 -0
- package/src/operations/mask.js +18 -16
- package/src/operations/match.js +74 -0
- package/src/operations/merge.js +22 -20
- package/src/operations/paginate.js +3 -5
- package/src/operations/parent.js +13 -0
- package/src/operations/paths.js +51 -0
- package/src/operations/plain.js +34 -0
- package/src/operations/regExpKeys.js +4 -5
- package/src/operations/reverse.js +4 -6
- package/src/operations/root.js +17 -0
- package/src/operations/scope.js +4 -6
- package/src/operations/shuffle.js +46 -0
- package/src/operations/sort.js +19 -12
- package/src/operations/take.js +3 -5
- package/src/operations/text.js +3 -3
- package/src/operations/toFunction.js +14 -0
- package/src/operations/traverse.js +24 -0
- package/src/operations/traverseOrThrow.js +59 -0
- package/src/operations/traversePath.js +16 -0
- package/src/operations/values.js +15 -0
- package/src/operations/withKeys.js +33 -0
- package/src/utilities/TypedArray.js +2 -0
- package/src/utilities/box.js +20 -0
- package/src/utilities/castArraylike.js +38 -0
- package/src/utilities/getParent.js +33 -0
- package/src/utilities/getRealmObjectPrototype.js +19 -0
- package/src/utilities/getTreeArgument.js +43 -0
- package/src/utilities/isPacked.js +20 -0
- package/src/utilities/isPlainObject.js +29 -0
- package/src/utilities/isPrimitive.js +13 -0
- package/src/utilities/isStringlike.js +25 -0
- package/src/utilities/isUnpackable.js +13 -0
- package/src/utilities/keysFromPath.js +34 -0
- package/src/utilities/naturalOrder.js +9 -0
- package/src/utilities/pathFromKeys.js +18 -0
- package/src/utilities/setParent.js +38 -0
- package/src/utilities/toFunction.js +40 -0
- package/src/utilities/toPlainValue.js +95 -0
- package/src/utilities/toString.js +37 -0
- package/test/drivers/ExplorableSiteTree.test.js +1 -1
- package/test/drivers/FileTree.test.js +1 -1
- package/test/drivers/calendarTree.test.js +1 -1
- package/test/jsonKeys.test.js +1 -1
- package/test/operations/assign.test.js +54 -0
- package/test/operations/cache.test.js +1 -1
- package/test/operations/cachedKeyFunctions.test.js +16 -16
- package/test/operations/clear.test.js +34 -0
- package/test/operations/deepMerge.test.js +2 -6
- package/test/operations/deepReverse.test.js +1 -1
- package/test/operations/delete.test.js +20 -0
- package/test/operations/entries.test.js +18 -0
- package/test/operations/extensionKeyFunctions.test.js +10 -10
- package/test/operations/first.test.js +15 -0
- package/test/operations/fixtures/README.md +1 -0
- package/test/operations/forEach.test.js +22 -0
- package/test/operations/from.test.js +67 -0
- package/test/operations/globKeys.test.js +3 -3
- package/test/operations/{group.test.js → groupBy.test.js} +4 -4
- package/test/operations/has.test.js +15 -0
- package/test/operations/inners.test.js +30 -0
- package/test/operations/invokeFunctions.test.js +1 -1
- package/test/operations/isAsyncMutableTree.test.js +17 -0
- package/test/operations/isAsyncTree.test.js +26 -0
- package/test/operations/isTreelike.test.js +13 -0
- package/test/operations/keys.test.js +15 -0
- package/test/operations/length.test.js +15 -0
- package/test/operations/map.test.js +39 -70
- package/test/operations/mapExtension.test.js +53 -0
- package/test/operations/mapReduce.test.js +23 -0
- package/test/operations/mask.test.js +1 -1
- package/test/operations/match.test.js +33 -0
- package/test/operations/merge.test.js +23 -9
- package/test/operations/paginate.test.js +1 -1
- package/test/operations/parent.test.js +15 -0
- package/test/operations/paths.test.js +40 -0
- package/test/operations/plain.test.js +69 -0
- package/test/operations/reverse.test.js +1 -1
- package/test/operations/scope.test.js +1 -1
- package/test/operations/shuffle.test.js +18 -0
- package/test/operations/sort.test.js +3 -3
- package/test/operations/toFunction.test.js +16 -0
- package/test/operations/traverse.test.js +43 -0
- package/test/operations/traversePath.test.js +16 -0
- package/test/operations/values.test.js +18 -0
- package/test/operations/withKeys.test.js +21 -0
- package/test/utilities/box.test.js +26 -0
- package/test/utilities/getRealmObjectPrototype.test.js +11 -0
- package/test/utilities/isPlainObject.test.js +13 -0
- package/test/utilities/keysFromPath.test.js +14 -0
- package/test/utilities/naturalOrder.test.js +11 -0
- package/test/utilities/pathFromKeys.test.js +12 -0
- package/test/utilities/setParent.test.js +34 -0
- package/test/utilities/toFunction.test.js +34 -0
- package/test/utilities/toPlainValue.test.js +27 -0
- package/test/utilities/toString.test.js +22 -0
- package/src/Tree.d.ts +0 -24
- package/src/utilities.d.ts +0 -21
- package/src/utilities.js +0 -443
- package/test/Tree.test.js +0 -407
- package/test/utilities.test.js +0 -141
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
3
|
+
import isTreelike from "./isTreelike.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Return an iterator that yields all values in a tree, including nested trees.
|
|
@@ -15,16 +16,17 @@ export default async function* deepValuesIterator(
|
|
|
15
16
|
treelike,
|
|
16
17
|
options = { expand: false }
|
|
17
18
|
) {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const tree = await getTreeArgument(treelike, "deepValuesIterator", {
|
|
20
|
+
deep: true,
|
|
21
|
+
});
|
|
20
22
|
|
|
21
23
|
for (const key of await tree.keys()) {
|
|
22
24
|
let value = await tree.get(key);
|
|
23
25
|
|
|
24
26
|
// Recurse into child trees, but don't expand functions.
|
|
25
27
|
const recurse =
|
|
26
|
-
|
|
27
|
-
(options.expand && typeof value !== "function" &&
|
|
28
|
+
isAsyncTree(value) ||
|
|
29
|
+
(options.expand && typeof value !== "function" && isTreelike(value));
|
|
28
30
|
if (recurse) {
|
|
29
31
|
yield* deepValuesIterator(value, options);
|
|
30
32
|
} else {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import has from "./has.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Removes the value for the given key from the specific node of the tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
7
|
+
*
|
|
8
|
+
* @param {AsyncMutableTree} tree
|
|
9
|
+
* @param {any} key
|
|
10
|
+
*/
|
|
11
|
+
// `delete` is a reserved word in JavaScript, so we use `del` instead.
|
|
12
|
+
export default async function del(tree, key) {
|
|
13
|
+
const exists = await has(tree, key);
|
|
14
|
+
if (exists) {
|
|
15
|
+
await tree.set(key, undefined);
|
|
16
|
+
return true;
|
|
17
|
+
} else {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a new `Iterator` object that contains a two-member array of `[key,
|
|
5
|
+
* value]` for each element in the specific node of the tree.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
*/
|
|
11
|
+
export default async function entries(treelike) {
|
|
12
|
+
const tree = await getTreeArgument(treelike, "entries");
|
|
13
|
+
const keys = Array.from(await tree.keys());
|
|
14
|
+
const promises = keys.map(async (key) => [key, await tree.get(key)]);
|
|
15
|
+
return Promise.all(promises);
|
|
16
|
+
}
|
|
@@ -30,7 +30,7 @@ export default function extensionKeyFunctions(
|
|
|
30
30
|
return basename ? `${basename}${sourceExtension}` : undefined;
|
|
31
31
|
},
|
|
32
32
|
|
|
33
|
-
async key(sourceKey, tree) {
|
|
33
|
+
async key(value, sourceKey, tree) {
|
|
34
34
|
return extension.match(sourceKey, sourceExtension)
|
|
35
35
|
? extension.replace(sourceKey, sourceExtension, resultExtension)
|
|
36
36
|
: undefined;
|
package/src/operations/filter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
2
|
import map from "./map.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -10,10 +10,9 @@ import map from "./map.js";
|
|
|
10
10
|
*
|
|
11
11
|
* @param {Treelike} treelike
|
|
12
12
|
* @param {function|any} options
|
|
13
|
-
* @returns {AsyncTree}
|
|
13
|
+
* @returns {Promise<AsyncTree>}
|
|
14
14
|
*/
|
|
15
|
-
export default function filter(treelike, options) {
|
|
16
|
-
assertIsTreelike(treelike, "map");
|
|
15
|
+
export default async function filter(treelike, options) {
|
|
17
16
|
let testFn;
|
|
18
17
|
let deep;
|
|
19
18
|
if (typeof options === "function") {
|
|
@@ -24,15 +23,15 @@ export default function filter(treelike, options) {
|
|
|
24
23
|
deep = options.deep ?? false;
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
const tree = await getTreeArgument(treelike, "filter", { deep });
|
|
27
|
+
return map(tree, {
|
|
28
28
|
deep,
|
|
29
29
|
|
|
30
30
|
// Assume source key is the same as result key
|
|
31
31
|
inverseKey: async (resultKey) => resultKey,
|
|
32
32
|
|
|
33
|
-
key: async (sourceKey, tree) => {
|
|
34
|
-
const
|
|
35
|
-
const passes = await testFn(value, sourceKey, tree);
|
|
33
|
+
key: async (sourceValue, sourceKey, tree) => {
|
|
34
|
+
const passes = await testFn(sourceValue, sourceKey, tree);
|
|
36
35
|
return passes ? sourceKey : undefined;
|
|
37
36
|
},
|
|
38
37
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the first value in the tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
7
|
+
*
|
|
8
|
+
* @param {Treelike} treelike
|
|
9
|
+
*/
|
|
10
|
+
export default async function first(treelike) {
|
|
11
|
+
const tree = await getTreeArgument(treelike, "first");
|
|
12
|
+
for (const key of await tree.keys()) {
|
|
13
|
+
// Just return first value immediately.
|
|
14
|
+
const value = await tree.get(key);
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calls callbackFn once for each key-value pair present in the specific node of
|
|
5
|
+
* the tree.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
* @param {Function} callbackFn
|
|
11
|
+
*/
|
|
12
|
+
export default async function forEach(treelike, callbackFn) {
|
|
13
|
+
const tree = await getTreeArgument(treelike, "forEach");
|
|
14
|
+
const keys = Array.from(await tree.keys());
|
|
15
|
+
const promises = keys.map(async (key) => {
|
|
16
|
+
const value = await tree.get(key);
|
|
17
|
+
return callbackFn(value, key, tree);
|
|
18
|
+
});
|
|
19
|
+
await Promise.all(promises);
|
|
20
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import DeepObjectTree from "../drivers/DeepObjectTree.js";
|
|
2
|
+
import DeferredTree from "../drivers/DeferredTree.js";
|
|
3
|
+
import FunctionTree from "../drivers/FunctionTree.js";
|
|
4
|
+
import MapTree from "../drivers/MapTree.js";
|
|
5
|
+
import ObjectTree from "../drivers/ObjectTree.js";
|
|
6
|
+
import SetTree from "../drivers/SetTree.js";
|
|
7
|
+
import * as symbols from "../symbols.js";
|
|
8
|
+
import box from "../utilities/box.js";
|
|
9
|
+
import isPlainObject from "../utilities/isPlainObject.js";
|
|
10
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
11
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Attempts to cast the indicated object to an async tree.
|
|
15
|
+
*
|
|
16
|
+
* If the object is a plain object, it will be converted to an ObjectTree. The
|
|
17
|
+
* optional `deep` option can be set to `true` to convert a plain object to a
|
|
18
|
+
* DeepObjectTree. The optional `parent` parameter will be used as the default
|
|
19
|
+
* parent of the new tree.
|
|
20
|
+
*
|
|
21
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
22
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
23
|
+
*
|
|
24
|
+
* @param {Treelike | Object} object
|
|
25
|
+
* @param {{ deep?: boolean, parent?: AsyncTree|null }} [options]
|
|
26
|
+
* @returns {AsyncTree}
|
|
27
|
+
*/
|
|
28
|
+
export default function from(object, options = {}) {
|
|
29
|
+
const deep = options.deep ?? object[symbols.deep];
|
|
30
|
+
let tree;
|
|
31
|
+
if (object == null) {
|
|
32
|
+
throw new TypeError("The tree argument wasn't defined.");
|
|
33
|
+
} else if (object instanceof Promise) {
|
|
34
|
+
// A common mistake
|
|
35
|
+
throw new TypeError(
|
|
36
|
+
"The tree argument was a Promise. Did you mean to use await?"
|
|
37
|
+
);
|
|
38
|
+
} else if (isAsyncTree(object)) {
|
|
39
|
+
// Argument already supports the tree interface.
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
return object;
|
|
42
|
+
} else if (typeof object === "function") {
|
|
43
|
+
tree = new FunctionTree(object);
|
|
44
|
+
} else if (object instanceof Map) {
|
|
45
|
+
tree = new MapTree(object);
|
|
46
|
+
} else if (object instanceof Set) {
|
|
47
|
+
tree = new SetTree(object);
|
|
48
|
+
} else if (isPlainObject(object) || object instanceof Array) {
|
|
49
|
+
tree = deep ? new DeepObjectTree(object) : new ObjectTree(object);
|
|
50
|
+
} else if (isUnpackable(object)) {
|
|
51
|
+
async function AsyncFunction() {} // Sample async function
|
|
52
|
+
tree =
|
|
53
|
+
object.unpack instanceof AsyncFunction.constructor
|
|
54
|
+
? // Async unpack: return a deferred tree.
|
|
55
|
+
new DeferredTree(object.unpack, { deep })
|
|
56
|
+
: // Synchronous unpack: cast the result of unpack() to a tree.
|
|
57
|
+
from(object.unpack());
|
|
58
|
+
} else if (object && typeof object === "object") {
|
|
59
|
+
// An instance of some class.
|
|
60
|
+
tree = new ObjectTree(object);
|
|
61
|
+
} else if (
|
|
62
|
+
typeof object === "string" ||
|
|
63
|
+
typeof object === "number" ||
|
|
64
|
+
typeof object === "boolean"
|
|
65
|
+
) {
|
|
66
|
+
// A primitive value; box it into an object and construct a tree.
|
|
67
|
+
const boxed = box(object);
|
|
68
|
+
tree = new ObjectTree(boxed);
|
|
69
|
+
} else {
|
|
70
|
+
throw new TypeError("Couldn't convert argument to an async tree");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!tree.parent && options.parent) {
|
|
74
|
+
tree.parent = options.parent;
|
|
75
|
+
}
|
|
76
|
+
return tree;
|
|
77
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ObjectTree from "../drivers/ObjectTree.js";
|
|
2
2
|
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
-
import
|
|
3
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
4
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
4
5
|
import merge from "./merge.js";
|
|
5
6
|
|
|
6
7
|
const globstar = "**";
|
|
7
8
|
const globstarSlash = `${globstar}/`;
|
|
8
9
|
|
|
9
|
-
export default function globKeys(treelike) {
|
|
10
|
-
|
|
11
|
-
const globs = Tree.from(treelike, { deep: true });
|
|
10
|
+
export default async function globKeys(treelike) {
|
|
11
|
+
const globs = await getTreeArgument(treelike, "globKeys", { deep: true });
|
|
12
12
|
return {
|
|
13
13
|
async get(key) {
|
|
14
14
|
if (typeof key !== "string") {
|
|
@@ -16,7 +16,7 @@ export default function globKeys(treelike) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
let value = await matchGlobs(globs, key);
|
|
19
|
-
if (
|
|
19
|
+
if (isAsyncTree(value)) {
|
|
20
20
|
value = globKeys(value);
|
|
21
21
|
}
|
|
22
22
|
return value;
|
|
@@ -57,7 +57,7 @@ async function matchGlobs(globs, key) {
|
|
|
57
57
|
// Text matches glob, get value
|
|
58
58
|
const globValue = await globs.get(glob);
|
|
59
59
|
if (globValue !== undefined) {
|
|
60
|
-
if (!
|
|
60
|
+
if (!isAsyncTree(globValue)) {
|
|
61
61
|
// Found a non-tree match, return immediately
|
|
62
62
|
return globValue;
|
|
63
63
|
}
|
|
@@ -75,7 +75,7 @@ async function matchGlobs(globs, key) {
|
|
|
75
75
|
} else {
|
|
76
76
|
// Try globstar
|
|
77
77
|
const globstarValue = await matchGlobs(globstarGlobs, key);
|
|
78
|
-
if (!
|
|
78
|
+
if (!isAsyncTree(globstarValue)) {
|
|
79
79
|
// Found a non-tree match, return immediately
|
|
80
80
|
return globstarValue;
|
|
81
81
|
} else if (trailingSlash.has(key)) {
|
package/src/operations/group.js
CHANGED
|
@@ -1,49 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { assertIsTreelike } from "../utilities.js";
|
|
1
|
+
import groupBy from "./groupBy.js";
|
|
3
2
|
|
|
4
|
-
/**
|
|
5
|
-
* Given a function that returns a grouping key for a value, returns a transform
|
|
6
|
-
* that applies that grouping function to a tree.
|
|
7
|
-
*
|
|
8
|
-
* @param {import("../../index.ts").Treelike} treelike
|
|
9
|
-
* @param {import("../../index.ts").ValueKeyFn} groupKeyFn
|
|
10
|
-
*/
|
|
11
3
|
export default async function group(treelike, groupKeyFn) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const keys = Array.from(await tree.keys());
|
|
16
|
-
|
|
17
|
-
// Are all the keys integers?
|
|
18
|
-
const isArray = keys.every((key) => !Number.isNaN(parseInt(key)));
|
|
19
|
-
|
|
20
|
-
const result = {};
|
|
21
|
-
for (const key of await tree.keys()) {
|
|
22
|
-
const value = await tree.get(key);
|
|
23
|
-
|
|
24
|
-
// Get the groups for this value.
|
|
25
|
-
let groups = await groupKeyFn(value, key, tree);
|
|
26
|
-
if (!groups) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!Tree.isTreelike(groups)) {
|
|
31
|
-
// A single value was returned
|
|
32
|
-
groups = [groups];
|
|
33
|
-
}
|
|
34
|
-
groups = Tree.from(groups);
|
|
35
|
-
|
|
36
|
-
// Add the value to each group.
|
|
37
|
-
for (const group of await Tree.values(groups)) {
|
|
38
|
-
if (isArray) {
|
|
39
|
-
result[group] ??= [];
|
|
40
|
-
result[group].push(value);
|
|
41
|
-
} else {
|
|
42
|
-
result[group] ??= {};
|
|
43
|
-
result[group][key] = value;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return new ObjectTree(result);
|
|
4
|
+
console.warn("Tree.group() is deprecated. Use Tree.groupBy() instead.");
|
|
5
|
+
return groupBy(treelike, groupKeyFn);
|
|
49
6
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import ObjectTree from "../drivers/ObjectTree.js";
|
|
2
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
|
+
import from from "./from.js";
|
|
4
|
+
import isTreelike from "./isTreelike.js";
|
|
5
|
+
import values from "./values.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Given a function that returns a grouping key for a value, returns a transform
|
|
9
|
+
* that applies that grouping function to a tree.
|
|
10
|
+
*
|
|
11
|
+
* @param {import("../../index.ts").Treelike} treelike
|
|
12
|
+
* @param {import("../../index.ts").ValueKeyFn} groupKeyFn
|
|
13
|
+
*/
|
|
14
|
+
export default async function groupBy(treelike, groupKeyFn) {
|
|
15
|
+
const tree = await getTreeArgument(treelike, "groupBy");
|
|
16
|
+
|
|
17
|
+
const keys = Array.from(await tree.keys());
|
|
18
|
+
|
|
19
|
+
// Are all the keys integers?
|
|
20
|
+
const isArray = keys.every((key) => !Number.isNaN(parseInt(key)));
|
|
21
|
+
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const key of await tree.keys()) {
|
|
24
|
+
const value = await tree.get(key);
|
|
25
|
+
|
|
26
|
+
// Get the groups for this value.
|
|
27
|
+
let groups = await groupKeyFn(value, key, tree);
|
|
28
|
+
if (!groups) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!isTreelike(groups)) {
|
|
33
|
+
// A single value was returned
|
|
34
|
+
groups = [groups];
|
|
35
|
+
}
|
|
36
|
+
groups = from(groups);
|
|
37
|
+
|
|
38
|
+
// Add the value to each group.
|
|
39
|
+
for (const group of await values(groups)) {
|
|
40
|
+
if (isArray) {
|
|
41
|
+
result[group] ??= [];
|
|
42
|
+
result[group].push(value);
|
|
43
|
+
} else {
|
|
44
|
+
result[group] ??= {};
|
|
45
|
+
result[group][key] = value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new ObjectTree(result);
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a boolean indicating whether the specific node of the tree has a
|
|
5
|
+
* value for the given `key`.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
* @param {any} key
|
|
11
|
+
*/
|
|
12
|
+
export default async function has(treelike, key) {
|
|
13
|
+
const tree = await getTreeArgument(treelike, "has");
|
|
14
|
+
const value = await tree.get(key);
|
|
15
|
+
return value !== undefined;
|
|
16
|
+
}
|
package/src/operations/indent.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import toString from "../utilities/toString.js";
|
|
2
|
+
import deepText from "./deepText.js";
|
|
3
|
+
import isTreelike from "./isTreelike.js";
|
|
2
4
|
|
|
3
5
|
const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
|
|
4
6
|
|
|
@@ -19,7 +21,7 @@ export default async function indent(strings, ...values) {
|
|
|
19
21
|
}
|
|
20
22
|
const { blockIndentations, strings: modifiedStrings } = modified;
|
|
21
23
|
const valueTexts = await Promise.all(
|
|
22
|
-
values.map((value) => (
|
|
24
|
+
values.map((value) => (isTreelike(value) ? deepText(value) : value))
|
|
23
25
|
);
|
|
24
26
|
return joinBlocks(modifiedStrings, valueTexts, blockIndentations);
|
|
25
27
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
3
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Return the interior nodes of the tree. This relies on subtree keys having
|
|
7
|
+
* trailing slashes.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
11
|
+
*
|
|
12
|
+
* @param {Treelike} treelike
|
|
13
|
+
*/
|
|
14
|
+
export default async function inners(treelike) {
|
|
15
|
+
const tree = await getTreeArgument(treelike, "inners");
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
async get(key) {
|
|
19
|
+
const value = await tree.get(key);
|
|
20
|
+
return isAsyncTree(value) ? inners(value) : undefined;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async keys() {
|
|
24
|
+
const keys = [...(await tree.keys())];
|
|
25
|
+
const subtreeKeys = keys.filter(trailingSlash.has);
|
|
26
|
+
return subtreeKeys;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import from from "./from.js";
|
|
2
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
2
3
|
|
|
3
|
-
export default function invokeFunctions(treelike) {
|
|
4
|
-
const tree =
|
|
4
|
+
export default async function invokeFunctions(treelike) {
|
|
5
|
+
const tree = from(treelike);
|
|
5
6
|
return {
|
|
6
7
|
async get(key) {
|
|
7
8
|
let value = await tree.get(key);
|
|
8
9
|
if (typeof value === "function") {
|
|
9
10
|
value = value();
|
|
10
|
-
} else if (
|
|
11
|
+
} else if (isAsyncTree(value)) {
|
|
11
12
|
value = invokeFunctions(value);
|
|
12
13
|
}
|
|
13
14
|
return value;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return true if the indicated object is an async mutable tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
7
|
+
*
|
|
8
|
+
* @param {any} obj
|
|
9
|
+
* @returns {obj is AsyncMutableTree}
|
|
10
|
+
*/
|
|
11
|
+
export default function isAsyncMutableTree(obj) {
|
|
12
|
+
return (
|
|
13
|
+
isAsyncTree(obj) && typeof (/** @type {any} */ (obj).set) === "function"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return true if the indicated object is an async tree.
|
|
3
|
+
*
|
|
4
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
5
|
+
*
|
|
6
|
+
* @param {any} obj
|
|
7
|
+
* @returns {obj is AsyncTree}
|
|
8
|
+
*/
|
|
9
|
+
export default function isAsyncTree(obj) {
|
|
10
|
+
return (
|
|
11
|
+
obj !== null &&
|
|
12
|
+
typeof obj === "object" &&
|
|
13
|
+
typeof obj.get === "function" &&
|
|
14
|
+
typeof obj.keys === "function" &&
|
|
15
|
+
// Regular JavaScript Map instances look like trees but can't have their
|
|
16
|
+
// prototype chains extended (the way map() does), so exclude them.
|
|
17
|
+
// Subclasses of Map that implement their own get() and keys() methods are
|
|
18
|
+
// allowed, because they shouldn't have the same limitations.
|
|
19
|
+
!(obj instanceof Map && Object.getPrototypeOf(obj) === Map.prototype)
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import isPacked from "../utilities/isPacked.js";
|
|
2
|
+
import isTreelike from "./isTreelike.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return true if the object can be traversed via the `traverse()` method. The
|
|
6
|
+
* object must be either treelike or a packed object with an `unpack()` method.
|
|
7
|
+
*
|
|
8
|
+
* @param {any} object
|
|
9
|
+
*/
|
|
10
|
+
export default function isTraversable(object) {
|
|
11
|
+
return (
|
|
12
|
+
isTreelike(object) ||
|
|
13
|
+
(isPacked(object) && /** @type {any} */ (object).unpack instanceof Function)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import isPlainObject from "../utilities/isPlainObject.js";
|
|
2
|
+
import isAsyncTree from "./isAsyncTree.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if the indicated object can be directly treated as an
|
|
6
|
+
* asynchronous tree. This includes:
|
|
7
|
+
*
|
|
8
|
+
* - An object that implements the AsyncTree interface (including
|
|
9
|
+
* AsyncTree instances)
|
|
10
|
+
* - A function
|
|
11
|
+
* - An `Array` instance
|
|
12
|
+
* - A `Map` instance
|
|
13
|
+
* - A `Set` instance
|
|
14
|
+
* - A plain object
|
|
15
|
+
*
|
|
16
|
+
* Note: the `from()` method accepts any JavaScript object, but `isTreelike`
|
|
17
|
+
* returns `false` for an object that isn't one of the above types.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
20
|
+
*
|
|
21
|
+
* @param {any} obj
|
|
22
|
+
* @returns {obj is Treelike}
|
|
23
|
+
*/
|
|
24
|
+
export default function isTreelike(obj) {
|
|
25
|
+
return (
|
|
26
|
+
isAsyncTree(obj) ||
|
|
27
|
+
obj instanceof Array ||
|
|
28
|
+
obj instanceof Function ||
|
|
29
|
+
obj instanceof Map ||
|
|
30
|
+
obj instanceof Set ||
|
|
31
|
+
isPlainObject(obj)
|
|
32
|
+
);
|
|
33
|
+
}
|
package/src/operations/json.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
|
+
import toPlainValue from "../utilities/toPlainValue.js";
|
|
4
|
+
import from from "./from.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Render the given tree in JSON format.
|
|
@@ -8,7 +9,7 @@ import { isUnpackable, toPlainValue } from "../utilities.js";
|
|
|
8
9
|
* @param {import("../../index.ts").Treelike} [treelike]
|
|
9
10
|
*/
|
|
10
11
|
export default async function json(treelike) {
|
|
11
|
-
let tree =
|
|
12
|
+
let tree = from(treelike);
|
|
12
13
|
if (tree === undefined) {
|
|
13
14
|
return undefined;
|
|
14
15
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the top-level keys in the tree as an array.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
7
|
+
*
|
|
8
|
+
* @param {Treelike} treelike
|
|
9
|
+
*/
|
|
10
|
+
export default async function keys(treelike) {
|
|
11
|
+
const tree = await getTreeArgument(treelike, "keys");
|
|
12
|
+
const keys = await tree.keys();
|
|
13
|
+
return Array.from(keys);
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return the number of keys in the tree.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
|
+
*
|
|
9
|
+
* @param {Treelike} treelike
|
|
10
|
+
*/
|
|
11
|
+
export default async function length(treelike) {
|
|
12
|
+
const tree = await getTreeArgument(treelike, "length");
|
|
13
|
+
const keys = Array.from(await tree.keys());
|
|
14
|
+
return keys.length;
|
|
15
|
+
}
|