@weborigami/async-tree 0.0.66-beta.1 → 0.0.66-beta.2
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 +2 -0
- package/package.json +4 -4
- package/src/BrowserFileTree.js +28 -6
- package/src/DeepMapTree.js +2 -2
- package/src/DeepObjectTree.js +6 -9
- package/src/FileTree.js +14 -10
- package/src/MapTree.js +27 -4
- package/src/ObjectTree.js +53 -6
- package/src/OpenSiteTree.js +41 -0
- package/src/SetTree.js +0 -6
- package/src/SiteTree.js +24 -85
- package/src/Tree.d.ts +0 -1
- package/src/Tree.js +18 -38
- package/src/jsonKeys.js +4 -37
- package/src/operations/cache.js +0 -4
- package/src/operations/deepMerge.js +7 -10
- package/src/operations/merge.js +7 -10
- package/src/trailingSlash.js +54 -0
- package/src/transforms/cachedKeyFunctions.js +72 -34
- package/src/transforms/keyFunctionsForExtensions.js +24 -10
- package/src/transforms/mapFn.js +11 -17
- package/src/transforms/regExpKeys.js +17 -12
- package/src/utilities.js +34 -6
- package/test/BrowserFileTree.test.js +28 -5
- package/test/DeepMapTree.test.js +17 -0
- package/test/DeepObjectTree.test.js +17 -7
- package/test/FileTree.test.js +14 -7
- package/test/MapTree.test.js +21 -0
- package/test/ObjectTree.test.js +16 -12
- package/test/OpenSiteTree.test.js +113 -0
- package/test/SiteTree.test.js +14 -49
- package/test/Tree.test.js +19 -39
- package/test/browser/assert.js +9 -0
- package/test/browser/index.html +4 -4
- package/test/calendarTree.test.js +1 -1
- package/test/fixtures/markdown/subfolder/README.md +1 -0
- package/test/jsonKeys.test.js +0 -9
- package/test/operations/cache.test.js +1 -1
- package/test/operations/merge.test.js +20 -1
- package/test/trailingSlash.test.js +36 -0
- package/test/transforms/cachedKeyFunctions.test.js +90 -0
- package/test/transforms/keyFunctionsForExtensions.test.js +7 -3
- package/test/transforms/mapFn.test.js +29 -20
- package/test/utilities.test.js +6 -2
- package/test/transforms/cachedKeyMaps.test.js +0 -41
package/src/Tree.js
CHANGED
|
@@ -3,6 +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 * as trailingSlash from "./trailingSlash.js";
|
|
6
7
|
import mapTransform from "./transforms/mapFn.js";
|
|
7
8
|
import * as utilities from "./utilities.js";
|
|
8
9
|
import {
|
|
@@ -175,7 +176,8 @@ export async function has(tree, key) {
|
|
|
175
176
|
*/
|
|
176
177
|
export function isAsyncTree(obj) {
|
|
177
178
|
return (
|
|
178
|
-
obj &&
|
|
179
|
+
obj !== null &&
|
|
180
|
+
typeof obj === "object" &&
|
|
179
181
|
typeof obj.get === "function" &&
|
|
180
182
|
typeof obj.keys === "function" &&
|
|
181
183
|
// JavaScript Map look like trees but can't be extended the same way, so we
|
|
@@ -196,22 +198,6 @@ export function isAsyncMutableTree(obj) {
|
|
|
196
198
|
);
|
|
197
199
|
}
|
|
198
200
|
|
|
199
|
-
/**
|
|
200
|
-
* Return true if the indicated key produces or is expected to produce an
|
|
201
|
-
* async tree.
|
|
202
|
-
*
|
|
203
|
-
* This defers to the tree's own isKeyForSubtree method. If not found, this
|
|
204
|
-
* gets the value of that key and returns true if the value is an async
|
|
205
|
-
* tree.
|
|
206
|
-
*/
|
|
207
|
-
export async function isKeyForSubtree(tree, key) {
|
|
208
|
-
if (tree.isKeyForSubtree) {
|
|
209
|
-
return tree.isKeyForSubtree(key);
|
|
210
|
-
}
|
|
211
|
-
const value = await tree.get(key);
|
|
212
|
-
return isAsyncTree(value);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
201
|
/**
|
|
216
202
|
* Return true if the object can be traversed via the `traverse()` method. The
|
|
217
203
|
* object must be either treelike or a packed object with an `unpack()` method.
|
|
@@ -315,7 +301,8 @@ export async function paths(treelike, base = "") {
|
|
|
315
301
|
const tree = from(treelike);
|
|
316
302
|
const result = [];
|
|
317
303
|
for (const key of await tree.keys()) {
|
|
318
|
-
const
|
|
304
|
+
const separator = trailingSlash.has(base) ? "" : "/";
|
|
305
|
+
const valuePath = base ? `${base}${separator}${key}` : key;
|
|
319
306
|
const value = await tree.get(key);
|
|
320
307
|
if (await isAsyncTree(value)) {
|
|
321
308
|
const subPaths = await paths(value, valuePath);
|
|
@@ -344,7 +331,10 @@ export async function plain(treelike) {
|
|
|
344
331
|
}
|
|
345
332
|
const object = {};
|
|
346
333
|
for (let i = 0; i < keys.length; i++) {
|
|
347
|
-
|
|
334
|
+
// Normalize slashes in keys.
|
|
335
|
+
const key = trailingSlash.remove(keys[i]);
|
|
336
|
+
const value = values[i];
|
|
337
|
+
object[key] = value;
|
|
348
338
|
}
|
|
349
339
|
return castArrayLike(object);
|
|
350
340
|
});
|
|
@@ -436,15 +426,6 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
436
426
|
value = await value.unpack();
|
|
437
427
|
}
|
|
438
428
|
|
|
439
|
-
if (!isTreelike(value)) {
|
|
440
|
-
// Value isn't treelike, so can't be traversed except for a special case:
|
|
441
|
-
// if there's only one key left and it's an empty string, return the
|
|
442
|
-
// non-treelike value.
|
|
443
|
-
if (remainingKeys.length === 1 && remainingKeys[0] === "") {
|
|
444
|
-
return value;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
429
|
if (value instanceof Function) {
|
|
449
430
|
// Value is a function: call it with the remaining keys.
|
|
450
431
|
const fn = value;
|
|
@@ -453,21 +434,20 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
453
434
|
const args = remainingKeys.splice(0, fnKeyCount);
|
|
454
435
|
key = null;
|
|
455
436
|
value = await fn.call(target, ...args);
|
|
456
|
-
} else {
|
|
457
|
-
const originalValue = value;
|
|
458
|
-
|
|
437
|
+
} else if (isTraversable(value) || typeof value === "object") {
|
|
459
438
|
// Value is some other treelike object: cast it to a tree.
|
|
460
439
|
const tree = from(value);
|
|
461
440
|
// Get the next key.
|
|
462
441
|
key = remainingKeys.shift();
|
|
463
442
|
// Get the value for the key.
|
|
464
443
|
value = await tree.get(key);
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
444
|
+
} else {
|
|
445
|
+
// Value can't be traversed
|
|
446
|
+
throw new TraverseError("Tried to traverse a value that's not treelike", {
|
|
447
|
+
tree: treelike,
|
|
448
|
+
keys,
|
|
449
|
+
position,
|
|
450
|
+
});
|
|
471
451
|
}
|
|
472
452
|
|
|
473
453
|
position++;
|
|
@@ -477,7 +457,7 @@ export async function traverseOrThrow(treelike, ...keys) {
|
|
|
477
457
|
}
|
|
478
458
|
|
|
479
459
|
/**
|
|
480
|
-
* Given a slash-separated path like "foo/bar", traverse the keys "foo" and
|
|
460
|
+
* Given a slash-separated path like "foo/bar", traverse the keys "foo/" and
|
|
481
461
|
* "bar" and return the resulting value.
|
|
482
462
|
*
|
|
483
463
|
* @param {Treelike} tree
|
package/src/jsonKeys.js
CHANGED
|
@@ -9,48 +9,15 @@ import { Tree } from "./internal.js";
|
|
|
9
9
|
* a trailing slash like "about/" for a subtree of that node.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Parse the JSON in a .keys.json file.
|
|
14
|
-
*
|
|
15
|
-
* This returns a flat dictionary of flags which are true for subtrees and
|
|
16
|
-
* false otherwise.
|
|
17
|
-
*
|
|
18
|
-
* Example: the JSON `["index.html","about/"]` parses as:
|
|
19
|
-
*
|
|
20
|
-
* {
|
|
21
|
-
* "index.html": false,
|
|
22
|
-
* about: true,
|
|
23
|
-
* }
|
|
24
|
-
*/
|
|
25
|
-
export function parse(json) {
|
|
26
|
-
const descriptors = JSON.parse(json);
|
|
27
|
-
const result = {};
|
|
28
|
-
for (const descriptor of descriptors) {
|
|
29
|
-
if (descriptor.endsWith("/")) {
|
|
30
|
-
result[descriptor.slice(0, -1)] = true;
|
|
31
|
-
} else {
|
|
32
|
-
result[descriptor] = false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
12
|
/**
|
|
39
13
|
* Given a tree node, return a JSON string that can be written to a .keys.json
|
|
40
14
|
* file.
|
|
41
15
|
*/
|
|
42
16
|
export async function stringify(treelike) {
|
|
43
17
|
const tree = Tree.from(treelike);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const isKeyForSubtree = await Tree.isKeyForSubtree(tree, key);
|
|
51
|
-
const keyDescriptor = isKeyForSubtree ? `${key}/` : key;
|
|
52
|
-
keyDescriptors.push(keyDescriptor);
|
|
53
|
-
}
|
|
54
|
-
const json = JSON.stringify(keyDescriptors);
|
|
18
|
+
let keys = Array.from(await tree.keys());
|
|
19
|
+
// Skip the key `.keys.json` if present.
|
|
20
|
+
keys = keys.filter((key) => key !== ".keys.json");
|
|
21
|
+
const json = JSON.stringify(keys);
|
|
55
22
|
return json;
|
|
56
23
|
}
|
package/src/operations/cache.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "../internal.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return a tree that performs a deep merge of the given trees.
|
|
@@ -42,21 +43,17 @@ export default function deepMerge(...sources) {
|
|
|
42
43
|
}
|
|
43
44
|
},
|
|
44
45
|
|
|
45
|
-
async isKeyForSubtree(key) {
|
|
46
|
-
// Check trees for the indicated key in reverse order.
|
|
47
|
-
for (let index = trees.length - 1; index >= 0; index--) {
|
|
48
|
-
if (await Tree.isKeyForSubtree(trees[index], key)) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
},
|
|
54
|
-
|
|
55
46
|
async keys() {
|
|
56
47
|
const keys = new Set();
|
|
57
48
|
// Collect keys in the order the trees were provided.
|
|
58
49
|
for (const tree of trees) {
|
|
59
50
|
for (const key of await tree.keys()) {
|
|
51
|
+
// Remove the alternate form of the key (if it exists)
|
|
52
|
+
const alternateKey = trailingSlash.toggle(key);
|
|
53
|
+
if (alternateKey !== key) {
|
|
54
|
+
keys.delete(alternateKey);
|
|
55
|
+
}
|
|
56
|
+
|
|
60
57
|
keys.add(key);
|
|
61
58
|
}
|
|
62
59
|
}
|
package/src/operations/merge.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tree } from "../internal.js";
|
|
2
2
|
import * as symbols from "../symbols.js";
|
|
3
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Return a tree that performs a shallow merge of the given trees.
|
|
@@ -40,21 +41,17 @@ export default function merge(...sources) {
|
|
|
40
41
|
return undefined;
|
|
41
42
|
},
|
|
42
43
|
|
|
43
|
-
async isKeyForSubtree(key) {
|
|
44
|
-
// Check trees for the indicated key in reverse order.
|
|
45
|
-
for (let index = trees.length - 1; index >= 0; index--) {
|
|
46
|
-
if (await Tree.isKeyForSubtree(trees[index], key)) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
},
|
|
52
|
-
|
|
53
44
|
async keys() {
|
|
54
45
|
const keys = new Set();
|
|
55
46
|
// Collect keys in the order the trees were provided.
|
|
56
47
|
for (const tree of trees) {
|
|
57
48
|
for (const key of await tree.keys()) {
|
|
49
|
+
// Remove the alternate form of the key (if it exists)
|
|
50
|
+
const alternateKey = trailingSlash.toggle(key);
|
|
51
|
+
if (alternateKey !== key) {
|
|
52
|
+
keys.delete(alternateKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
58
55
|
keys.add(key);
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add a trailing slash to a string key if the value is truthy. If the key
|
|
3
|
+
* is not a string, it will be returned as is.
|
|
4
|
+
*
|
|
5
|
+
* @param {any} key
|
|
6
|
+
*/
|
|
7
|
+
export function add(key) {
|
|
8
|
+
if (key == null) {
|
|
9
|
+
throw new ReferenceError("trailingSlash: key was undefined");
|
|
10
|
+
}
|
|
11
|
+
return typeof key === "string" && !key.endsWith("/") ? `${key}/` : key;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Return true if the indicated key is a string with a trailing slash,
|
|
16
|
+
* false otherwise.
|
|
17
|
+
*
|
|
18
|
+
* @param {any} key
|
|
19
|
+
*/
|
|
20
|
+
export function has(key) {
|
|
21
|
+
if (key == null) {
|
|
22
|
+
throw new ReferenceError("trailingSlash: key was undefined");
|
|
23
|
+
}
|
|
24
|
+
return typeof key === "string" && key.endsWith("/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Remove a trailing slash from a string key.
|
|
29
|
+
*
|
|
30
|
+
* @param {any} key
|
|
31
|
+
*/
|
|
32
|
+
export function remove(key) {
|
|
33
|
+
if (key == null) {
|
|
34
|
+
throw new ReferenceError("trailingSlash: key was undefined");
|
|
35
|
+
}
|
|
36
|
+
return typeof key === "string" ? key.replace(/\/$/, "") : key;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* If the key has a trailing slash, remove it; otherwise add it.
|
|
41
|
+
*
|
|
42
|
+
* @param {any} key
|
|
43
|
+
* @param {boolean} [force]
|
|
44
|
+
*/
|
|
45
|
+
export function toggle(key, force = undefined) {
|
|
46
|
+
if (key == null) {
|
|
47
|
+
throw new ReferenceError("trailingSlash: key was undefined");
|
|
48
|
+
}
|
|
49
|
+
if (typeof key !== "string") {
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
const addSlash = force ?? !has(key);
|
|
53
|
+
return addSlash ? add(key) : remove(key);
|
|
54
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
2
|
|
|
3
3
|
const treeToCaches = new WeakMap();
|
|
4
4
|
|
|
@@ -6,46 +6,50 @@ const treeToCaches = new WeakMap();
|
|
|
6
6
|
* Given a key function, return a new key function and inverse key function that
|
|
7
7
|
* cache the results of the original.
|
|
8
8
|
*
|
|
9
|
+
* If `skipSubtrees` is true, the inverse key function will skip any source keys
|
|
10
|
+
* that are keys for subtrees, returning the source key unmodified.
|
|
11
|
+
*
|
|
9
12
|
* @typedef {import("../../index.ts").KeyFn} KeyFn
|
|
10
13
|
*
|
|
11
14
|
* @param {KeyFn} keyFn
|
|
12
|
-
* @param {boolean}
|
|
15
|
+
* @param {boolean?} skipSubtrees
|
|
13
16
|
* @returns {{ key: KeyFn, inverseKey: KeyFn }}
|
|
14
17
|
*/
|
|
15
|
-
export default function
|
|
18
|
+
export default function cachedKeyFunctions(keyFn, skipSubtrees = false) {
|
|
16
19
|
return {
|
|
17
20
|
async inverseKey(resultKey, tree) {
|
|
18
|
-
const
|
|
21
|
+
const { resultKeyToSourceKey, sourceKeyToResultKey } =
|
|
22
|
+
getKeyMapsForTree(tree);
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return caches.resultKeyToSourceKey.get(resultKey);
|
|
24
|
+
const cachedSourceKey = searchKeyMap(resultKeyToSourceKey, resultKey);
|
|
25
|
+
if (cachedSourceKey !== undefined) {
|
|
26
|
+
return cachedSourceKey;
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Iterate through the tree's keys, calculating source keys as we go,
|
|
27
30
|
// until we find a match. Cache all the intermediate results and the
|
|
28
31
|
// final match. This is O(n), but we stop as soon as we find a match,
|
|
29
32
|
// and subsequent calls will benefit from the intermediate results.
|
|
33
|
+
const resultKeyWithoutSlash = trailingSlash.remove(resultKey);
|
|
30
34
|
for (const sourceKey of await tree.keys()) {
|
|
31
35
|
// Skip any source keys we already know about.
|
|
32
|
-
if (
|
|
36
|
+
if (sourceKeyToResultKey.has(sourceKey)) {
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
caches.sourceKeyToResultKey.set(sourceKey, computedResultKey);
|
|
44
|
-
caches.resultKeyToSourceKey.set(computedResultKey, sourceKey);
|
|
40
|
+
const computedResultKey = await computeAndCacheResultKey(
|
|
41
|
+
tree,
|
|
42
|
+
keyFn,
|
|
43
|
+
skipSubtrees,
|
|
44
|
+
sourceKey
|
|
45
|
+
);
|
|
45
46
|
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
if (
|
|
48
|
+
computedResultKey &&
|
|
49
|
+
trailingSlash.remove(computedResultKey) === resultKeyWithoutSlash
|
|
50
|
+
) {
|
|
51
|
+
// Match found, match trailing slash and return
|
|
52
|
+
return trailingSlash.toggle(sourceKey, trailingSlash.has(resultKey));
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -53,27 +57,42 @@ export default function createCachedKeysTransform(keyFn, deep = false) {
|
|
|
53
57
|
},
|
|
54
58
|
|
|
55
59
|
async key(sourceKey, tree) {
|
|
56
|
-
const
|
|
60
|
+
const { sourceKeyToResultKey } = getKeyMapsForTree(tree);
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (keyMaps.sourceKeyToResultKey.has(sourceKey)) {
|
|
62
|
-
return keyMaps.sourceKeyToResultKey.get(sourceKey);
|
|
62
|
+
const cachedResultKey = searchKeyMap(sourceKeyToResultKey, sourceKey);
|
|
63
|
+
if (cachedResultKey !== undefined) {
|
|
64
|
+
return cachedResultKey;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
const resultKey = await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
const resultKey = await computeAndCacheResultKey(
|
|
68
|
+
tree,
|
|
69
|
+
keyFn,
|
|
70
|
+
skipSubtrees,
|
|
71
|
+
sourceKey
|
|
72
|
+
);
|
|
71
73
|
return resultKey;
|
|
72
74
|
},
|
|
73
75
|
};
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
function
|
|
78
|
+
async function computeAndCacheResultKey(tree, keyFn, skipSubtrees, sourceKey) {
|
|
79
|
+
const { resultKeyToSourceKey, sourceKeyToResultKey } =
|
|
80
|
+
getKeyMapsForTree(tree);
|
|
81
|
+
|
|
82
|
+
const resultKey =
|
|
83
|
+
skipSubtrees && trailingSlash.has(sourceKey)
|
|
84
|
+
? sourceKey
|
|
85
|
+
: await keyFn(sourceKey, tree);
|
|
86
|
+
|
|
87
|
+
sourceKeyToResultKey.set(sourceKey, resultKey);
|
|
88
|
+
resultKeyToSourceKey.set(resultKey, sourceKey);
|
|
89
|
+
|
|
90
|
+
return resultKey;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Maintain key->inverseKey and inverseKey->key mappings for each tree. These
|
|
94
|
+
// store subtree keys in either direction with a trailing slash.
|
|
95
|
+
function getKeyMapsForTree(tree) {
|
|
77
96
|
let keyMaps = treeToCaches.get(tree);
|
|
78
97
|
if (!keyMaps) {
|
|
79
98
|
keyMaps = {
|
|
@@ -84,3 +103,22 @@ function getCachesForTree(tree) {
|
|
|
84
103
|
}
|
|
85
104
|
return keyMaps;
|
|
86
105
|
}
|
|
106
|
+
|
|
107
|
+
// Search the given key map for the key. Ignore trailing slashes in the search,
|
|
108
|
+
// but preserve them in the result.
|
|
109
|
+
function searchKeyMap(keyMap, key) {
|
|
110
|
+
// Check key as is
|
|
111
|
+
let match;
|
|
112
|
+
if (keyMap.has(key)) {
|
|
113
|
+
match = keyMap.get(key);
|
|
114
|
+
} else if (!trailingSlash.has(key)) {
|
|
115
|
+
// Check key without trailing slash
|
|
116
|
+
const withSlash = trailingSlash.add(key);
|
|
117
|
+
if (keyMap.has(withSlash)) {
|
|
118
|
+
match = keyMap.get(withSlash);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return match
|
|
122
|
+
? trailingSlash.toggle(match, trailingSlash.has(key))
|
|
123
|
+
: undefined;
|
|
124
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Given a source resultExtension and a result resultExtension, return a pair of key
|
|
3
5
|
* functions that map between them.
|
|
@@ -19,13 +21,23 @@ export default function keyFunctionsForExtensions({
|
|
|
19
21
|
|
|
20
22
|
return {
|
|
21
23
|
async inverseKey(resultKey, tree) {
|
|
22
|
-
|
|
24
|
+
// Remove trailing slash so that mapFn won't inadvertently unpack files.
|
|
25
|
+
const baseKey = trailingSlash.remove(resultKey);
|
|
26
|
+
const basename = matchExtension(baseKey, resultExtension);
|
|
23
27
|
return basename ? `${basename}${dotPrefix(sourceExtension)}` : undefined;
|
|
24
28
|
},
|
|
25
29
|
|
|
26
30
|
async key(sourceKey, tree) {
|
|
27
|
-
const
|
|
28
|
-
|
|
31
|
+
const hasSlash = trailingSlash.has(sourceKey);
|
|
32
|
+
const baseKey = trailingSlash.remove(sourceKey);
|
|
33
|
+
const basename = matchExtension(baseKey, sourceExtension);
|
|
34
|
+
return basename
|
|
35
|
+
? // Preserve trailing slash
|
|
36
|
+
trailingSlash.toggle(
|
|
37
|
+
`${basename}${dotPrefix(resultExtension)}`,
|
|
38
|
+
hasSlash
|
|
39
|
+
)
|
|
40
|
+
: undefined;
|
|
29
41
|
},
|
|
30
42
|
};
|
|
31
43
|
}
|
|
@@ -35,15 +47,17 @@ function dotPrefix(resultExtension) {
|
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
/**
|
|
38
|
-
* See if the key ends with the given resultExtension. If it does, return the
|
|
39
|
-
* name without the resultExtension; if it doesn't return null.
|
|
50
|
+
* See if the key ends with the given resultExtension. If it does, return the
|
|
51
|
+
* base name without the resultExtension; if it doesn't return null.
|
|
40
52
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
53
|
+
* A trailing slash in the key is ignored for purposes of comparison to comply
|
|
54
|
+
* with the way Origami can unpack files. Example: the keys "data.json" and
|
|
55
|
+
* "data.json/" are treated equally.
|
|
43
56
|
*
|
|
44
|
-
* This uses a different, more general interpretation of "resultExtension" to
|
|
45
|
-
* suffix, rather than Node's interpretation `path.extname`. In
|
|
46
|
-
* will match an "resultExtension" like ".foo.bar" that
|
|
57
|
+
* This uses a different, more general interpretation of "resultExtension" to
|
|
58
|
+
* mean any suffix, rather than Node's interpretation `path.extname`. In
|
|
59
|
+
* particular, this will match an "resultExtension" like ".foo.bar" that
|
|
60
|
+
* contains more than one dot.
|
|
47
61
|
*/
|
|
48
62
|
function matchExtension(key, resultExtension) {
|
|
49
63
|
if (resultExtension) {
|
package/src/transforms/mapFn.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "../internal.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Return a transform function that maps the keys and/or values of a tree.
|
|
@@ -65,13 +66,9 @@ export default function createMapTransform(options = {}) {
|
|
|
65
66
|
if (keyFn || valueFn) {
|
|
66
67
|
transformed.get = async (resultKey) => {
|
|
67
68
|
// Step 1: Map the result key to the source key.
|
|
68
|
-
const
|
|
69
|
-
const sourceKey =
|
|
70
|
-
!isSubtree && inverseKeyFn
|
|
71
|
-
? await inverseKeyFn(resultKey, tree)
|
|
72
|
-
: resultKey;
|
|
69
|
+
const sourceKey = (await inverseKeyFn?.(resultKey, tree)) ?? resultKey;
|
|
73
70
|
|
|
74
|
-
if (sourceKey
|
|
71
|
+
if (sourceKey === undefined) {
|
|
75
72
|
// No source key means no value.
|
|
76
73
|
return undefined;
|
|
77
74
|
}
|
|
@@ -81,8 +78,8 @@ export default function createMapTransform(options = {}) {
|
|
|
81
78
|
if (needsSourceValue) {
|
|
82
79
|
// Normal case: get the value from the source tree.
|
|
83
80
|
sourceValue = await tree.get(sourceKey);
|
|
84
|
-
} else if (deep &&
|
|
85
|
-
// Only get the source value if it's a subtree.
|
|
81
|
+
} else if (deep && trailingSlash.has(sourceKey)) {
|
|
82
|
+
// Only get the source value if it's expected to be a subtree.
|
|
86
83
|
sourceValue = tree;
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -111,15 +108,12 @@ export default function createMapTransform(options = {}) {
|
|
|
111
108
|
// Apply the keyFn to source keys for leaf values (not subtrees).
|
|
112
109
|
const sourceKeys = Array.from(await tree.keys());
|
|
113
110
|
const mapped = await Promise.all(
|
|
114
|
-
sourceKeys.map(async (sourceKey) =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
return resultKey;
|
|
122
|
-
})
|
|
111
|
+
sourceKeys.map(async (sourceKey) =>
|
|
112
|
+
// Deep maps leave source keys for subtrees alone
|
|
113
|
+
deep && trailingSlash.has(sourceKey)
|
|
114
|
+
? sourceKey
|
|
115
|
+
: await keyFn(sourceKey, tree)
|
|
116
|
+
)
|
|
123
117
|
);
|
|
124
118
|
// Filter out any cases where the keyFn returned undefined.
|
|
125
119
|
const resultKeys = mapped.filter((key) => key !== undefined);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Tree } from "../internal.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A tree whose keys are strings interpreted as regular expressions.
|
|
@@ -57,25 +58,29 @@ export default async function regExpKeys(treelike) {
|
|
|
57
58
|
continue;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
// Construct regular expression.
|
|
61
|
-
let text = key;
|
|
62
|
-
if (!text.startsWith("^")) {
|
|
63
|
-
text = "^" + text;
|
|
64
|
-
}
|
|
65
|
-
if (!text.endsWith("$")) {
|
|
66
|
-
text = text + "$";
|
|
67
|
-
}
|
|
68
|
-
const regExp = new RegExp(text);
|
|
69
|
-
|
|
70
61
|
// Get value.
|
|
71
62
|
let value = await tree.get(key);
|
|
72
|
-
|
|
63
|
+
|
|
64
|
+
let regExp;
|
|
65
|
+
if (trailingSlash.has(key) || Tree.isAsyncTree(value)) {
|
|
66
|
+
const baseKey = trailingSlash.remove(key);
|
|
67
|
+
regExp = new RegExp("^" + baseKey + "/?$");
|
|
68
|
+
// Subtree
|
|
73
69
|
value = regExpKeys(value);
|
|
74
70
|
if (!value.parent) {
|
|
75
71
|
value.parent = result;
|
|
76
72
|
}
|
|
73
|
+
} else {
|
|
74
|
+
// Construct regular expression.
|
|
75
|
+
let text = key;
|
|
76
|
+
if (!text.startsWith("^")) {
|
|
77
|
+
text = "^" + text;
|
|
78
|
+
}
|
|
79
|
+
if (!text.endsWith("$")) {
|
|
80
|
+
text = text + "$";
|
|
81
|
+
}
|
|
82
|
+
regExp = new RegExp(text);
|
|
77
83
|
}
|
|
78
|
-
|
|
79
84
|
map.set(regExp, value);
|
|
80
85
|
}
|
|
81
86
|
|