@weborigami/async-tree 0.0.73 → 0.2.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 (70) 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 +3 -2
  7. package/src/Tree.js +26 -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} +25 -5
  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/operations/concat.js +10 -2
  22. package/src/{transforms → operations}/deepReverse.js +1 -1
  23. package/src/operations/deepTake.js +30 -3
  24. package/src/operations/group.js +44 -3
  25. package/src/operations/keyFunctionsForExtensions.js +48 -0
  26. package/src/operations/map.js +118 -3
  27. package/src/operations/sort.js +45 -2
  28. package/src/operations/take.js +19 -2
  29. package/src/symbols.js +1 -0
  30. package/test/Tree.test.js +25 -2
  31. package/test/{BrowserFileTree.test.js → drivers/BrowserFileTree.test.js} +2 -2
  32. package/test/{DeepMapTree.test.js → drivers/DeepMapTree.test.js} +2 -2
  33. package/test/{DeepObjectTree.test.js → drivers/DeepObjectTree.test.js} +1 -1
  34. package/test/{DeferredTree.test.js → drivers/DeferredTree.test.js} +2 -2
  35. package/test/{ExplorableSiteTree.test.js → drivers/ExplorableSiteTree.test.js} +6 -3
  36. package/test/{FileTree.test.js → drivers/FileTree.test.js} +6 -5
  37. package/test/{FunctionTree.test.js → drivers/FunctionTree.test.js} +1 -1
  38. package/test/{MapTree.test.js → drivers/MapTree.test.js} +2 -2
  39. package/test/{ObjectTree.test.js → drivers/ObjectTree.test.js} +2 -2
  40. package/test/{SetTree.test.js → drivers/SetTree.test.js} +2 -2
  41. package/test/{SiteTree.test.js → drivers/SiteTree.test.js} +1 -1
  42. package/test/{calendarTree.test.js → drivers/calendarTree.test.js} +5 -5
  43. package/test/extension.test.js +41 -0
  44. package/test/{transforms → operations}/cachedKeyFunctions.test.js +1 -1
  45. package/test/operations/concat.test.js +1 -1
  46. package/test/{transforms → operations}/deepReverse.test.js +1 -1
  47. package/test/operations/{deepTakeFn.test.js → deepTake.test.js} +3 -3
  48. package/test/{transforms/groupFn.test.js → operations/group.test.js} +4 -4
  49. package/test/{transforms → operations}/invokeFunctions.test.js +1 -1
  50. package/test/{transforms → operations}/keyFunctionsForExtensions.test.js +21 -12
  51. package/test/{transforms/mapFn.test.js → operations/map.test.js} +23 -31
  52. package/test/{transforms → operations}/regExpKeys.test.js +1 -1
  53. package/test/{transforms → operations}/reverse.test.js +1 -1
  54. package/test/{transforms/sortFn.test.js → operations/sort.test.js} +6 -7
  55. package/test/{transforms/takeFn.test.js → operations/take.test.js} +3 -3
  56. package/src/operations/deepTakeFn.js +0 -43
  57. package/src/operations/groupFn.js +0 -57
  58. package/src/transforms/keyFunctionsForExtensions.js +0 -78
  59. package/src/transforms/mapFn.js +0 -126
  60. package/src/transforms/sortFn.js +0 -71
  61. package/src/transforms/takeFn.js +0 -31
  62. /package/src/{ExplorableSiteTree.js → drivers/ExplorableSiteTree.js} +0 -0
  63. /package/src/{transforms → operations}/cachedKeyFunctions.js +0 -0
  64. /package/src/{transforms → operations}/invokeFunctions.js +0 -0
  65. /package/src/{transforms → operations}/regExpKeys.js +0 -0
  66. /package/src/{transforms → operations}/reverse.js +0 -0
  67. /package/test/{fixtures → drivers/fixtures}/markdown/Alice.md +0 -0
  68. /package/test/{fixtures → drivers/fixtures}/markdown/Bob.md +0 -0
  69. /package/test/{fixtures → drivers/fixtures}/markdown/Carol.md +0 -0
  70. /package/test/{fixtures → drivers/fixtures}/markdown/subfolder/README.md +0 -0
