@weborigami/language 0.5.8 → 0.6.1

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 (57) hide show
  1. package/index.ts +3 -4
  2. package/main.js +2 -4
  3. package/package.json +4 -5
  4. package/src/compiler/optimize.js +3 -1
  5. package/src/compiler/parserHelpers.js +1 -7
  6. package/src/handlers/csv_handler.js +13 -8
  7. package/src/handlers/getSource.js +36 -0
  8. package/src/handlers/ori_handler.js +5 -20
  9. package/src/handlers/oridocument_handler.js +5 -28
  10. package/src/handlers/tsv_handler.js +10 -1
  11. package/src/handlers/yaml_handler.js +75 -3
  12. package/src/project/jsGlobals.js +5 -1
  13. package/src/project/projectConfig.js +4 -4
  14. package/src/project/projectRoot.js +8 -9
  15. package/src/protocols/constructSiteTree.js +4 -4
  16. package/src/protocols/explore.js +2 -3
  17. package/src/protocols/fetchAndHandleExtension.js +0 -1
  18. package/src/protocols/files.js +2 -3
  19. package/src/protocols/http.js +0 -1
  20. package/src/protocols/https.js +0 -1
  21. package/src/protocols/httpstree.js +2 -3
  22. package/src/protocols/httptree.js +2 -3
  23. package/src/runtime/HandleExtensionsTransform.js +32 -8
  24. package/src/runtime/ImportModulesMixin.js +2 -2
  25. package/src/runtime/{OrigamiFiles.js → OrigamiFileMap.d.ts} +3 -3
  26. package/src/runtime/{OrigamiFiles.d.ts → OrigamiFileMap.js} +3 -5
  27. package/src/runtime/errors.js +9 -2
  28. package/src/runtime/evaluate.js +1 -1
  29. package/src/runtime/expressionFunction.js +3 -3
  30. package/src/runtime/expressionObject.js +19 -8
  31. package/src/runtime/handleExtension.js +2 -11
  32. package/src/runtime/mergeTrees.js +4 -7
  33. package/src/runtime/ops.js +13 -13
  34. package/test/cases/logicalAndExpression.yaml +7 -8
  35. package/test/compiler/compile.test.js +1 -1
  36. package/test/compiler/optimize.test.js +2 -2
  37. package/test/compiler/parse.test.js +13 -19
  38. package/test/generated/logicalAndExpression.test.js +4 -0
  39. package/test/handlers/csv_handler.test.js +5 -5
  40. package/test/handlers/js_handler.test.js +2 -2
  41. package/test/handlers/ori_handler.test.js +8 -8
  42. package/test/handlers/oridocument_handler.test.js +3 -3
  43. package/test/handlers/tsv_handler.test.js +5 -5
  44. package/test/handlers/wasm_handler.test.js +2 -2
  45. package/test/handlers/yaml_handler.test.js +31 -1
  46. package/test/runtime/OrigamiFileMap.test.js +40 -0
  47. package/test/runtime/evaluate.test.js +3 -3
  48. package/test/runtime/expressionObject.test.js +14 -6
  49. package/test/runtime/handleExtension.test.js +2 -9
  50. package/test/runtime/mergeTrees.test.js +2 -2
  51. package/test/runtime/ops.test.js +18 -12
  52. package/src/runtime/InvokeFunctionsTransform.d.ts +0 -5
  53. package/src/runtime/InvokeFunctionsTransform.js +0 -25
  54. package/src/runtime/functionResultsMap.js +0 -17
  55. package/test/runtime/OrigamiFiles.test.js +0 -35
  56. package/test/runtime/fixtures/subgraph = this.js +0 -5
  57. package/test/runtime/functionResultsMap.test.js +0 -20
@@ -1,17 +1,41 @@
1
1
  import handleExtension from "./handleExtension.js";
2
2
 
3
3
  /**
4
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
5
- * @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
4
+ * @typedef {import("../../index.ts").Constructor<Map>} MapConstructor
6
5
  * @typedef {import("@weborigami/async-tree").UnpackFunction} FileUnpackFunction
7
6
  *
8
- * @param {AsyncTreeConstructor} Base
7
+ * @param {MapConstructor} Base
9
8
  */
