@weborigami/language 0.5.5 → 0.5.6
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 +16 -6
- package/main.js +9 -4
- package/package.json +4 -3
- package/src/compiler/compile.js +10 -4
- package/src/compiler/optimize.js +115 -97
- package/src/compiler/origami.pegjs +1 -4
- package/src/compiler/parse.js +568 -588
- package/src/compiler/parserHelpers.js +2 -2
- package/src/handlers/css_handler.js +7 -0
- package/src/handlers/csv_handler.js +129 -0
- package/src/handlers/handlers.js +33 -0
- package/src/handlers/htm_handler.js +2 -0
- package/src/handlers/html_handler.js +7 -0
- package/src/handlers/jpeg_handler.js +62 -0
- package/src/handlers/jpg_handler.js +2 -0
- package/src/handlers/js_handler.js +51 -0
- package/src/handlers/json_handler.js +26 -0
- package/src/handlers/md_handler.js +7 -0
- package/src/handlers/mjs_handler.js +2 -0
- package/src/handlers/ori_handler.js +47 -0
- package/src/handlers/oridocument_handler.js +77 -0
- package/src/handlers/parseFrontMatter.js +16 -0
- package/src/handlers/ts_handler.js +1 -0
- package/src/handlers/txt_handler.js +108 -0
- package/src/handlers/wasm_handler.js +15 -0
- package/src/handlers/xhtml_handler.js +2 -0
- package/src/handlers/yaml_handler.js +33 -0
- package/src/handlers/yml_handler.js +2 -0
- package/src/project/builtins.js +5 -0
- package/src/project/coreGlobals.js +17 -0
- package/src/{runtime → project}/jsGlobals.js +3 -1
- package/src/project/projectConfig.js +36 -0
- package/src/project/projectGlobals.js +19 -0
- package/src/project/projectRoot.js +59 -0
- package/src/protocols/constructHref.js +20 -0
- package/src/protocols/constructSiteTree.js +26 -0
- package/src/protocols/explore.js +14 -0
- package/src/protocols/fetchAndHandleExtension.js +25 -0
- package/src/protocols/files.js +26 -0
- package/src/protocols/http.js +15 -0
- package/src/protocols/https.js +15 -0
- package/src/protocols/httpstree.js +14 -0
- package/src/protocols/httptree.js +14 -0
- package/src/protocols/node.js +13 -0
- package/src/protocols/package.js +67 -0
- package/src/protocols/protocolGlobals.js +12 -0
- package/src/protocols/protocols.js +8 -0
- package/src/runtime/EventTargetMixin.js +1 -1
- package/src/runtime/HandleExtensionsTransform.js +3 -12
- package/src/runtime/ImportModulesMixin.js +4 -10
- package/src/runtime/InvokeFunctionsTransform.js +1 -1
- package/src/runtime/evaluate.js +15 -8
- package/src/runtime/expressionFunction.js +5 -7
- package/src/runtime/expressionObject.js +10 -20
- package/src/runtime/functionResultsMap.js +1 -3
- package/src/runtime/{handlers.js → handleExtension.js} +13 -11
- package/src/runtime/mergeTrees.js +1 -8
- package/src/runtime/ops.js +83 -90
- package/test/compiler/compile.test.js +20 -19
- package/test/compiler/optimize.test.js +60 -25
- package/test/compiler/parse.test.js +4 -4
- package/test/generator/oriEval.js +4 -5
- package/test/handlers/csv.handler.test.js +36 -0
- package/test/handlers/fixtures/add.wasm +0 -0
- package/test/handlers/fixtures/exif.jpeg +0 -0
- package/test/handlers/fixtures/frontMatter.md +5 -0
- package/test/handlers/fixtures/list.js +4 -0
- package/test/handlers/fixtures/multiple.js +4 -0
- package/test/handlers/fixtures/obj.js +3 -0
- package/test/handlers/fixtures/site.ori +5 -0
- package/test/handlers/fixtures/string.js +1 -0
- package/test/handlers/fixtures/tag.yaml +5 -0
- package/test/handlers/fixtures/test.ori +9 -0
- package/test/handlers/jpeg.handler.test.js +18 -0
- package/test/handlers/js.handler.test.js +46 -0
- package/test/handlers/json.handler.test.js +14 -0
- package/test/handlers/ori.handler.test.js +87 -0
- package/test/handlers/oridocument.handler.test.js +68 -0
- package/test/handlers/txt.handler.test.js +41 -0
- package/test/handlers/wasm.handler.test.js +20 -0
- package/test/handlers/yaml.handler.test.js +17 -0
- package/test/project/fixtures/withConfig/config.ori +4 -0
- package/test/project/fixtures/withConfig/subfolder/greet.js +1 -0
- package/test/project/fixtures/withPackageJson/package.json +0 -0
- package/test/project/jsGlobals.test.js +21 -0
- package/test/project/projectConfig.test.js +28 -0
- package/test/project/projectRoot.test.js +40 -0
- package/test/protocols/package.test.js +11 -0
- package/test/runtime/evaluate.test.js +26 -42
- package/test/runtime/expressionObject.test.js +16 -20
- package/test/runtime/{handlers.test.js → handleExtension.test.js} +4 -20
- package/test/runtime/jsGlobals.test.js +4 -6
- package/test/runtime/mergeTrees.test.js +2 -4
- package/test/runtime/ops.test.js +66 -68
- package/src/runtime/getHandlers.js +0 -10
|
@@ -259,8 +259,8 @@ function makeMerge(spreads, location) {
|
|
|
259
259
|
// Also add an object to the result with indirect references
|
|
260
260
|
const indirectEntries = spread.slice(1).map((entry) => {
|
|
261
261
|
const [key] = entry;
|
|
262
|
-
const
|
|
263
|
-
const reference = annotate([
|
|
262
|
+
const parent = annotate([ops.inherited, 1], entry.location);
|
|
263
|
+
const reference = annotate([parent, key], entry.location);
|
|
264
264
|
const getter = annotate([ops.getter, reference], entry.location);
|
|
265
265
|
return annotate([key, getter], entry.location);
|
|
266
266
|
});
|
|
@@ -0,0 +1,129 @@
|
|
|
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("CSV handler can only unpack text");
|
|
11
|
+
}
|
|
12
|
+
const data = csvParse(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 CSV following RFC 4180
|
|
26
|
+
*
|
|
27
|
+
* This assumes the presence of a header row, and accepts both CRLF and LF line
|
|
28
|
+
* endings.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} text
|
|
31
|
+
* @returns {any[]}
|
|
32
|
+
*/
|
|
33
|
+
function csvParse(text) {
|
|
34
|
+
const rows = [];
|
|
35
|
+
let currentRow = [];
|
|
36
|
+
let currentField = "";
|
|
37
|
+
|
|
38
|
+
const pushField = () => {
|
|
39
|
+
// Push the completed field and reset for the next field.
|
|
40
|
+
currentRow.push(currentField);
|
|
41
|
+
currentField = "";
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const pushRow = () => {
|
|
45
|
+
// Push the row if there is at least one field (accounts for potential trailing newline)
|
|
46
|
+
rows.push(currentRow);
|
|
47
|
+
currentRow = [];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Main state machine
|
|
51
|
+
let i = 0;
|
|
52
|
+
let inQuotes = false;
|
|
53
|
+
while (i < text.length) {
|
|
54
|
+
const char = text[i];
|
|
55
|
+
|
|
56
|
+
if (inQuotes) {
|
|
57
|
+
// In a quoted field
|
|
58
|
+
if (char === '"') {
|
|
59
|
+
// Check if next character is also a quote
|
|
60
|
+
if (i + 1 < text.length && text[i + 1] === '"') {
|
|
61
|
+
// Append a literal double quote and skip the next character
|
|
62
|
+
currentField += '"';
|
|
63
|
+
i += 2;
|
|
64
|
+
continue;
|
|
65
|
+
} else {
|
|
66
|
+
// End of the quoted field
|
|
67
|
+
inQuotes = false;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// All other characters within quotes are taken literally.
|
|
73
|
+
currentField += char;
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
} else if (char === '"') {
|
|
78
|
+
// Start of a quoted field
|
|
79
|
+
inQuotes = true;
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
} else if (char === ",") {
|
|
83
|
+
// End of field
|
|
84
|
+
pushField();
|
|
85
|
+
i++;
|
|
86
|
+
continue;
|
|
87
|
+
} else if (char === "\n" || (char === "\r" && text[i + 1] === "\n")) {
|
|
88
|
+
// End of row: push the last field, then row.
|
|
89
|
+
pushField();
|
|
90
|
+
pushRow();
|
|
91
|
+
if (char === "\r" && text[i + 1] === "\n") {
|
|
92
|
+
i++; // Handle CRLF line endings
|
|
93
|
+
}
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
} else {
|
|
97
|
+
// Regular character
|
|
98
|
+
currentField += char;
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Handle any remaining data after the loop.
|
|
105
|
+
// This will capture the last field/row if the text did not end with a newline.
|
|
106
|
+
if (inQuotes) {
|
|
107
|
+
// Mismatched quotes: you might choose to throw an error or handle it gracefully.
|
|
108
|
+
throw new Error("CSV parsing error: unmatched quote in the input.");
|
|
109
|
+
}
|
|
110
|
+
if (currentField !== "" || text.at(-1) === ",") {
|
|
111
|
+
pushField();
|
|
112
|
+
}
|
|
113
|
+
if (currentRow.length > 0) {
|
|
114
|
+
pushRow();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// The first row is assumed to be the header.
|
|
118
|
+
if (rows.length === 0) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const header = rows.shift();
|
|
123
|
+
|
|
124
|
+
const data = rows.map((row) =>
|
|
125
|
+
Object.fromEntries(row.map((value, index) => [header[index], value]))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//
|
|
2
|
+
// This library includes a number of modules with circular dependencies. This
|
|
3
|
+
// module exists to explicitly set the loading order for those modules. To
|
|
4
|
+
// enforce use of this loading order, other modules should only load the modules
|
|
5
|
+
// below via this module.
|
|
6
|
+
//
|
|
7
|
+
// About this pattern:
|
|
8
|
+
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
|
9
|
+
//
|
|
10
|
+
// Note: to avoid having VS Code auto-sort the imports, keep lines between them.
|
|
11
|
+
|
|
12
|
+
export { default as js_handler } from "./js_handler.js";
|
|
13
|
+
export { default as ts_handler } from "./ts_handler.js";
|
|
14
|
+
|
|
15
|
+
export { default as ori_handler } from "./ori_handler.js";
|
|
16
|
+
|
|
17
|
+
export { default as oridocument_handler } from "./oridocument_handler.js";
|
|
18
|
+
|
|
19
|
+
export { default as txt_handler } from "./txt_handler.js";
|
|
20
|
+
|
|
21
|
+
export { default as css_handler } from "./css_handler.js";
|
|
22
|
+
export { default as csv_handler } from "./csv_handler.js";
|
|
23
|
+
export { default as htm_handler } from "./htm_handler.js";
|
|
24
|
+
export { default as html_handler } from "./html_handler.js";
|
|
25
|
+
export { default as jpeg_handler } from "./jpeg_handler.js";
|
|
26
|
+
export { default as jpg_handler } from "./jpg_handler.js";
|
|
27
|
+
export { default as json_handler } from "./json_handler.js";
|
|
28
|
+
export { default as md_handler } from "./md_handler.js";
|
|
29
|
+
export { default as mjs_handler } from "./mjs_handler.js";
|
|
30
|
+
export { default as wasm_handler } from "./wasm_handler.js";
|
|
31
|
+
export { default as xhtml_handler } from "./xhtml_handler.js";
|
|
32
|
+
export { default as yaml_handler } from "./yaml_handler.js";
|
|
33
|
+
export { default as yml_handler } from "./yml_handler.js";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import exifParser from "exif-parser";
|
|
2
|
+
|
|
3
|
+
const exifDateTags = [
|
|
4
|
+
"ModifyDate",
|
|
5
|
+
"MDPrepDate",
|
|
6
|
+
"DateTimeOriginal",
|
|
7
|
+
"CreateDate",
|
|
8
|
+
"PreviewDateTime",
|
|
9
|
+
"GPSDateStamp",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A JPEG file with possible Exif metadata
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
mediaType: "image/jpeg",
|
|
17
|
+
|
|
18
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
19
|
+
async unpack(packed, options) {
|
|
20
|
+
if (packed instanceof Uint8Array) {
|
|
21
|
+
// Downgrade to old Node Buffer for exif-parser.
|
|
22
|
+
packed = Buffer.from(packed);
|
|
23
|
+
}
|
|
24
|
+
const parser = exifParser.create(packed);
|
|
25
|
+
parser.enableTagNames(true);
|
|
26
|
+
parser.enableSimpleValues(true);
|
|
27
|
+
const parsed = await parser.parse();
|
|
28
|
+
|
|
29
|
+
// The exif-parser `enableSimpleValues` option should convert dates to
|
|
30
|
+
// JavaScript Date objects, but that doesn't seem to work. Ensure dates are
|
|
31
|
+
// Date objects.
|
|
32
|
+
const exif = parsed.tags;
|
|
33
|
+
for (const tag of exifDateTags) {
|
|
34
|
+
if (typeof exif[tag] === "number") {
|
|
35
|
+
exif[tag] = new Date(exif[tag] * 1000);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const result = {
|
|
40
|
+
height: parsed.imageSize.height,
|
|
41
|
+
width: parsed.imageSize.width,
|
|
42
|
+
exif,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Promote some Exif properties to the top level.
|
|
46
|
+
const tagsToPromote = {
|
|
47
|
+
ImageDescription: "caption",
|
|
48
|
+
ModifyDate: "modified",
|
|
49
|
+
Orientation: "orientation",
|
|
50
|
+
};
|
|
51
|
+
for (const [tag, key] of Object.entries(tagsToPromote)) {
|
|
52
|
+
if (exif[tag] !== undefined) {
|
|
53
|
+
result[key] = exif[tag];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Add aspect ratio for use with `aspect-ratio` CSS.
|
|
58
|
+
result.aspectRatio = result.width / result.height;
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JavaScript file
|
|
3
|
+
*
|
|
4
|
+
* Unpacking a JavaScript file returns its default export, or its set of exports
|
|
5
|
+
* if there is more than one.
|
|
6
|
+
*/
|
|
7
|
+
export default {
|
|
8
|
+
mediaType: "application/javascript",
|
|
9
|
+
|
|
10
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
11
|
+
async unpack(packed, options = {}) {
|
|
12
|
+
const { key, parent } = options;
|
|
13
|
+
if (!(parent && "import" in parent)) {
|
|
14
|
+
throw new TypeError(
|
|
15
|
+
"The parent tree must support importing modules to unpack JavaScript files."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const object = await /** @type {any} */ (parent).import?.(key);
|
|
20
|
+
|
|
21
|
+
let bound;
|
|
22
|
+
if ("default" in object) {
|
|
23
|
+
// Module with a default export; return that.
|
|
24
|
+
bound = bindToParent(object.default, parent);
|
|
25
|
+
} else {
|
|
26
|
+
// Module with multiple named exports.
|
|
27
|
+
bound = {};
|
|
28
|
+
for (const [name, value] of Object.entries(object)) {
|
|
29
|
+
bound[name] = bindToParent(value, parent);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return bound;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// If the value is a function, bind it to the parent so that the function can,
|
|
38
|
+
// e.g., find local files. Note: evaluate() supports a related but separate
|
|
39
|
+
// mechanism called `containerAsTarget`. We want to use binding here so that, if
|
|
40
|
+
// a function is handed to another to be called later, it still has the correct
|
|
41
|
+
// `this`.
|
|
42
|
+
function bindToParent(value, parent) {
|
|
43
|
+
if (typeof value === "function") {
|
|
44
|
+
const result = value.bind(parent);
|
|
45
|
+
// Copy over any properties that were attached to the function
|
|
46
|
+
Object.assign(result, value);
|
|
47
|
+
return result;
|
|
48
|
+
} else {
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { symbols, toString } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A JSON file
|
|
5
|
+
*
|
|
6
|
+
* Unpacking a JSON file returns the parsed data.
|
|
7
|
+
*/
|
|
8
|
+
export default {
|
|
9
|
+
mediaType: "application/json",
|
|
10
|
+
|
|
11
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
12
|
+
unpack(packed) {
|
|
13
|
+
const json = toString(packed);
|
|
14
|
+
if (!json) {
|
|
15
|
+
throw new Error("Tried to parse something as JSON but it wasn't text.");
|
|
16
|
+
}
|
|
17
|
+
const data = JSON.parse(json);
|
|
18
|
+
if (data && typeof data === "object" && Object.isExtensible(data)) {
|
|
19
|
+
Object.defineProperty(data, symbols.deep, {
|
|
20
|
+
enumerable: false,
|
|
21
|
+
value: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getParent, toString } from "@weborigami/async-tree";
|
|
2
|
+
import * as compile from "../compiler/compile.js";
|
|
3
|
+
import projectGlobals from "../project/projectGlobals.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* An Origami expression file
|
|
7
|
+
*
|
|
8
|
+
* Unpacking an Origami file returns the result of evaluating the expression.
|
|
9
|
+
*/
|
|
10
|
+
export default {
|
|
11
|
+
mediaType: "text/plain",
|
|
12
|
+
|
|
13
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
14
|
+
async unpack(packed, options = {}) {
|
|
15
|
+
const parent = getParent(packed, options);
|
|
16
|
+
|
|
17
|
+
// Construct an object to represent the source code.
|
|
18
|
+
const sourceName = options.key;
|
|
19
|
+
let url;
|
|
20
|
+
if (sourceName && /** @type {any} */ (parent)?.url) {
|
|
21
|
+
let parentHref = /** @type {any} */ (parent).url.href;
|
|
22
|
+
if (!parentHref.endsWith("/")) {
|
|
23
|
+
parentHref += "/";
|
|
24
|
+
}
|
|
25
|
+
url = new URL(sourceName, parentHref);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const source = {
|
|
29
|
+
text: toString(packed),
|
|
30
|
+
name: options.key,
|
|
31
|
+
url,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Compile the source code as an Origami program and evaluate it.
|
|
35
|
+
const compiler = options.compiler ?? compile.program;
|
|
36
|
+
const globals = options.globals ?? (await projectGlobals());
|
|
37
|
+
|
|
38
|
+
const fn = compiler(source, {
|
|
39
|
+
globals,
|
|
40
|
+
mode: "program",
|
|
41
|
+
parent,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = await fn();
|
|
45
|
+
return result;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extension,
|
|
3
|
+
getParent,
|
|
4
|
+
toString,
|
|
5
|
+
trailingSlash,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
7
|
+
import * as compile from "../compiler/compile.js";
|
|
8
|
+
import projectGlobals from "../project/projectGlobals.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An Origami template document: a plain text file that contains Origami
|
|
12
|
+
* expressions.
|
|
13
|
+
*/
|
|
14
|
+
export default {
|
|
15
|
+
mediaType: "text/plain",
|
|
16
|
+
|
|
17
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
18
|
+
async unpack(packed, options = {}) {
|
|
19
|
+
const parent = getParent(packed, options);
|
|
20
|
+
|
|
21
|
+
// Unpack as a text document
|
|
22
|
+
const text = toString(packed);
|
|
23
|
+
|
|
24
|
+
// See if we can construct a URL to use in error messages
|
|
25
|
+
const key = options.key;
|
|
26
|
+
let url;
|
|
27
|
+
if (key && /** @type {any} */ (parent)?.url) {
|
|
28
|
+
let parentHref = /** @type {any} */ (parent).url.href;
|
|
29
|
+
if (!parentHref.endsWith("/")) {
|
|
30
|
+
parentHref += "/";
|
|
31
|
+
}
|
|
32
|
+
url = new URL(key, parentHref);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Compile the text as an Origami template document
|
|
36
|
+
const source = {
|
|
37
|
+
name: key,
|
|
38
|
+
text,
|
|
39
|
+
url,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const globals = options.globals ?? (await projectGlobals());
|
|
43
|
+
|
|
44
|
+
const defineFn = compile.templateDocument(source, {
|
|
45
|
+
front: options.front,
|
|
46
|
+
globals,
|
|
47
|
+
mode: "program",
|
|
48
|
+
parent,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Invoke the definition to get back the template function
|
|
52
|
+
const result = await defineFn();
|
|
53
|
+
|
|
54
|
+
const resultExtension = key ? extension.extname(key) : null;
|
|
55
|
+
if (resultExtension && Object.isExtensible(result)) {
|
|
56
|
+
// Add sidecar function so this template can be used in a map.
|
|
57
|
+
result.key = addExtension(resultExtension);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Return a function that adds the given extension
|
|
65
|
+
function addExtension(resultExtension) {
|
|
66
|
+
return (sourceValue, sourceKey) => {
|
|
67
|
+
if (sourceKey === undefined) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const normalizedKey = trailingSlash.remove(sourceKey);
|
|
71
|
+
const sourceExtension = extension.extname(normalizedKey);
|
|
72
|
+
const resultKey = sourceExtension
|
|
73
|
+
? extension.replace(normalizedKey, sourceExtension, resultExtension)
|
|
74
|
+
: normalizedKey + resultExtension;
|
|
75
|
+
return resultKey;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import isOrigamiFrontMatter from "../compiler/isOrigamiFrontMatter.js";
|
|
2
|
+
|
|
3
|
+
export default function parseFrontMatter(text) {
|
|
4
|
+
const regex =
|
|
5
|
+
/^(---\r?\n(?<frontText>[\s\S]*?\r?\n?)---\r?\n)(?<body>[\s\S]*$)/;
|
|
6
|
+
const match = regex.exec(text);
|
|
7
|
+
if (!match?.groups) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const isOrigami = isOrigamiFrontMatter(match.groups.frontText);
|
|
11
|
+
return {
|
|
12
|
+
body: match.groups.body,
|
|
13
|
+
frontText: match.groups.frontText,
|
|
14
|
+
isOrigami,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { js_handler as default } from "./handlers.js";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPacked,
|
|
3
|
+
symbols,
|
|
4
|
+
toPlainValue,
|
|
5
|
+
toString,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
7
|
+
import * as YAMLModule from "yaml";
|
|
8
|
+
import * as compile from "../compiler/compile.js";
|
|
9
|
+
import projectGlobals from "../project/projectGlobals.js";
|
|
10
|
+
import parseFrontMatter from "./parseFrontMatter.js";
|
|
11
|
+
|
|
12
|
+
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
13
|
+
// recognize, so we have to handle two ways to accommodate Node and the browser.
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A text file with possible front matter
|
|
19
|
+
*
|
|
20
|
+
* The unpacking process will parse out any YAML or JSON front matter and attach
|
|
21
|
+
* it to the document as data. The first line of the text must be "---",
|
|
22
|
+
* followed by a block of JSON or YAML, followed by another line of "---". Any
|
|
23
|
+
* lines following will be treated as the document text.
|
|
24
|
+
*
|
|
25
|
+
* If there is no front matter, the document will be treated as plain text and
|
|
26
|
+
* returned as a String object.
|
|
27
|
+
*
|
|
28
|
+
* If there is front matter, any Origami expressions in the front matter will be
|
|
29
|
+
* evaluated. The result will be a plain JavaScript object with the evaluated
|
|
30
|
+
* data and a `_body` property containing the document text.
|
|
31
|
+
*/
|
|
32
|
+
export default {
|
|
33
|
+
mediaType: "text/plain",
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* If the input is already in some packed format, it will be returned as is.
|
|
37
|
+
*
|
|
38
|
+
* Otherwise, the properties of the object will be formatted as YAML. If the
|
|
39
|
+
* object has a `_body` property, that will be used as the body of the text
|
|
40
|
+
* document; otherwise, an empty string will be used.
|
|
41
|
+
*
|
|
42
|
+
* @param {any} object
|
|
43
|
+
* @returns {Promise<import("@weborigami/async-tree").Packed>}
|
|
44
|
+
*/
|
|
45
|
+
async pack(object) {
|
|
46
|
+
if (isPacked(object)) {
|
|
47
|
+
return object;
|
|
48
|
+
} else if (!object || typeof object !== "object") {
|
|
49
|
+
throw new TypeError("The input to pack must be a JavaScript object.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isDocument = object._body !== undefined;
|
|
53
|
+
const text = isDocument ? object._body : toString(object);
|
|
54
|
+
|
|
55
|
+
/** @type {any} */
|
|
56
|
+
const dataWithoutText = Object.assign({}, object);
|
|
57
|
+
delete dataWithoutText._body;
|
|
58
|
+
if (Object.keys(dataWithoutText).length > 0) {
|
|
59
|
+
const serializable = await toPlainValue(dataWithoutText);
|
|
60
|
+
const yamlText = YAML.stringify(serializable);
|
|
61
|
+
const frontMatter = yamlText.trimEnd();
|
|
62
|
+
return `---\n${frontMatter}\n---\n${text}`;
|
|
63
|
+
} else {
|
|
64
|
+
return text;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
69
|
+
async unpack(packed, options = {}) {
|
|
70
|
+
const parent = options.parent ?? null;
|
|
71
|
+
const text = toString(packed);
|
|
72
|
+
if (text === null) {
|
|
73
|
+
throw new Error("Tried to treat a file as text but it wasn't text.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const parsed = parseFrontMatter(text);
|
|
77
|
+
let unpacked;
|
|
78
|
+
if (parsed) {
|
|
79
|
+
// Document object with front matter
|
|
80
|
+
const { body, frontText, isOrigami } = parsed;
|
|
81
|
+
let frontData;
|
|
82
|
+
if (isOrigami) {
|
|
83
|
+
const globals = await projectGlobals();
|
|
84
|
+
const compiled = compile.expression(frontText.trim(), {
|
|
85
|
+
globals,
|
|
86
|
+
parent,
|
|
87
|
+
});
|
|
88
|
+
frontData = await compiled();
|
|
89
|
+
} else {
|
|
90
|
+
frontData = YAML.parse(frontText);
|
|
91
|
+
}
|
|
92
|
+
unpacked = { ...frontData };
|
|
93
|
+
Object.defineProperty(unpacked, "_body", {
|
|
94
|
+
configurable: true,
|
|
95
|
+
enumerable: true,
|
|
96
|
+
value: body,
|
|
97
|
+
writable: true,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
// Plain text
|
|
101
|
+
unpacked = new String(text);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
unpacked[symbols.parent] = parent;
|
|
105
|
+
|
|
106
|
+
return unpacked;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A WebAssembly module
|
|
3
|
+
*
|
|
4
|
+
* Unpacking a WebAssembly module returns its exports.
|
|
5
|
+
*/
|
|
6
|
+
export default {
|
|
7
|
+
mediaType: "application/wasm",
|
|
8
|
+
|
|
9
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
10
|
+
async unpack(packed) {
|
|
11
|
+
const wasmModule = await WebAssembly.instantiate(packed);
|
|
12
|
+
// @ts-ignore TypeScript thinks wasmModule is already an Instance.
|
|
13
|
+
return wasmModule.instance.exports;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { symbols, toString } from "@weborigami/async-tree";
|
|
2
|
+
import * as YAMLModule from "yaml";
|
|
3
|
+
|
|
4
|
+
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
5
|
+
// recognize, so we have to handle two ways to accommodate Node and the browser.
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A YAML file
|
|
11
|
+
*
|
|
12
|
+
* Unpacking a YAML file returns the parsed data.
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
mediaType: "application/yaml",
|
|
17
|
+
|
|
18
|
+
/** @type {import("@weborigami/async-tree").UnpackFunction} */
|
|
19
|
+
unpack(packed) {
|
|
20
|
+
const yaml = toString(packed);
|
|
21
|
+
if (!yaml) {
|
|
22
|
+
throw new Error("Tried to parse something as YAML but it wasn't text.");
|
|
23
|
+
}
|
|
24
|
+
const data = YAML.parse(yaml);
|
|
25
|
+
if (data && typeof data === "object" && Object.isExtensible(data)) {
|
|
26
|
+
Object.defineProperty(data, symbols.deep, {
|
|
27
|
+
enumerable: false,
|
|
28
|
+
value: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
},
|
|
33
|
+
};
|