@weborigami/origami 0.0.39 → 0.0.40
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 +6 -6
- package/src/builtins/@explore.js +30 -10
- package/src/builtins/@inline.js +3 -7
- package/src/builtins/@loaders/ori.js +18 -1
- package/src/builtins/@ori.js +5 -3
- package/src/builtins/@package.js +27 -3
- package/src/common/FilterTree.js +4 -2
- package/src/common/GlobTree.js +10 -2
- package/src/common/processUnpackedContent.js +4 -1
- package/src/misc/OriCommandTransform.js +1 -1
- package/src/server/server.js +23 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/origami",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.40",
|
|
4
4
|
"description": "Web Origami language, CLI, framework, and server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/chai": "4.3.11",
|
|
17
17
|
"@types/mocha": "10.0.6",
|
|
18
|
-
"@types/node": "20.11.
|
|
18
|
+
"@types/node": "20.11.7",
|
|
19
19
|
"typescript": "5.3.3"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@weborigami/async-tree": "0.0.
|
|
23
|
-
"@weborigami/language": "0.0.
|
|
24
|
-
"@weborigami/types": "0.0.
|
|
22
|
+
"@weborigami/async-tree": "0.0.40",
|
|
23
|
+
"@weborigami/language": "0.0.40",
|
|
24
|
+
"@weborigami/types": "0.0.40",
|
|
25
25
|
"graphviz-wasm": "3.0.1",
|
|
26
26
|
"highlight.js": "11.9.0",
|
|
27
27
|
"marked": "11.1.1",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "ori exports/buildExports.js src > exports/exports.js",
|
|
36
36
|
"prepublishOnly": "npm run build",
|
|
37
|
-
"test": "node --test --test-reporter=spec
|
|
37
|
+
"test": "node --test --test-reporter=spec",
|
|
38
38
|
"typecheck": "node node_modules/typescript/bin/tsc"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/builtins/@explore.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
|
2
|
-
import { ObjectTree } from "@weborigami/async-tree";
|
|
2
|
+
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
3
3
|
import { OrigamiFiles, Scope } from "@weborigami/language";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -14,14 +14,8 @@ const miscFiles = Scope.treeWithScope(new OrigamiFiles(miscDir), builtins);
|
|
|
14
14
|
/**
|
|
15
15
|
* @this {AsyncTree|null}
|
|
16
16
|
*/
|
|
17
|
-
export default async function explore() {
|
|
17
|
+
export default async function explore(...keys) {
|
|
18
18
|
const scope = Scope.getScope(this);
|
|
19
|
-
const templateFile = await miscFiles.get("explore.ori");
|
|
20
|
-
const template = await templateFile.unpack();
|
|
21
|
-
|
|
22
|
-
const data = await getScopeData(scope);
|
|
23
|
-
const text = await template(data);
|
|
24
|
-
|
|
25
19
|
const ambientsTree = new ObjectTree({
|
|
26
20
|
"@current": this,
|
|
27
21
|
});
|
|
@@ -29,8 +23,34 @@ export default async function explore() {
|
|
|
29
23
|
const extendedScope = new Scope(ambientsTree, scope);
|
|
30
24
|
|
|
31
25
|
/** @type {any} */
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
let result;
|
|
27
|
+
if (keys.length > 0) {
|
|
28
|
+
// Traverse the scope using the given keys.
|
|
29
|
+
const debugScope = await debug.call(scope, extendedScope);
|
|
30
|
+
if (!debugScope) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// HACK: reproduce logic of ExplorableSiteTransform that turns a trailing
|
|
35
|
+
// slash into index.html. Calling `debug` applies that transform and the
|
|
36
|
+
// transform should handle that logic, but unfortunately the `traverse`
|
|
37
|
+
// operation has special casing to treat a trailing slash, and never gives
|
|
38
|
+
// ExplorableSiteTransform a chance.
|
|
39
|
+
if (keys.at(-1) === "") {
|
|
40
|
+
keys[keys.length - 1] = "index.html";
|
|
41
|
+
}
|
|
42
|
+
result = await Tree.traverse(debugScope, ...keys);
|
|
43
|
+
} else {
|
|
44
|
+
// Return the Explore page for the current scope.
|
|
45
|
+
const templateFile = await miscFiles.get("explore.ori");
|
|
46
|
+
const template = await templateFile.unpack();
|
|
47
|
+
|
|
48
|
+
const data = await getScopeData(scope);
|
|
49
|
+
const text = await template(data);
|
|
50
|
+
|
|
51
|
+
result = new String(text);
|
|
52
|
+
result.unpack = () => debug.call(scope, extendedScope);
|
|
53
|
+
}
|
|
34
54
|
|
|
35
55
|
return result;
|
|
36
56
|
}
|
package/src/builtins/@inline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { compile } from "@weborigami/language";
|
|
1
2
|
import unpackText from "../builtins/@loaders/txt.js";
|
|
2
|
-
import * as utilities from "../common/utilities.js";
|
|
3
3
|
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
4
4
|
import unpackOrigamiExpression from "./@loaders/ori.js";
|
|
5
5
|
|
|
@@ -28,13 +28,9 @@ export default async function inline(input) {
|
|
|
28
28
|
inputDocument = await unpackText(input);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const templateDocument = Object.assign({}, inputDocument, {
|
|
34
|
-
"@text": `=\`${inputText}\``,
|
|
31
|
+
const templateFn = await unpackOrigamiExpression(inputDocument, {
|
|
32
|
+
compiler: compile.templateDocument,
|
|
35
33
|
});
|
|
36
|
-
|
|
37
|
-
const templateFn = await unpackOrigamiExpression(templateDocument);
|
|
38
34
|
const templateResult = await templateFn(inputDocument);
|
|
39
35
|
return inputDocument
|
|
40
36
|
? Object.assign({}, inputDocument, { "@text": String(templateResult) })
|
|
@@ -17,10 +17,27 @@ export default async function unpackOrigamiExpression(
|
|
|
17
17
|
options.parent ??
|
|
18
18
|
/** @type {any} */ (inputDocument).parent ??
|
|
19
19
|
/** @type {any} */ (inputDocument)[utilities.parentSymbol];
|
|
20
|
+
const compiler = options.compiler ?? compile.expression;
|
|
20
21
|
|
|
21
22
|
// Compile the body text as an Origami expression and evaluate it.
|
|
22
23
|
const inputText = utilities.toString(inputDocument);
|
|
23
|
-
|
|
24
|
+
let fn;
|
|
25
|
+
try {
|
|
26
|
+
fn = compiler(inputText);
|
|
27
|
+
} catch (/** @type {any} */ error) {
|
|
28
|
+
let location = "";
|
|
29
|
+
if (options.key) {
|
|
30
|
+
location += `${options.key}`;
|
|
31
|
+
}
|
|
32
|
+
if (error.location) {
|
|
33
|
+
const { start } = error.location;
|
|
34
|
+
location += `, line ${start.line}, column ${start.column}`;
|
|
35
|
+
}
|
|
36
|
+
if (location) {
|
|
37
|
+
error.message += ` (${location})`;
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
24
41
|
const parentScope = parent ? Scope.getScope(parent) : builtins;
|
|
25
42
|
let content = await fn.call(parentScope);
|
|
26
43
|
|
package/src/builtins/@ori.js
CHANGED
|
@@ -15,7 +15,10 @@ const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
|
15
15
|
* @this {AsyncTree|null}
|
|
16
16
|
* @param {string} expression
|
|
17
17
|
*/
|
|
18
|
-
export default async function ori(
|
|
18
|
+
export default async function ori(
|
|
19
|
+
expression,
|
|
20
|
+
options = { formatResult: true }
|
|
21
|
+
) {
|
|
19
22
|
assertScopeIsDefined(this);
|
|
20
23
|
// In case expression is a Buffer, cast it to a string.
|
|
21
24
|
expression = String(expression);
|
|
@@ -34,8 +37,7 @@ export default async function ori(expression) {
|
|
|
34
37
|
result = await result.call(scope);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
return formatted;
|
|
40
|
+
return options.formatResult ? await formatResult(result) : result;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
async function formatResult(result) {
|
package/src/builtins/@package.js
CHANGED
|
@@ -4,25 +4,49 @@ import project from "./@project.js";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @this {import("@weborigami/types").AsyncTree|null}
|
|
7
|
-
* @param {string[]}
|
|
7
|
+
* @param {string[]} keys
|
|
8
8
|
*/
|
|
9
|
-
export default async function packageBuiltin(...
|
|
9
|
+
export default async function packageBuiltin(...keys) {
|
|
10
10
|
let scope = this;
|
|
11
11
|
if (!scope) {
|
|
12
12
|
const projectRoot = await project.call(null);
|
|
13
13
|
scope = Scope.getScope(projectRoot);
|
|
14
14
|
}
|
|
15
|
+
|
|
16
|
+
const packageKeys = [keys.shift()];
|
|
17
|
+
if (packageKeys[0]?.startsWith("@")) {
|
|
18
|
+
// First key is an npm organization, get the next key too.
|
|
19
|
+
packageKeys.push(keys.shift());
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
const packageRoot = await Tree.traverse(
|
|
16
23
|
// @ts-ignore
|
|
17
24
|
scope,
|
|
18
25
|
"node_modules",
|
|
19
26
|
...packageKeys
|
|
20
27
|
);
|
|
28
|
+
if (!packageRoot) {
|
|
29
|
+
throw new Error(`Can't find node_modules/${packageKeys.join("/")}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
const mainPath = await Tree.traverse(packageRoot, "package.json", "main");
|
|
33
|
+
if (!mainPath) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`node_modules/${keys.join(
|
|
36
|
+
"/"
|
|
37
|
+
)} doesn't contain a package.json with a "main" entry.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
22
41
|
const mainKeys = keysFromPath(mainPath);
|
|
23
42
|
const mainContainerKeys = mainKeys.slice(0, -1);
|
|
24
43
|
const mainFileName = mainKeys[mainKeys.length - 1];
|
|
25
44
|
const mainContainer = await Tree.traverse(packageRoot, ...mainContainerKeys);
|
|
26
45
|
const packageExports = await mainContainer.import(mainFileName);
|
|
27
|
-
|
|
46
|
+
|
|
47
|
+
const result =
|
|
48
|
+
keys.length > 0
|
|
49
|
+
? await Tree.traverse(packageExports, ...keys)
|
|
50
|
+
: packageExports;
|
|
51
|
+
return result;
|
|
28
52
|
}
|
package/src/common/FilterTree.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
1
|
+
import { DeepObjectTree, Tree, isPlainObject } from "@weborigami/async-tree";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
@@ -7,7 +7,9 @@ import { Tree } from "@weborigami/async-tree";
|
|
|
7
7
|
export default class FilterTree {
|
|
8
8
|
constructor(tree, filter) {
|
|
9
9
|
this.tree = Tree.from(tree);
|
|
10
|
-
this.filter =
|
|
10
|
+
this.filter = isPlainObject(filter)
|
|
11
|
+
? new DeepObjectTree(filter)
|
|
12
|
+
: Tree.from(filter);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
async get(key) {
|
package/src/common/GlobTree.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DeepObjectTree,
|
|
3
|
+
ObjectTree,
|
|
4
|
+
Tree,
|
|
5
|
+
isPlainObject,
|
|
6
|
+
merge,
|
|
7
|
+
} from "@weborigami/async-tree";
|
|
2
8
|
|
|
3
9
|
const globstar = "**";
|
|
4
10
|
|
|
@@ -8,7 +14,9 @@ const globstar = "**";
|
|
|
8
14
|
*/
|
|
9
15
|
export default class GlobTree {
|
|
10
16
|
constructor(globs) {
|
|
11
|
-
this.globs =
|
|
17
|
+
this.globs = isPlainObject(globs)
|
|
18
|
+
? new DeepObjectTree(globs)
|
|
19
|
+
: Tree.from(globs);
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
async get(key) {
|
|
@@ -31,7 +31,10 @@ export default function processUnpackedContent(content, parent, inputDocument) {
|
|
|
31
31
|
|
|
32
32
|
extendScope.code = fn.code;
|
|
33
33
|
return extendScope;
|
|
34
|
-
} else if (
|
|
34
|
+
} else if (
|
|
35
|
+
Tree.isAsyncTree(content) &&
|
|
36
|
+
!(/** @type {any} */ (content).scope)
|
|
37
|
+
) {
|
|
35
38
|
const result = Object.create(content);
|
|
36
39
|
result.parent = parent;
|
|
37
40
|
return result;
|
|
@@ -33,7 +33,7 @@ export default function OriCommandTransform(Base) {
|
|
|
33
33
|
ambientsTree[keySymbol] = "ori command";
|
|
34
34
|
const extendedScope = new Scope(ambientsTree, Scope.getScope(this));
|
|
35
35
|
const source = key.slice(1).trim();
|
|
36
|
-
value = await ori.call(extendedScope, source);
|
|
36
|
+
value = await ori.call(extendedScope, source, { formatResult: false });
|
|
37
37
|
|
|
38
38
|
// Ensure this transform is applied to any subtree.
|
|
39
39
|
if (Tree.isAsyncTree(value)) {
|
package/src/server/server.js
CHANGED
|
@@ -53,21 +53,7 @@ export function treeRouter(tree) {
|
|
|
53
53
|
export async function handleRequest(request, response, tree) {
|
|
54
54
|
// For parsing purposes, we assume HTTPS -- it doesn't affect parsing.
|
|
55
55
|
const url = new URL(request.url, `https://${request.headers.host}`);
|
|
56
|
-
|
|
57
|
-
// Split on occurrences of `/!`, which represent Origami debug commands.
|
|
58
|
-
// Command arguments can contain slashes; don't treat those as path keys.
|
|
59
|
-
const parts = url.pathname.split(/\/!/);
|
|
60
|
-
const keys = parts.flatMap((part, index) => {
|
|
61
|
-
const decoded = decodeURIComponent(part);
|
|
62
|
-
// Split keys that aren't commands; add back the `!` to commands.
|
|
63
|
-
return index % 2 === 0 ? keysFromPath(decoded) : `!${decoded}`;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// If the path ends with a trailing slash, the final key will be an empty
|
|
67
|
-
// string. Change that to "index.html".
|
|
68
|
-
if (keys[keys.length - 1] === "") {
|
|
69
|
-
keys[keys.length - 1] = "index.html";
|
|
70
|
-
}
|
|
56
|
+
const keys = keysFromUrl(url);
|
|
71
57
|
|
|
72
58
|
const extendedTree =
|
|
73
59
|
url.searchParams && "parent" in tree
|
|
@@ -176,6 +162,28 @@ export async function handleRequest(request, response, tree) {
|
|
|
176
162
|
return true;
|
|
177
163
|
}
|
|
178
164
|
|
|
165
|
+
function keysFromUrl(url) {
|
|
166
|
+
// Split on occurrences of `/!`, which represent Origami debug commands.
|
|
167
|
+
// Command arguments can contain slashes; don't treat those as path keys.
|
|
168
|
+
const parts = url.pathname.split(/\/!/);
|
|
169
|
+
|
|
170
|
+
// Split everything before the first command by slashes and decode those.
|
|
171
|
+
const path = parts.shift();
|
|
172
|
+
const pathKeys = keysFromPath(path).map((key) => decodeURIComponent(key));
|
|
173
|
+
|
|
174
|
+
// If there are no commands, and the path ends with a trailing slash, the
|
|
175
|
+
// final key will be an empty string. Change that to "index.html".
|
|
176
|
+
if (parts.length === 0 && pathKeys[pathKeys.length - 1] === "") {
|
|
177
|
+
pathKeys[pathKeys.length - 1] = "index.html";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Add back the `!` to commands.
|
|
181
|
+
const commandKeys = parts.map((command) => `!${command}`);
|
|
182
|
+
|
|
183
|
+
const keys = [...pathKeys, ...commandKeys];
|
|
184
|
+
return keys;
|
|
185
|
+
}
|
|
186
|
+
|
|
179
187
|
/**
|
|
180
188
|
* A request listener for use with the node http.createServer and
|
|
181
189
|
* https.createServer calls, letting you serve an async tree as a set of pages.
|