@weborigami/origami 0.0.35
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/LICENSE +21 -0
- package/ReadMe.md +3 -0
- package/exports/PathTransform.d.ts +5 -0
- package/exports/PathTransform.js +18 -0
- package/exports/buildExports.js +109 -0
- package/exports/exports.js +121 -0
- package/index.ts +25 -0
- package/package.json +40 -0
- package/src/builtins/!.js +21 -0
- package/src/builtins/@apply.js +6 -0
- package/src/builtins/@arrows.js +34 -0
- package/src/builtins/@builtins.js +18 -0
- package/src/builtins/@cache.js +36 -0
- package/src/builtins/@config.js +25 -0
- package/src/builtins/@copy.js +71 -0
- package/src/builtins/@crawl.js +507 -0
- package/src/builtins/@debug.js +89 -0
- package/src/builtins/@document.js +18 -0
- package/src/builtins/@equals.js +6 -0
- package/src/builtins/@explore.js +68 -0
- package/src/builtins/@false.js +1 -0
- package/src/builtins/@files.js +22 -0
- package/src/builtins/@filter.js +23 -0
- package/src/builtins/@globs.js +23 -0
- package/src/builtins/@help.js +49 -0
- package/src/builtins/@http.js +19 -0
- package/src/builtins/@https.js +19 -0
- package/src/builtins/@if.js +27 -0
- package/src/builtins/@image/format.js +5 -0
- package/src/builtins/@image/resize.js +5 -0
- package/src/builtins/@index.js +72 -0
- package/src/builtins/@inherited.js +17 -0
- package/src/builtins/@inline.js +29 -0
- package/src/builtins/@invoke.js +30 -0
- package/src/builtins/@js.js +33 -0
- package/src/builtins/@json.js +22 -0
- package/src/builtins/@loaders/css.js +4 -0
- package/src/builtins/@loaders/htm.js +4 -0
- package/src/builtins/@loaders/html.js +4 -0
- package/src/builtins/@loaders/js.js +14 -0
- package/src/builtins/@loaders/json.js +8 -0
- package/src/builtins/@loaders/md.js +4 -0
- package/src/builtins/@loaders/mjs.js +4 -0
- package/src/builtins/@loaders/ori.js +21 -0
- package/src/builtins/@loaders/orit.js +48 -0
- package/src/builtins/@loaders/txt.js +33 -0
- package/src/builtins/@loaders/xhtml.js +4 -0
- package/src/builtins/@loaders/yaml.js +18 -0
- package/src/builtins/@loaders/yml.js +4 -0
- package/src/builtins/@map.js +182 -0
- package/src/builtins/@match.js +92 -0
- package/src/builtins/@mdHtml.js +45 -0
- package/src/builtins/@new.js +6 -0
- package/src/builtins/@node.js +15 -0
- package/src/builtins/@not.js +6 -0
- package/src/builtins/@or.js +6 -0
- package/src/builtins/@ori.js +83 -0
- package/src/builtins/@pack.js +13 -0
- package/src/builtins/@parse/json.js +7 -0
- package/src/builtins/@parse/yaml.js +9 -0
- package/src/builtins/@project.js +71 -0
- package/src/builtins/@repeat.js +8 -0
- package/src/builtins/@rss.js +49 -0
- package/src/builtins/@scope/extend.js +22 -0
- package/src/builtins/@scope/get.js +25 -0
- package/src/builtins/@scope/invoke.js +22 -0
- package/src/builtins/@scope/set.js +25 -0
- package/src/builtins/@serve.js +74 -0
- package/src/builtins/@shell.js +16 -0
- package/src/builtins/@stdin.js +26 -0
- package/src/builtins/@svg.js +42 -0
- package/src/builtins/@tree/concat.js +21 -0
- package/src/builtins/@tree/count.js +24 -0
- package/src/builtins/@tree/defineds.js +37 -0
- package/src/builtins/@tree/dot.js +201 -0
- package/src/builtins/@tree/exceptions.js +50 -0
- package/src/builtins/@tree/first.js +28 -0
- package/src/builtins/@tree/flowSvg.js +55 -0
- package/src/builtins/@tree/fn.js +34 -0
- package/src/builtins/@tree/from.js +27 -0
- package/src/builtins/@tree/fromJson.js +6 -0
- package/src/builtins/@tree/fromYaml.js +24 -0
- package/src/builtins/@tree/groupBy.js +39 -0
- package/src/builtins/@tree/inners.js +44 -0
- package/src/builtins/@tree/isAsyncTree.js +17 -0
- package/src/builtins/@tree/keys.js +24 -0
- package/src/builtins/@tree/keysJson.js +44 -0
- package/src/builtins/@tree/map.d.ts +19 -0
- package/src/builtins/@tree/merge.js +47 -0
- package/src/builtins/@tree/mergeDeep.js +44 -0
- package/src/builtins/@tree/nextKey.js +29 -0
- package/src/builtins/@tree/parent.js +24 -0
- package/src/builtins/@tree/paths.js +35 -0
- package/src/builtins/@tree/plain.js +22 -0
- package/src/builtins/@tree/previousKey.js +29 -0
- package/src/builtins/@tree/reverse.js +51 -0
- package/src/builtins/@tree/setDeep.js +45 -0
- package/src/builtins/@tree/shuffle.js +31 -0
- package/src/builtins/@tree/sitemap.js +59 -0
- package/src/builtins/@tree/sort.js +25 -0
- package/src/builtins/@tree/sortBy.js +40 -0
- package/src/builtins/@tree/static.js +51 -0
- package/src/builtins/@tree/table.js +74 -0
- package/src/builtins/@tree/take.js +40 -0
- package/src/builtins/@tree/values.js +23 -0
- package/src/builtins/@tree/valuesDeep.js +23 -0
- package/src/builtins/@treeHttp.js +19 -0
- package/src/builtins/@treeHttps.js +19 -0
- package/src/builtins/@true.js +1 -0
- package/src/builtins/@unpack.js +13 -0
- package/src/builtins/@watch.js +108 -0
- package/src/builtins/@with.js +22 -0
- package/src/builtins/@yaml.js +23 -0
- package/src/builtins/~.js +9 -0
- package/src/cli/cli.js +86 -0
- package/src/cli/defaultModuleExport.js +16 -0
- package/src/cli/showUsage.js +86 -0
- package/src/common/CommandModulesTransform.d.ts +5 -0
- package/src/common/CommandModulesTransform.js +37 -0
- package/src/common/ConstantTree.js +17 -0
- package/src/common/ExplorableSiteTransform.d.ts +5 -0
- package/src/common/ExplorableSiteTransform.js +77 -0
- package/src/common/FilterTree.js +60 -0
- package/src/common/GlobTree.js +67 -0
- package/src/common/ShuffleTransform.js +29 -0
- package/src/common/TextDocument.js +57 -0
- package/src/common/addValueKeyToScope.js +30 -0
- package/src/common/arrowFunctionsMap.js +35 -0
- package/src/common/processUnpackedContent.js +39 -0
- package/src/common/serialize.d.ts +8 -0
- package/src/common/serialize.js +138 -0
- package/src/common/utilities.d.ts +7 -0
- package/src/common/utilities.js +132 -0
- package/src/misc/OriCommandTransform.d.ts +5 -0
- package/src/misc/OriCommandTransform.js +54 -0
- package/src/misc/assertScopeIsDefined.js +7 -0
- package/src/misc/explore.orit +241 -0
- package/src/misc/yamlOrigamiTag.js +17 -0
- package/src/server/mediaTypes.js +97 -0
- package/src/server/server.js +258 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This mixin can be used to turn a collection of .js modules in a folder into a collection
|
|
5
|
+
* of commands. For every module `foo.js`, the tree will expose a key `foo` with the value
|
|
6
|
+
* of the module's export(s).
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {import("../../index.ts").Constructor<AsyncTree & { import: function }>} BaseConstructor
|
|
10
|
+
* @param {BaseConstructor} Base
|
|
11
|
+
*/
|
|
12
|
+
export default function CommandsModulesTransform(Base) {
|
|
13
|
+
return class CommandModules extends Base {
|
|
14
|
+
async get(key) {
|
|
15
|
+
const value = await super.get(key);
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// See if we have a JS module for the requested key.
|
|
21
|
+
if (key === undefined || key.endsWith?.(".js")) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const moduleKey = `${key}.js`;
|
|
26
|
+
return this.import?.(moduleKey);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async keys() {
|
|
30
|
+
const keys = [...(await super.keys())];
|
|
31
|
+
// If we find a key like "foo.js", then return "foo" as the key.
|
|
32
|
+
return keys.map((key) =>
|
|
33
|
+
key.endsWith(".js") ? path.basename(key, ".js") : key
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
3
|
+
* @implements {AsyncTree}
|
|
4
|
+
*/
|
|
5
|
+
export default class ConstantTree {
|
|
6
|
+
constructor(value) {
|
|
7
|
+
this.value = value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async get(key) {
|
|
11
|
+
return this.value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async keys() {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Tree, keysJson } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import index from "../builtins/@index.js";
|
|
4
|
+
import { isTransformApplied, transformObject } from "../common/utilities.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a tree (typically a SiteTree) to turn a standard site into an
|
|
8
|
+
* explorable site.
|
|
9
|
+
*
|
|
10
|
+
* An explorable site follows three conventions:
|
|
11
|
+
* 1. if route /foo has any resources beneath it (/foo/bar.jpg), then /foo
|
|
12
|
+
* redirects to /foo/
|
|
13
|
+
* 2. /foo/ is a synonym for foo/index.html
|
|
14
|
+
* 3. /foo/.keys.json returns the public keys below foo/
|
|
15
|
+
*
|
|
16
|
+
* The first convention is handled by the Tree Origami server. This transform
|
|
17
|
+
* handles the second and third conventions.
|
|
18
|
+
*
|
|
19
|
+
* As a convenience, this transform also provides a default index.html page if
|
|
20
|
+
* the tree doesn't define one.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
23
|
+
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
24
|
+
* @param {AsyncTreeConstructor} Base
|
|
25
|
+
*/
|
|
26
|
+
export default function ExplorableSiteTransform(Base) {
|
|
27
|
+
return class ExplorableSite extends Base {
|
|
28
|
+
async get(key) {
|
|
29
|
+
// The empty string key represents "index.html".
|
|
30
|
+
if (key === "") {
|
|
31
|
+
key = "index.html";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Ask the tree if it has the key.
|
|
35
|
+
let value = await super.get(key);
|
|
36
|
+
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
// The tree doesn't have the key; try the defaults.
|
|
39
|
+
const scope = Scope.getScope(this);
|
|
40
|
+
if (key === "index.html") {
|
|
41
|
+
value = await index.call(scope, this);
|
|
42
|
+
} else if (key === ".keys.json") {
|
|
43
|
+
value = await keysJson.stringify(this);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Ensure this transform is applied to any explorable result. This lets
|
|
48
|
+
// the user browse into data and explorable trees of types other than the
|
|
49
|
+
// current class.
|
|
50
|
+
if (
|
|
51
|
+
Tree.isAsyncTree(value) &&
|
|
52
|
+
!isTransformApplied(ExplorableSiteTransform, value)
|
|
53
|
+
) {
|
|
54
|
+
value = transformObject(ExplorableSiteTransform, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value?.unpack) {
|
|
58
|
+
// If the value isn't a tree, but has a tree attached via a `unpack`
|
|
59
|
+
// method, wrap the unpack method to add this transform.
|
|
60
|
+
const original = value.unpack.bind(value);
|
|
61
|
+
value.unpack = async () => {
|
|
62
|
+
const content = await original();
|
|
63
|
+
if (!Tree.isTreelike(content)) {
|
|
64
|
+
return content;
|
|
65
|
+
}
|
|
66
|
+
/** @type {any} */
|
|
67
|
+
let tree = Tree.from(content);
|
|
68
|
+
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
|
69
|
+
tree = transformObject(ExplorableSiteTransform, tree);
|
|
70
|
+
}
|
|
71
|
+
return tree;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
5
|
+
* @implements {AsyncTree}
|
|
6
|
+
*/
|
|
7
|
+
export default class FilterTree {
|
|
8
|
+
constructor(tree, filter) {
|
|
9
|
+
this.tree = Tree.from(tree);
|
|
10
|
+
this.filter = Tree.from(filter);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async get(key) {
|
|
14
|
+
let value = await this.tree.get(key);
|
|
15
|
+
|
|
16
|
+
let filterValue = await this.filter.get(key);
|
|
17
|
+
if (!Tree.isAsyncTree(value)) {
|
|
18
|
+
if (filterValue === undefined) {
|
|
19
|
+
value = undefined;
|
|
20
|
+
} else if (Tree.isAsyncTree(filterValue)) {
|
|
21
|
+
value = undefined;
|
|
22
|
+
}
|
|
23
|
+
} else if (Tree.isAsyncTree(filterValue)) {
|
|
24
|
+
// Wrap value with corresponding filter.
|
|
25
|
+
value = Reflect.construct(this.constructor, [value, filterValue]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async keys() {
|
|
32
|
+
const keys = new Set();
|
|
33
|
+
|
|
34
|
+
// Enumerate all keys in the tree that can be found in the filter tree.
|
|
35
|
+
for (const key of await this.tree.keys()) {
|
|
36
|
+
const filterValue = await this.filter.get(key);
|
|
37
|
+
const isFilterValueTree = Tree.isAsyncTree(filterValue);
|
|
38
|
+
// If the filter value is a tree, the corresponding value in the tree
|
|
39
|
+
// must be a tree too.
|
|
40
|
+
const match =
|
|
41
|
+
(!isFilterValueTree && filterValue) ||
|
|
42
|
+
(isFilterValueTree && (await Tree.isKeyForSubtree(this.tree, key)));
|
|
43
|
+
if (match) {
|
|
44
|
+
keys.add(key);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Also include any keys in the filter that are found in the tree. This
|
|
49
|
+
// lets the filter "pull" values from a tree that, e.g., is defined by a
|
|
50
|
+
// function without an explicit domain.
|
|
51
|
+
for (const key of await this.filter.keys()) {
|
|
52
|
+
const value = await this.tree.get(key);
|
|
53
|
+
if (value !== undefined) {
|
|
54
|
+
keys.add(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return keys;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ObjectTree, Tree, merge } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
const globstar = "**";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
7
|
+
* @implements {AsyncTree}
|
|
8
|
+
*/
|
|
9
|
+
export default class GlobTree {
|
|
10
|
+
constructor(globs) {
|
|
11
|
+
this.globs = Tree.from(globs);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async get(key) {
|
|
15
|
+
if (typeof key !== "string") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
let value = await matchGlobs(this.globs, key);
|
|
19
|
+
if (Tree.isAsyncTree(value)) {
|
|
20
|
+
value = Reflect.construct(this.constructor, [value]);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async keys() {
|
|
26
|
+
return this.globs.keys();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function matchGlob(glob, text) {
|
|
31
|
+
// Convert the glob to a regular expression
|
|
32
|
+
const regexText = glob
|
|
33
|
+
// Escape special regex characters
|
|
34
|
+
.replace(/[+?^${}()|\[\]\\]/g, "\\$&")
|
|
35
|
+
// Replace the glob wildcards with regex wildcards
|
|
36
|
+
.replace(/\*/g, ".+")
|
|
37
|
+
.replace(/\?/g, ".");
|
|
38
|
+
const regex = new RegExp(`^${regexText}$`);
|
|
39
|
+
return regex.test(text);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function matchGlobs(globs, text) {
|
|
43
|
+
let value;
|
|
44
|
+
for (const glob of await globs.keys()) {
|
|
45
|
+
if (typeof glob !== "string") {
|
|
46
|
+
continue;
|
|
47
|
+
} else if (glob !== globstar && matchGlob(glob, text)) {
|
|
48
|
+
value = await globs.get(glob);
|
|
49
|
+
if (value !== undefined) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const globstarGlobs = await globs.get(globstar);
|
|
56
|
+
if (globstarGlobs) {
|
|
57
|
+
const globstarTree = new ObjectTree({ [globstar]: globstarGlobs });
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
const globstarValue = await matchGlobs(globstarGlobs, text);
|
|
60
|
+
value = globstarValue !== undefined ? globstarValue : globstarTree;
|
|
61
|
+
} else if (Tree.isAsyncTree(value)) {
|
|
62
|
+
value = merge(value, globstarTree);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
3
|
+
* @typedef {import("../../index.ts").Constructor<AsyncTree>} AsyncTreeConstructor
|
|
4
|
+
* @param {AsyncTreeConstructor} Base
|
|
5
|
+
*/
|
|
6
|
+
export default function ShuffleTransform(Base) {
|
|
7
|
+
return class Shuffle extends Base {
|
|
8
|
+
async keys() {
|
|
9
|
+
const keys = Array.from(await super.keys());
|
|
10
|
+
shuffle(keys);
|
|
11
|
+
return keys;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Shuffle an array.
|
|
18
|
+
*
|
|
19
|
+
* Performs a Fisher-Yates shuffle. From http://sedition.com/perl/javascript-fy.html
|
|
20
|
+
*/
|
|
21
|
+
function shuffle(array) {
|
|
22
|
+
var i = array.length;
|
|
23
|
+
while (--i >= 0) {
|
|
24
|
+
var j = Math.floor(Math.random() * (i + 1));
|
|
25
|
+
var temp = array[i];
|
|
26
|
+
array[i] = array[j];
|
|
27
|
+
array[j] = temp;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isStringLike } from "@weborigami/async-tree";
|
|
2
|
+
import { toYaml } from "./serialize.js";
|
|
3
|
+
import * as utilities from "./utilities.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A text document is any object with a `@text` property and a `toString()`
|
|
7
|
+
* method that returns that text. This class is a helper for constructing such
|
|
8
|
+
* text documents.
|
|
9
|
+
*/
|
|
10
|
+
export default class TextDocument {
|
|
11
|
+
/**
|
|
12
|
+
* The `input` parameter can be anything that can be converted to a string.
|
|
13
|
+
* The optional `data` parameter can be any object; if the object is a plain
|
|
14
|
+
* object, its properties will be copied to the new document; otherwise, that
|
|
15
|
+
* parameter is ignored.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {import("@weborigami/types").AsyncTree|null} AsyncTree
|
|
18
|
+
*
|
|
19
|
+
* @param {any} [data]
|
|
20
|
+
* @param {AsyncTree} [parent]
|
|
21
|
+
*/
|
|
22
|
+
constructor(data, parent) {
|
|
23
|
+
Object.assign(this, data);
|
|
24
|
+
if (parent) {
|
|
25
|
+
this[utilities.parentSymbol] = parent;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static from(input, parent) {
|
|
30
|
+
if (input["@text"]) {
|
|
31
|
+
return input;
|
|
32
|
+
} else if (isStringLike(input)) {
|
|
33
|
+
const text = utilities.toString(input);
|
|
34
|
+
return new TextDocument({ "@text": text }, parent);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render the text and data as a document with YAML front matter.
|
|
40
|
+
*/
|
|
41
|
+
async pack() {
|
|
42
|
+
const text = this["@text"];
|
|
43
|
+
/** @type {any} */
|
|
44
|
+
const dataWithoutText = Object.assign({}, this);
|
|
45
|
+
delete dataWithoutText["@text"];
|
|
46
|
+
if (Object.keys(dataWithoutText).length > 0) {
|
|
47
|
+
const frontMatter = (await toYaml(dataWithoutText)).trimEnd();
|
|
48
|
+
return `---\n${frontMatter}\n---\n${text}`;
|
|
49
|
+
} else {
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toString() {
|
|
55
|
+
return this["@text"];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Scope } from "@weborigami/language";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A number of transforms accept functions that can accept a single value. This
|
|
5
|
+
* helper adds the value and key to the scope as ambients.
|
|
6
|
+
*
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {import("../../index.js").Invocable} Invocable
|
|
10
|
+
*
|
|
11
|
+
* @param {AsyncTree|null} scope
|
|
12
|
+
* @param {any} value
|
|
13
|
+
* @param {any} key
|
|
14
|
+
* @param {string} [valueName]
|
|
15
|
+
* @param {string} [keyName]
|
|
16
|
+
*/
|
|
17
|
+
export default function addValueKeyToScope(
|
|
18
|
+
scope,
|
|
19
|
+
value,
|
|
20
|
+
key,
|
|
21
|
+
valueName = "_",
|
|
22
|
+
keyName = "@key"
|
|
23
|
+
) {
|
|
24
|
+
// Add the key and value to the scope as ambients.
|
|
25
|
+
const ambients = {
|
|
26
|
+
[keyName]: key,
|
|
27
|
+
[valueName]: value,
|
|
28
|
+
};
|
|
29
|
+
return new Scope(ambients, scope);
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cachedKeyMaps, map } from "@weborigami/async-tree";
|
|
2
|
+
import { toFunction } from "./utilities.js";
|
|
3
|
+
|
|
4
|
+
export default function arrowFunctionsMap() {
|
|
5
|
+
const deep = true;
|
|
6
|
+
return map({
|
|
7
|
+
deep,
|
|
8
|
+
description: "arrowFunctions",
|
|
9
|
+
valueMap,
|
|
10
|
+
...cachedKeyMaps(keyMap, deep),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function keyMap(sourceKey, tree) {
|
|
15
|
+
return parseArrowKey(sourceKey) ?? sourceKey;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// If the key is of the form "lhs←rhs", return "lhs".
|
|
19
|
+
// Whitespace between the lhs and the arrow is ignored.
|
|
20
|
+
function parseArrowKey(sourceKey) {
|
|
21
|
+
const regex = /^(?<lhs>.+?)\s*←.+$/;
|
|
22
|
+
const match = sourceKey.match(regex);
|
|
23
|
+
return match?.groups.lhs;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function valueMap(sourceValue, sourceKey, tree) {
|
|
27
|
+
let resultValue;
|
|
28
|
+
if (parseArrowKey(sourceKey)) {
|
|
29
|
+
// Treat the value as a function to be invoked.
|
|
30
|
+
resultValue = toFunction(sourceValue);
|
|
31
|
+
} else {
|
|
32
|
+
resultValue = sourceValue;
|
|
33
|
+
}
|
|
34
|
+
return resultValue;
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import builtins from "../builtins/@builtins.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Perform any necessary post-processing on the unpacked content of a file. This
|
|
7
|
+
* lets treat the contents of various file types consistently.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
*
|
|
11
|
+
* @param {any} content
|
|
12
|
+
* @param {AsyncTree|null} parent
|
|
13
|
+
* @param {any} [attachedData]
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
export default function processUnpackedContent(content, parent, attachedData) {
|
|
17
|
+
if (typeof content === "function") {
|
|
18
|
+
// Wrap the function such to add ambients to the scope.
|
|
19
|
+
const fn = content;
|
|
20
|
+
|
|
21
|
+
// Use the parent's scope, adding any attached data.
|
|
22
|
+
const parentScope = parent ? Scope.getScope(parent) : builtins;
|
|
23
|
+
const extendedScope = new Scope(attachedData, parentScope);
|
|
24
|
+
|
|
25
|
+
/** @this {AsyncTree|null} */
|
|
26
|
+
async function extendScope(input, ...rest) {
|
|
27
|
+
return fn.call(extendedScope, input, ...rest);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
extendScope.code = fn.code;
|
|
31
|
+
return extendScope;
|
|
32
|
+
} else if (Tree.isAsyncTree(content)) {
|
|
33
|
+
const result = Object.create(content);
|
|
34
|
+
result.parent = parent;
|
|
35
|
+
return result;
|
|
36
|
+
} else {
|
|
37
|
+
return content;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AsyncTree } from "@weborigami/types";
|
|
2
|
+
import type { JsonValue } from "../../index.ts";
|
|
3
|
+
|
|
4
|
+
export function evaluateYaml(text: string, parent?: AsyncTree|null): Promise<JsonValue>;
|
|
5
|
+
export function parseYaml(text: string): JsonValue|AsyncTree;
|
|
6
|
+
export function toJson(obj: JsonValue | AsyncTree): Promise<string>;
|
|
7
|
+
export function toJsonValue(obj: any): Promise<JsonValue>;
|
|
8
|
+
export function toYaml(obj: JsonValue | AsyncTree): Promise<string>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../../index.ts").JsonValue} JsonValue
|
|
3
|
+
* @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
|
|
4
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
5
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Tree, isPlainObject, isStringLike } from "@weborigami/async-tree";
|
|
9
|
+
import { OrigamiTree } from "@weborigami/language";
|
|
10
|
+
import * as YAMLModule from "yaml";
|
|
11
|
+
import yamlOrigamiTag from "../misc/yamlOrigamiTag.js";
|
|
12
|
+
|
|
13
|
+
const textDecoder = new TextDecoder();
|
|
14
|
+
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
15
|
+
|
|
16
|
+
// The "yaml" package doesn't seem to provide a default export that the browser can
|
|
17
|
+
// recognize, so we have to handle two ways to accommodate Node and the browser.
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
const YAML = YAMLModule.default ?? YAMLModule.YAML;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {string} text
|
|
24
|
+
* @param {AsyncTree|null} [parent]
|
|
25
|
+
*/
|
|
26
|
+
export async function evaluateYaml(text, parent) {
|
|
27
|
+
const data = parseYaml(String(text));
|
|
28
|
+
if (Tree.isAsyncTree(data)) {
|
|
29
|
+
data.parent = parent;
|
|
30
|
+
return Tree.plain(data);
|
|
31
|
+
} else {
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {any} obj
|
|
38
|
+
* @returns {obj is JsonValue}
|
|
39
|
+
*/
|
|
40
|
+
function isJsonValue(obj) {
|
|
41
|
+
const t = typeof obj;
|
|
42
|
+
return (
|
|
43
|
+
t === "boolean" ||
|
|
44
|
+
t === "number" ||
|
|
45
|
+
t === "string" ||
|
|
46
|
+
obj instanceof Date ||
|
|
47
|
+
obj === null
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Return true if the given object has any functions in it.
|
|
52
|
+
function objectContainsFunctions(obj) {
|
|
53
|
+
for (const key in obj) {
|
|
54
|
+
const value = obj[key];
|
|
55
|
+
if (typeof value === "function") {
|
|
56
|
+
return true;
|
|
57
|
+
} else if (isPlainObject(value)) {
|
|
58
|
+
const valueContainsExpression = objectContainsFunctions(value);
|
|
59
|
+
if (valueContainsExpression) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} text
|
|
69
|
+
* @returns {JsonValue|AsyncTree}
|
|
70
|
+
*/
|
|
71
|
+
export function parseYaml(text) {
|
|
72
|
+
const data = YAML.parse(text, {
|
|
73
|
+
customTags: [yamlOrigamiTag],
|
|
74
|
+
});
|
|
75
|
+
if (objectContainsFunctions(data)) {
|
|
76
|
+
return new OrigamiTree(data);
|
|
77
|
+
} else {
|
|
78
|
+
return data;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Serializes an object as a JSON string.
|
|
84
|
+
*
|
|
85
|
+
* @param {any} obj
|
|
86
|
+
*/
|
|
87
|
+
export async function toJson(obj) {
|
|
88
|
+
const serializable = await toJsonValue(obj);
|
|
89
|
+
return JSON.stringify(serializable, null, 2);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Convert the given object to a corresponding JSON value that can be serialized
|
|
94
|
+
* as JSON or YAML.
|
|
95
|
+
*
|
|
96
|
+
* If the object is already a JSON value, it is returned as is.
|
|
97
|
+
*
|
|
98
|
+
* If the object implements the `pack()` method, that method's result will be
|
|
99
|
+
* returned.
|
|
100
|
+
*
|
|
101
|
+
* If the object is treelike, it will be converted to a plain JavaScript
|
|
102
|
+
* object, recursively traversing the tree and converting all values to native
|
|
103
|
+
* types.
|
|
104
|
+
*
|
|
105
|
+
* If the object has a `valueOf()` or `toString()` method, that method's result
|
|
106
|
+
* will be returned.
|
|
107
|
+
*
|
|
108
|
+
* @param {any} object
|
|
109
|
+
* @returns {Promise<JsonValue>}
|
|
110
|
+
*/
|
|
111
|
+
export async function toJsonValue(object) {
|
|
112
|
+
if (isJsonValue(object)) {
|
|
113
|
+
return object;
|
|
114
|
+
} else if (object && typeof object.pack === "function") {
|
|
115
|
+
return object.pack();
|
|
116
|
+
} else if (isStringLike(object) && !(object instanceof Array)) {
|
|
117
|
+
return String(object);
|
|
118
|
+
} else if (Tree.isTreelike(object)) {
|
|
119
|
+
const mapped = await Tree.map(object, (value) => toJsonValue(value));
|
|
120
|
+
return Tree.plain(mapped);
|
|
121
|
+
} else if (object instanceof ArrayBuffer || object instanceof TypedArray) {
|
|
122
|
+
// Serialize data as UTF-8.
|
|
123
|
+
return textDecoder.decode(object);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw new TypeError("Couldn't serialize object");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Serializes an object as a JSON string.
|
|
131
|
+
*
|
|
132
|
+
* @param {any} obj
|
|
133
|
+
* @returns {Promise<string>}
|
|
134
|
+
*/
|
|
135
|
+
export async function toYaml(obj) {
|
|
136
|
+
const serializable = await toJsonValue(obj);
|
|
137
|
+
return YAML.stringify(serializable);
|
|
138
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
export const keySymbol: unique symbol;
|
|
3
|
+
export const parentSymbol: unique symbol;
|
|
4
|
+
export function isTransformApplied(Transform: Function, object: any): boolean;
|
|
5
|
+
export function toFunction(object: any): Function;
|
|
6
|
+
export function toString(object: any): string|null;
|
|
7
|
+
export function transformObject(Transform: Function, object: any): any;
|