@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.
- package/index.ts +3 -4
- package/main.js +2 -4
- package/package.json +4 -5
- package/src/compiler/optimize.js +3 -1
- package/src/compiler/parserHelpers.js +1 -7
- package/src/handlers/csv_handler.js +13 -8
- package/src/handlers/getSource.js +36 -0
- package/src/handlers/ori_handler.js +5 -20
- package/src/handlers/oridocument_handler.js +5 -28
- package/src/handlers/tsv_handler.js +10 -1
- package/src/handlers/yaml_handler.js +75 -3
- package/src/project/jsGlobals.js +5 -1
- package/src/project/projectConfig.js +4 -4
- package/src/project/projectRoot.js +8 -9
- package/src/protocols/constructSiteTree.js +4 -4
- package/src/protocols/explore.js +2 -3
- package/src/protocols/fetchAndHandleExtension.js +0 -1
- package/src/protocols/files.js +2 -3
- package/src/protocols/http.js +0 -1
- package/src/protocols/https.js +0 -1
- package/src/protocols/httpstree.js +2 -3
- package/src/protocols/httptree.js +2 -3
- package/src/runtime/HandleExtensionsTransform.js +32 -8
- package/src/runtime/ImportModulesMixin.js +2 -2
- package/src/runtime/{OrigamiFiles.js → OrigamiFileMap.d.ts} +3 -3
- package/src/runtime/{OrigamiFiles.d.ts → OrigamiFileMap.js} +3 -5
- package/src/runtime/errors.js +9 -2
- package/src/runtime/evaluate.js +1 -1
- package/src/runtime/expressionFunction.js +3 -3
- package/src/runtime/expressionObject.js +19 -8
- package/src/runtime/handleExtension.js +2 -11
- package/src/runtime/mergeTrees.js +4 -7
- package/src/runtime/ops.js +13 -13
- package/test/cases/logicalAndExpression.yaml +7 -8
- package/test/compiler/compile.test.js +1 -1
- package/test/compiler/optimize.test.js +2 -2
- package/test/compiler/parse.test.js +13 -19
- package/test/generated/logicalAndExpression.test.js +4 -0
- package/test/handlers/csv_handler.test.js +5 -5
- package/test/handlers/js_handler.test.js +2 -2
- package/test/handlers/ori_handler.test.js +8 -8
- package/test/handlers/oridocument_handler.test.js +3 -3
- package/test/handlers/tsv_handler.test.js +5 -5
- package/test/handlers/wasm_handler.test.js +2 -2
- package/test/handlers/yaml_handler.test.js +31 -1
- package/test/runtime/OrigamiFileMap.test.js +40 -0
- package/test/runtime/evaluate.test.js +3 -3
- package/test/runtime/expressionObject.test.js +14 -6
- package/test/runtime/handleExtension.test.js +2 -9
- package/test/runtime/mergeTrees.test.js +2 -2
- package/test/runtime/ops.test.js +18 -12
- package/src/runtime/InvokeFunctionsTransform.d.ts +0 -5
- package/src/runtime/InvokeFunctionsTransform.js +0 -25
- package/src/runtime/functionResultsMap.js +0 -17
- package/test/runtime/OrigamiFiles.test.js +0 -35
- package/test/runtime/fixtures/subgraph = this.js +0 -5
- 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("
|
|
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 {
|
|
7
|
+
* @param {MapConstructor} Base
|
|
9
8
|
*/
|
|
10
9
|
export default function HandleExtensionsTransform(Base) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return
|
|
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/
|
|
9
|
-
* @typedef {import("../../index.ts").Constructor<
|
|
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 {
|
|
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
|
|
8
|
-
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(
|
|
7
|
+
export default class OrigamiFileMap extends HandleExtensionsTransform(
|
|
8
|
+
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap)))
|
|
9
9
|
) {}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
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
|
|
8
|
-
(
|
|
9
|
-
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileTree)))
|
|
10
|
-
)
|
|
7
|
+
export default class OrigamiFileMap extends HandleExtensionsTransform(
|
|
8
|
+
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap)))
|
|
11
9
|
) {}
|
package/src/runtime/errors.js
CHANGED
|
@@ -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: ${
|
|
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
|
}
|
package/src/runtime/evaluate.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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.
|
|
34
|
-
throw new TypeError(`Parent must be
|
|
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
|
|
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
|
-
|
|
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
|
|
145
|
-
if (eagerProperties.includes(key) && Tree.
|
|
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/
|
|
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/
|
|
7
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
6
|
+
* @typedef {import("@weborigami/async-tree").Maplike} Maplike
|
|
8
7
|
*
|
|
9
|
-
* @param {(
|
|
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 {
|
|
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 = {};
|
package/src/runtime/ops.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
410
|
-
// if (!
|
|
409
|
+
// export function optionalTraverse(maplike, key) {
|
|
410
|
+
// if (!maplike) {
|
|
411
411
|
// return undefined;
|
|
412
412
|
// }
|
|
413
|
-
// return Tree.traverseOrThrow(
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
237
|
-
[markers.
|
|
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
|
-
|
|
247
|
-
[
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
1206
|
-
[markers.
|
|
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
|
-
|
|
1215
|
-
[
|
|
1216
|
-
|
|
1217
|
-
|
|
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:
|
|
14
|
-
{ name: "Bob", age:
|
|
15
|
-
{ name: 'Carol "CJ"', age:
|
|
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:
|
|
23
|
-
{ name: "Bob", age:
|
|
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 {
|
|
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(
|
|
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 {
|
|
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
|
|
5
|
+
import OrigamiFileMap from "../../src/runtime/OrigamiFileMap.js";
|
|
6
6
|
|
|
7
7
|
const fixturesUrl = new URL("fixtures", import.meta.url);
|
|
8
|
-
const fixtures = new
|
|
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
|
|
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
|
|
47
|
-
const parent = new
|
|
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
|
|
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
|
|
65
|
+
const parent = new ObjectMap({
|
|
66
66
|
name: "Alice",
|
|
67
67
|
});
|
|
68
68
|
const source = `() => \`Hello, \${name}!\``;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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:
|
|
14
|
-
{ name: "Bob", age:
|
|
15
|
-
{ name: "Carol", age:
|
|
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:
|
|
23
|
-
{ name: "Bob", age:
|
|
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);
|