@weborigami/async-tree 0.2.3 → 0.2.5
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 +2 -0
- package/src/Tree.js +3 -10
- package/src/drivers/constantTree.js +19 -0
- package/src/operations/cache.js +18 -36
- package/src/operations/deepMerge.js +6 -3
- package/src/operations/filter.js +51 -0
- package/src/operations/globKeys.js +87 -0
- package/src/operations/map.js +31 -14
- package/src/operations/regExpKeys.js +2 -9
- package/test/drivers/constantTree.test.js +13 -0
- package/test/operations/cache.test.js +1 -25
- package/test/operations/filter.test.js +32 -0
- package/test/operations/globKeys.test.js +53 -0
- package/test/operations/regExpKeys.test.js +8 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
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.7.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.2.
|
|
14
|
+
"@weborigami/types": "0.2.5"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/shared.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Exports for both Node.js and browser
|
|
2
2
|
|
|
3
3
|
export { default as calendarTree } from "./src/drivers/calendarTree.js";
|
|
4
|
+
export { default as constantTree } from "./src/drivers/constantTree.js";
|
|
4
5
|
export { default as DeepMapTree } from "./src/drivers/DeepMapTree.js";
|
|
5
6
|
export { default as DeferredTree } from "./src/drivers/DeferredTree.js";
|
|
6
7
|
export { default as ExplorableSiteTree } from "./src/drivers/ExplorableSiteTree.js";
|
|
@@ -18,6 +19,7 @@ export { default as deepReverse } from "./src/operations/deepReverse.js";
|
|
|
18
19
|
export { default as deepTake } from "./src/operations/deepTake.js";
|
|
19
20
|
export { default as deepValues } from "./src/operations/deepValues.js";
|
|
20
21
|
export { default as deepValuesIterator } from "./src/operations/deepValuesIterator.js";
|
|
22
|
+
export { default as filter } from "./src/operations/filter.js";
|
|
21
23
|
export { default as group } from "./src/operations/group.js";
|
|
22
24
|
export { default as invokeFunctions } from "./src/operations/invokeFunctions.js";
|
|
23
25
|
export { default as keyFunctionsForExtensions } from "./src/operations/keyFunctionsForExtensions.js";
|
package/src/Tree.js
CHANGED
|
@@ -422,7 +422,7 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
422
422
|
const remainingKeys = keys.slice();
|
|
423
423
|
let key;
|
|
424
424
|
while (remainingKeys.length > 0) {
|
|
425
|
-
if (value
|
|
425
|
+
if (value == null) {
|
|
426
426
|
throw new TraverseError("A null or undefined value can't be traversed", {
|
|
427
427
|
tree: treelike,
|
|
428
428
|
keys,
|
|
@@ -443,20 +443,13 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
443
443
|
const args = remainingKeys.splice(0, fnKeyCount);
|
|
444
444
|
key = null;
|
|
445
445
|
value = await fn.call(target, ...args);
|
|
446
|
-
} else
|
|
447
|
-
//
|
|
446
|
+
} else {
|
|
447
|
+
// Cast value to a tree.
|
|
448
448
|
const tree = from(value);
|
|
449
449
|
// Get the next key.
|
|
450
450
|
key = remainingKeys.shift();
|
|
451
451
|
// Get the value for the key.
|
|
452
452
|
value = await tree.get(key);
|
|
453
|
-
} else {
|
|
454
|
-
// Value can't be traversed
|
|
455
|
-
throw new TraverseError("Tried to traverse a value that's not treelike", {
|
|
456
|
-
tree: treelike,
|
|
457
|
-
keys,
|
|
458
|
-
position,
|
|
459
|
-
});
|
|
460
453
|
}
|
|
461
454
|
|
|
462
455
|
position++;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { trailingSlash } from "../../main.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A tree that returns a constant value for any key. If the key ends with a
|
|
5
|
+
* slash, then the same type of subtree is returned.
|
|
6
|
+
*
|
|
7
|
+
* @param {any} constant
|
|
8
|
+
*/
|
|
9
|
+
export default function constantTree(constant) {
|
|
10
|
+
return {
|
|
11
|
+
async get(key) {
|
|
12
|
+
return trailingSlash.has(key) ? constantTree(constant) : constant;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async keys() {
|
|
16
|
+
return [];
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
package/src/operations/cache.js
CHANGED
|
@@ -6,23 +6,15 @@ import { ObjectTree, Tree } from "../internal.js";
|
|
|
6
6
|
*
|
|
7
7
|
* If no second tree is supplied, an in-memory value cache is used.
|
|
8
8
|
*
|
|
9
|
-
* An optional third filter tree can be supplied. If a filter tree is supplied,
|
|
10
|
-
* only values for keys that match the filter will be cached.
|
|
11
|
-
*
|
|
12
9
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
13
10
|
* @typedef {import("@weborigami/types").AsyncMutableTree} AsyncMutableTree
|
|
14
11
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
15
12
|
*
|
|
16
13
|
* @param {Treelike} sourceTreelike
|
|
17
14
|
* @param {AsyncMutableTree} [cacheTreelike]
|
|
18
|
-
* @param {Treelike} [filterTreelike]
|
|
19
15
|
* @returns {AsyncTree & { description: string }}
|
|
20
16
|
*/
|
|
21
|
-
export default function treeCache(
|
|
22
|
-
sourceTreelike,
|
|
23
|
-
cacheTreelike,
|
|
24
|
-
filterTreelike
|
|
25
|
-
) {
|
|
17
|
+
export default function treeCache(sourceTreelike, cacheTreelike) {
|
|
26
18
|
if (!sourceTreelike) {
|
|
27
19
|
const error = new TypeError(`cache: The source tree isn't defined.`);
|
|
28
20
|
/** @type {any} */ (error).position = 0;
|
|
@@ -30,7 +22,6 @@ export default function treeCache(
|
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
const source = Tree.from(sourceTreelike);
|
|
33
|
-
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
|
34
25
|
|
|
35
26
|
/** @type {AsyncMutableTree} */
|
|
36
27
|
let cache;
|
|
@@ -58,36 +49,27 @@ export default function treeCache(
|
|
|
58
49
|
|
|
59
50
|
// Cache miss or interior node cache hit.
|
|
60
51
|
let value = await source.get(key);
|
|
61
|
-
if (value
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Coerce to tree and then save it back to the cache. This is
|
|
74
|
-
// necessary, e.g., if cache is an ObjectTree; we want the
|
|
75
|
-
// subtree to also be an ObjectTree, not a plain object.
|
|
76
|
-
cacheValue = Tree.from(cacheValue);
|
|
77
|
-
await cache.set(key, cacheValue);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
value = treeCache(value, cacheValue, filterValue);
|
|
81
|
-
} else {
|
|
82
|
-
// Save in cache before returning.
|
|
83
|
-
await cache.set(key, value);
|
|
52
|
+
if (Tree.isAsyncTree(value)) {
|
|
53
|
+
// Construct merged tree for a tree result.
|
|
54
|
+
if (cacheValue === undefined) {
|
|
55
|
+
// Construct new empty container in cache
|
|
56
|
+
await cache.set(key, {});
|
|
57
|
+
cacheValue = await cache.get(key);
|
|
58
|
+
if (!Tree.isAsyncTree(cacheValue)) {
|
|
59
|
+
// Coerce to tree and then save it back to the cache. This is
|
|
60
|
+
// necessary, e.g., if cache is an ObjectTree; we want the
|
|
61
|
+
// subtree to also be an ObjectTree, not a plain object.
|
|
62
|
+
cacheValue = Tree.from(cacheValue);
|
|
63
|
+
await cache.set(key, cacheValue);
|
|
84
64
|
}
|
|
85
65
|
}
|
|
86
|
-
|
|
87
|
-
|
|
66
|
+
value = treeCache(value, cacheValue);
|
|
67
|
+
} else if (value !== undefined) {
|
|
68
|
+
// Save in cache before returning.
|
|
69
|
+
await cache.set(key, value);
|
|
88
70
|
}
|
|
89
71
|
|
|
90
|
-
return
|
|
72
|
+
return value;
|
|
91
73
|
},
|
|
92
74
|
|
|
93
75
|
async keys() {
|
|
@@ -21,10 +21,13 @@ export default function deepMerge(...sources) {
|
|
|
21
21
|
for (let index = trees.length - 1; index >= 0; index--) {
|
|
22
22
|
const tree = trees[index];
|
|
23
23
|
const value = await tree.get(key);
|
|
24
|
-
if (
|
|
25
|
-
|
|
24
|
+
if (
|
|
25
|
+
Tree.isAsyncTree(value) ||
|
|
26
|
+
(Tree.isTreelike(value) && trailingSlash.has(key))
|
|
27
|
+
) {
|
|
28
|
+
if (/** @type {any} */ (value).parent === tree) {
|
|
26
29
|
// Merged tree acts as parent instead of the source tree.
|
|
27
|
-
value.parent = this;
|
|
30
|
+
/** @type {any} */ (value).parent = this;
|
|
28
31
|
}
|
|
29
32
|
subtrees.unshift(value);
|
|
30
33
|
} else if (value !== undefined) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { trailingSlash, Tree } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given trees `a` and `b`, return a filtered version of `a` where only the keys
|
|
5
|
+
* that exist in `b` and have truthy values are kept. The filter operation is
|
|
6
|
+
* deep: if a value from `a` is a subtree, it will be filtered recursively.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
10
|
+
*
|
|
11
|
+
* @param {Treelike} a
|
|
12
|
+
* @param {Treelike} b
|
|
13
|
+
* @returns {AsyncTree}
|
|
14
|
+
*/
|
|
15
|
+
export default function filter(a, b) {
|
|
16
|
+
a = Tree.from(a);
|
|
17
|
+
b = Tree.from(b, { deep: true });
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
async get(key) {
|
|
21
|
+
// The key must exist in b and return a truthy value
|
|
22
|
+
const bValue = await b.get(key);
|
|
23
|
+
if (!bValue) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
let aValue = await a.get(key);
|
|
27
|
+
if (Tree.isTreelike(aValue)) {
|
|
28
|
+
// Filter the subtree
|
|
29
|
+
return filter(aValue, bValue);
|
|
30
|
+
} else {
|
|
31
|
+
return aValue;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async keys() {
|
|
36
|
+
// Use a's keys as the basis
|
|
37
|
+
const aKeys = [...(await a.keys())];
|
|
38
|
+
const bValues = await Promise.all(aKeys.map((key) => b.get(key)));
|
|
39
|
+
// An async tree value in b implies that the a key should have a slash
|
|
40
|
+
const aKeySlashes = aKeys.map((key, index) =>
|
|
41
|
+
trailingSlash.toggle(
|
|
42
|
+
key,
|
|
43
|
+
trailingSlash.has(key) || Tree.isAsyncTree(bValues[index])
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
// Remove keys that don't have values in b
|
|
47
|
+
const keys = aKeySlashes.filter((key, index) => bValues[index] ?? false);
|
|
48
|
+
return keys;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ObjectTree, Tree, merge, trailingSlash } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
const globstar = "**";
|
|
4
|
+
const globstarSlash = `${globstar}/`;
|
|
5
|
+
|
|
6
|
+
export default function globKeys(treelike) {
|
|
7
|
+
const globs = Tree.from(treelike, { deep: true });
|
|
8
|
+
return {
|
|
9
|
+
async get(key) {
|
|
10
|
+
if (typeof key !== "string") {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let value = await matchGlobs(globs, key);
|
|
15
|
+
if (Tree.isAsyncTree(value)) {
|
|
16
|
+
value = globKeys(value);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async keys() {
|
|
22
|
+
return globs.keys();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Convert the glob to a regular expression
|
|
28
|
+
function matchGlob(glob, key) {
|
|
29
|
+
const regexText = glob
|
|
30
|
+
// Escape special regex characters
|
|
31
|
+
.replace(/[+?^${}()|\.\[\]\\]/g, "\\$&")
|
|
32
|
+
// Replace the glob wildcards with regex wildcards
|
|
33
|
+
.replace(/\*/g, ".*")
|
|
34
|
+
.replace(/\?/g, ".");
|
|
35
|
+
const regex = new RegExp(`^${regexText}$`);
|
|
36
|
+
return regex.test(key);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function matchGlobs(globs, key) {
|
|
40
|
+
let globstarGlobs;
|
|
41
|
+
|
|
42
|
+
// Collect all matches
|
|
43
|
+
let matches = [];
|
|
44
|
+
for (let glob of await globs.keys()) {
|
|
45
|
+
if (glob === globstarSlash) {
|
|
46
|
+
// Remember for later
|
|
47
|
+
globstarGlobs = await globs.get(glob);
|
|
48
|
+
if (trailingSlash.has(key)) {
|
|
49
|
+
// A key for a subtree matches the globstar
|
|
50
|
+
matches.push(new ObjectTree({ [globstar]: globstarGlobs }));
|
|
51
|
+
}
|
|
52
|
+
} else if (matchGlob(glob, key)) {
|
|
53
|
+
// Text matches glob, get value
|
|
54
|
+
const globValue = await globs.get(glob);
|
|
55
|
+
if (globValue !== undefined) {
|
|
56
|
+
if (!Tree.isAsyncTree(globValue)) {
|
|
57
|
+
// Found a non-tree match, return immediately
|
|
58
|
+
return globValue;
|
|
59
|
+
}
|
|
60
|
+
// Add to matches
|
|
61
|
+
matches.push(globValue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If we don't have a match yet, try globstar
|
|
67
|
+
if (matches.length === 0) {
|
|
68
|
+
if (!globstarGlobs) {
|
|
69
|
+
// No matches
|
|
70
|
+
return undefined;
|
|
71
|
+
} else {
|
|
72
|
+
// Try globstar
|
|
73
|
+
const globstarValue = await matchGlobs(globstarGlobs, key);
|
|
74
|
+
if (!Tree.isAsyncTree(globstarValue)) {
|
|
75
|
+
// Found a non-tree match, return immediately
|
|
76
|
+
return globstarValue;
|
|
77
|
+
} else if (trailingSlash.has(key)) {
|
|
78
|
+
// No match but key is for subtree, return globstar tree
|
|
79
|
+
return new ObjectTree({ [globstar]: globstarGlobs });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Merge all matches
|
|
85
|
+
const value = matches.length === 1 ? matches[0] : merge(...matches);
|
|
86
|
+
return value;
|
|
87
|
+
}
|
package/src/operations/map.js
CHANGED
|
@@ -65,37 +65,54 @@ export default function map(treelike, options = {}) {
|
|
|
65
65
|
|
|
66
66
|
if (keyFn || valueFn) {
|
|
67
67
|
transformed.get = async (resultKey) => {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
if (resultKey === undefined) {
|
|
69
|
+
throw new ReferenceError(`map: Cannot get an undefined key.`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 1: Map the result key to the source key
|
|
73
|
+
let sourceKey = await inverseKeyFn?.(resultKey, tree);
|
|
70
74
|
|
|
71
75
|
if (sourceKey === undefined) {
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
if (deep && trailingSlash.has(resultKey)) {
|
|
77
|
+
// Special case: deep tree and value is expected to be a subtree
|
|
78
|
+
const sourceValue = await tree.get(resultKey);
|
|
79
|
+
// If we did get a subtree, apply the map to it
|
|
80
|
+
const resultValue = Tree.isAsyncTree(sourceValue)
|
|
81
|
+
? mapFn(sourceValue)
|
|
82
|
+
: undefined;
|
|
83
|
+
return resultValue;
|
|
84
|
+
} else {
|
|
85
|
+
// No inverseKeyFn, or it returned undefined; use resultKey
|
|
86
|
+
sourceKey = resultKey;
|
|
87
|
+
}
|
|
74
88
|
}
|
|
75
89
|
|
|
76
|
-
//
|
|
90
|
+
// Regular path: map a single value
|
|
91
|
+
|
|
92
|
+
// Step 2: Get the source value
|
|
77
93
|
let sourceValue;
|
|
78
94
|
if (needsSourceValue) {
|
|
79
|
-
// Normal case: get the value from the source tree
|
|
95
|
+
// Normal case: get the value from the source tree
|
|
80
96
|
sourceValue = await tree.get(sourceKey);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
if (deep && sourceValue === undefined) {
|
|
98
|
+
// Key might be for a subtree, see if original key exists
|
|
99
|
+
sourceValue = await tree.get(resultKey);
|
|
100
|
+
}
|
|
84
101
|
}
|
|
85
102
|
|
|
86
|
-
// Step 3: Map the source value to the result value
|
|
103
|
+
// Step 3: Map the source value to the result value
|
|
87
104
|
let resultValue;
|
|
88
105
|
if (needsSourceValue && sourceValue === undefined) {
|
|
89
|
-
// No source value means no result value
|
|
106
|
+
// No source value means no result value
|
|
90
107
|
resultValue = undefined;
|
|
91
108
|
} else if (deep && Tree.isAsyncTree(sourceValue)) {
|
|
92
|
-
//
|
|
109
|
+
// We weren't expecting a subtree but got one; map it
|
|
93
110
|
resultValue = mapFn(sourceValue);
|
|
94
111
|
} else if (valueFn) {
|
|
95
|
-
// Map a single value
|
|
112
|
+
// Map a single value
|
|
96
113
|
resultValue = await valueFn(sourceValue, sourceKey, tree);
|
|
97
114
|
} else {
|
|
98
|
-
// Return source value as is
|
|
115
|
+
// Return source value as is
|
|
99
116
|
resultValue = sourceValue;
|
|
100
117
|
}
|
|
101
118
|
|
|
@@ -33,7 +33,7 @@ export default async function regExpKeys(treelike) {
|
|
|
33
33
|
if (key == null) {
|
|
34
34
|
// Reject nullish key.
|
|
35
35
|
throw new ReferenceError(
|
|
36
|
-
|
|
36
|
+
`regExpKeys: Cannot get a null or undefined key.`
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -72,14 +72,7 @@ export default async function regExpKeys(treelike) {
|
|
|
72
72
|
}
|
|
73
73
|
} else {
|
|
74
74
|
// Construct regular expression.
|
|
75
|
-
|
|
76
|
-
if (!text.startsWith("^")) {
|
|
77
|
-
text = "^" + text;
|
|
78
|
-
}
|
|
79
|
-
if (!text.endsWith("$")) {
|
|
80
|
-
text = text + "$";
|
|
81
|
-
}
|
|
82
|
-
regExp = new RegExp(text);
|
|
75
|
+
regExp = new RegExp(key);
|
|
83
76
|
}
|
|
84
77
|
map.set(regExp, value);
|
|
85
78
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import constantTree from "../../src/drivers/constantTree.js";
|
|
5
|
+
|
|
6
|
+
describe("constantTree", () => {
|
|
7
|
+
test("returns a deep tree that returns constant for all keys", async () => {
|
|
8
|
+
const fixture = constantTree(1);
|
|
9
|
+
assert.equal(await fixture.get("a"), 1);
|
|
10
|
+
assert.equal(await fixture.get("b"), 1);
|
|
11
|
+
assert.equal(await Tree.traverse(fixture, "c/", "d/", "e"), 1);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import { DeepObjectTree, ObjectTree
|
|
3
|
+
import { DeepObjectTree, ObjectTree } from "../../src/internal.js";
|
|
4
4
|
import cache from "../../src/operations/cache.js";
|
|
5
5
|
|
|
6
6
|
describe("cache", () => {
|
|
@@ -36,28 +36,4 @@ describe("cache", () => {
|
|
|
36
36
|
const moreCache = await objectCache.get("more");
|
|
37
37
|
assert.equal(await moreCache.get("d"), 4);
|
|
38
38
|
});
|
|
39
|
-
|
|
40
|
-
test("if a cache filter is supplied, it only caches values whose keys match the filter", async () => {
|
|
41
|
-
const objectCache = new ObjectTree({});
|
|
42
|
-
const fixture = cache(
|
|
43
|
-
Tree.from({
|
|
44
|
-
"a.txt": "a",
|
|
45
|
-
"b.txt": "b",
|
|
46
|
-
}),
|
|
47
|
-
objectCache,
|
|
48
|
-
Tree.from({
|
|
49
|
-
"a.txt": true,
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
// Access some values to populate the cache.
|
|
54
|
-
assert.equal(await fixture.get("a.txt"), "a");
|
|
55
|
-
assert.equal(await fixture.get("b.txt"), "b");
|
|
56
|
-
|
|
57
|
-
// The a.txt value should be cached because it matches the filter.
|
|
58
|
-
assert.equal(await objectCache.get("a.txt"), "a");
|
|
59
|
-
|
|
60
|
-
// The b.txt value should not be cached because it does not match the filter.
|
|
61
|
-
assert.equal(await objectCache.get("b.txt"), undefined);
|
|
62
|
-
});
|
|
63
39
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { Tree } from "../../src/internal.js";
|
|
4
|
+
import filter from "../../src/operations/filter.js";
|
|
5
|
+
|
|
6
|
+
describe("filter", () => {
|
|
7
|
+
test("removes keys and values whose filter values are falsy", async () => {
|
|
8
|
+
const result = filter(
|
|
9
|
+
{
|
|
10
|
+
a: 1,
|
|
11
|
+
b: 2,
|
|
12
|
+
c: {
|
|
13
|
+
d: 3,
|
|
14
|
+
e: 4,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
a: true,
|
|
19
|
+
c: {
|
|
20
|
+
d: true,
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
assert.deepEqual(await result.keys(), ["a", "c/"]);
|
|
25
|
+
assert.deepEqual(await Tree.plain(result), {
|
|
26
|
+
a: 1,
|
|
27
|
+
c: {
|
|
28
|
+
d: 3,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { describe, test } from "node:test";
|
|
4
|
+
import globKeys from "../../src/operations/globKeys.js";
|
|
5
|
+
|
|
6
|
+
describe("globKeys", () => {
|
|
7
|
+
test("matches globs", async () => {
|
|
8
|
+
const globTree = globKeys({
|
|
9
|
+
"*.txt": true,
|
|
10
|
+
"*.js": false,
|
|
11
|
+
"*.jsx": true,
|
|
12
|
+
"foo*baz": true,
|
|
13
|
+
});
|
|
14
|
+
assert(await globTree.get("file.txt"));
|
|
15
|
+
assert(!(await globTree.get("script.js")));
|
|
16
|
+
assert(await globTree.get("component.jsx"));
|
|
17
|
+
assert(await globTree.get("foobarbaz"));
|
|
18
|
+
assert(await globTree.get("foobaz"));
|
|
19
|
+
assert(!(await globTree.get("foo")));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("matches nested globs", async () => {
|
|
23
|
+
const globTree = globKeys({
|
|
24
|
+
sub: {
|
|
25
|
+
foo: "bar",
|
|
26
|
+
"*": "default",
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "file"), "default");
|
|
30
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "foo"), "bar");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("supports deep matches with globstar", async () => {
|
|
34
|
+
const globTree = globKeys({
|
|
35
|
+
"**": {
|
|
36
|
+
"*.txt": true, // More specific glob pattern must come
|
|
37
|
+
"*": false,
|
|
38
|
+
},
|
|
39
|
+
sub: {
|
|
40
|
+
"*.md": true,
|
|
41
|
+
},
|
|
42
|
+
"s*": {
|
|
43
|
+
"*.html": true,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
assert.equal(await Tree.traverse(globTree, "a/", "b/", "foo.txt"), true);
|
|
47
|
+
assert.equal(await Tree.traverse(globTree, "c/", "foo"), false);
|
|
48
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "file.md"), true);
|
|
49
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "file.txt"), true);
|
|
50
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "file.html"), true);
|
|
51
|
+
assert.equal(await Tree.traverse(globTree, "sub/", "file"), false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -7,19 +7,23 @@ describe("regExpKeys", () => {
|
|
|
7
7
|
test("matches keys using regular expressions", async () => {
|
|
8
8
|
const fixture = await regExpKeys(
|
|
9
9
|
new DeepObjectTree({
|
|
10
|
-
a: true,
|
|
11
|
-
"b.*": true,
|
|
10
|
+
"^a$": true,
|
|
11
|
+
"^b.*": true,
|
|
12
12
|
c: {
|
|
13
13
|
d: true,
|
|
14
14
|
"e*": true,
|
|
15
15
|
},
|
|
16
|
+
f: true,
|
|
16
17
|
})
|
|
17
18
|
);
|
|
18
19
|
assert(await Tree.traverse(fixture, "a"));
|
|
19
20
|
assert(!(await Tree.traverse(fixture, "alice")));
|
|
20
21
|
assert(await Tree.traverse(fixture, "bob"));
|
|
21
22
|
assert(await Tree.traverse(fixture, "brenda"));
|
|
22
|
-
assert(await Tree.traverse(fixture, "c", "d"));
|
|
23
|
-
assert(await Tree.traverse(fixture, "c", "eee"));
|
|
23
|
+
assert(await Tree.traverse(fixture, "c/", "d"));
|
|
24
|
+
assert(await Tree.traverse(fixture, "c/", "eee"));
|
|
25
|
+
assert(await Tree.traverse(fixture, "f"));
|
|
26
|
+
assert(await Tree.traverse(fixture, "stef")); // contains "f"
|
|
27
|
+
assert(!(await Tree.traverse(fixture, "gail")));
|
|
24
28
|
});
|
|
25
29
|
});
|