10
9
  export default function HandleExtensionsTransform(Base) {
11
- return class HandleExtensions extends Base {
12
- async get(key) {
13
- const value = await super.get(key);
14
- return handleExtension(value, key, this);
10
+ class HandleExtensions extends Base {
11
+ // Implement delete (and set) to keep the Map read-write
12
+ delete(key) {
13
+ return super.delete(key);
15
14
  }
16
- };
15
+
16
+ get(key) {
17
+ const value = super.get(key);
18
+ return value instanceof Promise
19
+ ? value.then((resolved) => handleExtension(resolved, key, this))
20
+ : handleExtension(value, key, this);
21
+ }
22
+
23
+ // See delete()
24
+ set(key, value) {
25
+ return super.set(key, value);
26
+ }
27
+ }
28
+
29
+ if (Base.prototype.readOnly) {
30
+ // Remove delete and set methods to keep the Map read-only. The base delete
31
+ // and set methods will exist (because it's a Map) but for our purposes the
32
+ // class is read-only.
33
+
34
+ // @ts-ignore
35
+ delete HandleExtensions.prototype.delete;
36
+ // @ts-ignore
37
+ delete HandleExtensions.prototype.set;
38
+ }
39
+
40
+ return HandleExtensions;
17
41
  }
@@ -5,8 +5,8 @@ import { maybeOrigamiSourceCode } from "./errors.js";
5
5
  import * as moduleCache from "./moduleCache.js";
6
6
 
7
7
  /**
8
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
- * @typedef {import("../../index.ts").Constructor<AsyncTree & { dirname: string }>} BaseConstructor
8
+ * @typedef {import("@weborigami/async-tree").AsyncMap} AsyncMap
9
+ * @typedef {import("../../index.ts").Constructor<AsyncMap & { dirname: string }>} BaseConstructor
10
10
  * @param {BaseConstructor} Base
11
11
  */
12
12
  export default function ImportModulesMixin(Base) {
@@ -1,9 +1,9 @@
1
- import { FileTree } from "@weborigami/async-tree";
1
+ import { FileMap } from "@weborigami/async-tree";
2
2
  import EventTargetMixin from "./EventTargetMixin.js";
3
3
  import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
4
4
  import ImportModulesMixin from "./ImportModulesMixin.js";
5
5
  import WatchFilesMixin from "./WatchFilesMixin.js";
6
6
 
7
- export default class OrigamiFiles extends HandleExtensionsTransform(
8
- ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileTree)))
7
+ export default class OrigamiFileMap extends HandleExtensionsTransform(
8
+ ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap)))
9
9
  ) {}
@@ -1,11 +1,9 @@
1
- import { FileTree } from "@weborigami/async-tree";
1
+ import { FileMap } from "@weborigami/async-tree";
2
2
  import EventTargetMixin from "./EventTargetMixin.js";
3
3
  import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
4
4
  import ImportModulesMixin from "./ImportModulesMixin.js";
5
5
  import WatchFilesMixin from "./WatchFilesMixin.js";
6
6
 
7
- export default class OrigamiFiles extends HandleExtensionsTransform(
8
- (
9
- ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileTree)))
10
- )
7
+ export default class OrigamiFileMap extends HandleExtensionsTransform(
8
+ ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap)))
11
9
  ) {}
@@ -95,7 +95,9 @@ export function formatError(error) {
95
95
  error.message === "A null or undefined value can't be traversed"
96
96
  ) {
97
97
  // Provide more meaningful message for TraverseError
98
- line = `TraverseError: This part of the path is null or undefined: ${fragment}`;
98
+ line = `TraverseError: This part of the path is null or undefined: ${highlightError(
99
+ fragment
100
+ )}`;
99
101
  fragmentInMessage = true;
100
102
  }
