@weborigami/async-tree 0.5.5 → 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/package.json +2 -2
- package/shared.js +1 -0
- package/src/Tree.js +1 -4
- package/src/drivers/FunctionTree.js +2 -2
- package/src/drivers/ObjectTree.js +2 -5
- package/src/operations/group.js +3 -48
- package/src/operations/groupBy.js +51 -0
- package/src/operations/map.js +5 -0
- package/src/operations/mapExtension.js +64 -13
- package/src/operations/match.js +1 -1
- package/src/operations/traverse.js +1 -2
- package/src/operations/traverseOrThrow.js +1 -6
- package/src/utilities/toFunction.js +1 -2
- package/test/operations/deepReverse.test.js +1 -1
- package/test/operations/{group.test.js → groupBy.test.js} +4 -4
- package/test/operations/map.test.js +0 -50
- package/test/operations/mapExtension.test.js +53 -0
- package/test/operations/paginate.test.js +1 -1
- package/test/operations/reverse.test.js +1 -1
- package/src/operations/concat.js +0 -17
- package/src/operations/defineds.js +0 -32
- package/src/operations/fromFn.js +0 -26
- package/src/operations/remove.js +0 -14
- package/src/operations/setDeep.js +0 -50
- package/test/operations/deepMap.test.js +0 -29
- package/test/operations/defineds.test.js +0 -25
- package/test/operations/setDeep.test.js +0 -53
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
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
|
"dependencies": {
|
|
10
|
-
"@weborigami/types": "0.5.
|
|
10
|
+
"@weborigami/types": "0.5.6"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@types/node": "24.3.0",
|
package/shared.js
CHANGED
|
@@ -12,6 +12,7 @@ export { default as ObjectTree } from "./src/drivers/ObjectTree.js";
|
|
|
12
12
|
export { default as SetTree } from "./src/drivers/SetTree.js";
|
|
13
13
|
export { default as SiteTree } from "./src/drivers/SiteTree.js";
|
|
14
14
|
export * as jsonKeys from "./src/jsonKeys.js";
|
|
15
|
+
export { default as scope } from "./src/operations/scope.js";
|
|
15
16
|
export * as symbols from "./src/symbols.js";
|
|
16
17
|
export * as trailingSlash from "./src/trailingSlash.js";
|
|
17
18
|
export { default as TraverseError } from "./src/TraverseError.js";
|
package/src/Tree.js
CHANGED
|
@@ -8,7 +8,6 @@ export { default as addNextPrevious } from "./operations/addNextPrevious.js";
|
|
|
8
8
|
export { default as assign } from "./operations/assign.js";
|
|
9
9
|
export { default as cache } from "./operations/cache.js";
|
|
10
10
|
export { default as clear } from "./operations/clear.js";
|
|
11
|
-
export { default as concat } from "./operations/concat.js";
|
|
12
11
|
export { default as deepMap } from "./operations/deepMap.js";
|
|
13
12
|
export { default as deepMerge } from "./operations/deepMerge.js";
|
|
14
13
|
export { default as deepReverse } from "./operations/deepReverse.js";
|
|
@@ -16,16 +15,15 @@ export { default as deepTake } from "./operations/deepTake.js";
|
|
|
16
15
|
export { default as deepText } from "./operations/deepText.js";
|
|
17
16
|
export { default as deepValues } from "./operations/deepValues.js";
|
|
18
17
|
export { default as deepValuesIterator } from "./operations/deepValuesIterator.js";
|
|
19
|
-
export { default as defineds } from "./operations/defineds.js";
|
|
20
18
|
export { default as delete } from "./operations/delete.js";
|
|
21
19
|
export { default as entries } from "./operations/entries.js";
|
|
22
20
|
export { default as filter } from "./operations/filter.js";
|
|
23
21
|
export { default as first } from "./operations/first.js";
|
|
24
22
|
export { default as forEach } from "./operations/forEach.js";
|
|
25
23
|
export { default as from } from "./operations/from.js";
|
|
26
|
-
export { default as fromFn } from "./operations/fromFn.js";
|
|
27
24
|
export { default as globKeys } from "./operations/globKeys.js";
|
|
28
25
|
export { default as group } from "./operations/group.js";
|
|
26
|
+
export { default as groupBy } from "./operations/groupBy.js";
|
|
29
27
|
export { default as has } from "./operations/has.js";
|
|
30
28
|
export { default as indent } from "./operations/indent.js";
|
|
31
29
|
export { default as inners } from "./operations/inners.js";
|
|
@@ -51,7 +49,6 @@ export { default as regExpKeys } from "./operations/regExpKeys.js";
|
|
|
51
49
|
export { default as reverse } from "./operations/reverse.js";
|
|
52
50
|
export { default as root } from "./operations/root.js";
|
|
53
51
|
export { default as scope } from "./operations/scope.js";
|
|
54
|
-
export { default as setDeep } from "./operations/setDeep.js";
|
|
55
52
|
export { default as shuffle } from "./operations/shuffle.js";
|
|
56
53
|
export { default as sort } from "./operations/sort.js";
|
|
57
54
|
export { default as take } from "./operations/take.js";
|
|
@@ -27,11 +27,11 @@ export default class FunctionTree {
|
|
|
27
27
|
this.fn.length <= 1
|
|
28
28
|
? // Function takes no arguments, one argument, or a variable number of
|
|
29
29
|
// arguments: invoke it.
|
|
30
|
-
await this.fn
|
|
30
|
+
await this.fn(key)
|
|
31
31
|
: // Bind the key to the first parameter. Subsequent get calls will
|
|
32
32
|
// eventually bind all parameters until only one remains. At that point,
|
|
33
33
|
// the above condition will apply and the function will be invoked.
|
|
34
|
-
Reflect.construct(this.constructor, [this.fn.bind(
|
|
34
|
+
Reflect.construct(this.constructor, [this.fn.bind(null, key)]);
|
|
35
35
|
setParent(value, this);
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
@@ -57,12 +57,9 @@ export default class ObjectTree {
|
|
|
57
57
|
|
|
58
58
|
setParent(value, this);
|
|
59
59
|
|
|
60
|
-
// Is value an instance method?
|
|
61
|
-
// constructor, in which case the method is likely a static method.
|
|
60
|
+
// Is value an instance method?
|
|
62
61
|
const isInstanceMethod =
|
|
63
|
-
!(this.object
|
|
64
|
-
value instanceof Function &&
|
|
65
|
-
!Object.hasOwn(this.object, key);
|
|
62
|
+
value instanceof Function && !Object.hasOwn(this.object, key);
|
|
66
63
|
if (isInstanceMethod) {
|
|
67
64
|
// Bind it to the object
|
|
68
65
|
value = value.bind(this.object);
|
package/src/operations/group.js
CHANGED
|
@@ -1,51 +1,6 @@
|
|
|
1
|
-
import
|
|
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";
|
|
1
|
+
import groupBy from "./groupBy.js";
|
|
6
2
|
|
|
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
3
|
export default async function group(treelike, groupKeyFn) {
|
|
15
|
-
|
|
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);
|
|
4
|
+
console.warn("Tree.group() is deprecated. Use Tree.groupBy() instead.");
|
|
5
|
+
return groupBy(treelike, groupKeyFn);
|
|
51
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
|
+
}
|
package/src/operations/map.js
CHANGED
|
@@ -186,6 +186,11 @@ function validateOptions(options) {
|
|
|
186
186
|
throw error;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
if (extension && !options._noExtensionWarning) {
|
|
190
|
+
console.warn(
|
|
191
|
+
`map: The 'extension' option for Tree.map() is deprecated and will be removed in a future release. Use Tree.mapExtension() instead.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
189
194
|
if (extension && (keyFn || inverseKeyFn)) {
|
|
190
195
|
throw new TypeError(
|
|
191
196
|
`map: You can't specify extensions and also a key or inverseKey function`
|
|
@@ -1,27 +1,78 @@
|
|
|
1
|
-
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
1
|
import isPlainObject from "../utilities/isPlainObject.js";
|
|
3
2
|
import map from "./map.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Shorthand for calling `map` with the `deep: true` option.
|
|
7
|
-
*
|
|
8
5
|
* @typedef {import("../../index.ts").TreeMapExtensionOptions} TreeMapExtensionOptions
|
|
9
6
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
10
7
|
* @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
|
|
11
8
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
12
|
-
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @overload
|
|
13
|
+
* @param {Treelike} treelike
|
|
14
|
+
* @param {string} extension
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @overload
|
|
19
|
+
* @param {Treelike} treelike
|
|
20
|
+
* @param {TreeMapExtensionOptions} options
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @overload
|
|
25
|
+
* @param {Treelike} treelike
|
|
26
|
+
* @param {string} extension
|
|
27
|
+
* @param {ValueKeyFn} fn
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @overload
|
|
13
32
|
* @param {Treelike} treelike
|
|
14
33
|
* @param {string} extension
|
|
15
|
-
* @param {
|
|
34
|
+
* @param {TreeMapExtensionOptions} options
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shorthand for calling `map` with the `deep: true` option.
|
|
39
|
+
*
|
|
40
|
+
* @param {Treelike} treelike
|
|
41
|
+
* @param {string|TreeMapExtensionOptions} arg2
|
|
42
|
+
* @param {ValueKeyFn|TreeMapExtensionOptions} [arg3]
|
|
16
43
|
* @returns {Promise<AsyncTree>}
|
|
17
44
|
*/
|
|
18
|
-
export default async function mapExtension(treelike,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
export default async function mapExtension(treelike, arg2, arg3) {
|
|
46
|
+
/** @type {TreeMapExtensionOptions} */
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
let options = { _noExtensionWarning: true };
|
|
49
|
+
if (arg3 === undefined) {
|
|
50
|
+
if (typeof arg2 === "string") {
|
|
51
|
+
options.extension = arg2;
|
|
52
|
+
} else if (isPlainObject(arg2)) {
|
|
53
|
+
Object.assign(options, arg2);
|
|
54
|
+
} else {
|
|
55
|
+
throw new TypeError(
|
|
56
|
+
"mapExtension: Expected a string or options object for the second argument."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
if (typeof arg2 !== "string") {
|
|
61
|
+
throw new TypeError(
|
|
62
|
+
"mapExtension: Expected a string for the second argument."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
options.extension = arg2;
|
|
66
|
+
if (typeof arg3 === "function") {
|
|
67
|
+
options.value = arg3;
|
|
68
|
+
} else if (isPlainObject(arg3)) {
|
|
69
|
+
Object.assign(options, arg3);
|
|
70
|
+
} else {
|
|
71
|
+
throw new TypeError(
|
|
72
|
+
"mapExtension: Expected a function or options object for the third argument."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
25
76
|
|
|
26
|
-
return map(
|
|
77
|
+
return map(treelike, options);
|
|
27
78
|
}
|
package/src/operations/match.js
CHANGED
|
@@ -15,7 +15,7 @@ import isAsyncTree from "./isAsyncTree.js";
|
|
|
15
15
|
*
|
|
16
16
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
17
17
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
18
|
-
* @typedef {import("
|
|
18
|
+
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
19
19
|
*
|
|
20
20
|
* @param {string|RegExp} pattern
|
|
21
21
|
* @param {Invocable} resultFn
|
|
@@ -6,7 +6,6 @@ import traverseOrThrow from "./traverseOrThrow.js";
|
|
|
6
6
|
*
|
|
7
7
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
8
8
|
*
|
|
9
|
-
* @this {any}
|
|
10
9
|
* @param {Treelike} treelike
|
|
11
10
|
* @param {...any} keys
|
|
12
11
|
*/
|
|
@@ -14,7 +13,7 @@ export default async function traverse(treelike, ...keys) {
|
|
|
14
13
|
try {
|
|
15
14
|
// Await the result here so that, if the path doesn't exist, the catch
|
|
16
15
|
// block below will catch the exception.
|
|
17
|
-
return await traverseOrThrow
|
|
16
|
+
return await traverseOrThrow(treelike, ...keys);
|
|
18
17
|
} catch (/** @type {any} */ error) {
|
|
19
18
|
if (error instanceof TraverseError) {
|
|
20
19
|
return undefined;
|
|
@@ -9,7 +9,6 @@ import from from "./from.js";
|
|
|
9
9
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
10
10
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
11
11
|
*
|
|
12
|
-
* @this {any}
|
|
13
12
|
* @param {Treelike} treelike
|
|
14
13
|
* @param {...any} keys
|
|
15
14
|
*/
|
|
@@ -19,10 +18,6 @@ export default async function traverseOrThrow(treelike, ...keys) {
|
|
|
19
18
|
let value = treelike;
|
|
20
19
|
let position = 0;
|
|
21
20
|
|
|
22
|
-
// If traversal operation was called with a `this` context, use that as the
|
|
23
|
-
// target for function calls.
|
|
24
|
-
const target = this;
|
|
25
|
-
|
|
26
21
|
// Process all the keys.
|
|
27
22
|
const remainingKeys = keys.slice();
|
|
28
23
|
let key;
|
|
@@ -47,7 +42,7 @@ export default async function traverseOrThrow(treelike, ...keys) {
|
|
|
47
42
|
let fnKeyCount = Math.max(fn.length, 1);
|
|
48
43
|
const args = remainingKeys.splice(0, fnKeyCount);
|
|
49
44
|
key = null;
|
|
50
|
-
value = await fn
|
|
45
|
+
value = await fn(...args);
|
|
51
46
|
} else {
|
|
52
47
|
// Cast value to a tree.
|
|
53
48
|
const tree = from(value);
|
|
@@ -17,7 +17,6 @@ export default function toFunction(obj) {
|
|
|
17
17
|
} else if (isUnpackable(obj)) {
|
|
18
18
|
// Extract the contents of the object and convert that to a function.
|
|
19
19
|
let fnPromise;
|
|
20
|
-
/** @this {any} */
|
|
21
20
|
return async function (...args) {
|
|
22
21
|
if (!fnPromise) {
|
|
23
22
|
// unpack() may return a function or a promise for a function; normalize
|
|
@@ -28,7 +27,7 @@ export default function toFunction(obj) {
|
|
|
28
27
|
fnPromise = unpackPromise.then((content) => toFunction(content));
|
|
29
28
|
}
|
|
30
29
|
const fn = await fnPromise;
|
|
31
|
-
return fn
|
|
30
|
+
return fn(...args);
|
|
32
31
|
};
|
|
33
32
|
} else if (isTreelike(obj)) {
|
|
34
33
|
// Return a function that invokes the tree's getter.
|
|
@@ -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 groupBy from "../../src/operations/groupBy.js";
|
|
5
5
|
|
|
6
|
-
describe("
|
|
6
|
+
describe("groupBy transform", () => {
|
|
7
7
|
test("groups an array using a group key function", async () => {
|
|
8
8
|
const fonts = [
|
|
9
9
|
{ name: "Aboreto", tags: ["Sans Serif"] },
|
|
@@ -12,7 +12,7 @@ describe("group 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 groupBy(tree, (value, key, tree) => value.tags);
|
|
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"] }],
|
|
@@ -33,7 +33,7 @@ describe("group transform", () => {
|
|
|
33
33
|
"Work Sans": { tags: ["Grotesque", "Sans Serif"] },
|
|
34
34
|
};
|
|
35
35
|
const tree = Tree.from(fonts);
|
|
36
|
-
const grouped = await
|
|
36
|
+
const grouped = await groupBy(tree, (value, key, tree) => value.tags);
|
|
37
37
|
assert.deepEqual(await Tree.plain(grouped), {
|
|
38
38
|
Geometric: {
|
|
39
39
|
"Albert Sans": { tags: ["Geometric", "Sans Serif"] },
|
|
@@ -208,56 +208,6 @@ describe("map", () => {
|
|
|
208
208
|
C: "letter c",
|
|
209
209
|
});
|
|
210
210
|
});
|
|
211
|
-
|
|
212
|
-
test("can add an extension to a key", async () => {
|
|
213
|
-
const treelike = {
|
|
214
|
-
"file0.txt": 1,
|
|
215
|
-
file1: 2,
|
|
216
|
-
file2: 3,
|
|
217
|
-
};
|
|
218
|
-
const fixture = await map(treelike, {
|
|
219
|
-
extension: "->.data",
|
|
220
|
-
});
|
|
221
|
-
assert.deepEqual(await Tree.plain(fixture), {
|
|
222
|
-
"file0.txt.data": 1,
|
|
223
|
-
"file1.data": 2,
|
|
224
|
-
"file2.data": 3,
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("can change a key's extension", async () => {
|
|
229
|
-
const treelike = {
|
|
230
|
-
"file1.lower": "will be mapped",
|
|
231
|
-
file2: "won't be mapped",
|
|
232
|
-
"file3.foo": "won't be mapped",
|
|
233
|
-
};
|
|
234
|
-
const fixture = await map(treelike, {
|
|
235
|
-
extension: ".lower->.upper",
|
|
236
|
-
value: (sourceValue) => sourceValue.toUpperCase(),
|
|
237
|
-
});
|
|
238
|
-
assert.deepEqual(await Tree.plain(fixture), {
|
|
239
|
-
"file1.upper": "WILL BE MAPPED",
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("can manipulate extensions deeply", async () => {
|
|
244
|
-
const treelike = {
|
|
245
|
-
"file1.txt": 1,
|
|
246
|
-
more: {
|
|
247
|
-
"file2.txt": 2,
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
const fixture = await map(treelike, {
|
|
251
|
-
deep: true,
|
|
252
|
-
extension: ".txt->",
|
|
253
|
-
});
|
|
254
|
-
assert.deepEqual(await Tree.plain(fixture), {
|
|
255
|
-
file1: 1,
|
|
256
|
-
more: {
|
|
257
|
-
file2: 2,
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
211
|
});
|
|
262
212
|
|
|
263
213
|
function addUnderscore(value, key) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import mapExtension from "../../src/operations/mapExtension.js";
|
|
4
|
+
import plain from "../../src/operations/plain.js";
|
|
5
|
+
|
|
6
|
+
describe("mapExtension", () => {
|
|
7
|
+
test("can add an extension to a key", async () => {
|
|
8
|
+
const treelike = {
|
|
9
|
+
"file0.txt": 1,
|
|
10
|
+
file1: 2,
|
|
11
|
+
file2: 3,
|
|
12
|
+
};
|
|
13
|
+
const fixture = await mapExtension(treelike, "->.data");
|
|
14
|
+
assert.deepEqual(await plain(fixture), {
|
|
15
|
+
"file0.txt.data": 1,
|
|
16
|
+
"file1.data": 2,
|
|
17
|
+
"file2.data": 3,
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("can change a key's extension", async () => {
|
|
22
|
+
const treelike = {
|
|
23
|
+
"file1.lower": "will be mapped",
|
|
24
|
+
file2: "won't be mapped",
|
|
25
|
+
"file3.foo": "won't be mapped",
|
|
26
|
+
};
|
|
27
|
+
const fixture = await mapExtension(treelike, {
|
|
28
|
+
extension: ".lower->.upper",
|
|
29
|
+
value: (sourceValue) => sourceValue.toUpperCase(),
|
|
30
|
+
});
|
|
31
|
+
assert.deepEqual(await plain(fixture), {
|
|
32
|
+
"file1.upper": "WILL BE MAPPED",
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("can manipulate extensions deeply", async () => {
|
|
37
|
+
const treelike = {
|
|
38
|
+
"file1.txt": 1,
|
|
39
|
+
more: {
|
|
40
|
+
"file2.txt": 2,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const fixture = await mapExtension(treelike, ".txt->", {
|
|
44
|
+
deep: true,
|
|
45
|
+
});
|
|
46
|
+
assert.deepEqual(await plain(fixture), {
|
|
47
|
+
file1: 1,
|
|
48
|
+
more: {
|
|
49
|
+
file2: 2,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/src/operations/concat.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import deepText from "./deepText.js";
|
|
2
|
-
import from from "./from.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Concatenate the text content of objects or trees.
|
|
6
|
-
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
-
*
|
|
9
|
-
* @param {any[]} args
|
|
10
|
-
*/
|
|
11
|
-
export default async function concat(...args) {
|
|
12
|
-
console.warn(
|
|
13
|
-
"Warning: the Tree.concat function is deprecated, use Tree.deepText instead."
|
|
14
|
-
);
|
|
15
|
-
const tree = from(args);
|
|
16
|
-
return deepText(tree);
|
|
17
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import getTreeArgument from "../utilities/getTreeArgument.js";
|
|
2
|
-
import mapReduce from "./mapReduce.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Return only the defined (not `undefined`) values in the deep tree.
|
|
6
|
-
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
-
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
9
|
-
*
|
|
10
|
-
* @param {Treelike} treelike
|
|
11
|
-
*/
|
|
12
|
-
export default async function defineds(treelike) {
|
|
13
|
-
console.warn(
|
|
14
|
-
"Warning: Tree.defineds is deprecated. If you have a use for it, please let us know."
|
|
15
|
-
);
|
|
16
|
-
const tree = await getTreeArgument(treelike, "defineds", { deep: true });
|
|
17
|
-
|
|
18
|
-
const result = await mapReduce(tree, null, async (values, keys) => {
|
|
19
|
-
const object = {};
|
|
20
|
-
let someValuesExist = false;
|
|
21
|
-
for (let i = 0; i < keys.length; i++) {
|
|
22
|
-
const value = values[i];
|
|
23
|
-
if (value != null) {
|
|
24
|
-
someValuesExist = true;
|
|
25
|
-
object[keys[i]] = values[i];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return someValuesExist ? object : null;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
return result;
|
|
32
|
-
}
|
package/src/operations/fromFn.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import FunctionTree from "../drivers/FunctionTree.js";
|
|
2
|
-
import isUnpackable from "../utilities/isUnpackable.js";
|
|
3
|
-
import toFunction from "../utilities/toFunction.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Create a tree from a function and a set of keys.
|
|
7
|
-
*
|
|
8
|
-
* @typedef {import("@weborigami/async-tree").Invocable} Invocable
|
|
9
|
-
*
|
|
10
|
-
* @param {Invocable} invocable
|
|
11
|
-
*/
|
|
12
|
-
export default async function fromFn(invocable, keys = []) {
|
|
13
|
-
console.warn("Tree.fromFn is deprecated, use Tree.withKeys instead.");
|
|
14
|
-
if (invocable === undefined) {
|
|
15
|
-
throw new Error(
|
|
16
|
-
"Tree.fromFn: the first argument must be a function or a tree."
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
const fn = toFunction(invocable);
|
|
20
|
-
if (isUnpackable(keys)) {
|
|
21
|
-
keys = await keys.unpack();
|
|
22
|
-
}
|
|
23
|
-
// @ts-ignore
|
|
24
|
-
const tree = new FunctionTree(fn, keys);
|
|
25
|
-
return tree;
|
|
26
|
-
}
|
package/src/operations/remove.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { default as del } from "./delete.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
|
-
export default async function remove(tree, key) {
|
|
12
|
-
console.warn("`Tree.remove` is deprecated. Use `Tree.delete` instead.");
|
|
13
|
-
return del(tree, key);
|
|
14
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import from from "./from.js";
|
|
2
|
-
import isAsyncTree from "./isAsyncTree.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
6
|
-
*
|
|
7
|
-
* @param {Treelike} target
|
|
8
|
-
* @param {Treelike} source
|
|
9
|
-
*/
|
|
10
|
-
export default async function setDeep(target, source) {
|
|
11
|
-
console.warn("Tree.setDeep is deprecated, use Tree.assign instead.");
|
|
12
|
-
const targetTree = from(target);
|
|
13
|
-
const sourceTree = from(source);
|
|
14
|
-
await applyUpdates(sourceTree, targetTree);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Apply all updates from the source to the target.
|
|
18
|
-
async function applyUpdates(source, target) {
|
|
19
|
-
// Fire off requests to update all keys, then wait for all of them to finish.
|
|
20
|
-
const promises = [];
|
|
21
|
-
for (const key of await source.keys()) {
|
|
22
|
-
const updateKeyPromise = applyUpdateForKey(source, target, key);
|
|
23
|
-
promises.push(updateKeyPromise);
|
|
24
|
-
}
|
|
25
|
-
await Promise.all(promises);
|
|
26
|
-
|
|
27
|
-
// HACK: Transforms like KeysTransform that maintain caches will need to
|
|
28
|
-
// recalculate things now that updates have been applied. This should be an
|
|
29
|
-
// automatic part of calling set() -- but triggering those changes inside
|
|
30
|
-
// set() produces cases where set() and get() calls can be interleaved. The
|
|
31
|
-
// atomicity of set() needs to be reconsidered. For now, we work around the
|
|
32
|
-
// problem by triggering `onChange` after the updates have been applied.
|
|
33
|
-
target.onChange?.();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Copy the value for the given key from the source to the target.
|
|
37
|
-
async function applyUpdateForKey(source, target, key) {
|
|
38
|
-
const sourceValue = await source.get(key);
|
|
39
|
-
if (isAsyncTree(sourceValue)) {
|
|
40
|
-
const targetValue = await target.get(key);
|
|
41
|
-
if (isAsyncTree(targetValue)) {
|
|
42
|
-
// Both source and target are async dictionaries; recurse.
|
|
43
|
-
await applyUpdates(sourceValue, targetValue);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Copy the value from the source to the target.
|
|
49
|
-
await target.set(key, sourceValue);
|
|
50
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { DeepObjectTree, Tree } from "../../src/internal.js";
|
|
4
|
-
import deepMap from "../../src/operations/deepMap.js";
|
|
5
|
-
|
|
6
|
-
describe("deepMap", () => {
|
|
7
|
-
test("can map extensions deeply", async () => {
|
|
8
|
-
const treelike = new DeepObjectTree({
|
|
9
|
-
"file1.txt": "will be mapped",
|
|
10
|
-
file2: "won't be mapped",
|
|
11
|
-
"file3.foo": "won't be mapped",
|
|
12
|
-
more: {
|
|
13
|
-
"file4.txt": "will be mapped",
|
|
14
|
-
"file5.bar": "won't be mapped",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
const fixture = await deepMap.call(null, treelike, {
|
|
18
|
-
deep: true,
|
|
19
|
-
extension: ".txt->.upper",
|
|
20
|
-
value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
|
|
21
|
-
});
|
|
22
|
-
assert.deepEqual(await Tree.plain(fixture), {
|
|
23
|
-
"file1.upper": "WILL BE MAPPED",
|
|
24
|
-
more: {
|
|
25
|
-
"file4.upper": "WILL BE MAPPED",
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { Tree } from "../../src/internal.js";
|
|
4
|
-
import defineds from "../../src/operations/defineds.js";
|
|
5
|
-
|
|
6
|
-
describe("defineds", () => {
|
|
7
|
-
test("returns only defined values in a tree", async () => {
|
|
8
|
-
const obj = {
|
|
9
|
-
a: 1,
|
|
10
|
-
b: undefined,
|
|
11
|
-
c: null,
|
|
12
|
-
d: {
|
|
13
|
-
e: 2,
|
|
14
|
-
f: undefined,
|
|
15
|
-
g: {
|
|
16
|
-
h: 3,
|
|
17
|
-
i: undefined,
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
const result = await defineds(obj);
|
|
22
|
-
const plain = await Tree.plain(result);
|
|
23
|
-
assert.deepEqual(plain, { a: 1, d: { e: 2, g: { h: 3 } } });
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
|
4
|
-
import setDeep from "../../src/operations/setDeep.js";
|
|
5
|
-
|
|
6
|
-
describe("tree/setDeep", () => {
|
|
7
|
-
test("can apply updates with a single argument to set", async () => {
|
|
8
|
-
const tree = new DeepObjectTree({
|
|
9
|
-
a: 1,
|
|
10
|
-
b: 2,
|
|
11
|
-
more: {
|
|
12
|
-
d: 3,
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Apply changes.
|
|
17
|
-
await setDeep.call(
|
|
18
|
-
null,
|
|
19
|
-
tree,
|
|
20
|
-
new DeepObjectTree({
|
|
21
|
-
a: 4, // Overwrite existing value
|
|
22
|
-
b: undefined, // Delete
|
|
23
|
-
c: 5, // Add
|
|
24
|
-
more: {
|
|
25
|
-
// Should leave existing `more` keys alone.
|
|
26
|
-
e: 6, // Add
|
|
27
|
-
},
|
|
28
|
-
// Add new subtree
|
|
29
|
-
extra: {
|
|
30
|
-
f: 7,
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
assert.deepEqual(await Tree.plain(tree), {
|
|
36
|
-
a: 4,
|
|
37
|
-
c: 5,
|
|
38
|
-
more: {
|
|
39
|
-
d: 3,
|
|
40
|
-
e: 6,
|
|
41
|
-
},
|
|
42
|
-
extra: {
|
|
43
|
-
f: 7,
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("can apply updates to an array", async () => {
|
|
49
|
-
const tree = new ObjectTree(["a", "b", "c"]);
|
|
50
|
-
await setDeep.call(null, tree, ["d", "e"]);
|
|
51
|
-
assert.deepEqual(await Tree.plain(tree), ["d", "e", "c"]);
|
|
52
|
-
});
|
|
53
|
-
});
|