@@ -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 () => {
@@ -1,13 +1,13 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { ObjectTree, Tree } from "../../src/internal.js";
4
- import keyFunctionsForExtensions from "../../src/transforms/keyFunctionsForExtensions.js";
5
- import map from "../../src/transforms/mapFn.js";
4
+ import keyFunctionsForExtensions from "../../src/operations/keyFunctionsForExtensions.js";
5
+ import map from "../../src/operations/map.js";
6
6
 
7
7
  describe("keyMapsForExtensions", () => {
8
8
  test("returns key functions that pass a matching key through", async () => {
9
9
  const { inverseKey, key } = keyFunctionsForExtensions({
10
- sourceExtension: "txt",
10
+ sourceExtension: ".txt",
11
11
  });
12
12
  assert.equal(await inverseKey("file.txt"), "file.txt");
13
13
  assert.equal(await inverseKey("file.txt/"), "file.txt");
@@ -19,8 +19,8 @@ describe("keyMapsForExtensions", () => {
19
19
 
20
20
  test("returns key functions that can map extensions", async () => {
21
21
  const { inverseKey, key } = keyFunctionsForExtensions({
22
- resultExtension: "json",
23
- sourceExtension: "md",
22
+ resultExtension: ".json",
23
+ sourceExtension: ".md",
24
24
  });
25
25
  assert.equal(await inverseKey("file.json"), "file.md");
26
26
  assert.equal(await inverseKey("file.json/"), "file.md");
@@ -30,6 +30,17 @@ describe("keyMapsForExtensions", () => {
30
30
  assert.equal(await key("file.foo"), undefined);
31
31
  });
32
32
 
33
+ test("key functions can handle a slash as an explicit extension", async () => {
34
+ const { inverseKey, key } = keyFunctionsForExtensions({
35
+ resultExtension: ".html",
36
+ sourceExtension: "/",
37
+ });
38
+ assert.equal(await inverseKey("file.html"), "file/");
39
+ assert.equal(await inverseKey("file.html/"), "file/");
40
+ assert.equal(await key("file"), undefined);
41
+ assert.equal(await key("file/"), "file.html");
42
+ });
43
+
33
44
  test("works with map to handle keys that end in a given resultExtension", async () => {
34
45
  const files = new ObjectTree({
35
46
  "file1.txt": "will be mapped",
@@ -37,14 +48,13 @@ describe("keyMapsForExtensions", () => {
37
48
  "file3.foo": "won't be mapped",
38
49
  });
39
50
  const { inverseKey, key } = keyFunctionsForExtensions({
40
- sourceExtension: "txt",
51
+ sourceExtension: ".txt",
41
52
  });
42
- const transform = map({
53
+ const fixture = map(files, {
43
54
  inverseKey,
44
55
  key,
45
56
  value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
46
57
  });
47
- const fixture = transform(files);
48
58
  assert.deepEqual(await Tree.plain(fixture), {
49
59
  "file1.txt": "WILL BE MAPPED",
50
60
  });
@@ -57,15 +67,14 @@ describe("keyMapsForExtensions", () => {
57
67
  "file3.foo": "won't be mapped",
58
68
  });
59
69
  const { inverseKey, key } = keyFunctionsForExtensions({
60
- resultExtension: "upper",
61
- sourceExtension: "txt",
70
+ resultExtension: ".upper",
71
+ sourceExtension: ".txt",
62
72
  });
63
- const transform = map({
73
+ const fixture = map(files, {
64
74
  inverseKey,
65
75
  key,
66
76
  value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
67
77
  });
68
- const fixture = transform(files);
69
78
  assert.deepEqual(await Tree.plain(fixture), {
70
79
  "file1.upper": "WILL BE MAPPED",
71
80
  });
@@ -1,17 +1,17 @@
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 { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
5
+ import map from "../../src/operations/map.js";
5
6
  import * as trailingSlash from "../../src/trailingSlash.js";
6
- import mapFn from "../../src/transforms/mapFn.js";
7
7
 
8
- describe("mapFn", () => {
9
- test("returns identity graph if no key or value", async () => {
8
+ describe("map", () => {
9
+ test("returns identity graph if no key or value function is supplied", async () => {
10
10
  const tree = {
11
11
  a: "letter a",
12
12
  b: "letter b",
13
13
  };
14
- const mapped = mapFn()(tree);
14
+ const mapped = map(tree, {});
15
15
  assert.deepEqual(await Tree.plain(mapped), {
16
16
  a: "letter a",
17
17
  b: "letter b",
@@ -24,14 +24,13 @@ describe("mapFn", () => {
24
24
  b: "letter b",
25
25
  c: undefined, // Won't be mapped
26
26
  });
27
- const uppercaseValues = mapFn({
27
+ const mapped = map(tree, {
28
28
  value: (sourceValue, sourceKey, innerTree) => {
29
29
  assert(sourceKey === "a" || sourceKey === "b");
30
30
  assert.equal(innerTree, tree);
31
31
  return sourceValue.toUpperCase();
32
32
  },
33
33
  });
34
- const mapped = uppercaseValues(tree);
35
34
  assert.deepEqual(await Tree.plain(mapped), {
36
35
  a: "LETTER A",
37
36
  b: "LETTER B",
@@ -44,12 +43,11 @@ describe("mapFn", () => {
44
43
  a: "letter a",
45
44
  b: "letter b",
46
45
  };
47
- const uppercaseValues = mapFn((sourceValue, sourceKey, tree) => {
46
+ const uppercaseValues = map(tree, (sourceValue, sourceKey, tree) => {
48
47
  assert(sourceKey === "a" || sourceKey === "b");
49
48
  return sourceValue.toUpperCase();
50
49
  });
51
- const mapped = uppercaseValues(tree);
52
- assert.deepEqual(await Tree.plain(mapped), {
50
+ assert.deepEqual(await Tree.plain(uppercaseValues), {
53
51
  a: "LETTER A",
54
52
  b: "LETTER B",
55
53
  });
@@ -60,12 +58,11 @@ describe("mapFn", () => {
60
58
  a: "letter a",
61
59
  b: "letter b",
62
60
  };
63
- const underscopeKeys = mapFn({
61
+ const underscoreKeys = map(tree, {
64
62
  key: addUnderscore,
65
63
  inverseKey: removeUnderscore,
66
64
  });
67
- const mapped = underscopeKeys(tree);
68
- assert.deepEqual(await Tree.plain(mapped), {
65
+ assert.deepEqual(await Tree.plain(underscoreKeys), {
69
66
  _a: "letter a",
70
67
  _b: "letter b",
71
68
  });
@@ -76,13 +73,12 @@ describe("mapFn", () => {
76
73
  a: "letter a",
77
74
  b: "letter b",
78
75
  };
79
- const underscoreKeysUppercaseValues = mapFn({
76
+ const underscoreKeysUppercaseValues = map(tree, {
80
77
  key: addUnderscore,
81
78
  inverseKey: removeUnderscore,
82
79
  value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
83
80
  });
84
- const mapped = underscoreKeysUppercaseValues(tree);
85
- assert.deepEqual(await Tree.plain(mapped), {
81
+ assert.deepEqual(await Tree.plain(underscoreKeysUppercaseValues), {
86
82
  _a: "LETTER A",
87
83
  _b: "LETTER B",
88
84
  });
@@ -95,13 +91,12 @@ describe("mapFn", () => {
95
91
  b: "letter b",
96
92
  },
97
93
  };
98
- const underscopeKeys = mapFn({
94
+ const underscoreKeys = map(tree, {
99
95
  key: async (sourceKey, tree) => `_${sourceKey}`,
100
96
  inverseKey: async (resultKey, tree) => resultKey.slice(1),
101
97
  value: async (sourceValue, sourceKey, tree) => sourceKey,
102
98
  });
103
- const mapped = underscopeKeys(tree);
104
- assert.deepEqual(await Tree.plain(mapped), {
99
+ assert.deepEqual(await Tree.plain(underscoreKeys), {
105
100
  _a: "a",
106
101
  _more: "more",
107
102
  });
@@ -115,7 +110,7 @@ describe("mapFn", () => {
115
110
  a: "letter a",
116
111
  b: "letter b",
117
112
  };
118
- const mapped = mapFn(uppercase)(tree);
113
+ const mapped = map(tree, uppercase);
119
114
  assert.deepEqual(await Tree.plain(mapped), {
120
115
  _a: "LETTER A",
121
116
  _b: "LETTER B",
@@ -129,12 +124,11 @@ describe("mapFn", () => {
129
124
  b: "letter b",
130
125
  },
131
126
  });
132
- const uppercaseValues = mapFn({
127
+ const uppercaseValues = map(tree, {
133
128
  deep: true,
134
129
  value: (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
135
130
  });
136
- const mapped = uppercaseValues(tree);
137
- assert.deepEqual(await Tree.plain(mapped), {
131
+ assert.deepEqual(await Tree.plain(uppercaseValues), {
138
132
  a: "LETTER A",
139
133
  more: {
140
134
  b: "LETTER B",
@@ -149,13 +143,12 @@ describe("mapFn", () => {
149
143
  b: "letter b",
150
144
  },
151
145
  });
152
- const underscoreKeys = mapFn({
146
+ const underscoreKeys = map(tree, {
153
147
  deep: true,
154
148
  key: addUnderscore,
155
149
  inverseKey: removeUnderscore,
156
150
  });
157
- const mapped = underscoreKeys(tree);
158
- assert.deepEqual(await Tree.plain(mapped), {
151
+ assert.deepEqual(await Tree.plain(underscoreKeys), {
159
152
  _a: "letter a",
160
153
  more: {
161
154
  _b: "letter b",
@@ -170,14 +163,13 @@ describe("mapFn", () => {
170
163
  b: "letter b",
171
164
  },
172
165
  });
173
- const underscoreKeysUppercaseValues = mapFn({
166
+ const underscoreKeysUppercaseValues = map(tree, {
174
167
  deep: true,
175
168
  key: addUnderscore,
176
169
  inverseKey: removeUnderscore,
177
170
  value: async (sourceValue, sourceKey, tree) => sourceValue.toUpperCase(),
178
171
  });
179
- const mapped = underscoreKeysUppercaseValues(tree);
180
- assert.deepEqual(await Tree.plain(mapped), {
172
+ assert.deepEqual(await Tree.plain(underscoreKeysUppercaseValues), {
181
173
  _a: "LETTER A",
182
174
  more: {
183
175
  _b: "LETTER B",
@@ -190,10 +182,10 @@ describe("mapFn", () => {
190
182
  const tree = new FunctionTree(() => {
191
183
  flag = true;
192
184
  }, ["a", "b", "c"]);
193
- const mapped = mapFn({
185
+ const mapped = map(tree, {
194
186
  needsSourceValue: false,
195
187
  value: () => "X",
196
- })(tree);
188
+ });
197
189
  assert.deepEqual(await Tree.plain(mapped), {
198
190
  a: "X",
199
191
  b: "X",
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
3
  import { DeepObjectTree, Tree } from "../../src/internal.js";
4
- import regExpKeys from "../../src/transforms/regExpKeys.js";
4
+ import regExpKeys from "../../src/operations/regExpKeys.js";
5
5
 
6
6
  describe("regExpKeys", () => {
7
7
  test("matches keys using regular expressions", async () => {
@@ -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 reverse from "../../src/transforms/reverse.js";
4
+ import reverse from "../../src/operations/reverse.js";
5
5
 
6
6
  describe("reverse", () => {
7
7
  test("reverses a tree's top-level keys", async () => {
@@ -1,16 +1,16 @@
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 sortFn from "../../src/transforms/sortFn.js";
4
+ import sort from "../../src/operations/sort.js";
5
5
 
6
- describe("sortFn", () => {
6
+ describe("sort", () => {
7
7
  test("sorts keys using default sort order", async () => {
8
8
  const tree = Tree.from({
9
9
  file10: null,
10
10
  file1: null,
11
11
  file9: null,
12
12
  });
13
- const sorted = sortFn()(tree);
13
+ const sorted = sort(tree);
14
14
  assert.deepEqual(Array.from(await sorted.keys()), [
15
15
  "file1",
16
16
  "file10",
@@ -26,7 +26,7 @@ describe("sortFn", () => {
26
26
  });
27
27
  // Reverse order
28
28
  const compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0);
29
- const sorted = sortFn({ compare })(tree);
29
+ const sorted = sort(tree, { compare });
30
30
  assert.deepEqual(Array.from(await sorted.keys()), ["c", "b", "a"]);
31
31
  });
32
32
 
@@ -36,11 +36,10 @@ describe("sortFn", () => {
36
36
  Bob: { age: 36 },
37
37
  Carol: { age: 42 },
38
38
  };
39
- const transform = await sortFn({
39
+ const sorted = await sort(tree, {
40
40
  sortKey: async (key, tree) => Tree.traverse(tree, key, "age"),
41
41
  });
42
- const result = transform(tree);
43
- assert.deepEqual(Array.from(await result.keys()), [
42
+ assert.deepEqual(Array.from(await sorted.keys()), [
44
43
  "Bob",
45
44
  "Carol",
46
45
  "Alice",
@@ -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 takeFn from "../../src/transforms/takeFn.js";
4
+ import take from "../../src/operations/take.js";
5
5
 
6
- describe("takeFn", () => {
6
+ describe("take", () => {
7
7
  test("limits the number of keys to the indicated count", async () => {
8
8
  const tree = {
9
9
  a: 1,
@@ -11,7 +11,7 @@ describe("takeFn", () => {
11
11
  c: 3,
12
12
  d: 4,
13
13
  };
14
- const result = await takeFn(2)(tree);
14
+ const result = await take(tree, 2);
15
15
  assert.deepEqual(await Tree.plain(result), {
16
16
  a: 1,
17
17
  b: 2,
@@ -1,43 +0,0 @@
1
- import { Tree } from "../internal.js";
2
-
3
- /**
4
- * Returns a function that traverses a tree deeply and returns the values of the
5
- * first `count` keys.
6
- *
7
- * @param {number} count
8
- */
9
- export default function deepTakeFn(count) {
10
- /**
11
- * @param {import("../../index.ts").Treelike} treelike
12
- */
13
- return async function deepTakeFn(treelike) {
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);
23
- };
24
- }
25
-
26
- async function traverse(tree, count) {
27
- const values = [];
28
- for (const key of await tree.keys()) {
29
- if (count <= 0) {
30
- break;
31
- }
32
- let value = await tree.get(key);
33
- if (Tree.isAsyncTree(value)) {
34
- const traversed = await traverse(value, count);
35
- values.push(...traversed.values);
36
- count = traversed.count;
37
- } else {
38
- values.push(value);
39
- count--;
40
- }
41
- }
42
- return { count, values };
43
- }
@@ -1,57 +0,0 @@
1
- import { ObjectTree, Tree } from "../internal.js";
2
-
3
- /**
4
- * Given a function that returns a grouping key for a value, returns a transform
5
- * that applies that grouping function to a tree.
6
- *
7
- * @param {import("../../index.ts").ValueKeyFn} groupKeyFn
8
- */
9
- export default function createGroupByTransform(groupKeyFn) {
10
- /**
11
- * @type {import("../../index.ts").TreeTransform}
12
- */
13
- return async function groupByTransform(treelike) {
14
- if (!treelike) {
15
- const error = new TypeError(`groupBy: The tree to group isn't defined.`);
16
- /** @type {any} */ (error).position = 0;
17
- throw error;
18
- }
19
-
20
- const tree = Tree.from(treelike);
21
-
22
- const keys = Array.from(await tree.keys());
23
-
24
- // Are all the keys integers?
25
- const isArray = keys.every((key) => !Number.isNaN(parseInt(key)));
26
-
27
- const result = {};
28
- for (const key of await tree.keys()) {
29
- const value = await tree.get(key);
30
-
31
- // Get the groups for this value.
32
- let groups = await groupKeyFn(value, key, tree);
33
- if (!groups) {
34
- continue;
35
- }
36
-
37
- if (!Tree.isTreelike(groups)) {
38
- // A single value was returned
39
- groups = [groups];
40
- }
41
- groups = Tree.from(groups);
42
-
43
- // Add the value to each group.
44
- for (const group of await Tree.values(groups)) {
45
- if (isArray) {
46
- result[group] ??= [];
47
- result[group].push(value);
48
- } else {
49
- result[group] ??= {};
50
- result[group][key] = value;
51
- }
52
- }
53
- }
54
-
55
- return new ObjectTree(result);
56
- };
57
- }
@@ -1,78 +0,0 @@
1
- import * as trailingSlash from "../trailingSlash.js";
2
-
3
- /**
4
- * Given a source resultExtension and a result resultExtension, return a pair of key
5
- * functions that map between them.
6
- *
7
- * The resulting `inverseKey` and `key` functions are compatible with those
8
- * expected by map and other transforms.
9
- *
10
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
11
- * @param {{ resultExtension?: string, sourceExtension: string }}
12
- * options
13
- */
14
- export default function keyFunctionsForExtensions({
15
- resultExtension,
16
- sourceExtension,
17
- }) {
18
- if (resultExtension === undefined) {
19
- resultExtension = sourceExtension;
20
- }
21
-
22
- return {
23
- async inverseKey(resultKey, tree) {
24
- // Remove trailing slash so that mapFn won't inadvertently unpack files.
25
- const baseKey = trailingSlash.remove(resultKey);
26
- const basename = matchExtension(baseKey, resultExtension);
27
- return basename ? `${basename}${dotPrefix(sourceExtension)}` : undefined;
28
- },
29
-
30
- async key(sourceKey, tree) {
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;
41
- },
42
- };
43
- }
44
-
45
- function dotPrefix(resultExtension) {
46
- return resultExtension ? `.${resultExtension}` : "";
47
- }
48
-
49
- /**
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.
52
- *
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.
56
- *
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.
61
- */
62
- function matchExtension(key, resultExtension) {
63
- if (resultExtension) {
64
- // Key matches if it ends with the same resultExtension
65
- const dotExtension = dotPrefix(resultExtension);
66
- if (
67
- key.length > dotExtension.length &&
68
- key.toLowerCase().endsWith(dotExtension)
69
- ) {
70
- return key.substring(0, key.length - dotExtension.length);
71
- }
72
- } else if (!key.includes?.(".")) {
73
- // Key matches if it has no resultExtension
74
- return key;
75
- }
76
- // Didn't match
77
- return null;
78
- }
@@ -1,126 +0,0 @@
1
- import { Tree } from "../internal.js";
2
- import * as trailingSlash from "../trailingSlash.js";
3
-
4
- /**
5
- * Return a transform function that maps the keys and/or values of a tree.
6
- *
7
- * @typedef {import("../../index.ts").KeyFn} KeyFn
8
- * @typedef {import("../../index.ts").ValueKeyFn} ValueKeyFn
9
- *
10
- * @param {ValueKeyFn|{ deep?: boolean, description?: string, needsSourceValue?: boolean, inverseKey?: KeyFn, key?: KeyFn, value?: ValueKeyFn }} options
11
- * @returns {import("../../index.ts").TreeTransform}
12
- */
13
- export default function createMapTransform(options = {}) {
14
- let deep;
15
- let description;
16
- let inverseKeyFn;
17
- let keyFn;
18
- let needsSourceValue;
19
- let valueFn;
20
- if (typeof options === "function") {
21
- // Take the single function argument as the valueFn
22
- valueFn = options;
23
- } else {
24
- deep = options.deep;
25
- description = options.description;
26
- inverseKeyFn = options.inverseKey;
27
- keyFn = options.key;
28
- needsSourceValue = options.needsSourceValue;
29
- valueFn = options.value;
30
- }
31
-
32
- deep ??= false;
33
- description ??= "key/value map";
34
- // @ts-ignore
35
- inverseKeyFn ??= valueFn?.inverseKey;
36
- // @ts-ignore
37
- keyFn ??= valueFn?.key;
38
- needsSourceValue ??= true;
39
-
40
- if ((keyFn && !inverseKeyFn) || (!keyFn && inverseKeyFn)) {
41
- throw new TypeError(
42
- `map: You must specify both key and inverseKey functions, or neither.`
43
- );
44
- }
45
-
46
- /**
47
- * @type {import("../../index.ts").TreeTransform}
48
- */
49
- return function map(treelike) {
50
- if (!treelike) {
51
- const error = new TypeError(`map: The tree to map isn't defined.`);
52
- /** @type {any} */ (error).position = 0;
53
- throw error;
54
- }
55
-
56
- const tree = Tree.from(treelike, { deep });
57
-
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 = map(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
- }