@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.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/ReadMe.md +3 -0
  3. package/exports/PathTransform.d.ts +5 -0
  4. package/exports/PathTransform.js +18 -0
  5. package/exports/buildExports.js +109 -0
  6. package/exports/exports.js +121 -0
  7. package/index.ts +25 -0
  8. package/package.json +40 -0
  9. package/src/builtins/!.js +21 -0
  10. package/src/builtins/@apply.js +6 -0
  11. package/src/builtins/@arrows.js +34 -0
  12. package/src/builtins/@builtins.js +18 -0
  13. package/src/builtins/@cache.js +36 -0
  14. package/src/builtins/@config.js +25 -0
  15. package/src/builtins/@copy.js +71 -0
  16. package/src/builtins/@crawl.js +507 -0
  17. package/src/builtins/@debug.js +89 -0
  18. package/src/builtins/@document.js +18 -0
  19. package/src/builtins/@equals.js +6 -0
  20. package/src/builtins/@explore.js +68 -0
  21. package/src/builtins/@false.js +1 -0
  22. package/src/builtins/@files.js +22 -0
  23. package/src/builtins/@filter.js +23 -0
  24. package/src/builtins/@globs.js +23 -0
  25. package/src/builtins/@help.js +49 -0
  26. package/src/builtins/@http.js +19 -0
  27. package/src/builtins/@https.js +19 -0
  28. package/src/builtins/@if.js +27 -0
  29. package/src/builtins/@image/format.js +5 -0
  30. package/src/builtins/@image/resize.js +5 -0
  31. package/src/builtins/@index.js +72 -0
  32. package/src/builtins/@inherited.js +17 -0
  33. package/src/builtins/@inline.js +29 -0
  34. package/src/builtins/@invoke.js +30 -0
  35. package/src/builtins/@js.js +33 -0
  36. package/src/builtins/@json.js +22 -0
  37. package/src/builtins/@loaders/css.js +4 -0
  38. package/src/builtins/@loaders/htm.js +4 -0
  39. package/src/builtins/@loaders/html.js +4 -0
  40. package/src/builtins/@loaders/js.js +14 -0
  41. package/src/builtins/@loaders/json.js +8 -0
  42. package/src/builtins/@loaders/md.js +4 -0
  43. package/src/builtins/@loaders/mjs.js +4 -0
  44. package/src/builtins/@loaders/ori.js +21 -0
  45. package/src/builtins/@loaders/orit.js +48 -0
  46. package/src/builtins/@loaders/txt.js +33 -0
  47. package/src/builtins/@loaders/xhtml.js +4 -0
  48. package/src/builtins/@loaders/yaml.js +18 -0
  49. package/src/builtins/@loaders/yml.js +4 -0
  50. package/src/builtins/@map.js +182 -0
  51. package/src/builtins/@match.js +92 -0
  52. package/src/builtins/@mdHtml.js +45 -0
  53. package/src/builtins/@new.js +6 -0
  54. package/src/builtins/@node.js +15 -0
  55. package/src/builtins/@not.js +6 -0
  56. package/src/builtins/@or.js +6 -0
  57. package/src/builtins/@ori.js +83 -0
  58. package/src/builtins/@pack.js +13 -0
  59. package/src/builtins/@parse/json.js +7 -0
  60. package/src/builtins/@parse/yaml.js +9 -0
  61. package/src/builtins/@project.js +71 -0
  62. package/src/builtins/@repeat.js +8 -0
  63. package/src/builtins/@rss.js +49 -0
  64. package/src/builtins/@scope/extend.js +22 -0
  65. package/src/builtins/@scope/get.js +25 -0
  66. package/src/builtins/@scope/invoke.js +22 -0
  67. package/src/builtins/@scope/set.js +25 -0
  68. package/src/builtins/@serve.js +74 -0
  69. package/src/builtins/@shell.js +16 -0
  70. package/src/builtins/@stdin.js +26 -0
  71. package/src/builtins/@svg.js +42 -0
  72. package/src/builtins/@tree/concat.js +21 -0
  73. package/src/builtins/@tree/count.js +24 -0
  74. package/src/builtins/@tree/defineds.js +37 -0
  75. package/src/builtins/@tree/dot.js +201 -0
  76. package/src/builtins/@tree/exceptions.js +50 -0
  77. package/src/builtins/@tree/first.js +28 -0
  78. package/src/builtins/@tree/flowSvg.js +55 -0
  79. package/src/builtins/@tree/fn.js +34 -0
  80. package/src/builtins/@tree/from.js +27 -0
  81. package/src/builtins/@tree/fromJson.js +6 -0
  82. package/src/builtins/@tree/fromYaml.js +24 -0
  83. package/src/builtins/@tree/groupBy.js +39 -0
  84. package/src/builtins/@tree/inners.js +44 -0
  85. package/src/builtins/@tree/isAsyncTree.js +17 -0
  86. package/src/builtins/@tree/keys.js +24 -0
  87. package/src/builtins/@tree/keysJson.js +44 -0
  88. package/src/builtins/@tree/map.d.ts +19 -0
  89. package/src/builtins/@tree/merge.js +47 -0
  90. package/src/builtins/@tree/mergeDeep.js +44 -0
  91. package/src/builtins/@tree/nextKey.js +29 -0
  92. package/src/builtins/@tree/parent.js +24 -0
  93. package/src/builtins/@tree/paths.js +35 -0
  94. package/src/builtins/@tree/plain.js +22 -0
  95. package/src/builtins/@tree/previousKey.js +29 -0
  96. package/src/builtins/@tree/reverse.js +51 -0
  97. package/src/builtins/@tree/setDeep.js +45 -0
  98. package/src/builtins/@tree/shuffle.js +31 -0
  99. package/src/builtins/@tree/sitemap.js +59 -0
  100. package/src/builtins/@tree/sort.js +25 -0
  101. package/src/builtins/@tree/sortBy.js +40 -0
  102. package/src/builtins/@tree/static.js +51 -0
  103. package/src/builtins/@tree/table.js +74 -0
  104. package/src/builtins/@tree/take.js +40 -0
  105. package/src/builtins/@tree/values.js +23 -0
  106. package/src/builtins/@tree/valuesDeep.js +23 -0
  107. package/src/builtins/@treeHttp.js +19 -0
  108. package/src/builtins/@treeHttps.js +19 -0
  109. package/src/builtins/@true.js +1 -0
  110. package/src/builtins/@unpack.js +13 -0
  111. package/src/builtins/@watch.js +108 -0
  112. package/src/builtins/@with.js +22 -0
  113. package/src/builtins/@yaml.js +23 -0
  114. package/src/builtins/~.js +9 -0
  115. package/src/cli/cli.js +86 -0
  116. package/src/cli/defaultModuleExport.js +16 -0
  117. package/src/cli/showUsage.js +86 -0
  118. package/src/common/CommandModulesTransform.d.ts +5 -0
  119. package/src/common/CommandModulesTransform.js +37 -0
  120. package/src/common/ConstantTree.js +17 -0
  121. package/src/common/ExplorableSiteTransform.d.ts +5 -0
  122. package/src/common/ExplorableSiteTransform.js +77 -0
  123. package/src/common/FilterTree.js +60 -0
  124. package/src/common/GlobTree.js +67 -0
  125. package/src/common/ShuffleTransform.js +29 -0
  126. package/src/common/TextDocument.js +57 -0
  127. package/src/common/addValueKeyToScope.js +30 -0
  128. package/src/common/arrowFunctionsMap.js +35 -0
  129. package/src/common/processUnpackedContent.js +39 -0
  130. package/src/common/serialize.d.ts +8 -0
  131. package/src/common/serialize.js +138 -0
  132. package/src/common/utilities.d.ts +7 -0
  133. package/src/common/utilities.js +132 -0
  134. package/src/misc/OriCommandTransform.d.ts +5 -0
  135. package/src/misc/OriCommandTransform.js +54 -0
  136. package/src/misc/assertScopeIsDefined.js +7 -0
  137. package/src/misc/explore.orit +241 -0
  138. package/src/misc/yamlOrigamiTag.js +17 -0
  139. package/src/server/mediaTypes.js +97 -0
  140. 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,5 @@
1
+ import { Mixin } from "@weborigami/language";
2
+
3
+ declare const ExplorableSiteTransform: Mixin<{}>;
4
+
5
+ export default ExplorableSiteTransform;
@@ -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;