@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
@@ -1,14 +1,129 @@
1
- import mapFn from "../transforms/mapFn.js";
1
+ import { Tree } from "../internal.js";
2
+ import * as trailingSlash from "../trailingSlash.js";
2
3
 
3
4
  /**
4
5
  * Transform the keys and/or values of a tree.
5
6
  *
6
7
  * @typedef {import("../../index.ts").KeyFn} KeyFn
8
+ * @typedef {import("../../index.ts").TreeMapOptions} MapOptions
7
9
  * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
8
10
  *
9
11
  * @param {import("../../index.ts").Treelike} treelike
10
- * @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
12
+ * @param {MapOptions|ValueKeyFn} options
11
13
  */
12
14
  export default function map(treelike, options = {}) {
13
- return mapFn(options)(treelike);
15
+ let deep;
16
+ let description;
17
+ let inverseKeyFn;
18
+ let keyFn;
19
+ let needsSourceValue;
20
+ let valueFn;
21
+
22
+ if (!treelike) {
23
+ const error = new TypeError(`map: The tree to map isn't defined.`);
24
+ /** @type {any} */ (error).position = 0;
25
+ throw error;
26
+ }
27
+
28
+ if (typeof options === "function") {
29
+ // Take the single function argument as the valueFn
30
+ valueFn = options;
31
+ } else {
32
+ deep = options.deep;
33
+ description = options.description;
34
+ inverseKeyFn = options.inverseKey;
35
+ keyFn = options.key;
36
+ needsSourceValue = options.needsSourceValue;
37
+ valueFn = options.value;
38
+ }
39
+
40
+ deep ??= false;
41
+ description ??= "key/value map";
42
+ // @ts-ignore
43
+ inverseKeyFn ??= valueFn?.inverseKey;
44
+ // @ts-ignore
45
+ keyFn ??= valueFn?.key;
46
+ needsSourceValue ??= true;
47
+
48
+ if ((keyFn && !inverseKeyFn) || (!keyFn && inverseKeyFn)) {
49
+ throw new TypeError(
50
+ `map: You must specify both key and inverseKey functions, or neither.`
51
+ );
52
+ }
53
+
54
+ /**
55
+ * @param {import("@weborigami/types").AsyncTree} tree
56
+ */
57
+ function mapFn(tree) {
58
+ // The transformed tree is actually an extension of the original tree's
59
+ // prototype chain. This allows the transformed tree to inherit any
60
+ // properties/methods. For example, the `parent` of the transformed tree is
61
+ // the original tree's parent.
62
+ const transformed = Object.create(tree);
63
+
64
+ transformed.description = description;
65
+
66
+ if (keyFn || valueFn) {
67
+ transformed.get = async (resultKey) => {
68
+ // Step 1: Map the result key to the source key.
69
+ const sourceKey = (await inverseKeyFn?.(resultKey, tree)) ?? resultKey;
70
+
71
+ if (sourceKey === undefined) {
72
+ // No source key means no value.
73
+ return undefined;
74
+ }
75
+
76
+ // Step 2: Get the source value.
77
+ let sourceValue;
78
+ if (needsSourceValue) {
79
+ // Normal case: get the value from the source tree.
80
+ sourceValue = await tree.get(sourceKey);
81
+ } else if (deep && trailingSlash.has(sourceKey)) {
82
+ // Only get the source value if it's expected to be a subtree.
83
+ sourceValue = tree;
84
+ }
85
+
86
+ // Step 3: Map the source value to the result value.
87
+ let resultValue;
88
+ if (needsSourceValue && sourceValue === undefined) {
89
+ // No source value means no result value.
90
+ resultValue = undefined;
91
+ } else if (deep && Tree.isAsyncTree(sourceValue)) {
92
+ // Map a subtree.
93
+ resultValue = mapFn(sourceValue);
94
+ } else if (valueFn) {
95
+ // Map a single value.
96
+ resultValue = await valueFn(sourceValue, sourceKey, tree);
97
+ } else {
98
+ // Return source value as is.
99
+ resultValue = sourceValue;
100
+ }
101
+
102
+ return resultValue;
103
+ };
104
+ }
105
+
106
+ if (keyFn) {
107
+ transformed.keys = async () => {
108
+ // Apply the keyFn to source keys for leaf values (not subtrees).
109
+ const sourceKeys = Array.from(await tree.keys());
110
+ const mapped = await Promise.all(
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
+ )
117
+ );
118
+ // Filter out any cases where the keyFn returned undefined.
119
+ const resultKeys = mapped.filter((key) => key !== undefined);
120
+ return resultKeys;
121
+ };
122
+ }
123
+
124
+ return transformed;
125
+ }
126
+
127
+ const tree = Tree.from(treelike, { deep });
128
+ return mapFn(tree);
14
129
  }
