@weborigami/language 0.6.13 → 0.6.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.6.13",
3
+ "version": "0.6.15",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,7 +11,7 @@
11
11
  "typescript": "5.9.3"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/async-tree": "0.6.13",
14
+ "@weborigami/async-tree": "0.6.15",
15
15
  "exif-parser": "0.1.12",
16
16
  "watcher": "2.3.1",
17
17
  "yaml": "2.8.2"
@@ -1,7 +1,8 @@
1
1
  import { Mixin } from "../../index.ts";
2
2
 
3
3
  declare const WatchFilesMixin: Mixin<{
4
- watch(): Promise<void>;
4
+ unwatch(): void;
5
+ watch(): void;
5
6
  }>;
6
7
 
7
8
  export default WatchFilesMixin;
@@ -27,7 +27,7 @@ export default function WatchFilesMixin(Base) {
27
27
  this.dispatchEvent(new TreeEvent("change", { filePath }));
28
28
  }
29
29
 
30
- async unwatch() {
30
+ unwatch() {
31
31
  if (!this.watching) {
32
32
  return;
33
33
  }
@@ -37,7 +37,7 @@ export default function WatchFilesMixin(Base) {
37
37
  }
38
38
 
39
39
  // Turn on watching for the directory.
40
- async watch() {
40
+ watch() {
41
41
  if (this.watching) {
42
42
  return;
43
43
  }
@@ -27,8 +27,9 @@ export default async function explainTraverseError(error) {
27
27
  }
28
28
 
29
29
  // The key that caused the error is the one before the current position
30
- const path = pathFromKeys(keys.slice(0, position));
31
- let message = `The path traversal ended unexpectedly at: ${path}`;
30
+ const path = pathFromKeys(keys);
31
+ const problemKey = keys[position - 1];
32
+ let message = `Tried to traverse path: ${path}\nStopped unexpectedly at: ${problemKey}`;
32
33
 
33
34
  const key = trailingSlash.remove(keys[position - 1]);
34
35
 
@@ -57,7 +57,7 @@ export default async function handleExtension(value, key, parent = null) {
57
57
  }
58
58
  }
59
59
  }
60
- value;
60
+
61
61
  return value;
62
62
  }
63
63
 
@@ -1,4 +1,10 @@
1
- import { isPlainObject, isUnpackable, Tree } from "@weborigami/async-tree";
1
+ import {
2
+ isPlainObject,
3
+ isUnpackable,
4
+ symbols,
5
+ trailingSlash,
6
+ Tree,
7
+ } from "@weborigami/async-tree";
2
8
  import assignPropertyDescriptors from "./assignPropertyDescriptors.js";
3
9
 
4
10
  /**
@@ -28,11 +34,48 @@ export default async function mergeTrees(...trees) {
28
34
 
29
35
  // If all trees are plain objects, return a plain object.
30
36
  if (unpacked.every((tree) => isPlainObject(tree) && !Tree.isMap(tree))) {
31
- // Use our Object.assign variation that avoids invoking property getters.
32
- return assignPropertyDescriptors({}, ...unpacked);
37
+ return mergePlainObjects(...unpacked);
33
38
  }
34
39
 
35
40
  // Merge the trees.
36
41
  const result = Tree.merge(...unpacked);
37
42
  return result;
38
43
  }
44
+
45
+ // Return the merged keys for the given objects, respecting any keys method on
46
+ // objects that were created using Origami object literals. This preserves
47
+ // trailing slashes.
48
+ function mergeObjectKeys(objects) {
49
+ const mergedKeys = new Set();
50
+ for (const obj of objects) {
51
+ const objectKeys =
52
+ typeof obj[symbols.keys] === "function"
53
+ ? obj[symbols.keys]()
54
+ : Object.keys(obj);
55
+ for (const key of objectKeys) {
56
+ const alternateKey = trailingSlash.toggle(key);
57
+ if (mergedKeys.has(alternateKey)) {
58
+ // The new key differs from a key from an earlier object key only by the
59
+ // presence or absence of a trailing slash. Replace that old key.
60
+ mergedKeys.delete(alternateKey);
61
+ }
62
+ mergedKeys.add(key);
63
+ }
64
+ }
65
+ return Array.from(mergedKeys);
66
+ }
67
+
68
+ function mergePlainObjects(...objects) {
69
+ // Use our Object.assign variation that avoids invoking property getters.
70
+ const result = assignPropertyDescriptors({}, ...objects);
71
+
72
+ // Attach a keys method
73
+ Object.defineProperty(result, symbols.keys, {
74
+ configurable: true,
75
+ enumerable: false,
76
+ value: () => mergeObjectKeys(objects),
77
+ writable: true,
78
+ });
79
+
80
+ return result;
81
+ }
@@ -173,12 +173,12 @@ export function exponentiation(a, b) {
173
173
  addOpLabel(exponentiation, "«ops.exponentiation»");
174
174
 
175
175
  /**
176
- * Flatten the values of the given trees to a depth of 1
176
+ * Flatten the values of the given trees by one level.
177
177
  *
178
178
  * @param {...any} args
179
179
  */
180
180
  export async function flat(...args) {
181
- return Tree.flat(args, 2); // add 1 to account for the array of arguments
181
+ return Tree.flat(args);
182
182
  }
183
183
  addOpLabel(flat, "«ops.flat»");
184
184
 
@@ -210,7 +210,8 @@ evaluating: \x1B[31mrepeat\x1B[0m`,
210
210
  await assertError(
211
211
  `a/b/sup/c`,
212
212
  `TraverseError: A path hit a null or undefined value.
213
- The path traversal ended unexpectedly at: a/b/sup/
213
+ Tried to traverse path: a/b/sup/c
214
+ Stopped unexpectedly at: sup/
214
215
  Perhaps you intended: sub/
215
216
  evaluating: \x1B[31msup/\x1B[0m`,
216
217
  { parent },
@@ -224,7 +225,8 @@ evaluating: \x1B[31msup/\x1B[0m`,
224
225
  await assertError(
225
226
  `map/1/a`,
226
227
  `TraverseError: A path hit a null or undefined value.
227
- The path traversal ended unexpectedly at: map/1/
228
+ Tried to traverse path: map/1/a
229
+ Stopped unexpectedly at: 1/
228
230
  Slash-separated keys are searched as strings. Here there's no string "1" key, but there is a number 1 key.
229
231
  To get the value for that number key, use parentheses: map/(1)
230
232
  evaluating: \x1B[31m1/\x1B[0m`,
@@ -239,7 +241,8 @@ evaluating: \x1B[31m1/\x1B[0m`,
239
241
  await assertError(
240
242
  `file.foo/bar`,
241
243
  `TraverseError: A path hit binary file data that can't be unpacked.
242
- The path traversal ended unexpectedly at: file.foo/
244
+ Tried to traverse path: file.foo/bar
245
+ Stopped unexpectedly at: file.foo/
243
246
  The value couldn't be unpacked because no file extension handler is registered for ".foo".
244
247
  evaluating: \x1B[31mfile.foo/\x1B[0m`,
245
248
  { parent },
@@ -255,7 +258,8 @@ evaluating: \x1B[31mfile.foo/\x1B[0m`,
255
258
  await assertError(
256
259
  `a/b/`,
257
260
  `TraverseError: A path tried to unpack data that's already unpacked.
258
- The path traversal ended unexpectedly at: a/b/
261
+ Tried to traverse path: a/b/
262
+ Stopped unexpectedly at: b/
259
263
  You can drop the trailing slash and just use: b
260
264
  evaluating: \x1B[31mb/\x1B[0m`,
261
265
  { parent },
@@ -269,7 +273,8 @@ evaluating: \x1B[31mb/\x1B[0m`,
269
273
  await assertError(
270
274
  `a/b/`,
271
275
  `TraverseError: A path tried to unpack a value that doesn't exist.
272
- The path traversal ended unexpectedly at: a/b/
276
+ Tried to traverse path: a/b/
277
+ Stopped unexpectedly at: b/
273
278
  evaluating: \x1B[31mb/\x1B[0m`,
274
279
  { parent },
275
280
  );
@@ -1,4 +1,4 @@
1
- import { ObjectMap, Tree } from "@weborigami/async-tree";
1
+ import { ObjectMap, symbols, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import mergeTrees from "../../src/runtime/mergeTrees.js";
@@ -23,7 +23,7 @@ describe("mergeTrees", () => {
23
23
  calledBar = true;
24
24
  return true;
25
25
  },
26
- }
26
+ },
27
27
  );
28
28
 
29
29
  // Shouldn't call functions when just getting keys
@@ -40,6 +40,22 @@ describe("mergeTrees", () => {
40
40
  });
41
41
  });
42
42
 
43
+ test("respects trailing slashes in keys", async () => {
44
+ const result = await mergeTrees(
45
+ {
46
+ "a/": 1, // key will be overwritten
47
+ b: 2, // key will be overwritten
48
+ c: 3,
49
+ },
50
+ {
51
+ a: 4,
52
+ "b/": 5,
53
+ },
54
+ );
55
+ const keys = result[symbols.keys]();
56
+ assert.deepEqual(keys, ["c", "a", "b/"]);
57
+ });
58
+
43
59
  test("merges heterogenous arguments as trees", async () => {
44
60
  const tree = await mergeTrees(
45
61
  new ObjectMap({
@@ -49,7 +65,7 @@ describe("mergeTrees", () => {
49
65
  {
50
66
  b: 3,
51
67
  c: 4,
52
- }
68
+ },
53
69
  );
54
70
  assert.deepEqual(await Tree.plain(tree), {
55
71
  a: 1,
@@ -159,7 +159,14 @@ describe("ops", () => {
159
159
  });
160
160
  const array = [5, 6];
161
161
  const result = await ops.flat(object, tree, array);
162
- assert.deepEqual(result, [1, 2, 3, 4, 5, 6]);
162
+ assert.deepEqual(await Tree.plain(result), {
163
+ a: 1,
164
+ b: 2,
165
+ c: 3,
166
+ d: 4,
167
+ 0: 5,
168
+ 1: 6,
169
+ });
163
170
  });
164
171
  });
165
172