101
103
  if (message) {
@@ -110,7 +112,7 @@ export function formatError(error) {
110
112
  // Add location
111
113
  if (location) {
112
114
  if (!fragmentInMessage) {
113
- message += `\nevaluating: ${fragment}`;
115
+ message += `\nevaluating: ${highlightError(fragment)}`;
114
116
  }
115
117
  message += lineInfo(location);
116
118
  }
@@ -130,6 +132,11 @@ export async function formatScopeTypos(scope, key) {
130
132
  return `Maybe you meant ${list}?`;
131
133
  }
132
134
 
135
+ export function highlightError(text) {
136
+ // ANSI escape sequence to highlight text in red
137
+ return `\x1b[31m${text}\x1b[0m`;
138
+ }
139
+
133
140
  export function maybeOrigamiSourceCode(text) {
134
141
  return origamiSourceSignals.some((signal) => text.includes(signal));
135
142
  }
@@ -36,7 +36,7 @@ export default async function evaluate(code, state = {}) {
36
36
  const error = ReferenceError(
37
37
  `${codeFragment(code[0].location)} is not defined`
38
38
  );
39
- /** @type {any} */ (error).location = code.location;
39
+ /** @type {any} */ (error).location = code[0].location;
40
40
  throw error;
41
41
  }
42
42
 
@@ -1,12 +1,12 @@
1
- /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
-
3
1
  import { evaluate } from "./internal.js";
4
2
 
5
3
  /**
6
4
  * Given parsed Origami code, return a function that executes that code.
7
5
  *
6
+ * @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
7
+ *
8
8
  * @param {import("../../index.js").AnnotatedCode} code - parsed Origami expression
9
- * @param {AsyncTree} parent - the parent tree in which the code is running
9
+ * @param {SyncOrAsyncMap} parent - the parent tree in which the code is running
10
10
  */
11
11
  export function createExpressionFunction(code, parent) {
12
12
  async function fn() {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  extension,
3
- ObjectTree,
3
+ ObjectMap,
4
4
  setParent,
5
5
  symbols,
6
6
  trailingSlash,
@@ -30,14 +30,15 @@ export default async function expressionObject(entries, state = {}) {
30
30
  // Create the object and set its parent
31
31
  const object = {};
32
32
  const parent = state?.object ?? null;
33
- if (parent !== null && !Tree.isAsyncTree(parent)) {
34
- throw new TypeError(`Parent must be an AsyncTree or null`);
33
+ if (parent !== null && !Tree.isMap(parent)) {
34
+ throw new TypeError(`Parent must be a map or null`);
35
35
  }
36
36
  setParent(object, parent);
37
37
 
38
38
  let tree;
39
39
  const eagerProperties = [];
40
40
  const propertyIsEnumerable = {};
41
+ let hasLazyProperties = false;
41
42
  for (let [key, value] of entries) {
42
43
  // Determine if we need to define a getter or a regular property. If the key
43
44
  // has an extension, we need to define a getter. If the value is code (an
@@ -80,6 +81,7 @@ export default async function expressionObject(entries, state = {}) {
80
81
  // Property getter
81
82
  let code;
82
83
  if (value[0] === ops.getter) {
84
+ hasLazyProperties = true;
83
85
  code = value[1];
84
86
  } else {
85
87
  eagerProperties.push(key);
@@ -87,7 +89,7 @@ export default async function expressionObject(entries, state = {}) {
87
89
  }
88
90
 
89
91
  const get = async () => {
90
- tree ??= new ObjectTree(object);
92
+ tree ??= new ObjectMap(object);
91
93
  const newState = Object.assign({}, state, { object: tree });
92
94
  const result = await evaluate(code, newState);
93
95
  return extname ? handleExtension(result, key, tree) : result;
@@ -113,8 +115,7 @@ export default async function expressionObject(entries, state = {}) {
113
115
  // and overwrite the property getter with the actual value.
114
116
  for (const key of eagerProperties) {
115
117
  const value = await object[key];
116
- // @ts-ignore Unclear why TS thinks `object` might be undefined here
117
- const enumerable = Object.getOwnPropertyDescriptor(object, key).enumerable;
118
+ const enumerable = Object.getOwnPropertyDescriptor(object, key)?.enumerable;
118
119
  Object.defineProperty(object, key, {
119
120
  configurable: true,
120
121
  enumerable,
@@ -123,6 +124,16 @@ export default async function expressionObject(entries, state = {}) {
123
124
  });
124
125
  }
125
126
 
127
+ // If there are any getters, mark the object as async
128
+ if (hasLazyProperties) {
129
+ Object.defineProperty(object, symbols.async, {
130
+ configurable: true,
131
+ enumerable: false,
132
+ value: true,
133
+ writable: true,
134
+ });
135
+ }
136
+
126
137
  return object;
127
138
  }
128
139
 
@@ -141,8 +152,8 @@ export function entryKey(entry, object = null, eagerProperties = []) {
141
152
  return key;
142
153
  }
143
154
 
144
- // If eager property value is treelike, add slash to the key
145
- if (eagerProperties.includes(key) && Tree.isTreelike(object?.[key])) {
155
+ // If eager property value is maplike, add slash to the key
156
+ if (eagerProperties.includes(key) && Tree.isMaplike(object?.[key])) {
146
157
  return trailingSlash.add(key);
147
158
  }
148
159
 
@@ -18,7 +18,7 @@ let projectGlobals;
18
18
  *
19
19
  * @param {any} value
20
20
  * @param {any} key
21
- * @param {import("@weborigami/types").AsyncTree} [parent]
21
+ * @param {import("@weborigami/async-tree").SyncOrAsyncMap} [parent]
22
22
  */
23
23
  export default async function handleExtension(value, key, parent) {
24
24
  projectGlobals ??= await globals();
@@ -28,13 +28,9 @@ export default async function handleExtension(value, key, parent) {
28
28
  key = trailingSlash.remove(key);
29
29
  }
30
30
 
31
- // Special cases: `.ori.<ext>` extensions are Origami documents,
32
- // `.jse.<ext>` are JSE documents.
33
- // TODO: Remove .jse.<ext>
31
+ // Special cases: `.ori.<ext>` extensions are Origami documents
34
32
  const extname = key.match(/\.ori\.\S+$/)
35
33
  ? ".oridocument"
36
- : key.match(/\.jse\.\S+$/)
37
- ? ".jsedocument"
38
34
  : extension.extname(key);
39
35
  if (extname) {
40
36
  const handlerName = `${extname.slice(1)}_handler`;
@@ -45,11 +41,6 @@ export default async function handleExtension(value, key, parent) {
45
41
  handler = await handler.unpack();
46
42
  }
47
43
 
48
- if (hasSlash && handler.unpack) {
49
- // Key like `data.json/` ends in slash -- unpack immediately
50
- return handler.unpack(value, { key, parent });
51
- }
52
-
53
44
  // If the value is a primitive, box it so we can attach data to it.
54
45
  value = box(value);
55
46
 
@@ -3,14 +3,13 @@ import { isPlainObject, isUnpackable, Tree } from "@weborigami/async-tree";
3
3
  /**
4
4
  * Create a tree that's the result of merging the given trees.
5
5
  *
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
6
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
8
7
  *
9
- * @param {(Treelike|null)[]} trees
8
+ * @param {(Maplike|null)[]} trees
10
9
  */
11
10
  export default async function mergeTrees(...trees) {
12
11
  // Filter out null or undefined trees.
13
- /** @type {Treelike[]}
12
+ /** @type {Maplike[]}
14
13
  * @ts-ignore */
15
14
  const filtered = trees.filter((tree) => tree);
16
15
 
@@ -27,9 +26,7 @@ export default async function mergeTrees(...trees) {
27
26
  );
28
27
 
29
28
  // If all trees are plain objects, return a plain object.
30
- if (
31
- unpacked.every((tree) => isPlainObject(tree) && !Tree.isAsyncTree(tree))
32
- ) {
29
+ if (unpacked.every((tree) => isPlainObject(tree) && !Tree.isMap(tree))) {
33
30
  // If we do an Object.assign, we'd evaluate getters.
34
31
  // To avoid that, we'll merge property descriptors.
35
32
  const result = {};
@@ -1,17 +1,17 @@
1
1
  /**
2
2
  * @typedef {import("../../index.ts").AnnotatedCode} AnnotatedCode
3
- * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
4
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
5
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
3
  * @typedef {import("../../index.ts").RuntimeState} RuntimeState
4
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
5
+ * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
6
+ * @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
7
7
  */
8
8
 
9
- import { isUnpackable, symbols, Tree } from "@weborigami/async-tree";
9
+ import { getParent, isUnpackable, Tree } from "@weborigami/async-tree";
10
10
  import os from "node:os";
11
11
  import expressionObject from "./expressionObject.js";
12
12
  import { evaluate } from "./internal.js";
13
13
  import mergeTrees from "./mergeTrees.js";
14
- import OrigamiFiles from "./OrigamiFiles.js";
14
+ import OrigamiFileMap from "./OrigamiFileMap.js";
15
15
  import { codeSymbol } from "./symbols.js";
16
16
 
17
17
  function addOpLabel(op, label) {
@@ -176,7 +176,7 @@ addOpLabel(greaterThanOrEqual, "«ops.greaterThanOrEqual»");
176
176
  * Files tree for the user's home directory.
177
177
  */
178
178
  export async function homeDirectory(...keys) {
179
- const tree = new OrigamiFiles(os.homedir());
179
+ const tree = new OrigamiFileMap(os.homedir());
180
180
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
181
181
  }
182
182
  addOpLabel(homeDirectory, "«ops.homeDirectory»");
@@ -196,7 +196,7 @@ export async function inherited(depth, state) {
196
196
  `Origami internal error: Can't find context object`
197
197
  );
198
198
  }
199
- current = current.parent ?? current[symbols.parent];
199
+ current = getParent(current);
200
200
  }
201
201
  return current;
202
202
  }
@@ -406,11 +406,11 @@ export async function params(depth, state = {}) {
406
406
  addOpLabel(params, "«ops.params»");
407
407
  params.needsState = true;
408
408
 
409
- // export function optionalTraverse(treelike, key) {
410
- // if (!treelike) {
409
+ // export function optionalTraverse(maplike, key) {
410
+ // if (!maplike) {
411
411
  // return undefined;
412
412
  // }
413
- // return Tree.traverseOrThrow(treelike, key);
413
+ // return Tree.traverseOrThrow(maplike, key);
414
414
  // }
415
415
  // addOpLabel(optionalTraverse, "«ops.optionalTraverse");
416
416
 
@@ -436,7 +436,7 @@ export async function property(object, key) {
436
436
  if (key in object) {
437
437
  // Object defines the property, get it
438
438
  let value = object[key];
439
- // Is value an instance method? Copied from ObjectTree.
439
+ // Is value an instance method? Copied from ObjectMap.
440
440
  const isInstanceMethod =
441
441
  value instanceof Function && !Object.hasOwn(object, key);
442
442
  if (isInstanceMethod) {
@@ -460,7 +460,7 @@ addOpLabel(remainder, "«ops.remainder»");
460
460
  * Files tree for the filesystem root.
461
461
  */
462
462
  export async function rootDirectory(...keys) {
463
- const tree = new OrigamiFiles("/");
463
+ const tree = new OrigamiFileMap("/");
464
464
  return keys.length > 0 ? Tree.traverse(tree, ...keys) : tree;
465
465
  }
466
466
  addOpLabel(rootDirectory, "«ops.rootDirectory»");
@@ -468,7 +468,7 @@ addOpLabel(rootDirectory, "«ops.rootDirectory»");
468
468
  /**
469
469
  * Return the scope of the current tree
470
470
  *
471
- * @param {AsyncTree} parent
471
+ * @param {SyncOrAsyncMap} parent
472
472
  */
473
473
  export async function scope(parent) {
474
474
  if (!parent) {
@@ -108,14 +108,13 @@
108
108
  expected: false
109
109
  description: "Complex nesting with false at inner-most"
110
110
 
111
- # TODO: Uncomment when we can do math
112
- # - source: "true && (1 + 1 === 2)"
113
- # expected: true
114
- # description: "Combines logical AND with equality comparison"
115
-
116
- # - source: "false && (5 > 2)"
117
- # expected: false
118
- # description: "Logical AND with greater-than comparison"
111
+ - source: "true && (1 + 1 === 2)"
112
+ expected: true
113
+ description: "Combines logical AND with equality comparison"
114
+
115
+ - source: "false && (5 > 2)"
116
+ expected: false
117
+ description: "Logical AND with greater-than comparison"
119
118
 
120
119
  - source: "true && (3 || 0)"
121
120
  expected: 3
@@ -130,7 +130,7 @@ async function assertCompile(text, expected, options = {}) {
130
130
  const parent = options.target ?? null;
131
131
  const fn = compile.expression(text, { globals, mode, parent });
132
132
  let result = await fn();
133
- if (Tree.isTreelike(result)) {
133
+ if (Tree.isMaplike(result)) {
134
134
  result = await Tree.plain(result);
135
135
  }
136
136
  assert.deepEqual(result, expected);
@@ -1,4 +1,4 @@
1
- import { ObjectTree } from "@weborigami/async-tree";
1
+ import { SyncMap } from "@weborigami/async-tree";
2
2
  import { describe, test } from "node:test";
3
3
  import * as compile from "../../src/compiler/compile.js";
4
4
  import {
@@ -313,7 +313,7 @@ describe("optimize", () => {
313
313
  });
314
314
 
315
315
  function assertCompile(expression, expected, mode = "shell") {
316
- const parent = new ObjectTree({});
316
+ const parent = new SyncMap();
317
317
  const globals = {};
318
318
  const fn = compile.expression(expression, { globals, mode, parent });
319
319
  const actual = fn.code;
@@ -233,8 +233,8 @@ describe("Origami parser", () => {
233
233
 
234
234
  test("with paths", () => {
235
235
  assertParse("callExpression", "tree/", [
236
- ops.unpack,
237
- [markers.traverse, [markers.reference, "tree/"]],
236
+ markers.traverse,
237
+ [markers.reference, "tree/"],
238
238
  ]);
239
239
  assertParse("callExpression", "tree/foo/bar", [
240
240
  markers.traverse,
@@ -243,13 +243,10 @@ describe("Origami parser", () => {
243
243
  [ops.literal, "bar"],
244
244
  ]);
245
245
  assertParse("callExpression", "tree/foo/bar/", [
246
- ops.unpack,
247
- [
248
- markers.traverse,
249
- [markers.reference, "tree/"],
250
- [ops.literal, "foo/"],
251
- [ops.literal, "bar/"],
252
- ],
246
+ markers.traverse,
247
+ [markers.reference, "tree/"],
248
+ [ops.literal, "foo/"],
249
+ [ops.literal, "bar/"],
253
250
  ]);
254
251
  // Consecutive slahes in a path are removed
255
252
  assertParse("callExpression", "tree//key", [
@@ -1032,7 +1029,7 @@ Body`,
1032
1029
  ]);
1033
1030
  assertParse("objectEntry", "folder/", [
1034
1031
  "folder/",
1035
- [ops.unpack, [markers.traverse, [markers.reference, "folder/"]]],
1032
+ [markers.traverse, [markers.reference, "folder/"]],
1036
1033
  ]);
1037
1034
  assertParse("objectEntry", "path/to/file.txt", [
1038
1035
  "file.txt",
@@ -1202,8 +1199,8 @@ Body`,
1202
1199
  [markers.reference, "tree"],
1203
1200
  ]);
1204
1201
  assertParse("pathLiteral", "tree/", [
1205
- ops.unpack,
1206
- [markers.traverse, [markers.reference, "tree/"]],
1202
+ markers.traverse,
1203
+ [markers.reference, "tree/"],
1207
1204
  ]);
1208
1205
  assertParse("pathLiteral", "month/12", [
1209
1206
  markers.traverse,
@@ -1211,13 +1208,10 @@ Body`,
1211
1208
  [ops.literal, "12"],
1212
1209
  ]);
1213
1210
  assertParse("pathLiteral", "a/b/c/", [
1214
- ops.unpack,
1215
- [
1216
- markers.traverse,
1217
- [markers.reference, "a/"],
1218
- [ops.literal, "b/"],
1219
- [ops.literal, "c/"],
1220
- ],
1211
+ markers.traverse,
1212
+ [markers.reference, "a/"],
1213
+ [ops.literal, "b/"],
1214
+ [ops.literal, "c/"],
1221
1215
  ]);
1222
1216
  assertParse("pathLiteral", "~/.cshrc", [
1223
1217
  markers.traverse,
@@ -33,6 +33,8 @@ describe("logicalAndExpression - JavaScript", () => {
33
33
  assert.strictEqual((true && true) && true, true, "Nested logical ANDs with all true");
34
34
  assert.strictEqual(true && (true && false), false, "Nested logical ANDs with false in inner");
35
35
  assert.strictEqual((true && (false && true)), false, "Complex nesting with false at inner-most");
36
+ assert.strictEqual(true && (1 + 1 === 2), true, "Combines logical AND with equality comparison");
37
+ assert.strictEqual(false && (5 > 2), false, "Logical AND with greater-than comparison");
36
38
  assert.strictEqual(true && (3 || 0), 3, "Logical AND with logical OR");
37
39
  assert.strictEqual(true && (0 || 3), 3, "Logical AND with logical OR and falsy values");
38
40
  assert.strictEqual('' && false, "", "Falsy string and false");
@@ -70,6 +72,8 @@ describe("logicalAndExpression - Origami", async() => {
70
72
  assert.strictEqual(await oriEval("(true && true) && true"), true, "Nested logical ANDs with all true");
71
73
  assert.strictEqual(await oriEval("true && (true && false)"), false, "Nested logical ANDs with false in inner");
72
74
  assert.strictEqual(await oriEval("(true && (false && true))"), false, "Complex nesting with false at inner-most");
75
+ assert.strictEqual(await oriEval("true && (1 + 1 === 2)"), true, "Combines logical AND with equality comparison");
76
+ assert.strictEqual(await oriEval("false && (5 > 2)"), false, "Logical AND with greater-than comparison");
73
77
  assert.strictEqual(await oriEval("true && (3 || 0)"), 3, "Logical AND with logical OR");
74
78
  assert.strictEqual(await oriEval("true && (0 || 3)"), 3, "Logical AND with logical OR and falsy values");
75
79
  assert.strictEqual(await oriEval("'' && false"), "", "Falsy string and false");
@@ -10,17 +10,17 @@ Bob,25,Los Angeles
10
10
  "Carol ""CJ""",22,Chicago`;
11
11
  const result = csv_handler.unpack(csvText);
12
12
  assert.deepStrictEqual(result, [
13
- { name: "Alice", age: "30", city: "New York, NY" },
14
- { name: "Bob", age: "25", city: "Los Angeles" },
15
- { name: 'Carol "CJ"', age: "22", city: "Chicago" },
13
+ { name: "Alice", age: 30, city: "New York, NY" },
14
+ { name: "Bob", age: 25, city: "Los Angeles" },
15
+ { name: 'Carol "CJ"', age: 22, city: "Chicago" },
16
16
  ]);
17
17
  });
18
18
 
19
19
  test("handles CRLF line endings", () => {
20
20
  const textCRLF = `name,age,city\r\nAlice,30,"New York, NY"\r\nBob,25,Los Angeles\r\n`;
21
21
  const expected = [
22
- { name: "Alice", age: "30", city: "New York, NY" },
23
- { name: "Bob", age: "25", city: "Los Angeles" },
22
+ { name: "Alice", age: 30, city: "New York, NY" },
23
+ { name: "Bob", age: 25, city: "Los Angeles" },
24
24
  ];
25
25
  const result = csv_handler.unpack(textCRLF);
26
26
  assert.deepStrictEqual(result, expected);
@@ -1,11 +1,11 @@
1
- import { FileTree } from "@weborigami/async-tree";
1
+ import { FileMap } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import js_handler from "../../src/handlers/js_handler.js";
5
5
  import ImportModulesMixin from "../../src/runtime/ImportModulesMixin.js";
6
6
 
7
7
  const fixturesUrl = new URL("fixtures", import.meta.url);
8
- const fixturesTree = new (ImportModulesMixin(FileTree))(fixturesUrl);
8
+ const fixturesTree = new (ImportModulesMixin(FileMap))(fixturesUrl);
9
9
 
10
10
  describe(".js handler", () => {
11
11
  test("loads .js file that exports a string", async () => {
@@ -1,11 +1,11 @@
1
- import { ObjectTree, Tree } from "@weborigami/async-tree";
1
+ import { ObjectMap, Tree } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import ori_handler from "../../src/handlers/ori_handler.js";
5
- import OrigamiFiles from "../../src/runtime/OrigamiFiles.js";
5
+ import OrigamiFileMap from "../../src/runtime/OrigamiFileMap.js";
6
6
 
7
7
  const fixturesUrl = new URL("fixtures", import.meta.url);
8
- const fixtures = new OrigamiFiles(fixturesUrl);
8
+ const fixtures = new OrigamiFileMap(fixturesUrl);
9
9
 
10
10
  describe(".ori handler", async () => {
11
11
  test("loads a string expression", async () => {
@@ -15,7 +15,7 @@ describe(".ori handler", async () => {
15
15
  });
16
16
 
17
17
  test("loads a tree expression", async () => {
18
- const parent = new ObjectTree({
18
+ const parent = new ObjectMap({
19
19
  name: "world",
20
20
  });
21
21
  const source = `{
@@ -43,15 +43,15 @@ describe(".ori handler", async () => {
43
43
  });
44
44
 
45
45
  test("loads an object containing an object shorthand", async () => {
46
- const assets = new ObjectTree({});
47
- const parent = new ObjectTree({ assets });
46
+ const assets = new ObjectMap({});
47
+ const parent = new ObjectMap({ assets });
48
48
  const source = `{ assets }`;
49
49
  const object = await ori_handler.unpack(source, { parent });
50
50
  assert.equal(object.assets, assets);
51
51
  });
52
52
 
53
53
  test("loads a template literal", async () => {
54
- const scope = new ObjectTree({
54
+ const scope = new ObjectMap({
55
55
  name: "Alice",
56
56
  });
57
57
  const source = `\`Hello, \${name}!\``;
@@ -62,7 +62,7 @@ describe(".ori handler", async () => {
62
62
  });
63
63
 
64
64
  test("loads a template lambda that reads from parent scope", async () => {
65
- const parent = new ObjectTree({
65
+ const parent = new ObjectMap({
66
66
  name: "Alice",
67
67
  });
68
68
  const source = `() => \`Hello, \${name}!\``;
@@ -1,11 +1,11 @@
1
- import { ObjectTree } from "@weborigami/async-tree";
1
+ import { ObjectMap } from "@weborigami/async-tree";
2
2
  import assert from "node:assert";
3
3
  import { describe, test } from "node:test";
4
4
  import oridocument_handler from "../../src/handlers/oridocument_handler.js";
5
5
 
6
6
  describe("Origami document handler", () => {
7
7
  test("unpacks text with Origami expressions", async () => {
8
- const parent = new ObjectTree({
8
+ const parent = new ObjectMap({
9
9
  name: "world",
10
10
  });
11
11
  const text = "Hello, ${ name }!";
@@ -28,7 +28,7 @@ describe("Origami document handler", () => {
28
28
  });
29
29
 
30
30
  test("YAML front matter is returned with _body", async () => {
31
- const parent = new ObjectTree({
31
+ const parent = new ObjectMap({
32
32
  message: "Hello",
33
33
  });
34
34
  const text = `---
@@ -10,17 +10,17 @@ Bob\t25\tLos Angeles
10
10
  Carol\t22\tChicago`;
11
11
  const result = tsv_handler.unpack(TSVText);
12
12
  assert.deepStrictEqual(result, [
13
- { name: "Alice", age: "30", city: "New York, NY" },
14
- { name: "Bob", age: "25", city: "Los Angeles" },
15
- { name: "Carol", age: "22", city: "Chicago" },
13
+ { name: "Alice", age: 30, city: "New York, NY" },
14
+ { name: "Bob", age: 25, city: "Los Angeles" },
15
+ { name: "Carol", age: 22, city: "Chicago" },
16
16
  ]);
17
17
  });
18
18
 
19
19
  test("handles CRLF line endings", () => {
20
20
  const textCRLF = `name\tage\tcity\r\nAlice\t30\tNew York, NY\r\nBob\t25\tLos Angeles\r\n`;
21
21
  const expected = [
22
- { name: "Alice", age: "30", city: "New York, NY" },
23
- { name: "Bob", age: "25", city: "Los Angeles" },
22
+ { name: "Alice", age: 30, city: "New York, NY" },
23
+ { name: "Bob", age: 25, city: "Los Angeles" },
24
24
  ];
25
25
  const result = tsv_handler.unpack(textCRLF);
26
26
  assert.deepStrictEqual(result, expected);