@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.
Files changed (69) hide show
  1. package/browser.js +2 -11
  2. package/index.ts +9 -0
  3. package/main.js +3 -37
  4. package/package.json +2 -2
  5. package/shared.js +32 -0
  6. package/src/Tree.d.ts +2 -2
  7. package/src/Tree.js +13 -16
  8. package/src/{BrowserFileTree.js → drivers/BrowserFileTree.js} +3 -3
  9. package/src/{DeepMapTree.js → drivers/DeepMapTree.js} +1 -1
  10. package/src/{DeepObjectTree.js → drivers/DeepObjectTree.js} +2 -2
  11. package/src/{DeferredTree.js → drivers/DeferredTree.js} +5 -3
  12. package/src/{FileTree.js → drivers/FileTree.js} +3 -3
  13. package/src/{FunctionTree.js → drivers/FunctionTree.js} +1 -1
  14. package/src/{MapTree.js → drivers/MapTree.js} +3 -3
  15. package/src/{ObjectTree.js → drivers/ObjectTree.js} +4 -4
  16. package/src/{SetTree.js → drivers/SetTree.js} +1 -1
  17. package/src/{SiteTree.js → drivers/SiteTree.js} +2 -2
  18. package/src/{calendarTree.js → drivers/calendarTree.js} +1 -1
  19. package/src/extension.js +140 -0
  20. package/src/internal.js +3 -2
  21. package/src/{transforms → operations}/deepReverse.js +1 -1
  22. package/src/operations/deepTake.js +30 -3
  23. package/src/operations/group.js +44 -3
  24. package/src/operations/keyFunctionsForExtensions.js +48 -0
  25. package/src/operations/map.js +118 -3
  26. package/src/operations/sort.js +45 -2
  27. package/src/operations/take.js +19 -2
  28. package/src/symbols.js +1 -0
  29. package/test/Tree.test.js +25 -2
  30. package/test/{BrowserFileTree.test.js → drivers/BrowserFileTree.test.js} +2 -2
  31. package/test/{DeepMapTree.test.js → drivers/DeepMapTree.test.js} +2 -2
  32. package/test/{DeepObjectTree.test.js → drivers/DeepObjectTree.test.js} +1 -1
  33. package/test/{DeferredTree.test.js → drivers/DeferredTree.test.js} +2 -2
  34. package/test/{ExplorableSiteTree.test.js → drivers/ExplorableSiteTree.test.js} +6 -3
  35. package/test/{FileTree.test.js → drivers/FileTree.test.js} +6 -5
  36. package/test/{FunctionTree.test.js → drivers/FunctionTree.test.js} +1 -1
  37. package/test/{MapTree.test.js → drivers/MapTree.test.js} +2 -2
  38. package/test/{ObjectTree.test.js → drivers/ObjectTree.test.js} +2 -2
  39. package/test/{SetTree.test.js → drivers/SetTree.test.js} +2 -2
  40. package/test/{SiteTree.test.js → drivers/SiteTree.test.js} +1 -1
  41. package/test/{calendarTree.test.js → drivers/calendarTree.test.js} +5 -5
  42. package/test/extension.test.js +41 -0
  43. package/test/{transforms → operations}/cachedKeyFunctions.test.js +1 -1
  44. package/test/operations/concat.test.js +1 -1
  45. package/test/{transforms → operations}/deepReverse.test.js +1 -1
  46. package/test/operations/{deepTakeFn.test.js → deepTake.test.js} +3 -3
  47. package/test/{transforms/groupFn.test.js → operations/group.test.js} +4 -4
  48. package/test/{transforms → operations}/invokeFunctions.test.js +1 -1
  49. package/test/{transforms → operations}/keyFunctionsForExtensions.test.js +21 -12
  50. package/test/{transforms/mapFn.test.js → operations/map.test.js} +23 -31
  51. package/test/{transforms → operations}/regExpKeys.test.js +1 -1
  52. package/test/{transforms → operations}/reverse.test.js +1 -1
  53. package/test/{transforms/sortFn.test.js → operations/sort.test.js} +6 -7
  54. package/test/{transforms/takeFn.test.js → operations/take.test.js} +3 -3
  55. package/src/operations/deepTakeFn.js +0 -43
  56. package/src/operations/groupFn.js +0 -57
  57. package/src/transforms/keyFunctionsForExtensions.js +0 -78
  58. package/src/transforms/mapFn.js +0 -126
  59. package/src/transforms/sortFn.js +0 -71
  60. package/src/transforms/takeFn.js +0 -31
  61. /package/src/{ExplorableSiteTree.js → drivers/ExplorableSiteTree.js} +0 -0
  62. /package/src/{transforms → operations}/cachedKeyFunctions.js +0 -0
  63. /package/src/{transforms → operations}/invokeFunctions.js +0 -0
  64. /package/src/{transforms → operations}/regExpKeys.js +0 -0
  65. /package/src/{transforms → operations}/reverse.js +0 -0
  66. /package/test/{fixtures → drivers/fixtures}/markdown/Alice.md +0 -0
  67. /package/test/{fixtures → drivers/fixtures}/markdown/Bob.md +0 -0
  68. /package/test/{fixtures → drivers/fixtures}/markdown/Carol.md +0 -0
  69. /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 { default as DeferredTree } from "./src/DeferredTree.js";
