@weborigami/async-tree 0.0.72 → 0.1.0
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/browser.js +2 -11
- package/index.ts +9 -0
- package/main.js +3 -37
- package/package.json +2 -2
- package/shared.js +32 -0
- package/src/Tree.d.ts +2 -2
- package/src/Tree.js +13 -16
- package/src/{BrowserFileTree.js → drivers/BrowserFileTree.js} +3 -3
- package/src/{DeepMapTree.js → drivers/DeepMapTree.js} +1 -1
- package/src/{DeepObjectTree.js → drivers/DeepObjectTree.js} +2 -2
- package/src/{DeferredTree.js → drivers/DeferredTree.js} +5 -3
- package/src/{FileTree.js → drivers/FileTree.js} +3 -3
- package/src/{FunctionTree.js → drivers/FunctionTree.js} +1 -1
- package/src/{MapTree.js → drivers/MapTree.js} +3 -3
- package/src/{ObjectTree.js → drivers/ObjectTree.js} +4 -4
- package/src/{SetTree.js → drivers/SetTree.js} +1 -1
- package/src/{SiteTree.js → drivers/SiteTree.js} +2 -2
- package/src/{calendarTree.js → drivers/calendarTree.js} +1 -1
- package/src/extension.js +140 -0
- package/src/internal.js +3 -2
- package/src/{transforms → operations}/deepReverse.js +1 -1
- package/src/operations/deepTake.js +30 -3
- package/src/operations/group.js +44 -3
- package/src/operations/keyFunctionsForExtensions.js +48 -0
- package/src/operations/map.js +118 -3
- package/src/operations/sort.js +45 -2
- package/src/operations/take.js +19 -2
- package/src/symbols.js +1 -0
- package/test/Tree.test.js +25 -2
- package/test/{BrowserFileTree.test.js → drivers/BrowserFileTree.test.js} +2 -2
- package/test/{DeepMapTree.test.js → drivers/DeepMapTree.test.js} +2 -2
- package/test/{DeepObjectTree.test.js → drivers/DeepObjectTree.test.js} +1 -1
- package/test/{DeferredTree.test.js → drivers/DeferredTree.test.js} +2 -2
- package/test/{ExplorableSiteTree.test.js → drivers/ExplorableSiteTree.test.js} +6 -3
- package/test/{FileTree.test.js → drivers/FileTree.test.js} +6 -5
- package/test/{FunctionTree.test.js → drivers/FunctionTree.test.js} +1 -1
- package/test/{MapTree.test.js → drivers/MapTree.test.js} +2 -2
- package/test/{ObjectTree.test.js → drivers/ObjectTree.test.js} +2 -2
- package/test/{SetTree.test.js → drivers/SetTree.test.js} +2 -2
- package/test/{SiteTree.test.js → drivers/SiteTree.test.js} +1 -1
- package/test/{calendarTree.test.js → drivers/calendarTree.test.js} +5 -5
- package/test/extension.test.js +41 -0
- package/test/{transforms → operations}/cachedKeyFunctions.test.js +1 -1
- package/test/operations/concat.test.js +1 -1
- package/test/{transforms → operations}/deepReverse.test.js +1 -1
- package/test/operations/{deepTakeFn.test.js → deepTake.test.js} +3 -3
- package/test/{transforms/groupFn.test.js → operations/group.test.js} +4 -4
- package/test/{transforms → operations}/invokeFunctions.test.js +1 -1
- package/test/{transforms → operations}/keyFunctionsForExtensions.test.js +21 -12
- package/test/{transforms/mapFn.test.js → operations/map.test.js} +23 -31
- package/test/{transforms → operations}/regExpKeys.test.js +1 -1
- package/test/{transforms → operations}/reverse.test.js +1 -1
- package/test/{transforms/sortFn.test.js → operations/sort.test.js} +6 -7
- package/test/{transforms/takeFn.test.js → operations/take.test.js} +3 -3
- package/src/operations/deepTakeFn.js +0 -43
- package/src/operations/groupFn.js +0 -57
- package/src/transforms/keyFunctionsForExtensions.js +0 -78
- package/src/transforms/mapFn.js +0 -126
- package/src/transforms/sortFn.js +0 -71
- package/src/transforms/takeFn.js +0 -31
- /package/src/{ExplorableSiteTree.js → drivers/ExplorableSiteTree.js} +0 -0
- /package/src/{transforms → operations}/cachedKeyFunctions.js +0 -0
- /package/src/{transforms → operations}/invokeFunctions.js +0 -0
- /package/src/{transforms → operations}/regExpKeys.js +0 -0
- /package/src/{transforms → operations}/reverse.js +0 -0
- /package/test/{fixtures → drivers/fixtures}/markdown/Alice.md +0 -0
- /package/test/{fixtures → drivers/fixtures}/markdown/Bob.md +0 -0
- /package/test/{fixtures → drivers/fixtures}/markdown/Carol.md +0 -0
- /package/test/{fixtures → drivers/fixtures}/markdown/subfolder/README.md +0 -0
package/browser.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
// Exports for browser
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
export { default as BrowserFileTree } from "./src/BrowserFileTree.js";
|
|
6
|
-
export { default as FunctionTree } from "./src/FunctionTree.js";
|
|
7
|
-
export * as keysJson from "./src/jsonKeys.js";
|
|
8
|
-
export { default as MapTree } from "./src/MapTree.js";
|
|
9
|
-
export { default as ObjectTree } from "./src/ObjectTree.js";
|
|
10
|
-
export { default as SetTree } from "./src/SetTree.js";
|
|
11
|
-
export { default as SiteTree } from "./src/SiteTree.js";
|
|
12
|
-
export * as Tree from "./src/Tree.js";
|
|
13
|
-
export * from "./src/utilities.js";
|
|
3
|
+
export * from "./shared.js";
|
|
4
|
+
export { default as BrowserFileTree } from "./src/drivers/BrowserFileTree.js";
|
package/index.ts
CHANGED
|
@@ -43,6 +43,15 @@ export type Treelike =
|
|
|
43
43
|
NativeTreelike |
|
|
44
44
|
Unpackable<NativeTreelike>;
|
|
45
45
|
|
|
46
|
+
export type TreeMapOptions = {
|
|
47
|
+
deep?: boolean;
|
|
48
|
+
description?: string;
|
|
49
|
+
needsSourceValue?: boolean;
|
|
50
|
+
inverseKey?: KeyFn;
|
|
51
|
+
key?: KeyFn;
|
|
52
|
+
value?: ValueKeyFn;
|
|
53
|
+
};
|
|
54
|
+
|
|
46
55
|
export type TreeTransform = (treelike: Treelike) => AsyncTree;
|
|
47
56
|
|
|
48
57
|
export type TypedArray =
|
package/main.js
CHANGED
|
@@ -1,39 +1,5 @@
|
|
|
1
1
|
// Exports for Node.js
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
export { default as FileTree } from "./src/FileTree.js";
|
|
5
|
-
export
|
|
6
|
-
export { default as MapTree } from "./src/MapTree.js";
|
|
7
|
-
// Skip BrowserFileTree.js, which is browser-only.
|
|
8
|
-
export { default as calendarTree } from "./src/calendarTree.js";
|
|
9
|
-
export { default as DeepMapTree } from "./src/DeepMapTree.js";
|
|
10
|
-
export { default as ExplorableSiteTree } from "./src/ExplorableSiteTree.js";
|
|
11
|
-
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
12
|
-
export * as jsonKeys from "./src/jsonKeys.js";
|
|
13
|
-
export { default as cache } from "./src/operations/cache.js";
|
|
14
|
-
export { default as concat } from "./src/operations/concat.js";
|
|
15
|
-
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
|
16
|
-
export { default as deepTake } from "./src/operations/deepTake.js";
|
|
17
|
-
export { default as deepTakeFn } from "./src/operations/deepTakeFn.js";
|
|
18
|
-
export { default as deepValues } from "./src/operations/deepValues.js";
|
|
19
|
-
export { default as deepValuesIterator } from "./src/operations/deepValuesIterator.js";
|
|
20
|
-
export { default as group } from "./src/operations/group.js";
|
|
21
|
-
export { default as groupFn } from "./src/operations/groupFn.js";
|
|
22
|
-
export { default as map } from "./src/operations/map.js";
|
|
23
|
-
export { default as merge } from "./src/operations/merge.js";
|
|
24
|
-
export { default as scope } from "./src/operations/scope.js";
|
|
25
|
-
export { default as sort } from "./src/operations/sort.js";
|
|
26
|
-
export { default as take } from "./src/operations/take.js";
|
|
27
|
-
export { default as SetTree } from "./src/SetTree.js";
|
|
28
|
-
export { default as SiteTree } from "./src/SiteTree.js";
|
|
29
|
-
export * as symbols from "./src/symbols.js";
|
|
30
|
-
export * as trailingSlash from "./src/trailingSlash.js";
|
|
31
|
-
export { default as cachedKeyFunctions } from "./src/transforms/cachedKeyFunctions.js";
|
|
32
|
-
export { default as deepReverse } from "./src/transforms/deepReverse.js";
|
|
33
|
-
export { default as invokeFunctions } from "./src/transforms/invokeFunctions.js";
|
|
34
|
-
export { default as keyFunctionsForExtensions } from "./src/transforms/keyFunctionsForExtensions.js";
|
|
35
|
-
export { default as mapFn } from "./src/transforms/mapFn.js";
|
|
36
|
-
export { default as reverse } from "./src/transforms/reverse.js";
|
|
37
|
-
export { default as sortFn } from "./src/transforms/sortFn.js";
|
|
38
|
-
export { default as takeFn } from "./src/transforms/takeFn.js";
|
|
39
|
-
export * from "./src/utilities.js";
|
|
3
|
+
export * from "./shared.js";
|
|
4
|
+
export { default as FileTree } from "./src/drivers/FileTree.js";
|
|
5
|
+
export * as extension from "./src/extension.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
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.6.2"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0
|
|
14
|
+
"@weborigami/types": "0.1.0"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/shared.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Exports for both Node.js and browser
|
|
2
|
+
|
|
3
|
+
export { default as calendarTree } from "./src/drivers/calendarTree.js";
|
|
4
|
+
export { default as DeepMapTree } from "./src/drivers/DeepMapTree.js";
|
|
5
|
+
export { default as DeferredTree } from "./src/drivers/DeferredTree.js";
|
|
6
|
+
export { default as ExplorableSiteTree } from "./src/drivers/ExplorableSiteTree.js";
|
|
7
|
+
export { default as FunctionTree } from "./src/drivers/FunctionTree.js";
|
|
8
|
+
export { default as MapTree } from "./src/drivers/MapTree.js";
|
|
9
|
+
export { default as SetTree } from "./src/drivers/SetTree.js";
|
|
10
|
+
export { default as SiteTree } from "./src/drivers/SiteTree.js";
|
|
11
|
+
export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
|
|
12
|
+
export * as jsonKeys from "./src/jsonKeys.js";
|
|
13
|
+
export { default as cache } from "./src/operations/cache.js";
|
|
14
|
+
export { default as cachedKeyFunctions } from "./src/operations/cachedKeyFunctions.js";
|
|
15
|
+
export { default as concat } from "./src/operations/concat.js";
|
|
16
|
+
export { default as deepMerge } from "./src/operations/deepMerge.js";
|
|
17
|
+
export { default as deepReverse } from "./src/operations/deepReverse.js";
|
|
18
|
+
export { default as deepTake } from "./src/operations/deepTake.js";
|
|
19
|
+
export { default as deepValues } from "./src/operations/deepValues.js";
|
|
20
|
+
export { default as deepValuesIterator } from "./src/operations/deepValuesIterator.js";
|
|
21
|
+
export { default as group } from "./src/operations/group.js";
|
|
22
|
+
export { default as invokeFunctions } from "./src/operations/invokeFunctions.js";
|
|
23
|
+
export { default as keyFunctionsForExtensions } from "./src/operations/keyFunctionsForExtensions.js";
|
|
24
|
+
export { default as map } from "./src/operations/map.js";
|
|
25
|
+
export { default as merge } from "./src/operations/merge.js";
|
|
26
|
+
export { default as reverse } from "./src/operations/reverse.js";
|
|
27
|
+
export { default as scope } from "./src/operations/scope.js";
|
|
28
|
+
export { default as sort } from "./src/operations/sort.js";
|
|
29
|
+
export { default as take } from "./src/operations/take.js";
|
|
30
|
+
export * as symbols from "./src/symbols.js";
|
|
31
|
+
export * as trailingSlash from "./src/trailingSlash.js";
|
|
32
|
+
export * from "./src/utilities.js";
|
package/src/Tree.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AsyncMutableTree, AsyncTree } from "@weborigami/types";
|
|
2
|
-
import { PlainObject, ReduceFn, Treelike, ValueKeyFn } from "../index.ts";
|
|
2
|
+
import { PlainObject, ReduceFn, Treelike, TreeMapOptions, ValueKeyFn } from "../index.ts";
|
|
3
3
|
|
|
4
4
|
export function assign(target: Treelike, source: Treelike): Promise<AsyncTree>;
|
|
5
5
|
export function clear(AsyncTree: AsyncMutableTree): Promise<void>;
|
|
@@ -11,7 +11,7 @@ export function isAsyncMutableTree(obj: any): obj is AsyncMutableTree;
|
|
|
11
11
|
export function isAsyncTree(obj: any): obj is AsyncTree;
|
|
12
12
|
export function isTraversable(obj: any): boolean;
|
|
13
13
|
export function isTreelike(obj: any): obj is Treelike;
|
|
14
|
-
export function map(tree: Treelike,
|
|
14
|
+
export function map(tree: Treelike, options: TreeMapOptions|ValueKeyFn): AsyncTree;
|
|
15
15
|
export function mapReduce(tree: Treelike, mapFn: ValueKeyFn | null, reduceFn: ReduceFn): Promise<any>;
|
|
16
16
|
export function paths(tree: Treelike, base?: string): string[];
|
|
17
17
|
export function plain(tree: Treelike): Promise<PlainObject>;
|
package/src/Tree.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import DeferredTree from "./DeferredTree.js";
|
|
2
|
-
import FunctionTree from "./FunctionTree.js";
|
|
3
|
-
import MapTree from "./MapTree.js";
|
|
4
|
-
import SetTree from "./SetTree.js";
|
|
1
|
+
import DeferredTree from "./drivers/DeferredTree.js";
|
|
2
|
+
import FunctionTree from "./drivers/FunctionTree.js";
|
|
3
|
+
import MapTree from "./drivers/MapTree.js";
|
|
4
|
+
import SetTree from "./drivers/SetTree.js";
|
|
5
5
|
import { DeepObjectTree, ObjectTree } from "./internal.js";
|
|
6
|
+
import * as symbols from "./symbols.js";
|
|
6
7
|
import * as trailingSlash from "./trailingSlash.js";
|
|
7
|
-
import mapTransform from "./transforms/mapFn.js";
|
|
8
8
|
import * as utilities from "./utilities.js";
|
|
9
9
|
import {
|
|
10
10
|
castArrayLike,
|
|
11
11
|
isPacked,
|
|
12
12
|
isPlainObject,
|
|
13
13
|
isUnpackable,
|
|
14
|
+
toPlainValue,
|
|
14
15
|
} from "./utilities.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -67,10 +68,9 @@ export async function assign(target, source) {
|
|
|
67
68
|
* @param {AsyncMutableTree} tree
|
|
68
69
|
*/
|
|
69
70
|
export async function clear(tree) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
71
|
+
const keys = Array.from(await tree.keys());
|
|
72
|
+
const promises = keys.map((key) => tree.set(key, undefined));
|
|
73
|
+
await Promise.all(promises);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -114,7 +114,7 @@ export async function forEach(tree, callbackFn) {
|
|
|
114
114
|
* @returns {AsyncTree}
|
|
115
115
|
*/
|
|
116
116
|
export function from(object, options = {}) {
|
|
117
|
-
const deep = options.deep ??
|
|
117
|
+
const deep = options.deep ?? object[symbols.deep];
|
|
118
118
|
let tree;
|
|
119
119
|
if (isAsyncTree(object)) {
|
|
120
120
|
// Argument already supports the tree interface.
|
|
@@ -247,10 +247,7 @@ export function isTreelike(obj) {
|
|
|
247
247
|
* @param {Treelike} treelike
|
|
248
248
|
* @param {ValueKeyFn} valueFn
|
|
249
249
|
*/
|
|
250
|
-
export
|
|
251
|
-
const tree = from(treelike);
|
|
252
|
-
return mapTransform({ deep: true, value: valueFn })(tree);
|
|
253
|
-
}
|
|
250
|
+
export { default as map } from "./operations/map.js";
|
|
254
251
|
|
|
255
252
|
/**
|
|
256
253
|
* Map and reduce a tree.
|
|
@@ -327,10 +324,10 @@ export async function paths(treelike, base = "") {
|
|
|
327
324
|
* @returns {Promise<PlainObject|Array>}
|
|
328
325
|
*/
|
|
329
326
|
export async function plain(treelike) {
|
|
330
|
-
return mapReduce(treelike,
|
|
327
|
+
return mapReduce(treelike, toPlainValue, (values, keys, tree) => {
|
|
331
328
|
// Special case for an empty tree: if based on array, return array.
|
|
332
329
|
if (tree instanceof ObjectTree && keys.length === 0) {
|
|
333
|
-
return tree.object instanceof Array ? [] : {};
|
|
330
|
+
return /** @type {any} */ (tree).object instanceof Array ? [] : {};
|
|
334
331
|
}
|
|
335
332
|
// Normalize slashes in keys.
|
|
336
333
|
keys = keys.map(trailingSlash.remove);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
3
3
|
import {
|
|
4
4
|
hiddenFileNames,
|
|
5
5
|
isStringLike,
|
|
6
6
|
naturalOrder,
|
|
7
7
|
setParent,
|
|
8
|
-
} from "
|
|
8
|
+
} from "../utilities.js";
|
|
9
9
|
|
|
10
10
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
11
11
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ObjectTree, Tree } from "
|
|
2
|
-
import { isPlainObject } from "
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
|
+
import { isPlainObject } from "../utilities.js";
|
|
3
3
|
|
|
4
4
|
export default class DeepObjectTree extends ObjectTree {
|
|
5
5
|
async get(key) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tree } from "
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A tree that is loaded lazily.
|
|
@@ -21,7 +21,7 @@ export default class DeferredTree {
|
|
|
21
21
|
this.treePromise = null;
|
|
22
22
|
this._tree = null;
|
|
23
23
|
this._parentUntilLoaded = null;
|
|
24
|
-
this._deep = options?.deep
|
|
24
|
+
this._deep = options?.deep;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async get(key) {
|
|
@@ -62,7 +62,9 @@ export default class DeferredTree {
|
|
|
62
62
|
|
|
63
63
|
// Use a promise to ensure the treelike is only converted to a tree once.
|
|
64
64
|
this.treePromise ??= this.loadResult().then((treelike) => {
|
|
65
|
-
|
|
65
|
+
const options =
|
|
66
|
+
this._deep !== undefined ? { deep: this._deep } : undefined;
|
|
67
|
+
this._tree = Tree.from(treelike, options);
|
|
66
68
|
if (this._parentUntilLoaded) {
|
|
67
69
|
// Now that the tree has been loaded, we can set its parent if it hasn't
|
|
68
70
|
// already been set.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import { Tree } from "
|
|
5
|
-
import * as trailingSlash from "
|
|
4
|
+
import { Tree } from "../internal.js";
|
|
5
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
6
6
|
import {
|
|
7
7
|
getRealmObjectPrototype,
|
|
8
8
|
hiddenFileNames,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isPlainObject,
|
|
11
11
|
naturalOrder,
|
|
12
12
|
setParent,
|
|
13
|
-
} from "
|
|
13
|
+
} from "../utilities.js";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* A file system tree via the Node file system API.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Tree } from "
|
|
2
|
-
import * as trailingSlash from "
|
|
3
|
-
import { setParent } from "
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
+
import { setParent } from "../utilities.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A tree backed by a JavaScript `Map` object.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Tree } from "
|
|
2
|
-
import * as symbols from "
|
|
3
|
-
import * as trailingSlash from "
|
|
4
|
-
import { getRealmObjectPrototype, setParent } from "
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
|
+
import * as symbols from "../symbols.js";
|
|
3
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
4
|
+
import { getRealmObjectPrototype, setParent } from "../utilities.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A tree defined by a plain object or array.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as trailingSlash from "
|
|
2
|
-
import { setParent } from "
|
|
1
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
2
|
+
import { setParent } from "../utilities.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A tree of values obtained via HTTP/HTTPS calls. These values will be strings
|
package/src/extension.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as trailingSlash from "./trailingSlash.js";
|
|
2
|
+
import { isStringLike, toString } from "./utilities.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Replicate the logic of Node POSIX path.extname at
|
|
6
|
+
* https://github.com/nodejs/node/blob/main/lib/path.js so that we can use this
|
|
7
|
+
* in the browser.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} path
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
export function extname(path) {
|
|
13
|
+
if (typeof path !== "string") {
|
|
14
|
+
throw new TypeError(`Expected a string, got ${typeof path}`);
|
|
15
|
+
}
|
|
16
|
+
let startDot = -1;
|
|
17
|
+
let startPart = 0;
|
|
18
|
+
let end = -1;
|
|
19
|
+
let matchedSlash = true;
|
|
20
|
+
// Track the state of characters (if any) we see before our first dot and
|
|
21
|
+
// after any path separator we find
|
|
22
|
+
let preDotState = 0;
|
|
23
|
+
for (let i = path.length - 1; i >= 0; --i) {
|
|
24
|
+
const char = path[i];
|
|
25
|
+
if (char === "/") {
|
|
26
|
+
// If we reached a path separator that was not part of a set of path
|
|
27
|
+
// separators at the end of the string, stop now
|
|
28
|
+
if (!matchedSlash) {
|
|
29
|
+
startPart = i + 1;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (end === -1) {
|
|
35
|
+
// We saw the first non-path separator, mark this as the end of our
|
|
36
|
+
// extension
|
|
37
|
+
matchedSlash = false;
|
|
38
|
+
end = i + 1;
|
|
39
|
+
}
|
|
40
|
+
if (char === ".") {
|
|
41
|
+
// If this is our first dot, mark it as the start of our extension
|
|
42
|
+
if (startDot === -1) startDot = i;
|
|
43
|
+
else if (preDotState !== 1) preDotState = 1;
|
|
44
|
+
} else if (startDot !== -1) {
|
|
45
|
+
// We saw a non-dot and non-path separator before our dot, so we should
|
|
46
|
+
// have a good chance at having a non-empty extension
|
|
47
|
+
preDotState = -1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
startDot === -1 ||
|
|
53
|
+
end === -1 ||
|
|
54
|
+
// We saw a non-dot character immediately before the dot
|
|
55
|
+
preDotState === 0 ||
|
|
56
|
+
// The (right-most) trimmed path component is exactly '..'
|
|
57
|
+
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
|
|
58
|
+
) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
return path.slice(startDot, end);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* See if the key ends with the given extension. If it does, return the base
|
|
66
|
+
* name without the extension; if it doesn't return null.
|
|
67
|
+
*
|
|
68
|
+
* If the extension is empty, the key must not have an extension to match.
|
|
69
|
+
*
|
|
70
|
+
* If the extension is a slash, then the key must end with a slash for the match
|
|
71
|
+
* to succeed. Otherwise, a trailing slash in the key is ignored for purposes of
|
|
72
|
+
* comparison to comply with the way Origami can unpack files. Example: the keys
|
|
73
|
+
* "data.json" and "data.json/" are treated equally.
|
|
74
|
+
*
|
|
75
|
+
* This uses a different, more general interpretation of "extension" to mean any
|
|
76
|
+
* suffix, rather than Node's interpretation in `extname`. In particular, this
|
|
77
|
+
* will match a multi-part extension like ".foo.bar" that contains more than one
|
|
78
|
+
* dot.
|
|
79
|
+
*/
|
|
80
|
+
export function match(key, ext) {
|
|
81
|
+
if (!isStringLike(key)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
key = toString(key);
|
|
85
|
+
|
|
86
|
+
if (ext === "/") {
|
|
87
|
+
return trailingSlash.has(key) ? trailingSlash.remove(key) : null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Key matches if it ends with the same extension
|
|
91
|
+
const normalized = trailingSlash.remove(key);
|
|
92
|
+
if (normalized.endsWith(ext)) {
|
|
93
|
+
const removed =
|
|
94
|
+
ext.length > 0 ? normalized.slice(0, -ext.length) : normalized;
|
|
95
|
+
return trailingSlash.toggle(removed, trailingSlash.has(key));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Didn't match
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* If the given key ends in the source extension (which will generally include a
|
|
104
|
+
* period), replace that extension with the result extension (which again should
|
|
105
|
+
* generally include a period). Otherwise, return the key as is.
|
|
106
|
+
*
|
|
107
|
+
* If the key ends in a trailing slash, that will be preserved in the result.
|
|
108
|
+
* Exception: if the source extension is empty, and the key doesn't have an
|
|
109
|
+
* extension, the result extension will be appended to the key without a slash.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} key
|
|
112
|
+
* @param {string} sourceExtension
|
|
113
|
+
* @param {string} resultExtension
|
|
114
|
+
*/
|
|
115
|
+
export function replace(key, sourceExtension, resultExtension) {
|
|
116
|
+
if (!isStringLike(key)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
key = toString(key);
|
|
120
|
+
|
|
121
|
+
if (!match(key, sourceExtension)) {
|
|
122
|
+
return key;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let replaced;
|
|
126
|
+
const normalizedKey = trailingSlash.remove(key);
|
|
127
|
+
if (sourceExtension === "") {
|
|
128
|
+
replaced = normalizedKey + resultExtension;
|
|
129
|
+
if (!normalizedKey.includes(".")) {
|
|
130
|
+
return replaced;
|
|
131
|
+
}
|
|
132
|
+
} else if (sourceExtension === "/") {
|
|
133
|
+
return trailingSlash.remove(key) + resultExtension;
|
|
134
|
+
} else {
|
|
135
|
+
replaced =
|
|
136
|
+
normalizedKey.slice(0, -sourceExtension.length) + resultExtension;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return trailingSlash.toggle(replaced, trailingSlash.has(key));
|
|
140
|
+
}
|
package/src/internal.js
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
// About this pattern:
|
|
8
8
|
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
|
9
9
|
//
|
|
10
|
+
// Note: to avoid having VS Code auto-sort the imports, keep lines between them.
|
|
10
11
|
|
|
11
12
|
export * as Tree from "./Tree.js";
|
|
12
13
|
|
|
13
|
-
export { default as ObjectTree } from "./ObjectTree.js";
|
|
14
|
+
export { default as ObjectTree } from "./drivers/ObjectTree.js";
|
|
14
15
|
|
|
15
|
-
export { default as DeepObjectTree } from "./DeepObjectTree.js";
|
|
16
|
+
export { default as DeepObjectTree } from "./drivers/DeepObjectTree.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns a function that traverses a tree deeply and returns the values of the
|
|
@@ -10,6 +10,33 @@ import deepTakeFn from "./deepTakeFn.js";
|
|
|
10
10
|
* @param {import("../../index.ts").Treelike} treelike
|
|
11
11
|
* @param {number} count
|
|
12
12
|
*/
|
|
13
|
-
export default function deepTake(treelike, count) {
|
|
14
|
-
|
|
13
|
+
export default async function deepTake(treelike, count) {
|
|
14
|
+
if (!treelike) {
|
|
15
|
+
const error = new TypeError(`deepTake: The tree isn't defined.`);
|
|
16
|
+
/** @type {any} */ (error).position = 0;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tree = await Tree.from(treelike, { deep: true });
|
|
21
|
+
const { values } = await traverse(tree, count);
|
|
22
|
+
return Tree.from(values, { deep: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function traverse(tree, count) {
|
|
26
|
+
const values = [];
|
|
27
|
+
for (const key of await tree.keys()) {
|
|
28
|
+
if (count <= 0) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
let value = await tree.get(key);
|
|
32
|
+
if (Tree.isAsyncTree(value)) {
|
|
33
|
+
const traversed = await traverse(value, count);
|
|
34
|
+
values.push(...traversed.values);
|
|
35
|
+
count = traversed.count;
|
|
36
|
+
} else {
|
|
37
|
+
values.push(value);
|
|
38
|
+
count--;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { count, values };
|
|
15
42
|
}
|
package/src/operations/group.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Given a function that returns a grouping key for a value, returns a transform
|
|
@@ -7,6 +7,47 @@ import groupFn from "./groupFn.js";
|
|
|
7
7
|
* @param {import("../../index.ts").Treelike} treelike
|
|
8
8
|
* @param {import("../../index.ts").ValueKeyFn} groupKeyFn
|
|
9
9
|
*/
|
|
10
|
-
export default function group(treelike, groupKeyFn) {
|
|
11
|
-
|
|
10
|
+
export default async function group(treelike, groupKeyFn) {
|
|
11
|
+
if (!treelike) {
|
|
12
|
+
const error = new TypeError(`groupBy: The tree to group isn't defined.`);
|
|
13
|
+
/** @type {any} */ (error).position = 0;
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const tree = Tree.from(treelike);
|
|
18
|
+
|
|
19
|
+
const keys = Array.from(await tree.keys());
|
|
20
|
+
|
|
21
|
+
// Are all the keys integers?
|
|
22
|
+
const isArray = keys.every((key) => !Number.isNaN(parseInt(key)));
|
|
23
|
+
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const key of await tree.keys()) {
|
|
26
|
+
const value = await tree.get(key);
|
|
27
|
+
|
|
28
|
+
// Get the groups for this value.
|
|
29
|
+
let groups = await groupKeyFn(value, key, tree);
|
|
30
|
+
if (!groups) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!Tree.isTreelike(groups)) {
|
|
35
|
+
// A single value was returned
|
|
36
|
+
groups = [groups];
|
|
37
|
+
}
|
|
38
|
+
groups = Tree.from(groups);
|
|
39
|
+
|
|
40
|
+
// Add the value to each group.
|
|
41
|
+
for (const group of await Tree.values(groups)) {
|
|
42
|
+
if (isArray) {
|
|
43
|
+
result[group] ??= [];
|
|
44
|
+
result[group].push(value);
|
|
45
|
+
} else {
|
|
46
|
+
result[group] ??= {};
|
|
47
|
+
result[group][key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new ObjectTree(result);
|
|
12
53
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as extension from "../extension.js";
|
|
2
|
+
import * as trailingSlash from "../trailingSlash.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Given a source resultExtension and a result resultExtension, return a pair of key
|
|
6
|
+
* functions that map between them.
|
|
7
|
+
*
|
|
8
|
+
* The resulting `inverseKey` and `key` functions are compatible with those
|
|
9
|
+
* expected by map and other transforms.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
12
|
+
* @param {{ resultExtension?: string, sourceExtension: string }}
|
|
13
|
+
* options
|
|
14
|
+
*/
|
|
15
|
+
export default function keyFunctionsForExtensions({
|
|
16
|
+
resultExtension,
|
|
17
|
+
sourceExtension,
|
|
18
|
+
}) {
|
|
19
|
+
if (resultExtension === undefined) {
|
|
20
|
+
resultExtension = sourceExtension;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
checkDeprecatedExtensionWithoutDot(resultExtension);
|
|
24
|
+
checkDeprecatedExtensionWithoutDot(sourceExtension);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
async inverseKey(resultKey, tree) {
|
|
28
|
+
// Remove trailing slash so that mapFn won't inadvertently unpack files.
|
|
29
|
+
const baseKey = trailingSlash.remove(resultKey);
|
|
30
|
+
const basename = extension.match(baseKey, resultExtension);
|
|
31
|
+
return basename ? `${basename}${sourceExtension}` : undefined;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async key(sourceKey, tree) {
|
|
35
|
+
return extension.match(sourceKey, sourceExtension)
|
|
36
|
+
? extension.replace(sourceKey, sourceExtension, resultExtension)
|
|
37
|
+
: undefined;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function checkDeprecatedExtensionWithoutDot(extension) {
|
|
43
|
+
if (extension && extension !== "/" && !extension.startsWith(".")) {
|
|
44
|
+
throw new RangeError(
|
|
45
|
+
`map: Warning: the extension "${extension}" should start with a period.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|