@weborigami/origami 0.0.40 → 0.0.42
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/exports/exports.js +33 -34
- package/package.json +4 -4
- package/src/builtins/@arrows.js +2 -11
- package/src/builtins/@builtins.js +2 -5
- package/src/builtins/@cache.js +1 -1
- package/src/builtins/{@tree/concat.js → @concat.js} +3 -3
- package/src/builtins/@copy.js +2 -2
- package/src/builtins/{@tree/count.js → @count.js} +3 -9
- package/src/builtins/@crawl.js +1 -1
- package/src/builtins/@debug.js +2 -8
- package/src/builtins/{@tree/defineds.js → @defineds.js} +3 -7
- package/src/builtins/@document.js +1 -1
- package/src/builtins/{@tree/exceptions.js → @exceptions.js} +7 -8
- package/src/builtins/@explore.js +2 -5
- package/src/builtins/@files.js +1 -1
- package/src/builtins/@filter.js +1 -1
- package/src/builtins/{@tree/first.js → @first.js} +3 -9
- package/src/builtins/{@tree/fn.js → @fnTree.js} +13 -7
- package/src/builtins/@globs.js +1 -1
- package/src/builtins/{@tree/groupBy.js → @groupBy.js} +7 -9
- package/src/builtins/@help.js +1 -1
- package/src/builtins/@http.js +1 -1
- package/src/builtins/@https.js +1 -1
- package/src/builtins/@if.js +1 -1
- package/src/builtins/@image/format.js +36 -2
- package/src/builtins/@image/resize.js +30 -2
- package/src/builtins/@index.js +2 -8
- package/src/builtins/@inherited.js +1 -1
- package/src/builtins/@inline.js +1 -1
- package/src/builtins/{@tree/inners.js → @inners.js} +3 -8
- package/src/builtins/@invoke.js +8 -0
- package/src/builtins/{@tree/isAsyncTree.js → @isAsyncTree.js} +1 -1
- package/src/builtins/@json.js +7 -1
- package/src/builtins/{@tree/keys.js → @keys.js} +3 -9
- package/src/builtins/{@tree/keysJson.js → @keysJson.js} +3 -8
- package/src/builtins/@loaders/ori.js +18 -18
- package/src/builtins/@map.js +17 -19
- package/src/builtins/@match.js +1 -1
- package/src/builtins/{@tree/merge.js → @merge.js} +2 -2
- package/src/builtins/{@tree/mergeDeep.js → @mergeDeep.js} +3 -3
- package/src/builtins/@once.js +1 -1
- package/src/builtins/@ori.js +1 -1
- package/src/builtins/@pack.js +1 -1
- package/src/builtins/{@tree/parent.js → @parent.js} +3 -9
- package/src/builtins/{@tree/paths.js → @paths.js} +3 -8
- package/src/builtins/@perf.js +18 -0
- package/src/builtins/{@tree/plain.js → @plain.js} +4 -8
- package/src/builtins/@project.js +1 -1
- package/src/builtins/@redirect.js +8 -0
- package/src/builtins/{@tree/reverse.js → @reverse.js} +3 -8
- package/src/builtins/@rss.js +1 -1
- package/src/builtins/@scope/extend.js +6 -6
- package/src/builtins/@scope/get.js +1 -1
- package/src/builtins/@scope/set.js +4 -4
- package/src/builtins/@serve.js +1 -1
- package/src/builtins/{@tree/setDeep.js → @setDeep.js} +1 -1
- package/src/builtins/{@tree/shuffle.js → @shuffle.js} +5 -11
- package/src/builtins/{@tree/sitemap.js → @sitemap.js} +5 -8
- package/src/builtins/{@tree/sort.js → @sort.js} +5 -7
- package/src/builtins/{@tree/sortBy.js → @sortBy.js} +7 -9
- package/src/builtins/{@tree/static.js → @static.js} +5 -10
- package/src/builtins/@svg.js +3 -9
- package/src/builtins/{@tree/table.js → @table.js} +3 -8
- package/src/builtins/{@tree/take.js → @take.js} +3 -9
- package/src/builtins/@tree.js +19 -0
- package/src/builtins/@treeHttp.js +1 -1
- package/src/builtins/@treeHttps.js +1 -1
- package/src/builtins/@unpack.js +1 -1
- package/src/builtins/{@tree/values.js → @values.js} +3 -8
- package/src/builtins/{@tree/valuesDeep.js → @valuesDeep.js} +4 -8
- package/src/builtins/@watch.js +3 -8
- package/src/builtins/@with.js +1 -1
- package/src/builtins/@yaml.js +7 -1
- package/src/builtins/{@tree/map.d.ts → map.d.ts} +1 -3
- package/src/cli/cli.js +2 -15
- package/src/common/ExplorableSiteTransform.js +6 -4
- package/src/common/addValueKeyToScope.js +3 -11
- package/src/common/processUnpackedContent.js +2 -7
- package/src/misc/assertScopeIsDefined.js +2 -2
- package/src/misc/getTreeArgument.js +51 -0
- package/src/{builtins/@tree/dot.js → misc/treeDot.js} +2 -11
- package/src/server/constructResponse.js +126 -0
- package/src/server/mediaTypes.js +0 -16
- package/src/server/server.js +62 -131
- package/src/builtins/@tree/flowSvg.js +0 -55
- package/src/builtins/@tree/from.js +0 -27
- package/src/builtins/@tree/fromJson.js +0 -6
- package/src/builtins/@tree/fromYaml.js +0 -24
- package/src/builtins/@tree/nextKey.js +0 -29
- package/src/builtins/@tree/previousKey.js +0 -29
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SiteTree,
|
|
3
|
+
Tree,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
isStringLike,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
7
|
+
import { extname } from "@weborigami/language";
|
|
8
|
+
import * as serialize from "../common/serialize.js";
|
|
9
|
+
import { toString } from "../common/utilities.js";
|
|
10
|
+
import { mediaTypeForExtension } from "./mediaTypes.js";
|
|
11
|
+
|
|
12
|
+
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Given a resource that was returned from a route, construct an appropriate
|
|
16
|
+
* HTTP Response indicating what should be sent to the client. Return null
|
|
17
|
+
* if the resource is not a valid response.
|
|
18
|
+
*
|
|
19
|
+
* @param {import("node:http").IncomingMessage} request
|
|
20
|
+
* @param {any} resource
|
|
21
|
+
* @returns {Promise<Response|null>}
|
|
22
|
+
*/
|
|
23
|
+
export default async function constructResponse(request, resource) {
|
|
24
|
+
if (resource instanceof Response) {
|
|
25
|
+
// Already a Response, return as is.
|
|
26
|
+
return resource;
|
|
27
|
+
} else if (!resource) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Determine media type, what data we'll send, and encoding.
|
|
32
|
+
const url = new URL(request.url ?? "", `https://${request.headers.host}`);
|
|
33
|
+
const extension = extname(url.pathname).toLowerCase();
|
|
34
|
+
let mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
mediaType === undefined &&
|
|
38
|
+
!url.pathname.endsWith("/") &&
|
|
39
|
+
(Tree.isAsyncTree(resource) ||
|
|
40
|
+
isPlainObject(resource) ||
|
|
41
|
+
resource instanceof Array)
|
|
42
|
+
) {
|
|
43
|
+
// Redirect to an index page for the result.
|
|
44
|
+
const Location = `${request.url}/`;
|
|
45
|
+
return new Response("ok", {
|
|
46
|
+
headers: {
|
|
47
|
+
Location,
|
|
48
|
+
},
|
|
49
|
+
status: 307,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If the request is for a JSON or YAML result, and the resource we got
|
|
54
|
+
// isn't yet a string or Buffer, convert the resource to JSON or YAML now.
|
|
55
|
+
if (
|
|
56
|
+
(mediaType === "application/json" || mediaType === "text/yaml") &&
|
|
57
|
+
!isStringLike(resource)
|
|
58
|
+
) {
|
|
59
|
+
const tree = Tree.from(resource);
|
|
60
|
+
resource =
|
|
61
|
+
mediaType === "text/yaml"
|
|
62
|
+
? await serialize.toYaml(tree)
|
|
63
|
+
: await serialize.toJson(tree);
|
|
64
|
+
} else if (
|
|
65
|
+
mediaType === undefined &&
|
|
66
|
+
(isPlainObject(resource) || resource instanceof Array)
|
|
67
|
+
) {
|
|
68
|
+
// The resource is data, try showing it as YAML.
|
|
69
|
+
const tree = Tree.from(resource);
|
|
70
|
+
resource = await serialize.toYaml(tree);
|
|
71
|
+
mediaType = "text/yaml";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let body;
|
|
75
|
+
if (mediaType) {
|
|
76
|
+
body = SiteTree.mediaTypeIsText(mediaType) ? toString(resource) : resource;
|
|
77
|
+
} else {
|
|
78
|
+
body = textOrObject(resource);
|
|
79
|
+
// Infer media type.
|
|
80
|
+
mediaType =
|
|
81
|
+
typeof body !== "string"
|
|
82
|
+
? "application/octet-stream"
|
|
83
|
+
: body.trimStart().startsWith("<")
|
|
84
|
+
? "text/html"
|
|
85
|
+
: "text/plain";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Assume text is encoded in UTF-8.
|
|
89
|
+
if (SiteTree.mediaTypeIsText(mediaType)) {
|
|
90
|
+
mediaType += "; charset=utf-8";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// If we didn't get back some kind of data that response.write() accepts,
|
|
94
|
+
// assume it was an error.
|
|
95
|
+
const validResponse = typeof body === "string" || body instanceof TypedArray;
|
|
96
|
+
if (!validResponse) {
|
|
97
|
+
const typeName = body?.constructor?.name ?? typeof body;
|
|
98
|
+
console.error(
|
|
99
|
+
`A served tree must return a string or a TypedArray (such as a Buffer) but returned an instance of ${typeName}.`
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Response(body, {
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": mediaType,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert to a string if we can, but leave objects that convert to something
|
|
113
|
+
* like "[object Object]" alone.
|
|
114
|
+
*
|
|
115
|
+
* @param {any} object
|
|
116
|
+
*/
|
|
117
|
+
function textOrObject(object) {
|
|
118
|
+
if (object instanceof ArrayBuffer) {
|
|
119
|
+
// Convert to Buffer.
|
|
120
|
+
return Buffer.from(object);
|
|
121
|
+
} else if (object instanceof TypedArray) {
|
|
122
|
+
// Return typed arrays as is.
|
|
123
|
+
return object;
|
|
124
|
+
}
|
|
125
|
+
return toString(object);
|
|
126
|
+
}
|
package/src/server/mediaTypes.js
CHANGED
|
@@ -79,19 +79,3 @@ export const mediaTypeForExtension = {
|
|
|
79
79
|
".yaml": "text/yaml", // Not official
|
|
80
80
|
".zip": "application/zip",
|
|
81
81
|
};
|
|
82
|
-
|
|
83
|
-
export const mediaTypeIsText = {
|
|
84
|
-
"application/json": true,
|
|
85
|
-
"application/ld+json": true,
|
|
86
|
-
"application/vnd.oasis.opendocument.text": true,
|
|
87
|
-
"application/x-httpd-php": true,
|
|
88
|
-
"application/x-sh": true,
|
|
89
|
-
"application/xhtml+xml": true,
|
|
90
|
-
"text/css": true,
|
|
91
|
-
"text/csv": true,
|
|
92
|
-
"text/html": true,
|
|
93
|
-
"text/javascript": true,
|
|
94
|
-
"text/plain": true,
|
|
95
|
-
"text/yaml": true,
|
|
96
|
-
"text/yml": true,
|
|
97
|
-
};
|
package/src/server/server.js
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
isStringLike,
|
|
6
|
-
keysFromPath,
|
|
7
|
-
} from "@weborigami/async-tree";
|
|
8
|
-
import { Scope, extname } from "@weborigami/language";
|
|
9
|
-
import * as serialize from "../common/serialize.js";
|
|
10
|
-
import { toString } from "../common/utilities.js";
|
|
11
|
-
import { mediaTypeForExtension, mediaTypeIsText } from "./mediaTypes.js";
|
|
1
|
+
import { ObjectTree, Tree, keysFromPath } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope, formatError } from "@weborigami/language";
|
|
3
|
+
import { ServerResponse } from "node:http";
|
|
4
|
+
import constructResponse from "./constructResponse.js";
|
|
12
5
|
|
|
13
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Copy a constructed response to a ServerResponse. Return true if the response
|
|
8
|
+
* was successfully copied, and false if there was a problem.
|
|
9
|
+
*
|
|
10
|
+
* @param {Response} constructed
|
|
11
|
+
* @param {ServerResponse} response
|
|
12
|
+
*/
|
|
13
|
+
async function copyResponse(constructed, response) {
|
|
14
|
+
response.statusCode = constructed.status;
|
|
15
|
+
response.statusMessage = constructed.statusText;
|
|
16
|
+
|
|
17
|
+
for (const [key, value] of constructed.headers) {
|
|
18
|
+
response.setHeader(key, value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (constructed.body) {
|
|
22
|
+
try {
|
|
23
|
+
// Write the response body to the ServerResponse.
|
|
24
|
+
const reader = constructed.body.getReader();
|
|
25
|
+
let { done, value } = await reader.read();
|
|
26
|
+
while (!done) {
|
|
27
|
+
response.write(value);
|
|
28
|
+
({ done, value } = await reader.read());
|
|
29
|
+
}
|
|
30
|
+
response.end();
|
|
31
|
+
} catch (/** @type {any} */ error) {
|
|
32
|
+
console.error(error.message);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
14
39
|
|
|
15
40
|
// Extend the tree's scope with the URL's search parameters.
|
|
16
41
|
function extendTreeScopeWithParams(tree, url) {
|
|
@@ -38,21 +63,16 @@ function extendTreeScopeWithParams(tree, url) {
|
|
|
38
63
|
return extendedTree;
|
|
39
64
|
}
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
next();
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Handle a client request.
|
|
68
|
+
*
|
|
69
|
+
* @param {import("node:http").IncomingMessage} request
|
|
70
|
+
* @param {ServerResponse} response
|
|
71
|
+
* @param {import("@weborigami/types").AsyncTree} tree
|
|
72
|
+
*/
|
|
53
73
|
export async function handleRequest(request, response, tree) {
|
|
54
74
|
// For parsing purposes, we assume HTTPS -- it doesn't affect parsing.
|
|
55
|
-
const url = new URL(request.url, `https://${request.headers.host}`);
|
|
75
|
+
const url = new URL(request.url ?? "", `https://${request.headers.host}`);
|
|
56
76
|
const keys = keysFromUrl(url);
|
|
57
77
|
|
|
58
78
|
const extendedTree =
|
|
@@ -73,93 +93,15 @@ export async function handleRequest(request, response, tree) {
|
|
|
73
93
|
return true;
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Determine media type, what data we'll send, and encoding.
|
|
83
|
-
const extension = extname(url.pathname).toLowerCase();
|
|
84
|
-
mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
mediaType === undefined &&
|
|
88
|
-
!request.url.endsWith("/") &&
|
|
89
|
-
(Tree.isAsyncTree(resource) ||
|
|
90
|
-
isPlainObject(resource) ||
|
|
91
|
-
resource instanceof Array)
|
|
92
|
-
) {
|
|
93
|
-
// Redirect to an index page for the result.
|
|
94
|
-
// Redirect to the root of the tree.
|
|
95
|
-
const Location = `${request.url}/`;
|
|
96
|
-
response.writeHead(307, { Location });
|
|
97
|
-
response.end("ok");
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// If the request is for a JSON or YAML result, and the resource we got
|
|
102
|
-
// isn't yet a string or Buffer, convert the resource to JSON or YAML now.
|
|
103
|
-
if (
|
|
104
|
-
(mediaType === "application/json" || mediaType === "text/yaml") &&
|
|
105
|
-
!isStringLike(resource)
|
|
106
|
-
) {
|
|
107
|
-
const tree = Tree.from(resource);
|
|
108
|
-
resource =
|
|
109
|
-
mediaType === "text/yaml"
|
|
110
|
-
? await serialize.toYaml(tree)
|
|
111
|
-
: await serialize.toJson(tree);
|
|
112
|
-
} else if (
|
|
113
|
-
mediaType === undefined &&
|
|
114
|
-
(isPlainObject(resource) || resource instanceof Array)
|
|
115
|
-
) {
|
|
116
|
-
// The resource is data, try showing it as YAML.
|
|
117
|
-
const tree = Tree.from(resource);
|
|
118
|
-
resource = await serialize.toYaml(tree);
|
|
119
|
-
mediaType = "text/yaml";
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let data;
|
|
123
|
-
if (mediaType) {
|
|
124
|
-
data = mediaTypeIsText[mediaType] ? toString(resource) : resource;
|
|
125
|
-
} else {
|
|
126
|
-
data = textOrObject(resource);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (!mediaType) {
|
|
130
|
-
// Can't identify media type; infer default type.
|
|
131
|
-
mediaType =
|
|
132
|
-
typeof data !== "string"
|
|
133
|
-
? "application/octet-stream"
|
|
134
|
-
: data.trimStart().startsWith("<")
|
|
135
|
-
? "text/html"
|
|
136
|
-
: "text/plain";
|
|
137
|
-
}
|
|
138
|
-
const encoding = mediaTypeIsText[mediaType] ? "utf-8" : undefined;
|
|
139
|
-
|
|
140
|
-
// If we didn't get back some kind of data that response.write() accepts,
|
|
141
|
-
// assume it was an error.
|
|
142
|
-
const validResponse = typeof data === "string" || data instanceof TypedArray;
|
|
143
|
-
|
|
144
|
-
if (!validResponse) {
|
|
145
|
-
const typeName = data?.constructor?.name ?? typeof data;
|
|
146
|
-
console.error(
|
|
147
|
-
`A served tree must return a string or a TypedArray (such as a Buffer) but returned an instance of ${typeName}.`
|
|
148
|
-
);
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
response.writeHead(200, {
|
|
153
|
-
"Content-Type": mediaType,
|
|
154
|
-
});
|
|
155
|
-
try {
|
|
156
|
-
response.end(data, encoding);
|
|
157
|
-
} catch (/** @type {any} */ error) {
|
|
158
|
-
console.error(error.message);
|
|
96
|
+
// Construct the response.
|
|
97
|
+
const constructed = await constructResponse(request, resource);
|
|
98
|
+
if (!constructed) {
|
|
159
99
|
return false;
|
|
160
100
|
}
|
|
161
101
|
|
|
162
|
-
return true
|
|
102
|
+
// Copy the construct response to the ServerResponse and return true if
|
|
103
|
+
// the response was valid.
|
|
104
|
+
return copyResponse(constructed, response);
|
|
163
105
|
}
|
|
164
106
|
|
|
165
107
|
function keysFromUrl(url) {
|
|
@@ -211,16 +153,7 @@ export function requestListener(treelike) {
|
|
|
211
153
|
* the console.
|
|
212
154
|
*/
|
|
213
155
|
function respondWithError(response, error) {
|
|
214
|
-
let message =
|
|
215
|
-
// Work up to the root cause, displaying intermediate messages as we go up.
|
|
216
|
-
while (error.cause) {
|
|
217
|
-
message += error.message + `\n`;
|
|
218
|
-
error = error.cause;
|
|
219
|
-
}
|
|
220
|
-
if (error.name) {
|
|
221
|
-
message += `${error.name}: `;
|
|
222
|
-
}
|
|
223
|
-
message += error.message;
|
|
156
|
+
let message = formatError(error);
|
|
224
157
|
// Prevent HTML in the error message from being interpreted as HTML.
|
|
225
158
|
message = message.replace(/</g, "<").replace(/>/g, ">");
|
|
226
159
|
const html = `<!DOCTYPE html>
|
|
@@ -241,16 +174,14 @@ ${message}
|
|
|
241
174
|
console.error(message);
|
|
242
175
|
}
|
|
243
176
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
return toString(object);
|
|
177
|
+
// Asynchronous tree router as Express middleware.
|
|
178
|
+
export function treeRouter(tree) {
|
|
179
|
+
// Return a router for the tree source.
|
|
180
|
+
return async function (request, response, next) {
|
|
181
|
+
const handled = await handleRequest(request, response, tree);
|
|
182
|
+
if (!handled) {
|
|
183
|
+
// Module not found, let next middleware function try.
|
|
184
|
+
next();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
256
187
|
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import graphviz from "graphviz-wasm";
|
|
2
|
-
|
|
3
|
-
let graphvizLoaded = false;
|
|
4
|
-
|
|
5
|
-
export default async function flowSvg(flow) {
|
|
6
|
-
if (!graphvizLoaded) {
|
|
7
|
-
await graphviz.loadWASM();
|
|
8
|
-
graphvizLoaded = true;
|
|
9
|
-
}
|
|
10
|
-
const dot = flowDot(flow);
|
|
11
|
-
const svg = await graphviz.layout(dot, "svg");
|
|
12
|
-
return svg;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function flowDot(flow) {
|
|
16
|
-
const nodes = [];
|
|
17
|
-
const edges = [];
|
|
18
|
-
for (const [key, record] of Object.entries(flow)) {
|
|
19
|
-
const dependencies = record.dependencies ?? [];
|
|
20
|
-
let label = record.label ?? key;
|
|
21
|
-
if (record.undefined) {
|
|
22
|
-
label += " (?)";
|
|
23
|
-
}
|
|
24
|
-
const virtualNode = dependencies.length > 0;
|
|
25
|
-
const url = record.url ?? key;
|
|
26
|
-
const nodeLabel = `label="${label}"`;
|
|
27
|
-
const nodeUrl = `URL=".scope/${url}"`;
|
|
28
|
-
const nodeShape = record.undefined ? `shape="none"` : "";
|
|
29
|
-
const nodeStyle = virtualNode ? `style="dashed"` : null;
|
|
30
|
-
const attributes = [nodeLabel, nodeShape, nodeStyle, nodeUrl].filter(
|
|
31
|
-
(attribute) => attribute
|
|
32
|
-
);
|
|
33
|
-
const nodeDot = ` "${key}" [${attributes.join("; ")}];`;
|
|
34
|
-
nodes.push(nodeDot);
|
|
35
|
-
|
|
36
|
-
for (const dependency of dependencies) {
|
|
37
|
-
edges.push(` "${dependency}" -> "${key}";`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return `digraph dataflow {
|
|
42
|
-
nodesep=1;
|
|
43
|
-
rankdir=LR;
|
|
44
|
-
ranksep=1.5;
|
|
45
|
-
node [color="gray70"; fillcolor="white"; fontname="Helvetica"; fontsize="10"; nojustify="true"; style="filled"; shape="box"];
|
|
46
|
-
edge [arrowhead="onormal"; arrowsize="0.75"; color="gray60"; fontname="Helvetica"; fontsize="10"; labeldistance="5"];
|
|
47
|
-
|
|
48
|
-
${nodes.join("\n")}
|
|
49
|
-
|
|
50
|
-
${edges.join("\n")}
|
|
51
|
-
}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// flowSvg.usage = `flowSvg <dataflow>\tRenders the output of dataflow() as an SVG`;
|
|
55
|
-
// flowSvg.documentation = "https://weborigami.org/cli/builtins.html#flowSvg";
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import { Scope } from "@weborigami/language";
|
|
3
|
-
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Cast the indicated treelike to a tree.
|
|
7
|
-
*
|
|
8
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
10
|
-
* @this {AsyncTree|null}
|
|
11
|
-
* @param {Treelike} [treelike]
|
|
12
|
-
*/
|
|
13
|
-
export default async function tree(treelike) {
|
|
14
|
-
assertScopeIsDefined(this);
|
|
15
|
-
treelike = treelike ?? (await this?.get("@current"));
|
|
16
|
-
if (treelike === undefined) {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** @type {AsyncTree} */
|
|
21
|
-
let result = Tree.from(treelike);
|
|
22
|
-
result = Scope.treeWithScope(result, this);
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
tree.usage = `from <treelike>\tConvert JSON, YAML, function, or plain object to a tree`;
|
|
27
|
-
tree.documentation = "https://weborigami.org/cli/builtins.html#tree";
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import { Scope } from "@weborigami/language";
|
|
3
|
-
import * as serialize from "../../common/serialize.js";
|
|
4
|
-
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
|
-
* @typedef {import("@weborigami/async-tree").StringLike} StringLike
|
|
10
|
-
*
|
|
11
|
-
* @param {StringLike} text
|
|
12
|
-
* @this {AsyncTree|null}
|
|
13
|
-
*/
|
|
14
|
-
export default async function fromYaml(text) {
|
|
15
|
-
assertScopeIsDefined(this);
|
|
16
|
-
let result = text ? serialize.parseYaml(String(text)) : undefined;
|
|
17
|
-
if (this && Tree.isAsyncTree(result)) {
|
|
18
|
-
result = Scope.treeWithScope(result, this);
|
|
19
|
-
}
|
|
20
|
-
return result;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
fromYaml.usage = `fromYaml <text>\tParse text as YAML`;
|
|
24
|
-
fromYaml.documentation = "https://weborigami.org/cli/builtins.html#fromYaml";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Returns the key after the indicated key.
|
|
6
|
-
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
|
-
* @this {AsyncTree|null}
|
|
10
|
-
* @param {Treelike} treelike
|
|
11
|
-
* @param {any} key
|
|
12
|
-
*/
|
|
13
|
-
export default async function nextKey(treelike, key) {
|
|
14
|
-
assertScopeIsDefined(this);
|
|
15
|
-
const tree = Tree.from(treelike);
|
|
16
|
-
let returnNextKey = false;
|
|
17
|
-
for (const treeKey of await tree.keys()) {
|
|
18
|
-
if (returnNextKey) {
|
|
19
|
-
return treeKey;
|
|
20
|
-
}
|
|
21
|
-
if (treeKey === key) {
|
|
22
|
-
returnNextKey = true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
nextKey.usage = `nextKey <tree>, <key>\tReturns the key after the indicated key`;
|
|
29
|
-
nextKey.documentation = "https://weborigami.org/cli/builtins.html#nextKey";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Tree } from "@weborigami/async-tree";
|
|
2
|
-
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Returns the key before the indicated key.
|
|
6
|
-
*
|
|
7
|
-
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
-
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
|
-
*
|
|
10
|
-
* @param {Treelike} treelike
|
|
11
|
-
* @param {any} key
|
|
12
|
-
* @this {AsyncTree|null}
|
|
13
|
-
*/
|
|
14
|
-
export default async function previousKey(treelike, key) {
|
|
15
|
-
assertScopeIsDefined(this);
|
|
16
|
-
const tree = Tree.from(treelike);
|
|
17
|
-
let previousKey = undefined;
|
|
18
|
-
for (const treeKey of await tree.keys()) {
|
|
19
|
-
if (treeKey === key) {
|
|
20
|
-
return previousKey;
|
|
21
|
-
}
|
|
22
|
-
previousKey = treeKey;
|
|
23
|
-
}
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
previousKey.usage = `previousKey <tree>, <key>\tReturns the key before the indicated key`;
|
|
28
|
-
previousKey.documentation =
|
|
29
|
-
"https://weborigami.org/cli/builtins.html#previousKey";
|