@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,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,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,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,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";
|