@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,40 @@
|
|
|
1
|
+
import { Tree, sortBy } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import addValueKeyToScope from "../../common/addValueKeyToScope.js";
|
|
4
|
+
import { toFunction } from "../../common/utilities.js";
|
|
5
|
+
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Return a new tree with the original's keys sorted using the given function to
|
|
9
|
+
* obtain a sort key for each value in the tree.
|
|
10
|
+
*
|
|
11
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
12
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
13
|
+
* @typedef {import("../../../index.ts").Invocable} Invocable
|
|
14
|
+
*
|
|
15
|
+
* @this {AsyncTree|null}
|
|
16
|
+
* @param {Treelike} treelike
|
|
17
|
+
* @param {Invocable} sortKeyFn
|
|
18
|
+
*/
|
|
19
|
+
export default async function sortByBuiltin(treelike, sortKeyFn) {
|
|
20
|
+
assertScopeIsDefined(this);
|
|
21
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
22
|
+
const tree = Tree.from(treelike);
|
|
23
|
+
|
|
24
|
+
const fn = toFunction(sortKeyFn);
|
|
25
|
+
const baseScope = Scope.getScope(this);
|
|
26
|
+
async function extendedSortKeyFn(key, tree) {
|
|
27
|
+
const value = await tree.get(key);
|
|
28
|
+
const scope = addValueKeyToScope(baseScope, value, key);
|
|
29
|
+
const sortKey = await fn.call(scope, value, key);
|
|
30
|
+
return sortKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sorted = sortBy(extendedSortKeyFn)(tree);
|
|
34
|
+
const scoped = Scope.treeWithScope(sorted, this);
|
|
35
|
+
return scoped;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sortByBuiltin.usage = `sortBy <tree>, [sortKeyFn]\tReturn a new tree with the original's keys sorted`;
|
|
39
|
+
sortByBuiltin.documentation =
|
|
40
|
+
"https://graphorigami.org/cli/builtins.html#@sort";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Tree, keysJson } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import { transformObject } from "../../common/utilities.js";
|
|
4
|
+
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
5
|
+
import index from "../@index.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Expose common static keys (index.html, .keys.json) for a tree.
|
|
9
|
+
*
|
|
10
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
11
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
12
|
+
* @this {AsyncTree|null}
|
|
13
|
+
* @param {Treelike} treelike
|
|
14
|
+
*/
|
|
15
|
+
export default async function staticTree(treelike) {
|
|
16
|
+
assertScopeIsDefined(this);
|
|
17
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
18
|
+
if (treelike === undefined) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const tree = Tree.from(treelike);
|
|
22
|
+
let result = transformObject(StaticTransform, tree);
|
|
23
|
+
result = Scope.treeWithScope(result, this);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function StaticTransform(Base) {
|
|
28
|
+
return class Static extends Base {
|
|
29
|
+
async get(key) {
|
|
30
|
+
let value = await super.get(key);
|
|
31
|
+
if (value === undefined && key === "index.html") {
|
|
32
|
+
value = index.call(this, this);
|
|
33
|
+
} else if (value === undefined && key === ".keys.json") {
|
|
34
|
+
value = keysJson.stringify(this);
|
|
35
|
+
} else if (Tree.isAsyncTree(value)) {
|
|
36
|
+
value = transformObject(StaticTransform, value);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async keys() {
|
|
42
|
+
const keys = new Set(await super.keys());
|
|
43
|
+
keys.add("index.html");
|
|
44
|
+
keys.add(".keys.json");
|
|
45
|
+
return keys;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
staticTree.usage = `static <tree>\tAdd keys for generating common static files`;
|
|
51
|
+
staticTree.documentation = "https://graphorigami.org/cli/builtins.html#static";
|
|
@@ -0,0 +1,74 @@
|
|
|
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} treelike
|
|
9
|
+
*/
|
|
10
|
+
export default async function table(treelike) {
|
|
11
|
+
assertScopeIsDefined(this);
|
|
12
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
13
|
+
if (treelike === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const tree = Tree.from(treelike);
|
|
17
|
+
const firstValue = await valueForFirstKey(tree);
|
|
18
|
+
if (Tree.isAsyncTree(firstValue)) {
|
|
19
|
+
return fullTable(tree, firstValue);
|
|
20
|
+
} else {
|
|
21
|
+
return simpleTable(tree);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Tree's values are subtrees.
|
|
26
|
+
//
|
|
27
|
+
// Return an R x C table, where R is the number of top-level keys in the tree
|
|
28
|
+
// (plus a header row), and C is the number of 2nd-level keys (plus a labeling
|
|
29
|
+
// column).
|
|
30
|
+
//
|
|
31
|
+
// The 2nd-level keys are obtain by inspecting the given model object, which is
|
|
32
|
+
// taken to represent top-level objects in the tree.
|
|
33
|
+
async function fullTable(tree, model) {
|
|
34
|
+
// Construct the header.
|
|
35
|
+
const modelKeys = Array.from(await model.keys());
|
|
36
|
+
const header = " \t" + modelKeys.join("\t");
|
|
37
|
+
const rows = [header];
|
|
38
|
+
|
|
39
|
+
// Add a row for each top-level object.
|
|
40
|
+
for (const key of await tree.keys()) {
|
|
41
|
+
let row = key;
|
|
42
|
+
const value = await tree.get(key);
|
|
43
|
+
for (const modelKey of await model.keys()) {
|
|
44
|
+
const value2 = await value.get(modelKey);
|
|
45
|
+
row += `\t${value2}`;
|
|
46
|
+
}
|
|
47
|
+
rows.push(row);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return rows.join("\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Tree's values are not subtrees.
|
|
54
|
+
// Return the (key, value) pairs as a simple two-column table.
|
|
55
|
+
async function simpleTable(tree) {
|
|
56
|
+
const header = `Key\tValue`;
|
|
57
|
+
const rows = [header];
|
|
58
|
+
for (const key of await tree.keys()) {
|
|
59
|
+
const value = await tree.get(key);
|
|
60
|
+
rows.push(`${key}\t${value}`);
|
|
61
|
+
}
|
|
62
|
+
const text = rows.join("\n");
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Return the value for the tree's first key.
|
|
67
|
+
async function valueForFirstKey(tree) {
|
|
68
|
+
const [value] = await tree.keys();
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
table.usage =
|
|
73
|
+
"table <tree>\tFormat the tree's top level as a tab-delimited table";
|
|
74
|
+
table.documentation = "https://graphorigami.org/cli/builtins.html#table";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Given a tree, take the first n items from it.
|
|
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 {number} n
|
|
13
|
+
*/
|
|
14
|
+
export default async function take(treelike, n) {
|
|
15
|
+
assertScopeIsDefined(this);
|
|
16
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
17
|
+
if (treelike === undefined) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const tree = Tree.from(treelike);
|
|
21
|
+
|
|
22
|
+
/** @type {AsyncTree} */
|
|
23
|
+
let takeTree = {
|
|
24
|
+
async keys() {
|
|
25
|
+
const keys = Array.from(await tree.keys());
|
|
26
|
+
return keys.slice(0, n);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async get(key) {
|
|
30
|
+
return tree.get(key);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
takeTree = Scope.treeWithScope(takeTree, this);
|
|
35
|
+
|
|
36
|
+
return takeTree;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
take.usage = `take tree, n\tReturn the first n items from tree`;
|
|
40
|
+
take.documentation = "https://graphorigami.org/cli/builtins.html#take";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return the interior nodes of the tree.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
|
+
* @this {AsyncTree|null}
|
|
10
|
+
* @param {Treelike} [treelike]
|
|
11
|
+
*/
|
|
12
|
+
export default async function values(treelike) {
|
|
13
|
+
assertScopeIsDefined(this);
|
|
14
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
15
|
+
if (treelike === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const tree = Tree.from(treelike);
|
|
19
|
+
return Tree.values(tree);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
values.usage = `values <tree>\tThe top-level values in the tree`;
|
|
23
|
+
values.documentation = "https://graphorigami.org/cli/builtins.html#values";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return the in-order exterior values of a tree as a flat array.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
8
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
9
|
+
* @this {AsyncTree|null}
|
|
10
|
+
* @param {Treelike} [treelike]
|
|
11
|
+
*/
|
|
12
|
+
export default async function valuesDeep(treelike) {
|
|
13
|
+
assertScopeIsDefined(this);
|
|
14
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
15
|
+
if (treelike === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return Tree.mapReduce(treelike, null, async (values) => values.flat());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
valuesDeep.usage = `valuesDeep <tree>\tThe in-order tree values as a flat array`;
|
|
22
|
+
valuesDeep.documentation =
|
|
23
|
+
"https://graphorigami.org/cli/builtins.html#valuesDeep";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ops } from "@weborigami/language";
|
|
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
|
+
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
8
|
+
*
|
|
9
|
+
* @this {AsyncTree|null}
|
|
10
|
+
* @param {string} host
|
|
11
|
+
* @param {...string|Symbol} keys
|
|
12
|
+
*/
|
|
13
|
+
export default function treeHttp(host, ...keys) {
|
|
14
|
+
assertScopeIsDefined(this);
|
|
15
|
+
return ops.treeHttp.call(this, host, ...keys);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
treeHttp.usage = `@treeHttp <domain>, <...keys>\tA web site tree via HTTP`;
|
|
19
|
+
treeHttp.documentation = "https://graphorigami.org/language/@treeHttp.html";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ops } from "@weborigami/language";
|
|
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
|
+
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
8
|
+
*
|
|
9
|
+
* @this {AsyncTree|null}
|
|
10
|
+
* @param {string} host
|
|
11
|
+
* @param {...string|Symbol} keys
|
|
12
|
+
*/
|
|
13
|
+
export default function treeHttps(host, ...keys) {
|
|
14
|
+
assertScopeIsDefined(this);
|
|
15
|
+
return ops.treeHttps.call(this, host, ...keys);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
treeHttps.usage = `@treeHttps <domain>, <...keys>\tA web site tree via HTTPS`;
|
|
19
|
+
treeHttps.documentation = "https://graphorigami.org/language/@treeHttps.html";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default true;
|
|
@@ -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 unpack(obj) {
|
|
11
|
+
assertScopeIsDefined(this);
|
|
12
|
+
return obj?.unpack?.();
|
|
13
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import ConstantTree from "../common/ConstantTree.js";
|
|
4
|
+
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Let a tree (e.g., of files) respond to changes.
|
|
8
|
+
*
|
|
9
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
10
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
11
|
+
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
12
|
+
*
|
|
13
|
+
* @this {AsyncTree|null}
|
|
14
|
+
* @param {Treelike} [treelike]
|
|
15
|
+
* @param {Invocable} [fn]
|
|
16
|
+
*/
|
|
17
|
+
export default async function watch(treelike, fn) {
|
|
18
|
+
assertScopeIsDefined(this);
|
|
19
|
+
treelike = treelike ?? (await this?.get("@current"));
|
|
20
|
+
if (treelike === undefined) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Watch the indicated tree.
|
|
25
|
+
/** @type {any} */
|
|
26
|
+
const container = Tree.from(treelike);
|
|
27
|
+
await /** @type {any} */ (container).watch?.();
|
|
28
|
+
|
|
29
|
+
// // Watch trees in scope.
|
|
30
|
+
// const scope = /** @type {any} */ (container).scope;
|
|
31
|
+
// await scope?.watch?.();
|
|
32
|
+
|
|
33
|
+
if (fn === undefined) {
|
|
34
|
+
return container;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The caller supplied a function to reevaluate whenever the tree changes.
|
|
38
|
+
let tree = await evaluateTree(container.scope, fn);
|
|
39
|
+
|
|
40
|
+
// We want to return a stable reference to the tree, so we'll use a prototype
|
|
41
|
+
// chain that will always point to the latest tree. We'll extend the tree's
|
|
42
|
+
// prototype chain with an empty object, and use that as a handle (pointer to
|
|
43
|
+
// a pointer) to the tree. This is what we'll return to the caller.
|
|
44
|
+
const handle = Object.create(tree);
|
|
45
|
+
|
|
46
|
+
// Reevaluate the function whenever the tree changes.
|
|
47
|
+
container.addEventListener?.("change", async () => {
|
|
48
|
+
const tree = await evaluateTree(container.scope, fn);
|
|
49
|
+
updateIndirectPointer(handle, tree);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return handle;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function evaluateTree(scope, fn) {
|
|
56
|
+
let tree;
|
|
57
|
+
let message;
|
|
58
|
+
let result;
|
|
59
|
+
try {
|
|
60
|
+
result = await fn.call(scope);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
message = messageForError(error);
|
|
63
|
+
}
|
|
64
|
+
tree = result ? Tree.from(result) : undefined;
|
|
65
|
+
if (tree) {
|
|
66
|
+
return tree;
|
|
67
|
+
}
|
|
68
|
+
if (!message) {
|
|
69
|
+
message = `warning: watch expression did not return a tree`;
|
|
70
|
+
}
|
|
71
|
+
console.warn(message);
|
|
72
|
+
tree = new ConstantTree(message);
|
|
73
|
+
if (scope) {
|
|
74
|
+
tree = Scope.treeWithScope(tree, scope);
|
|
75
|
+
}
|
|
76
|
+
return tree;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function messageForError(error) {
|
|
80
|
+
let message = "";
|
|
81
|
+
// Work up to the root cause, displaying intermediate messages as we go up.
|
|
82
|
+
while (error.cause) {
|
|
83
|
+
message += error.message + `\n`;
|
|
84
|
+
error = error.cause;
|
|
85
|
+
}
|
|
86
|
+
if (error.name) {
|
|
87
|
+
message += `${error.name}: `;
|
|
88
|
+
}
|
|
89
|
+
message += error.message;
|
|
90
|
+
return message;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Update an indirect pointer to a target.
|
|
94
|
+
function updateIndirectPointer(indirect, target) {
|
|
95
|
+
// Clean the pointer of any named properties or symbols that have been set
|
|
96
|
+
// directly on it.
|
|
97
|
+
for (const key of Object.getOwnPropertyNames(indirect)) {
|
|
98
|
+
delete indirect[key];
|
|
99
|
+
}
|
|
100
|
+
for (const key of Object.getOwnPropertySymbols(indirect)) {
|
|
101
|
+
delete indirect[key];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Object.setPrototypeOf(indirect, target);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
watch.usage = `@watch <folder>, [expr]\tLet a folder tree respond to changes`;
|
|
108
|
+
watch.documentation = "https://graphorigami.org/language/@watch.html";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Tree } from "@weborigami/async-tree";
|
|
2
|
+
import { Scope } from "@weborigami/language";
|
|
3
|
+
import { toFunction } from "../common/utilities.js";
|
|
4
|
+
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
9
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
10
|
+
* @typedef {import("../../index.ts").Invocable} Invocable
|
|
11
|
+
*
|
|
12
|
+
* @this {AsyncTree|null}
|
|
13
|
+
* @param {Treelike} treelike
|
|
14
|
+
* @param {Invocable} invocable
|
|
15
|
+
*/
|
|
16
|
+
export default function withTree(treelike, invocable) {
|
|
17
|
+
assertScopeIsDefined(this);
|
|
18
|
+
const tree = Tree.from(treelike);
|
|
19
|
+
const fn = toFunction(invocable);
|
|
20
|
+
const scope = new Scope(tree, this);
|
|
21
|
+
return fn.call(scope);
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import * as serialize from "../common/serialize.js";
|
|
4
|
+
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Render the object as text in YAML format.
|
|
8
|
+
*
|
|
9
|
+
* @this {AsyncTree|null}
|
|
10
|
+
* @param {any} [obj]
|
|
11
|
+
*/
|
|
12
|
+
export default async function toYaml(obj) {
|
|
13
|
+
assertScopeIsDefined(this);
|
|
14
|
+
obj = obj ?? (await this?.get("@current"));
|
|
15
|
+
if (obj === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const value = await serialize.toJsonValue(obj);
|
|
19
|
+
return YAML.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toYaml.usage = `@yaml <obj>\tRender the object as text in YAML format`;
|
|
23
|
+
toYaml.documentation = "https://graphorigami.org/language/@yaml.html";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OrigamiFiles, Scope } from "@weborigami/language";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import builtins from "./@builtins.js";
|
|
4
|
+
|
|
5
|
+
/** @type {import("@weborigami/types").AsyncTree} */
|
|
6
|
+
let tree = new OrigamiFiles(os.homedir());
|
|
7
|
+
tree = Scope.treeWithScope(tree, builtins);
|
|
8
|
+
|
|
9
|
+
export default tree;
|
package/src/cli/cli.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ObjectTree, Tree } from "@weborigami/async-tree";
|
|
4
|
+
import { Scope } from "@weborigami/language";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process, { stdout } from "node:process";
|
|
7
|
+
import ori from "../builtins/@ori.js";
|
|
8
|
+
import project from "../builtins/@project.js";
|
|
9
|
+
import { keySymbol } from "../common/utilities.js";
|
|
10
|
+
import showUsage from "./showUsage.js";
|
|
11
|
+
|
|
12
|
+
async function main(...args) {
|
|
13
|
+
const expression = args.join(" ");
|
|
14
|
+
|
|
15
|
+
// Find the project root.
|
|
16
|
+
const projectTree = await project.call(null);
|
|
17
|
+
|
|
18
|
+
// HACK: get the configuration via the project.
|
|
19
|
+
const config = new Scope(...projectTree.scope.trees.slice(1));
|
|
20
|
+
|
|
21
|
+
// If no arguments were passed, show usage.
|
|
22
|
+
if (!expression) {
|
|
23
|
+
await showUsage(config);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Splice ambients tree into project tree scope.
|
|
28
|
+
const ambients = new ObjectTree({});
|
|
29
|
+
ambients[keySymbol] = "Origami CLI";
|
|
30
|
+
let tree = Scope.treeWithScope(projectTree, new Scope(ambients, config));
|
|
31
|
+
|
|
32
|
+
// Traverse from the project root to the current directory.
|
|
33
|
+
const currentDirectory = process.cwd();
|
|
34
|
+
const relative = path.relative(projectTree.path, currentDirectory);
|
|
35
|
+
if (relative !== "") {
|
|
36
|
+
tree = await Tree.traversePath(tree, relative);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add ambient property for the current tree.
|
|
40
|
+
await ambients.set("@current", tree);
|
|
41
|
+
|
|
42
|
+
const scope = Scope.getScope(tree);
|
|
43
|
+
const result = await ori.call(scope, expression);
|
|
44
|
+
if (result !== undefined) {
|
|
45
|
+
const output = result instanceof Buffer ? result : String(result);
|
|
46
|
+
await stdout.write(output);
|
|
47
|
+
|
|
48
|
+
// If stdout points to the console, and the result didn't end in a newline,
|
|
49
|
+
// then output a newline.
|
|
50
|
+
if (stdout.isTTY) {
|
|
51
|
+
const lastChar = output[output.length - 1];
|
|
52
|
+
const isNewLine = lastChar === "\n" || lastChar === 10;
|
|
53
|
+
if (!isNewLine) {
|
|
54
|
+
await stdout.write("\n");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Process command line arguments
|
|
61
|
+
const args = process.argv;
|
|
62
|
+
args.shift(); // "node"
|
|
63
|
+
args.shift(); // name of this script file
|
|
64
|
+
// Not sure why we end up with blank arguments; skip them.
|
|
65
|
+
while (args[0] === "") {
|
|
66
|
+
args.shift();
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await main(...args);
|
|
70
|
+
} catch (/** @type {any} */ error) {
|
|
71
|
+
// Work up to the root cause, displaying intermediate messages as we go up.
|
|
72
|
+
if (!error.cause && !error.stack) {
|
|
73
|
+
console.error(error.message);
|
|
74
|
+
} else {
|
|
75
|
+
while (error.cause) {
|
|
76
|
+
console.error(error.message);
|
|
77
|
+
error = error.cause;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (error.stack) {
|
|
81
|
+
// Display stack trace for root cause, under the theory that that's the most
|
|
82
|
+
// useful place to look for the problem.
|
|
83
|
+
console.error(error.stack);
|
|
84
|
+
}
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
export default async function defaultModuleExport(...keys) {
|
|
6
|
+
// On Windows, absolute paths must be valid file:// URLs.
|
|
7
|
+
const modulePath = path.resolve(process.cwd(), ...keys);
|
|
8
|
+
const url = pathToFileURL(modulePath);
|
|
9
|
+
const module = await import(url.href);
|
|
10
|
+
const result = module.default;
|
|
11
|
+
if (!result) {
|
|
12
|
+
console.error(`${modulePath} does not define a default export.`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const groupDescriptions = {
|
|
2
|
+
"@cache": "Functions for caching data",
|
|
3
|
+
"@tree": "Functions for working with trees",
|
|
4
|
+
"@image": "Functions for working with images",
|
|
5
|
+
"@parse": "Parsing functions",
|
|
6
|
+
"@scope": "Functions for working with tree scopes",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default async function showUsage(scope) {
|
|
10
|
+
console.log(`Usage: ori <expression>, which could be:
|
|
11
|
+
- a string in single quotes, or a number
|
|
12
|
+
- a file or folder name
|
|
13
|
+
- an https: or http: URL
|
|
14
|
+
- a name like "foo" that refers to an object/function exported by a project file foo.js
|
|
15
|
+
- the name of a function below:
|
|
16
|
+
`);
|
|
17
|
+
|
|
18
|
+
// Gather usages.
|
|
19
|
+
const usages = [];
|
|
20
|
+
for (const key of await scope.keys()) {
|
|
21
|
+
let command;
|
|
22
|
+
// We get the command associated with the key in a try/catch because
|
|
23
|
+
// we want to ignore any syntax errors.
|
|
24
|
+
try {
|
|
25
|
+
command = await scope.get(key);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof SyntaxError) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (groupDescriptions[key]) {
|
|
32
|
+
usages.push(`${key}\t${groupDescriptions[key]}`);
|
|
33
|
+
} else if (typeof command === "function") {
|
|
34
|
+
let usage = command?.usage;
|
|
35
|
+
if (!usage) {
|
|
36
|
+
usage = defaultUsage(key, command);
|
|
37
|
+
}
|
|
38
|
+
usages.push(usage);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Case-insensitive sort
|
|
43
|
+
usages.sort(function (a, b) {
|
|
44
|
+
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Split into signatures and descriptions.
|
|
48
|
+
const signatures = [];
|
|
49
|
+
const descriptions = [];
|
|
50
|
+
usages.forEach((usage) => {
|
|
51
|
+
const [signature, description] = usage.split("\t");
|
|
52
|
+
signatures.push(signature);
|
|
53
|
+
descriptions.push(description || "");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Calculate length of longest signature.
|
|
57
|
+
const lengths = signatures.map((signature) => signature.length);
|
|
58
|
+
const maxLength = Math.max(...lengths);
|
|
59
|
+
|
|
60
|
+
// Format lines, padding the descriptions to that length + gap.
|
|
61
|
+
const gap = 4;
|
|
62
|
+
const length = maxLength + gap;
|
|
63
|
+
const formatted = signatures.map(
|
|
64
|
+
(signature, index) => `${signature.padEnd(length)}${descriptions[index]}`
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
console.log(formatted.join("\n"));
|
|
68
|
+
console.log(
|
|
69
|
+
`\nMore details: "ori @help"; or get help on a function like @serve with "ori @help/@serve"`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function defaultUsage(name, fn) {
|
|
74
|
+
const arity = fn.length;
|
|
75
|
+
if (arity === 0) {
|
|
76
|
+
return `${name}()`;
|
|
77
|
+
} else if (arity === 1) {
|
|
78
|
+
return `${name}(arg)`;
|
|
79
|
+
} else {
|
|
80
|
+
let args = [];
|
|
81
|
+
for (let i = 0; i < arity; i++) {
|
|
82
|
+
args.push(`arg${i + 1}`);
|
|
83
|
+
}
|
|
84
|
+
return `${name}(${args.join(", ")})`;
|
|
85
|
+
}
|
|
86
|
+
}
|