@weborigami/language 0.5.7 → 0.6.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.
- package/index.ts +3 -4
- package/main.js +1 -3
- package/package.json +4 -5
- package/src/handlers/csv_handler.js +13 -8
- package/src/handlers/handlers.js +2 -0
- package/src/handlers/sh_handler.js +65 -0
- package/src/handlers/tsv_handler.js +63 -0
- 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/expressionFunction.js +3 -3
- package/src/runtime/expressionObject.js +19 -8
- package/src/runtime/handleExtension.js +2 -6
- 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/generated/logicalAndExpression.test.js +4 -0
- package/test/handlers/{csv.handler.test.js → csv_handler.test.js} +5 -5
- package/test/handlers/{js.handler.test.js → js_handler.test.js} +2 -2
- package/test/handlers/{ori.handler.test.js → ori_handler.test.js} +8 -8
- package/test/handlers/{oridocument.handler.test.js → oridocument_handler.test.js} +3 -3
- package/test/handlers/sh_handler.test.js +14 -0
- package/test/handlers/tsv_handler.test.js +28 -0
- package/test/handlers/{wasm.handler.test.js → wasm_handler.test.js} +2 -2
- 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 -2
- package/test/runtime/mergeTrees.test.js +2 -2
- package/test/runtime/ops.test.js +5 -5
- 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
- /package/test/handlers/{jpeg.handler.test.js → jpeg_handler.test.js} +0 -0
- /package/test/handlers/{json.handler.test.js → json_handler.test.js} +0 -0
- /package/test/handlers/{txt.handler.test.js → txt_handler.test.js} +0 -0
- /package/test/handlers/{yaml.handler.test.js → yaml_handler.test.js} +0 -0
package/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { UnpackFunction } from "@weborigami/async-tree";
|
|
2
|
-
import { AsyncTree } from "@weborigami/types";
|
|
1
|
+
import { SyncOrAsyncMap, UnpackFunction } from "@weborigami/async-tree";
|
|
3
2
|
|
|
4
3
|
export * from "./main.js";
|
|
5
4
|
|
|
@@ -59,9 +58,9 @@ export type Position = {
|
|
|
59
58
|
|
|
60
59
|
export type RuntimeState = {
|
|
61
60
|
/** The container (e.g., file system) that holds the code */
|
|
62
|
-
container?:
|
|
61
|
+
container?: SyncOrAsyncMap | null;
|
|
63
62
|
/** The object to which this code is attached */
|
|
64
|
-
object?:
|
|
63
|
+
object?: SyncOrAsyncMap | null;
|
|
65
64
|
/** The current stack of function parameter assignments */
|
|
66
65
|
stack?: Array<Record<string, any>>;
|
|
67
66
|
}
|
package/main.js
CHANGED
|
@@ -12,14 +12,12 @@ export { formatError } from "./src/runtime/errors.js";
|
|
|
12
12
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
13
13
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
|
14
14
|
export * as expressionFunction from "./src/runtime/expressionFunction.js";
|
|
15
|
-
export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
|
|
16
15
|
export * from "./src/runtime/handleExtension.js";
|
|
17
16
|
export { default as handleExtension } from "./src/runtime/handleExtension.js";
|
|
18
17
|
export { default as HandleExtensionsTransform } from "./src/runtime/HandleExtensionsTransform.js";
|
|
19
18
|
export { default as ImportModulesMixin } from "./src/runtime/ImportModulesMixin.js";
|
|
20
|
-
export { default as InvokeFunctionsTransform } from "./src/runtime/InvokeFunctionsTransform.js";
|
|
21
19
|
export * as moduleCache from "./src/runtime/moduleCache.js";
|
|
22
|
-
export { default as
|
|
20
|
+
export { default as OrigamiFileMap } from "./src/runtime/OrigamiFileMap.js";
|
|
23
21
|
export * as symbols from "./src/runtime/symbols.js";
|
|
24
22
|
export { default as TreeEvent } from "./src/runtime/TreeEvent.js";
|
|
25
23
|
export { default as WatchFilesMixin } from "./src/runtime/WatchFilesMixin.js";
|
package/package.json
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
7
7
|
"types": "./index.ts",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@types/node": "24.
|
|
9
|
+
"@types/node": "24.10.1",
|
|
10
10
|
"peggy": "5.0.6",
|
|
11
|
-
"typescript": "5.9.
|
|
11
|
+
"typescript": "5.9.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.
|
|
15
|
-
"@weborigami/types": "0.5.7",
|
|
14
|
+
"@weborigami/async-tree": "0.6.0",
|
|
16
15
|
"exif-parser": "0.1.12",
|
|
17
16
|
"watcher": "2.3.1",
|
|
18
17
|
"yaml": "2.8.1"
|
|
@@ -34,11 +34,21 @@ function csvParse(text) {
|
|
|
34
34
|
const rows = [];
|
|
35
35
|
let currentRow = [];
|
|
36
36
|
let currentField = "";
|
|
37
|
+
let wasQuoted = false; // True if the current field was quoted
|
|
37
38
|
|
|
38
39
|
const pushField = () => {
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
/** @type {string|number} */
|
|
41
|
+
let parsedField = currentField;
|
|
42
|
+
if (!wasQuoted) {
|
|
43
|
+
// Field wasn't quoted: if it's a valid number, convert it
|
|
44
|
+
const n = Number(currentField);
|
|
45
|
+
if (!isNaN(n)) {
|
|
46
|
+
parsedField = n;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
currentRow.push(parsedField);
|
|
41
50
|
currentField = "";
|
|
51
|
+
wasQuoted = false; // Reset the flag for the next field
|
|
42
52
|
};
|
|
43
53
|
|
|
44
54
|
const pushRow = () => {
|
|
@@ -54,29 +64,24 @@ function csvParse(text) {
|
|
|
54
64
|
const char = text[i];
|
|
55
65
|
|
|
56
66
|
if (inQuotes) {
|
|
57
|
-
// In a quoted field
|
|
58
67
|
if (char === '"') {
|
|
59
|
-
// Check if next character is also a quote
|
|
60
68
|
if (i + 1 < text.length && text[i + 1] === '"') {
|
|
61
|
-
// Append a literal double quote and skip the next character
|
|
62
69
|
currentField += '"';
|
|
63
70
|
i += 2;
|
|
64
71
|
continue;
|
|
65
72
|
} else {
|
|
66
|
-
// End of the quoted field
|
|
67
73
|
inQuotes = false;
|
|
68
74
|
i++;
|
|
69
75
|
continue;
|
|
70
76
|
}
|
|
71
77
|
} else {
|
|
72
|
-
// All other characters within quotes are taken literally.
|
|
73
78
|
currentField += char;
|
|
74
79
|
i++;
|
|
75
80
|
continue;
|
|
76
81
|
}
|
|
77
82
|
} else if (char === '"') {
|
|
78
|
-
// Start of a quoted field
|
|
79
83
|
inQuotes = true;
|
|
84
|
+
wasQuoted = true; // Mark the field as quoted
|
|
80
85
|
i++;
|
|
81
86
|
continue;
|
|
82
87
|
} else if (char === ",") {
|
package/src/handlers/handlers.js
CHANGED
|
@@ -27,6 +27,8 @@ export { default as jpg_handler } from "./jpg_handler.js";
|
|
|
27
27
|
export { default as json_handler } from "./json_handler.js";
|
|
28
28
|
export { default as md_handler } from "./md_handler.js";
|
|
29
29
|
export { default as mjs_handler } from "./mjs_handler.js";
|
|
30
|
+
export { default as sh_handler } from "./sh_handler.js";
|
|
31
|
+
export { default as tsv_handler } from "./tsv_handler.js";
|
|
30
32
|
export { default as wasm_handler } from "./wasm_handler.js";
|
|
31
33
|
export { default as xhtml_handler } from "./xhtml_handler.js";
|
|
32
34
|
export { default as yaml_handler } from "./yaml_handler.js";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { toString } from "@weborigami/async-tree";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shell script file extension handler
|
|
6
|
+
*/
|
|
7
|
+
export default {
|
|
8
|
+
mediaType: "text/plain",
|
|
9
|
+
|
|
10
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
11
|
+
async unpack(packed) {
|
|
12
|
+
const scriptText = toString(packed);
|
|
13
|
+
|
|
14
|
+
if (scriptText === null) {
|
|
15
|
+
throw new Error(".sh handler: input isn't text");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return async (input) => {
|
|
19
|
+
return runShellScript(scriptText, input);
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Run arbitrary shell script text in /bin/sh and feed it stdin.
|
|
26
|
+
* Supports multiple commands, pipelines, redirects, etc.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} scriptText - Shell code (may contain newlines/side effects)
|
|
29
|
+
* @param {string} inputText - Text to pipe to the script's stdin
|
|
30
|
+
* @returns {Promise<string>}
|
|
31
|
+
*/
|
|
32
|
+
function runShellScript(scriptText, inputText) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Use sh -c "<scriptText>" so stdin is free for inputText
|
|
35
|
+
const child = spawn("sh", ["-c", scriptText], {
|
|
36
|
+
env: { ...process.env },
|
|
37
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let stdout = "";
|
|
41
|
+
let stderr = "";
|
|
42
|
+
|
|
43
|
+
child.stdout.on("data", (c) => (stdout += c));
|
|
44
|
+
child.stderr.on("data", (c) => (stderr += c));
|
|
45
|
+
|
|
46
|
+
child.on("error", reject);
|
|
47
|
+
|
|
48
|
+
child.on("close", (code) => {
|
|
49
|
+
if (code !== 0) {
|
|
50
|
+
/** @type {any} */
|
|
51
|
+
const err = new Error(
|
|
52
|
+
`Shell exited with code ${code}${stderr ? `: ${stderr}` : ""}`
|
|
53
|
+
);
|
|
54
|
+
err.code = code;
|
|
55
|
+
err.stdout = stdout;
|
|
56
|
+
err.stderr = stderr;
|
|
57
|
+
return reject(err);
|
|
58
|
+
}
|
|
59
|
+
resolve(stdout);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Feed the input to the script's stdin and close it
|
|
63
|
+
child.stdin.end(inputText);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { symbols, toString } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
mediaType: "text/csv",
|
|
5
|
+
|
|
6
|
+
unpack(packed, options = {}) {
|
|
7
|
+
const parent = options.parent ?? null;
|
|
8
|
+
const text = toString(packed);
|
|
9
|
+
if (text === null) {
|
|
10
|
+
throw new TypeError(".tsv handler can only unpack text");
|
|
11
|
+
}
|
|
12
|
+
const data = tsvParse(text);
|
|
13
|
+
// Define `parent` as non-enumerable property
|
|
14
|
+
Object.defineProperty(data, symbols.parent, {
|
|
15
|
+
configurable: true,
|
|
16
|
+
enumerable: false,
|
|
17
|
+
value: parent,
|
|
18
|
+
writable: true,
|
|
19
|
+
});
|
|
20
|
+
return data;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse text as tab-separated values (TSV) format into an array of objects.
|
|
26
|
+
*
|
|
27
|
+
* This assumes the presence of a header row, and accepts both CRLF and LF line
|
|
28
|
+
* endings.
|
|
29
|
+
*
|
|
30
|
+
* Blank lines are ignored.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} text
|
|
33
|
+
* @returns {any[]}
|
|
34
|
+
*/
|
|
35
|
+
function tsvParse(text) {
|
|
36
|
+
const lines = text.split(/\r?\n/).filter((line) => line.trim() !== "");
|
|
37
|
+
if (lines.length === 0) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const headers = lines[0].split("\t");
|
|
42
|
+
const data = [];
|
|
43
|
+
|
|
44
|
+
for (let i = 1; i < lines.length; i++) {
|
|
45
|
+
const values = lines[i].split("\t");
|
|
46
|
+
const entry = {};
|
|
47
|
+
for (let j = 0; j < headers.length; j++) {
|
|
48
|
+
/** @type {string|number} */
|
|
49
|
+
let value = values[j];
|
|
50
|
+
if (value !== undefined) {
|
|
51
|
+
// Attempt to convert to number if possible
|
|
52
|
+
const n = Number(value);
|
|
53
|
+
if (!isNaN(n)) {
|
|
54
|
+
value = n;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
entry[headers[j]] = value ?? "";
|
|
58
|
+
}
|
|
59
|
+
data.push(entry);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return data;
|
|
63
|
+
}
|
package/src/project/jsGlobals.js
CHANGED
|
@@ -168,7 +168,11 @@ async function fetchWrapper(resource, options) {
|
|
|
168
168
|
return response.ok ? await response.arrayBuffer() : undefined;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* @typedef {import("@weborigami/async-tree").AsyncMap} AsyncMap
|
|
173
|
+
*
|
|
174
|
+
* @this {AsyncMap|null|undefined}
|
|
175
|
+
*/
|
|
172
176
|
async function importWrapper(modulePath) {
|
|
173
177
|
// Walk up parent tree looking for a FileTree or other object with a `path`
|
|
174
178
|
/** @type {any} */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FileMap, toString } from "@weborigami/async-tree";
|
|
2
2
|
import ori_handler from "../handlers/ori_handler.js";
|
|
3
3
|
import coreGlobals from "./coreGlobals.js";
|
|
4
4
|
import projectRoot from "./projectRoot.js";
|
|
@@ -14,9 +14,9 @@ export default async function config(dir = process.cwd()) {
|
|
|
14
14
|
return cached;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
// Use a plain
|
|
18
|
-
const
|
|
19
|
-
const configBuffer = await
|
|
17
|
+
// Use a plain FileMap to avoid loading extension handlers
|
|
18
|
+
const rootFileMap = new FileMap(rootPath);
|
|
19
|
+
const configBuffer = await rootFileMap.get("config.ori");
|
|
20
20
|
let configObject = {};
|
|
21
21
|
if (configBuffer) {
|
|
22
22
|
const configText = toString(configBuffer);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FileMap } from "@weborigami/async-tree";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import
|
|
3
|
+
import OrigamiFileMap from "../runtime/OrigamiFileMap.js";
|
|
4
4
|
|
|
5
5
|
const configFileName = "config.ori";
|
|
6
6
|
const packageFileName = "package.json";
|
|
@@ -8,7 +8,7 @@ const packageFileName = "package.json";
|
|
|
8
8
|
const mapPathToRoot = new Map();
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Return an
|
|
11
|
+
* Return an OrigamiFileMap object for the current project.
|
|
12
12
|
*
|
|
13
13
|
* This searches the current directory and its ancestors for an Origami file
|
|
14
14
|
* called `config.ori`. If an Origami configuration file is found, the
|
|
@@ -17,7 +17,6 @@ const mapPathToRoot = new Map();
|
|
|
17
17
|
* Otherwise, this looks for a package.json file to determine the project root.
|
|
18
18
|
* If no package.json is found, the current folder is used as the project root.
|
|
19
19
|
*
|
|
20
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
21
20
|
*
|
|
22
21
|
* @param {string} [dirname]
|
|
23
22
|
*/
|
|
@@ -29,19 +28,19 @@ export default async function projectRoot(dirname = process.cwd()) {
|
|
|
29
28
|
|
|
30
29
|
let root;
|
|
31
30
|
let value;
|
|
32
|
-
// Use a plain
|
|
33
|
-
const currentTree = new
|
|
31
|
+
// Use a plain FileMap to avoid loading extension handlers
|
|
32
|
+
const currentTree = new FileMap(dirname);
|
|
34
33
|
// Try looking for config file
|
|
35
34
|
value = await currentTree.get(configFileName);
|
|
36
35
|
if (value) {
|
|
37
36
|
// Found config file
|
|
38
|
-
root = new
|
|
37
|
+
root = new OrigamiFileMap(currentTree.path);
|
|
39
38
|
} else {
|
|
40
39
|
// Try looking for package.json
|
|
41
40
|
value = await currentTree.get(packageFileName);
|
|
42
41
|
if (value) {
|
|
43
42
|
// Found package.json
|
|
44
|
-
root = new
|
|
43
|
+
root = new OrigamiFileMap(currentTree.path);
|
|
45
44
|
} else {
|
|
46
45
|
// Move up a folder and try again
|
|
47
46
|
const parentPath = path.dirname(dirname);
|
|
@@ -49,7 +48,7 @@ export default async function projectRoot(dirname = process.cwd()) {
|
|
|
49
48
|
root = await projectRoot(parentPath);
|
|
50
49
|
} else {
|
|
51
50
|
// At filesystem root, use current working directory
|
|
52
|
-
root = new
|
|
51
|
+
root = new OrigamiFileMap(process.cwd());
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -5,14 +5,14 @@ import constructHref from "./constructHref.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* Given a protocol, a host, and a list of keys, construct an href.
|
|
7
7
|
*
|
|
8
|
-
* @typedef {import("@weborigami/
|
|
8
|
+
* @typedef {import("@weborigami/async-tree").SyncOrAsyncMap} SyncOrAsyncMap
|
|
9
9
|
*
|
|
10
10
|
* @param {string} protocol
|
|
11
|
-
* @param {import("../../index.ts").Constructor<
|
|
11
|
+
* @param {import("../../index.ts").Constructor<SyncOrAsyncMap>} mapClass
|
|
12
12
|
* @param {string} host
|
|
13
13
|
* @param {string[]} keys
|
|
14
14
|
*/
|
|
15
|
-
export default function constructSiteTree(protocol,
|
|
15
|
+
export default function constructSiteTree(protocol, mapClass, host, ...keys) {
|
|
16
16
|
// If the last key doesn't end in a slash, remove it for now.
|
|
17
17
|
let lastKey;
|
|
18
18
|
if (keys.length > 0 && keys.at(-1) && !trailingSlash.has(keys.at(-1))) {
|
|
@@ -20,7 +20,7 @@ export default function constructSiteTree(protocol, treeClass, host, ...keys) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const href = constructHref(protocol, host, ...keys);
|
|
23
|
-
let result = new (HandleExtensionsTransform(
|
|
23
|
+
let result = new (HandleExtensionsTransform(mapClass))(href);
|
|
24
24
|
|
|
25
25
|
return lastKey ? result.get(lastKey) : result;
|
|
26
26
|
}
|
package/src/protocols/explore.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExplorableSiteMap } from "@weborigami/async-tree";
|
|
2
2
|
import constructSiteTree from "./constructSiteTree.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A site tree with JSON Keys via HTTPS.
|
|
6
6
|
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
7
|
*
|
|
9
8
|
* @param {string} host
|
|
10
9
|
* @param {...string} keys
|
|
11
10
|
*/
|
|
12
11
|
export default function explore(host, ...keys) {
|
|
13
|
-
return constructSiteTree("https:",
|
|
12
|
+
return constructSiteTree("https:", ExplorableSiteMap, host, ...keys);
|
|
14
13
|
}
|
package/src/protocols/files.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import
|
|
4
|
+
import OrigamiFileMap from "../runtime/OrigamiFileMap.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
7
|
*
|
|
9
8
|
* @param {string[]} keys
|
|
10
9
|
*/
|
|
@@ -21,6 +20,6 @@ export default async function files(...keys) {
|
|
|
21
20
|
}
|
|
22
21
|
const resolved = path.resolve(basePath, relativePath);
|
|
23
22
|
|
|
24
|
-
const result = new
|
|
23
|
+
const result = new OrigamiFileMap(resolved);
|
|
25
24
|
return result;
|
|
26
25
|
}
|
package/src/protocols/http.js
CHANGED
package/src/protocols/https.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SiteMap } from "@weborigami/async-tree";
|
|
2
2
|
import constructSiteTree from "./constructSiteTree.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Return a website tree via HTTPS.
|
|
6
6
|
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
7
|
*
|
|
9
8
|
* @param {string} host
|
|
10
9
|
* @param {...string} keys
|
|
11
10
|
*/
|
|
12
11
|
export default function httpstree(host, ...keys) {
|
|
13
|
-
return constructSiteTree("https:",
|
|
12
|
+
return constructSiteTree("https:", SiteMap, host, ...keys);
|
|
14
13
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SiteMap } from "@weborigami/async-tree";
|
|
2
2
|
import constructSiteTree from "./constructSiteTree.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Return a website tree via HTTP.
|
|
6
6
|
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
7
|
*
|
|
9
8
|
* @param {string} host
|
|
10
9
|
* @param {...string} keys
|
|
11
10
|
*/
|
|
12
11
|
export default function httptree(host, ...keys) {
|
|
13
|
-
return constructSiteTree("http:",
|
|
12
|
+
return constructSiteTree("http:", SiteMap, host, ...keys);
|
|
14
13
|
}
|
|
@@ -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
|
) {}
|
|
@@ -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
|
|