4
- // Skip FileTree.js, which is Node.js only.
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 { default as DeferredTree } from "./src/DeferredTree.js";
4
- export { default as FileTree } from "./src/FileTree.js";
5
- export { default as FunctionTree } from "./src/FunctionTree.js";
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.72",
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.72"
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, valueFn: ValueKeyFn): AsyncTree;
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
- // @ts-ignore
71
- for (const key of await tree.keys()) {
72
- await tree.set(key, undefined);
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 ?? false;
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 function map(treelike, valueFn) {
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, null, (values, keys, tree) => {
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 * as trailingSlash from "../src/trailingSlash.js";
2
- import { Tree } from "./internal.js";
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 "./utilities.js";
8
+ } from "../utilities.js";
9
9
 
10
10
  const TypedArray = Object.getPrototypeOf(Uint8Array);
11
11
 
@@ -1,4 +1,4 @@
1
- import { Tree } from "./internal.js";
1
+ import { Tree } from "../internal.js";
2
2
  import MapTree from "./MapTree.js";
3
3
 
4
4
  export default class DeepMapTree extends MapTree {
@@ -1,5 +1,5 @@
1
- import { ObjectTree, Tree } from "./internal.js";
2
- import { isPlainObject } from "./utilities.js";
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 "./internal.js";
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 ?? false;
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
- this._tree = Tree.from(treelike, { deep: this._deep });
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 "./internal.js";
5
- import * as trailingSlash from "./trailingSlash.js";
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 "./utilities.js";
13
+ } from "../utilities.js";
14
14
 
15
15
  /**
16
16
  * A file system tree via the Node file system API.
@@ -1,4 +1,4 @@
1
- import { setParent } from "./utilities.js";
1
+ import { setParent } from "../utilities.js";
2
2
 
3
3
  /**
4
4
  * A tree defined by a function and an optional domain.
@@ -1,6 +1,6 @@
1
- import { Tree } from "./internal.js";
2
- import * as trailingSlash from "./trailingSlash.js";
3
- import { setParent } from "./utilities.js";
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 "./internal.js";
2
- import * as symbols from "./symbols.js";
3
- import * as trailingSlash from "./trailingSlash.js";
4
- import { getRealmObjectPrototype, setParent } from "./utilities.js";
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,4 +1,4 @@
1
- import { setParent } from "./utilities.js";
1
+ import { setParent } from "../utilities.js";
2
2
 
3
3
  /**
4
4
  * A tree of Set objects.
@@ -1,5 +1,5 @@
1
- import * as trailingSlash from "./trailingSlash.js";
2
- import { setParent } from "./utilities.js";
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
@@ -1,4 +1,4 @@
1
- import * as trailingSlash from "./trailingSlash.js";
1
+ import * as trailingSlash from "../trailingSlash.js";
2
2
 
3
3
  /**
4
4
  * Return a tree of years, months, and days from a start date to an end date.
@@ -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";
@@ -18,7 +18,7 @@ export default function deepReverse(treelike) {
18
18
  throw error;
19
19
  }
20
20
 
21
- const tree = Tree.from(treelike);
21
+ const tree = Tree.from(treelike, { deep: true });
22
22
  return {
23
23
  async get(key) {
24
24
  let value = await tree.get(key);
@@ -1,4 +1,4 @@
1
- import deepTakeFn from "./deepTakeFn.js";
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
- return deepTakeFn(count)(treelike);
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
  }
@@ -1,4 +1,4 @@
1
- import groupFn from "./groupFn.js";
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
- return groupFn(groupKeyFn)(treelike);
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
+ }