@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,182 @@
1
+ import {
2
+ cachedKeyMaps,
3
+ keyMapsForExtensions,
4
+ map,
5
+ Tree,
6
+ } from "@weborigami/async-tree";
7
+ import { Scope } from "@weborigami/language";
8
+ import addValueKeyToScope from "../common/addValueKeyToScope.js";
9
+ import { toFunction } from "../common/utilities.js";
10
+
11
+ /**
12
+ * Map a hierarchical tree of keys and values to a new tree of keys and values.
13
+ *
14
+ * @typedef {import("@weborigami/async-tree").KeyFn} KeyFn
15
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
16
+ * @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
17
+ * @typedef {import("@weborigami/async-tree").TreeTransform} TreeTransform
18
+ * @typedef {import("../../index.ts").TreelikeTransform} TreelikeTransform
19
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
20
+ *
21
+ * @typedef {{ deep?: boolean, description?: string, extensions?: string,
22
+ * inverseKeyMap?: KeyFn, keyMap?: ValueKeyFn, keyName?: string, valueMap?:
23
+ * ValueKeyFn, valueName?: string }} TreeMapOptions
24
+ *
25
+ * @this {import("@weborigami/types").AsyncTree|null}
26
+ *
27
+ * @overload
28
+ * @param {ValueKeyFn} param1
29
+ * @returns {TreelikeTransform}
30
+ *
31
+ * @overload
32
+ * @param {TreeMapOptions} param1
33
+ * @returns {TreelikeTransform}
34
+ *
35
+ * @overload
36
+ * @param {Treelike} param1
37
+ * @param {ValueKeyFn} param2
38
+ * @returns {AsyncTree}
39
+ *
40
+ * @overload
41
+ * @param {Treelike} param1
42
+ * @param {TreeMapOptions} param2
43
+ * @returns {AsyncTree}
44
+ */
45
+ export default function treeMap(param1, param2) {
46
+ // Identify whether the valueMap/options are the first parameter
47
+ // or the second.
48
+ let source;
49
+ let options;
50
+ if (param2 === undefined) {
51
+ options = param1;
52
+ } else {
53
+ source = param1;
54
+ options = param2;
55
+ }
56
+
57
+ // Identify whether the valueMap/options is a valueMap function
58
+ // or an options dictionary.
59
+ let valueMap;
60
+ if (
61
+ typeof options === "function" ||
62
+ typeof (/** @type {any} */ (options)?.unpack) === "function"
63
+ ) {
64
+ valueMap = options;
65
+ options = {};
66
+ } else {
67
+ valueMap = options.valueMap;
68
+ }
69
+
70
+ let {
71
+ deep,
72
+ description,
73
+ extensions,
74
+ inverseKeyMap,
75
+ keyMap,
76
+ keyName,
77
+ valueName,
78
+ } = options;
79
+
80
+ description ??= `@map ${extensions ?? ""}`;
81
+
82
+ if (extensions && (keyMap || inverseKeyMap)) {
83
+ throw new TypeError(
84
+ `@map: You can't specify both extensions and a keyMap or inverseKeyMap`
85
+ );
86
+ }
87
+
88
+ const baseScope = Scope.getScope(this);
89
+
90
+ // Extend the value function to include the value and key in scope.
91
+ let extendedValueFn;
92
+ if (valueMap) {
93
+ const resolvedValueFn = toFunction(valueMap);
94
+ extendedValueFn = function (sourceValue, sourceKey, tree) {
95
+ const scope = addValueKeyToScope(
96
+ baseScope,
97
+ sourceValue,
98
+ sourceKey,
99
+ valueName,
100
+ keyName
101
+ );
102
+ return resolvedValueFn.call(scope, sourceValue, sourceKey, tree);
103
+ };
104
+ }
105
+
106
+ // Extend the key function to include the value and key in scope.
107
+ let extendedKeyMap;
108
+ let extendedInnerKeyMap;
109
+ if (extensions) {
110
+ let { resultExtension, sourceExtension } = parseExtensions(extensions);
111
+ const keyFns = keyMapsForExtensions({
112
+ resultExtension,
113
+ sourceExtension,
114
+ });
115
+ extendedKeyMap = keyFns.keyMap;
116
+ extendedInnerKeyMap = keyFns.inverseKeyMap;
117
+ } else if (keyMap) {
118
+ const resolvedKeyFn = toFunction(keyMap);
119
+ async function scopedKeyFn(sourceKey, tree) {
120
+ const sourceValue = await tree.get(sourceKey);
121
+ const scope = addValueKeyToScope(
122
+ baseScope,
123
+ sourceValue,
124
+ sourceKey,
125
+ valueName,
126
+ keyName
127
+ );
128
+ const resultKey = await resolvedKeyFn.call(
129
+ scope,
130
+ sourceValue,
131
+ sourceKey,
132
+ tree
133
+ );
134
+ return resultKey;
135
+ }
136
+ const keyFns = cachedKeyMaps(scopedKeyFn);
137
+ extendedKeyMap = keyFns.keyMap;
138
+ extendedInnerKeyMap = keyFns.inverseKeyMap;
139
+ }
140
+
141
+ const transform = function mapTreelike(treelike) {
142
+ const tree = Tree.from(treelike);
143
+ return map({
144
+ deep,
145
+ description,
146
+ inverseKeyMap: extendedInnerKeyMap,
147
+ keyMap: extendedKeyMap,
148
+ valueMap: extendedValueFn,
149
+ })(tree);
150
+ };
151
+
152
+ return source ? transform(source) : transform;
153
+ }
154
+
155
+ /**
156
+ * Given a string specifying an extension or a mapping of one extension to another,
157
+ * return the source and result extensions.
158
+ *
159
+ * Syntax:
160
+ * foo
161
+ * foo→bar Unicode Rightwards Arrow
162
+ * foo->bar hyphen and greater-than sign
163
+ *
164
+ * @param {string} specifier
165
+ */
166
+ function parseExtensions(specifier) {
167
+ const lowercase = specifier?.toLowerCase() ?? "";
168
+ const extensionRegex =
169
+ /^\.?(?<sourceExtension>\S*)(?:\s*(→|->)\s*)\.?(?<extension>\S+)$/;
170
+ let resultExtension;
171
+ let sourceExtension;
172
+ const match = lowercase.match(extensionRegex);
173
+ if (match?.groups) {
174
+ // foo→bar
175
+ ({ extension: resultExtension, sourceExtension } = match.groups);
176
+ } else {
177
+ // foo
178
+ resultExtension = lowercase;
179
+ sourceExtension = lowercase;
180
+ }
181
+ return { resultExtension, sourceExtension };
182
+ }
@@ -0,0 +1,92 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+
5
+ /**
6
+ * Return a tree with the indicated keys (if provided).
7
+ *
8
+ * The pattern can a string with a simplified pattern syntax that tries to match
9
+ * against the entire key and uses brackets to identify named wildcard values.
10
+ * E.g. `[name].html` will match `Alice.html` with wildcard values { name:
11
+ * "Alice" }.
12
+ *
13
+ * The pattern can also be a JavaScript regular expression.
14
+ *
15
+ * If a key is requested, match against the given pattern and, if matches,
16
+ * incorporate the matched pattern's wildcard values into the scope and invoke
17
+ * the indicated function to produce a result.
18
+ *
19
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
20
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
21
+ * @typedef {import("../../index.ts").Invocable} Invocable
22
+ *
23
+ * @param {string|RegExp} pattern
24
+ * @param {Invocable} resultFn
25
+ * @param {Treelike} [keys]
26
+ * @this {AsyncTree|null}
27
+ */
28
+ export default function match(pattern, resultFn, keys = []) {
29
+ assertScopeIsDefined(this);
30
+ let regex;
31
+ if (typeof pattern === "string") {
32
+ // Convert the simple pattern format into a regular expression.
33
+ const regexText = pattern.replace(
34
+ /\[(?<variable>.+)\]/g,
35
+ (match, p1, offset, string, groups) => `(?<${groups.variable}>.+)`
36
+ );
37
+ regex = new RegExp(`^${regexText}$`);
38
+ } else if (pattern instanceof RegExp) {
39
+ regex = pattern;
40
+ } else {
41
+ throw new Error(`match(): Unsupported pattern`);
42
+ }
43
+
44
+ // Remember the scope used to invoke this function so we can extend it below.
45
+ const scope = this;
46
+
47
+ /** @type {AsyncTree} */
48
+ let result = {
49
+ async get(key) {
50
+ const keyMatch = regex.exec(key);
51
+ if (!keyMatch) {
52
+ return undefined;
53
+ }
54
+
55
+ if (
56
+ typeof resultFn !== "function" &&
57
+ !(Tree.isAsyncTree(resultFn) && "parent" in resultFn)
58
+ ) {
59
+ // Simple return value; return as is
60
+ return resultFn;
61
+ }
62
+
63
+ // If the pattern contained named wildcards, extend the scope. It appears
64
+ // that the `groups` property of a match is *not* a real plain object, so
65
+ // we have to make one.
66
+ const bindings = keyMatch.groups
67
+ ? Object.fromEntries(Object.entries(keyMatch.groups))
68
+ : null;
69
+ const fnScope = bindings ? new Scope(bindings, scope) : scope;
70
+
71
+ // Invoke the result function with the extended scope.
72
+ let value;
73
+ if (typeof resultFn === "function") {
74
+ value = await resultFn.call(fnScope);
75
+ } else {
76
+ value = Object.create(resultFn);
77
+ }
78
+
79
+ return value;
80
+ },
81
+
82
+ async keys() {
83
+ return typeof keys === "function" ? await keys.call(scope) : keys;
84
+ },
85
+ };
86
+
87
+ result = Scope.treeWithScope(result, this);
88
+ return result;
89
+ }
90
+
91
+ match.usage = `@match <pattern>, <fn>, [<keys>]\tMatches simple patterns or regular expressions`;
92
+ match.documentation = "https://graphorigami.org/language/@match.html";
@@ -0,0 +1,45 @@
1
+ import highlight from "highlight.js";
2
+ import { marked } from "marked";
3
+ import { gfmHeadingId as markedGfmHeadingId } from "marked-gfm-heading-id";
4
+ import { markedHighlight } from "marked-highlight";
5
+ import { markedSmartypants } from "marked-smartypants";
6
+
7
+ marked.use(
8
+ markedGfmHeadingId(),
9
+ markedHighlight({
10
+ highlight(code, lang) {
11
+ const language = highlight.getLanguage(lang) ? lang : "plaintext";
12
+ return highlight.highlight(code, { language }).value;
13
+ },
14
+
15
+ langPrefix: "hljs language-",
16
+ }),
17
+ markedSmartypants(),
18
+ {
19
+ gfm: true, // Use GitHub-flavored markdown.
20
+ // @ts-ignore
21
+ mangle: false,
22
+ }
23
+ );
24
+
25
+ /**
26
+ * @typedef {import("@weborigami/async-tree").StringLike} StringLike
27
+ * @typedef {import("@weborigami/async-tree").Unpackable<StringLike>} UnpackableStringlike
28
+ *
29
+ * @this {import("@weborigami/types").AsyncTree|null|void}
30
+ * @param {StringLike|UnpackableStringlike} input
31
+ */
32
+ export default async function mdHtml(input) {
33
+ if (/** @type {any} */ (input).unpack) {
34
+ input = await /** @type {any} */ (input).unpack();
35
+ }
36
+ const inputDocument = input["@text"] ? input : null;
37
+ const markdown = inputDocument?.["@text"] ?? String(input);
38
+ const html = marked(markdown);
39
+ return inputDocument
40
+ ? Object.assign({}, inputDocument, { "@text": html })
41
+ : html;
42
+ }
43
+
44
+ mdHtml.usage = `@mdHtml <markdown>\tRender the markdown text as HTML`;
45
+ mdHtml.documentation = "https://graphorigami.org/language/@mdHtml.html";
@@ -0,0 +1,6 @@
1
+ export default function newBuiltin(constructor, ...args) {
2
+ return Reflect.construct(constructor, args);
3
+ }
4
+
5
+ newBuiltin.usage = "@new <classFn>\tCreate a new instance of the given class";
6
+ newBuiltin.documentation = "https://graphorigami.org/language/@new.html";
@@ -0,0 +1,15 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import url from "node:url";
4
+
5
+ const node = {
6
+ Buffer,
7
+ path,
8
+ process,
9
+ url,
10
+ };
11
+
12
+ node.usage = "@node\tAccess Node classes and utility functions";
13
+ node.documentation = "https://graphorigami.org/language/@node.html";
14
+
15
+ export default node;
@@ -0,0 +1,6 @@
1
+ export default function not(value) {
2
+ return !value;
3
+ }
4
+
5
+ not.usage = `@not <value>\tThe logical inverse of the value`;
6
+ not.documentation = "https://graphorigami.org/language/@not.html";
@@ -0,0 +1,6 @@
1
+ export default function or(...args) {
2
+ return args.find((arg) => arg);
3
+ }
4
+
5
+ or.usage = `@or ...values\tReturns the first truthy value`;
6
+ or.documentation = "https://graphorigami.org/language/@or.html";
@@ -0,0 +1,83 @@
1
+ import { Tree, getRealmObjectPrototype } from "@weborigami/async-tree";
2
+ import * as compile from "../../../language/src/compiler/compile.js";
3
+ import builtins from "../builtins/@builtins.js";
4
+ import { toYaml } from "../common/serialize.js";
5
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
6
+
7
+ const TypedArray = Object.getPrototypeOf(Uint8Array);
8
+
9
+ /**
10
+ * Parse an Origami expression, evaluate it in the context of a tree (provided
11
+ * by `this`), and return the result as text.
12
+ *
13
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
14
+ *
15
+ * @this {AsyncTree|null}
16
+ * @param {string} expression
17
+ */
18
+ export default async function ori(expression) {
19
+ assertScopeIsDefined(this);
20
+ // In case expression is a Buffer, cast it to a string.
21
+ expression = String(expression);
22
+
23
+ // Obtain the scope from `this` or builtins.
24
+ let scope = this ?? builtins;
25
+
26
+ // Parse
27
+ const fn = compile.expression(expression);
28
+
29
+ // Execute
30
+ let result = await fn.call(scope);
31
+
32
+ // If result was a function, execute it.
33
+ if (typeof result === "function") {
34
+ result = await result.call(scope);
35
+ }
36
+
37
+ const formatted = await formatResult(result);
38
+ return formatted;
39
+ }
40
+
41
+ async function formatResult(result) {
42
+ if (typeof result === "string" || result instanceof TypedArray) {
43
+ // Use as is
44
+ return result;
45
+ }
46
+
47
+ /** @type {string|String|undefined} */
48
+ let text;
49
+
50
+ // Does the result have a meaningful toString() method (and not the dumb
51
+ // Object.toString)? Exception: if the result is an array, we'll use YAML
52
+ // instead.
53
+ if (!result) {
54
+ // Return falsy values as is.
55
+ text = result;
56
+ } else if (
57
+ !(result instanceof Array) &&
58
+ (typeof result !== "object" ||
59
+ result.toString !== getRealmObjectPrototype(result).toString)
60
+ ) {
61
+ text = result.toString();
62
+ } else if (typeof result === "object") {
63
+ // Render YAML
64
+ text = await toYaml(result);
65
+ } else {
66
+ // Use result itself.
67
+ text = result;
68
+ }
69
+
70
+ // If the result is a tree, attach the tree to the text output.
71
+ if (Tree.isTreelike(result)) {
72
+ if (typeof text === "string") {
73
+ // @ts-ignore
74
+ text = new String(text);
75
+ }
76
+ /** @type {any} */ (text).unpack = () => Tree.from(result);
77
+ }
78
+
79
+ return text;
80
+ }
81
+
82
+ ori.usage = `@ori <text>\tEvaluates the text as an Origami expression`;
83
+ ori.documentation = "https://graphorigami.org/language/@ori.html";
@@ -0,0 +1,13 @@
1
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+
3
+ /**
4
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
5
+ *
6
+ * @this {AsyncTree|null}
7
+ * @param {any} obj
8
+ * @returns
9
+ */
10
+ export default function pack(obj) {
11
+ assertScopeIsDefined(this);
12
+ return obj?.pack?.();
13
+ }
@@ -0,0 +1,7 @@
1
+ export default async function parseJson(text) {
2
+ return text ? JSON.parse(text) : undefined;
3
+ }
4
+
5
+ parseJson.usage = `parseJson <text>\tParse text as JSON`;
6
+ parseJson.documentation =
7
+ "https://graphorigami.org/cli/builtins.html#parseJson";
@@ -0,0 +1,9 @@
1
+ import * as serialize from "../../common/serialize.js";
2
+
3
+ export default async function parseYaml(text) {
4
+ return text ? serialize.parseYaml(String(text)) : undefined;
5
+ }
6
+
7
+ parseYaml.usage = `parseYaml <text>\tParse text as YAML (including JSON)`;
8
+ parseYaml.documentation =
9
+ "https://graphorigami.org/cli/builtins.html#parseYaml";
@@ -0,0 +1,71 @@
1
+ /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
+ import { OrigamiFiles, Scope } from "@weborigami/language";
3
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+ import builtins from "./@builtins.js";
5
+
6
+ const configFileName = "ori.config.js";
7
+
8
+ /**
9
+ * Return the tree for the current project's root folder.
10
+ *
11
+ * This searches the current directory and its ancestors for an Origami
12
+ * configuration file. If an Origami configuration file is found, the containing
13
+ * folder is considered to be the project root. This returns a tree for that
14
+ * folder, with the exported configuration as the context for that folder — that
15
+ * is, the tree exported by the configuration will be the scope.
16
+ *
17
+ * If no Origami configuration file is found, the current folder will be
18
+ * returned as a tree, with the builtins as its parent.
19
+ *
20
+ * @this {AsyncTree|null}
21
+ * @param {any} [key]
22
+ */
23
+ export default async function project(key) {
24
+ assertScopeIsDefined(this);
25
+
26
+ const dirname = process.cwd();
27
+ const currentTree = new OrigamiFiles(dirname);
28
+ let projectTree = await findConfigContainer(currentTree);
29
+
30
+ let config;
31
+ if (projectTree) {
32
+ // Load the configuration.
33
+ config = await projectTree.import(configFileName);
34
+ if (!config) {
35
+ throw new Error(
36
+ `Couldn't load the Origami configuration in ${projectTree.path}`
37
+ );
38
+ }
39
+ } else {
40
+ projectTree = currentTree;
41
+ config = builtins;
42
+ }
43
+
44
+ // Add the configuration as the context for the project root.
45
+ const result = Scope.treeWithScope(projectTree, config);
46
+ return key === undefined ? result : result.get(key);
47
+ }
48
+
49
+ async function findConfigContainer(start) {
50
+ let current = start;
51
+ while (current) {
52
+ const config = await current.get(configFileName);
53
+ if (config) {
54
+ // Found a configuration; its container is the project root.
55
+ return current;
56
+ }
57
+ // Not found; try the parent.
58
+ const parent = await current.get("..");
59
+ if (
60
+ !parent ||
61
+ (parent.path && current.path && parent.path === current.path)
62
+ ) {
63
+ break;
64
+ }
65
+ current = parent;
66
+ }
67
+ return undefined;
68
+ }
69
+
70
+ project.usage = `@project\tThe root of the current Tree Origami project`;
71
+ project.documentation = "https://graphorigami.org/language/@project.html";
@@ -0,0 +1,8 @@
1
+ export default async function repeat(count, content) {
2
+ const array = new Array(count);
3
+ array.fill(content);
4
+ return array;
5
+ }
6
+
7
+ repeat.usage = `@repeat <count>, <content>\tRepeats the content the given number of times`;
8
+ repeat.documentation = "https://graphorigami.org/language/@repeat.html";
@@ -0,0 +1,49 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
3
+
4
+ /**
5
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
7
+ * @this {AsyncTree|null}
8
+ * @param {Treelike} jsonFeedTree
9
+ */
10
+ export default async function rss(jsonFeedTree) {
11
+ assertScopeIsDefined(this);
12
+ const jsonFeed = await Tree.plain(jsonFeedTree);
13
+ const { description, home_page_url, items, feed_url, title } = jsonFeed;
14
+
15
+ // Presume that the RSS feed lives in same location as feed_url.
16
+ const parts = feed_url.split("/");
17
+ parts.pop();
18
+ parts.push("rss.xml");
19
+ const rssUrl = parts.join("/");
20
+
21
+ const itemsRss = items?.map((story) => itemRss(story)).join("\n") ?? [];
22
+
23
+ return `<?xml version="1.0" ?>
24
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
25
+ <channel>
26
+ <atom:link href="${rssUrl}" rel="self" type="application/rss+xml"/>
27
+ <title>${title}</title>
28
+ <link>${home_page_url}</link>
29
+ <description>${description}</description>
30
+ ${itemsRss}</channel>
31
+ </rss>`;
32
+ }
33
+
34
+ function itemRss(jsonFeedItem) {
35
+ const { content_html, date_published, id, title, url } = jsonFeedItem;
36
+ // RSS wants dates in RFC-822.
37
+ const date = date_published?.toUTCString() ?? null;
38
+ const dateElement = date ? ` <pubDate>${date}</pubDate>\n` : "";
39
+ return ` <item>
40
+ ${dateElement}<title>${title}</title>
41
+ <link>${url}</link>
42
+ <guid>${id}</guid>
43
+ <description><![CDATA[${content_html}]]></description>
44
+ </item>
45
+ `;
46
+ }
47
+
48
+ rss.usage = `@rss <feed>\tTransforms a JSON Feed tree to RSS XML`;
49
+ rss.documentation = "https://graphorigami.org/language/@rss.html";
@@ -0,0 +1,22 @@
1
+ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import setScope from "./set.js";
3
+
4
+ /**
5
+ * Return a copy of the given tree whose scope includes the given trees *and*
6
+ * the current scope.
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
+ * @param {...Treelike} scopeTrees
13
+ * @this {AsyncTree|null}
14
+ */
15
+ export default function extendScope(treelike, ...scopeTrees) {
16
+ assertScopeIsDefined(this);
17
+ const scope = this;
18
+ return setScope.call(scope, treelike, ...scopeTrees, scope);
19
+ }
20
+
21
+ extendScope.usage = `@scope/extend <tree>, <...trees>\tExtends tree's scope with the given trees`;
22
+ extendScope.documentation = "https://graphorigami.org/cli/builtins.html#@scope";
@@ -0,0 +1,25 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
4
+
5
+ /**
6
+ * Returns the scope of the indicated tree or the current scope.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ * @this {AsyncTree|null}
11
+ * @param {any} [obj]
12
+ */
13
+ export default async function getScope(obj) {
14
+ assertScopeIsDefined(this);
15
+ if (obj) {
16
+ /** @type {any} */
17
+ const tree = Tree.from(obj);
18
+ return Scope.getScope(tree);
19
+ } else {
20
+ return this;
21
+ }
22
+ }
23
+
24
+ getScope.usage = `@scope/get [<tree>]\tReturns the scope of the tree or the current scope`;
25
+ getScope.documentation = "https://graphorigami.org/cli/builtins.html#@scope";
@@ -0,0 +1,22 @@
1
+ import { Scope } from "@weborigami/language";
2
+ import { toFunction } from "../../common/utilities.js";
3
+
4
+ /**
5
+ * Invokes the given function in the context of the current scope.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ * @typedef {import("../../../index.ts").Invocable} Invocable
9
+ *
10
+ * @this {AsyncTree|null}
11
+ * @param {AsyncTree} context
12
+ * @param {Invocable} invocable
13
+ */
14
+ export default async function invoke(context, invocable, ...args) {
15
+ const scope = Scope.getScope(context);
16
+ const fn = toFunction(invocable);
17
+ const result = await fn.call(scope, ...args);
18
+ return result;
19
+ }
20
+
21
+ invoke.usage = `@scope/invoke fn, <...args>\tInvoke the function in the current scope`;
22
+ invoke.documentation = "https://graphorigami.org/cli/builtins.html#@scope";