@@ -1,4 +1,4 @@
1
- import sortFn from "../transforms/sortFn.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Return a new tree with the original's keys sorted. A comparison function can
@@ -14,5 +14,48 @@ import sortFn from "../transforms/sortFn.js";
14
14
  * @param {SortOptions} [options]
15
15
  */
16
16
  export default function sort(treelike, options) {
17
- return sortFn(options)(treelike);
17
+ if (!treelike) {
18
+ const error = new TypeError(`sort: The tree to sort isn't defined.`);
19
+ /** @type {any} */ (error).position = 0;
20
+ throw error;
21
+ }
22
+
23
+ const sortKey = options?.sortKey;
24
+ let compare = options?.compare;
25
+
26
+ const tree = Tree.from(treelike);
27
+ const transformed = Object.create(tree);
28
+ transformed.keys = async () => {
29
+ const keys = Array.from(await tree.keys());
30
+
31
+ if (sortKey) {
32
+ // Invoke the async sortKey function to get sort keys.
33
+ // Create { key, sortKey } tuples.
34
+ const tuples = await Promise.all(
35
+ keys.map(async (key) => {
36
+ const sort = await sortKey(key, tree);
37
+ if (sort === undefined) {
38
+ throw new Error(
39
+ `sortKey function returned undefined for key ${key}`
40
+ );
41
+ }
42
+ return { key, sort };
43
+ })
44
+ );
45
+
46
+ // Wrap the comparison function so it applies to sort keys.
47
+ const defaultCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
48
+ const originalCompare = compare ?? defaultCompare;
49
+ // Sort by the sort key.
50
+ tuples.sort((a, b) => originalCompare(a.sort, b.sort));
51
+ // Map back to the original keys.
52
+ const sorted = tuples.map((pair) => pair.key);
53
+ return sorted;
54
+ } else {
55
+ // Use original keys as sort keys.
56
+ // If compare is undefined, this uses default sort order.
57
+ return keys.slice().sort(compare);
58
+ }
59
+ };
60
+ return transformed;
18
61
  }
@@ -1,4 +1,4 @@
1
- import takeFn from "../transforms/takeFn.js";
1
+ import { Tree } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * Returns a new tree with the number of keys limited to the indicated count.
@@ -7,5 +7,22 @@ import takeFn from "../transforms/takeFn.js";
7
7
  * @param {number} count
8
8
  */
9
9
  export default function take(treelike, count) {
10
- return takeFn(count)(treelike);
10
+ if (!treelike) {
11
+ const error = new TypeError(`take: The tree to take from isn't defined.`);
12
+ /** @type {any} */ (error).position = 0;
13
+ throw error;
14
+ }
15
+
16
+ const tree = Tree.from(treelike);
17
+
18
+ return {
19
+ async keys() {
20
+ const keys = Array.from(await tree.keys());
21
+ return keys.slice(0, count);
22
+ },
23
+
24
+ async get(key) {
25
+ return tree.get(key);
26
+ },
27
+ };
11
28
  }
package/src/symbols.js CHANGED
@@ -1 +1,2 @@
1
+ export const deep = Symbol("deep");
1
2
  export const parent = Symbol("parent");
package/test/Tree.test.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import MapTree from "../src/MapTree.js";
3
+ import MapTree from "../src/drivers/MapTree.js";
4
4
  import { DeepObjectTree, ObjectTree, Tree } from "../src/internal.js";
5
+ import * as symbols from "../src/symbols.js";
5
6
 
6
7
  describe("Tree", () => {
7
8
  test("assign applies one tree to another", async () => {
@@ -108,6 +109,17 @@ describe("Tree", () => {
108
109
  assert(tree instanceof DeepObjectTree);
109
110
  });
110
111
 
112
+ test("from returns a deep object tree if object has [deep] symbol set", async () => {
113
+ const obj = {
114
+ sub: {
115
+ a: 1,
116
+ },
117
+ };
118
+ Object.defineProperty(obj, symbols.deep, { value: true });
119
+ const tree = Tree.from(obj);
120
+ assert(tree instanceof DeepObjectTree);
121
+ });
122
+
111
123
  test("from() creates a deferred tree if unpack() returns a promise", async () => {
112
124
  const obj = new String();
113
125
  /** @type {any} */ (obj).unpack = async () => ({
@@ -179,7 +191,10 @@ describe("Tree", () => {
179
191
  b: "Bob",
180
192
  },
181
193
  });
182
- const mapped = Tree.map(tree, (value) => value.toUpperCase());
194
+ const mapped = Tree.map(tree, {
195
+ deep: true,
196
+ value: (value) => value.toUpperCase(),
197
+ });
183
198
  assert.deepEqual(await Tree.plain(mapped), {
184
199
  a: "ALICE",
185
200
  more: {
@@ -275,6 +290,14 @@ describe("Tree", () => {
275
290
  assert.deepEqual(await Tree.plain(object), { name: "Alice" });
276
291
  });
277
292
 
293
+ test("plain() coerces TypedArray values to strings", async () => {
294
+ const tree = new ObjectTree({
295
+ a: new TextEncoder().encode("Hello, world."),
296
+ });
297
+ const plain = await Tree.plain(tree);
298
+ assert.equal(plain.a, "Hello, world.");
299
+ });
300
+
278
301
  test("remove method removes a value", async () => {
279
302
  const fixture = createFixture();
280
303
  await Tree.remove(fixture, "Alice.md");
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import BrowserFileTree from "../src/BrowserFileTree.js";
4
- import { Tree } from "../src/internal.js";
3
+ import BrowserFileTree from "../../src/drivers/BrowserFileTree.js";
4
+ import { Tree } from "../../src/internal.js";
5
5
 
6
6
  // Skip these tests if we're not in a browser.
7
7
  const isBrowser = typeof window !== "undefined";
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import DeepMapTree from "../src/DeepMapTree.js";
4
- import { Tree } from "../src/internal.js";
3
+ import DeepMapTree from "../../src/drivers/DeepMapTree.js";
4
+ import { Tree } from "../../src/internal.js";
5
5
 
6
6
  describe("DeepMapTree", () => {
7
7
  test("returns a DeepMapTree for value that's a Map", async () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import { DeepObjectTree, Tree } from "../src/internal.js";
3
+ import { DeepObjectTree, Tree } from "../../src/internal.js";
4
4
 
5
5
  describe("DeepObjectTree", () => {
6
6
  test("returns an ObjectTree for value that's a plain sub-object or sub-array", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import DeferredTree from "../src/DeferredTree.js";
4
- import { ObjectTree, Tree } from "../src/internal.js";
3
+ import DeferredTree from "../../src/drivers/DeferredTree.js";
4
+ import { ObjectTree, Tree } from "../../src/internal.js";
5
5
 
6
6
  describe("DeferredTree", () => {
7
7
  test("lazy-loads a treelike object", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { beforeEach, describe, mock, test } from "node:test";
3
- import ExplorableSiteTree from "../src/ExplorableSiteTree.js";
4
- import { Tree } from "../src/internal.js";
3
+ import ExplorableSiteTree from "../../src/drivers/ExplorableSiteTree.js";
4
+ import { Tree } from "../../src/internal.js";
5
5
 
6
6
  const textDecoder = new TextDecoder();
7
7
  const textEncoder = new TextEncoder();
@@ -77,7 +77,10 @@ describe("ExplorableSiteTree", () => {
77
77
  test("can convert a site to a plain object", async () => {
78
78
  const fixture = new ExplorableSiteTree(mockHost);
79
79
  // Convert buffers to strings.
80
- const strings = Tree.map(fixture, (value) => textDecoder.decode(value));
80
+ const strings = Tree.map(fixture, {
81
+ deep: true,
82
+ value: (value) => textDecoder.decode(value),
83
+ });
81
84
  assert.deepEqual(await Tree.plain(strings), {
82
85
  about: {
83
86
  "Alice.html": "Hello, Alice!",
@@ -3,8 +3,8 @@ import * as fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { describe, test } from "node:test";
5
5
  import { fileURLToPath } from "node:url";
6
- import FileTree from "../src/FileTree.js";
7
- import { ObjectTree, Tree } from "../src/internal.js";
6
+ import FileTree from "../../src/drivers/FileTree.js";
7
+ import { ObjectTree, Tree } from "../../src/internal.js";
8
8
 
9
9
  const dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  const tempDirectory = path.join(dirname, "fixtures/temp");
@@ -132,9 +132,10 @@ describe("FileTree", async () => {
132
132
 
133
133
  // Read them back in.
134
134
  const actualFiles = await tempFiles.get("folder");
135
- const strings = Tree.map(actualFiles, (buffer) =>
136
- textDecoder.decode(buffer)
137
- );
135
+ const strings = Tree.map(actualFiles, {
136
+ deep: true,
137
+ value: (buffer) => textDecoder.decode(buffer),
138
+ });
138
139
  const plain = await Tree.plain(strings);
139
140
  assert.deepEqual(plain, obj);
140
141
 
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import FunctionTree from "../src/FunctionTree.js";
3
+ import FunctionTree from "../../src/drivers/FunctionTree.js";
4
4
 
5
5
  describe("FunctionTree", async () => {
6
6
  test("can get the keys of the tree", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import MapTree from "../src/MapTree.js";
4
- import * as symbols from "../src/symbols.js";
3
+ import MapTree from "../../src/drivers/MapTree.js";
4
+ import * as symbols from "../../src/symbols.js";
5
5
 
6
6
  describe("MapTree", () => {
7
7
  test("can get the keys of the tree", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import { ObjectTree, Tree } from "../src/internal.js";
4
- import * as symbols from "../src/symbols.js";
3
+ import { ObjectTree, Tree } from "../../src/internal.js";
4
+ import * as symbols from "../../src/symbols.js";
5
5
 
6
6
  describe("ObjectTree", () => {
7
7
  test("can get the keys of the tree", async () => {
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import SetTree from "../src/SetTree.js";
4
- import { ObjectTree } from "../src/internal.js";
3
+ import SetTree from "../../src/drivers/SetTree.js";
4
+ import { ObjectTree } from "../../src/internal.js";
5
5
 
6
6
  describe("SetTree", () => {
7
7
  test("can get the keys of the tree", async () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { beforeEach, describe, mock, test } from "node:test";
3
- import SiteTree from "../src/SiteTree.js";
3
+ import SiteTree from "../../src/drivers/SiteTree.js";
4
4
 
5
5
  const textDecoder = new TextDecoder();
6
6
  const textEncoder = new TextEncoder();
@@ -1,11 +1,11 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import calendarTree from "../src/calendarTree.js";
4
- import { toPlainValue } from "../src/utilities.js";
3
+ import calendar from "../../src/drivers/calendarTree.js";
4
+ import { toPlainValue } from "../../src/utilities.js";
5
5
 
6
6
  describe("calendarTree", () => {
7
7
  test("without a start or end, returns a tree for today", async () => {
8
- const tree = calendarTree({
8
+ const tree = calendar({
9
9
  value: (year, month, day) => `${year}-${month}-${day}`,
10
10
  });
11
11
  const plain = await toPlainValue(tree);
@@ -23,7 +23,7 @@ describe("calendarTree", () => {
23
23
  });
24
24
 
25
25
  test("returns a tree for a month range", async () => {
26
- const tree = calendarTree({
26
+ const tree = calendar({
27
27
  start: "2025-01",
28
28
  end: "2025-02",
29
29
  value: (year, month, day) => `${year}-${month}-${day}`,
@@ -99,7 +99,7 @@ describe("calendarTree", () => {
99
99
  });
100
100
 
101
101
  test("returns a tree for a day range", async () => {
102
- const tree = calendarTree({
102
+ const tree = calendar({
103
103
  start: "2025-02-27",
104
104
  end: "2025-03-02",
105
105
  value: (year, month, day) => `${year}-${month}-${day}`,
@@ -0,0 +1,41 @@
1
+ import assert from "node:assert";
2
+ import { describe, test } from "node:test";
3
+ import { extname, match, replace } from "../src/extension.js";
4
+
5
+ describe("extension", () => {
6
+ test("extname", () => {
7
+ assert.equal(extname(".\\"), "");
8
+ assert.equal(extname("..\\"), ".\\");
9
+ assert.equal(extname("file.ext\\"), ".ext\\");
10
+ assert.equal(extname("file.ext\\\\"), ".ext\\\\");
11
+ assert.equal(extname("file\\"), "");
12
+ assert.equal(extname("file\\\\"), "");
13
+ assert.equal(extname("file.\\"), ".\\");
14
+ assert.equal(extname("file.\\\\"), ".\\\\");
15
+ });
16
+
17
+ test("match", () => {
18
+ assert.equal(match("file.md", ".md"), "file");
19
+ assert.equal(match("file.md", ".txt"), null);
20
+ assert.equal(match("file.md/", ".md"), "file/");
21
+ assert.equal(match("file", ""), "file");
22
+ assert.equal(match("file", "/"), null);
23
+ assert.equal(match("file/", "/"), "file");
24
+ });
25
+
26
+ test("match can handle multi-part extensions", () => {
27
+ assert.equal(match("foo.ori.html", ".ori.html"), "foo");
28
+ assert.equal(match("foo.ori.html", ".html"), "foo.ori");
29
+ assert.equal(match("foo.ori.html", ".txt"), null);
30
+ assert.equal(match("foo.ori.html/", ".ori.html"), "foo/");
31
+ });
32
+
33
+ test("replace", () => {
34
+ assert.equal(replace("file.md", ".md", ".html"), "file.html");
35
+ assert.equal(replace("file.md", ".txt", ".html"), "file.md");
36
+ assert.equal(replace("file.md/", ".md", ".html"), "file.html/");
37
+ assert.equal(replace("folder/", "", ".html"), "folder.html");
38
+ assert.equal(replace("folder", "/", ".html"), "folder");
39
+ assert.equal(replace("folder/", "/", ".html"), "folder.html");
40
+ });
41
+ });
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { DeepObjectTree, ObjectTree } from "../../src/internal.js";
4
- import cachedKeyFunctions from "../../src/transforms/cachedKeyFunctions.js";
4
+ import cachedKeyFunctions from "../../src/operations/cachedKeyFunctions.js";
5
5
 
6
6
  describe("cachedKeyFunctions", () => {
7
7
  test("maps keys with caching", async () => {
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import FunctionTree from "../../src/FunctionTree.js";
3
+ import FunctionTree from "../../src/drivers/FunctionTree.js";
4
4
  import { Tree } from "../../src/internal.js";
5
5
  import concat from "../../src/operations/concat.js";
6
6
 
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { Tree } from "../../src/internal.js";
4
- import deepReverse from "../../src/transforms/deepReverse.js";
4
+ import deepReverse from "../../src/operations/deepReverse.js";
5
5
 
6
6
  describe("deepReverse", () => {
7
7
  test("reverses keys at all levels of a tree", async () => {
@@ -1,9 +1,9 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { Tree } from "../../src/internal.js";
4
- import deepTakeFn from "../../src/operations/deepTakeFn.js";
4
+ import deepTake from "../../src/operations/deepTake.js";
5
5
 
6
- describe("deepTakeFn", () => {
6
+ describe("deepTake", () => {
7
7
  test("traverses deeply and returns a limited number of items", async () => {
8
8
  const tree = {
9
9
  a: 1,
@@ -16,7 +16,7 @@ describe("deepTakeFn", () => {
16
16
  },
17
17
  g: 5,
18
18
  };
19
- const result = await deepTakeFn(4)(tree);
19
+ const result = await deepTake(tree, 4);
20
20
  assert.deepEqual(await Tree.plain(result), [1, 2, 3, 4]);
21
21
  });
22
22
  });
@@ -1,9 +1,9 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { Tree } from "../../src/internal.js";
4
- import groupFn from "../../src/operations/groupFn.js";
4
+ import group from "../../src/operations/group.js";
5
5
 
6
- describe("groupFn transform", () => {
6
+ describe("group transform", () => {
7
7
  test("groups an array using a group key function", async () => {
8
8
  const fonts = [
9
9
  { name: "Aboreto", tags: ["Sans Serif"] },
@@ -12,7 +12,7 @@ describe("groupFn transform", () => {
12
12
  { name: "Work Sans", tags: ["Grotesque", "Sans Serif"] },
13
13
  ];
14
14
  const tree = Tree.from(fonts);
15
- const grouped = await groupFn((value, key, tree) => value.tags)(tree);
15
+ const grouped = await group(tree, (value, key, tree) => value.tags);
16
16
  assert.deepEqual(await Tree.plain(grouped), {
17
17
  Geometric: [{ name: "Albert Sans", tags: ["Geometric", "Sans Serif"] }],
18
18
  Grotesque: [{ name: "Work Sans", tags: ["Grotesque", "Sans Serif"] }],
@@ -33,7 +33,7 @@ describe("groupFn transform", () => {
33
33
  "Work Sans": { tags: ["Grotesque", "Sans Serif"] },
34
34
  };
35
35
  const tree = Tree.from(fonts);
36
- const grouped = await groupFn((value, key, tree) => value.tags)(tree);
36
+ const grouped = await group(tree, (value, key, tree) => value.tags);
37
37
  assert.deepEqual(await Tree.plain(grouped), {
38
38
  Geometric: {
39
39
  "Albert Sans": { tags: ["Geometric", "Sans Serif"] },
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { Tree } from "../../src/internal.js";
4
- import invokeFunctions from "../../src/transforms/invokeFunctions.js";
4
+ import invokeFunctions from "../../src/operations/invokeFunctions.js";
5
5
 
6
6
  describe("invokeFunctions", () => {
7
7
  test("invokes function values, leaves other values as is", async () => {