@weborigami/origami 0.0.39 → 0.0.41

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 (48) hide show
  1. package/exports/exports.js +1 -0
  2. package/package.json +6 -6
  3. package/src/builtins/@arrows.js +2 -11
  4. package/src/builtins/@debug.js +2 -8
  5. package/src/builtins/@explore.js +30 -10
  6. package/src/builtins/@index.js +2 -8
  7. package/src/builtins/@inline.js +3 -7
  8. package/src/builtins/@invoke.js +8 -0
  9. package/src/builtins/@json.js +6 -0
  10. package/src/builtins/@loaders/ori.js +18 -1
  11. package/src/builtins/@map.js +4 -18
  12. package/src/builtins/@ori.js +5 -3
  13. package/src/builtins/@package.js +27 -3
  14. package/src/builtins/@svg.js +2 -8
  15. package/src/builtins/@tree/count.js +2 -8
  16. package/src/builtins/@tree/defineds.js +3 -7
  17. package/src/builtins/@tree/dot.js +2 -7
  18. package/src/builtins/@tree/exceptions.js +5 -6
  19. package/src/builtins/@tree/first.js +2 -8
  20. package/src/builtins/@tree/fn.js +6 -0
  21. package/src/builtins/@tree/from.js +4 -12
  22. package/src/builtins/@tree/groupBy.js +3 -5
  23. package/src/builtins/@tree/inners.js +2 -7
  24. package/src/builtins/@tree/keys.js +2 -8
  25. package/src/builtins/@tree/keysJson.js +2 -7
  26. package/src/builtins/@tree/map.d.ts +0 -2
  27. package/src/builtins/@tree/parent.js +2 -8
  28. package/src/builtins/@tree/paths.js +2 -7
  29. package/src/builtins/@tree/plain.js +3 -7
  30. package/src/builtins/@tree/reverse.js +2 -7
  31. package/src/builtins/@tree/shuffle.js +2 -8
  32. package/src/builtins/@tree/sitemap.js +2 -5
  33. package/src/builtins/@tree/sort.js +3 -5
  34. package/src/builtins/@tree/sortBy.js +3 -5
  35. package/src/builtins/@tree/static.js +2 -7
  36. package/src/builtins/@tree/table.js +2 -7
  37. package/src/builtins/@tree/take.js +2 -8
  38. package/src/builtins/@tree/values.js +2 -7
  39. package/src/builtins/@tree/valuesDeep.js +3 -7
  40. package/src/builtins/@watch.js +3 -8
  41. package/src/builtins/@yaml.js +6 -0
  42. package/src/common/FilterTree.js +4 -2
  43. package/src/common/GlobTree.js +10 -2
  44. package/src/common/addValueKeyToScope.js +3 -11
  45. package/src/common/processUnpackedContent.js +6 -8
  46. package/src/misc/OriCommandTransform.js +1 -1
  47. package/src/misc/getTreeArgument.js +41 -0
  48. package/src/server/server.js +23 -15
@@ -119,6 +119,7 @@ export { default as ShuffleTransform } from "../src/common/ShuffleTransform.js";
119
119
  export { default as TextDocument } from "../src/common/TextDocument.js";
120
120
  export * from "../src/common/utilities.js";
121
121
  export { default as assertScopeIsDefined } from "../src/misc/assertScopeIsDefined.js";
122
+ export { default as getTreeArgument } from "../src/misc/getTreeArgument.js";
122
123
  export { default as OriCommandTransform } from "../src/misc/OriCommandTransform.js";
123
124
  export { default as yamlOrigamiTag } from "../src/misc/yamlOrigamiTag.js";
124
125
  export * from "../src/server/mediaTypes.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.0.39",
3
+ "version": "0.0.41",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -15,13 +15,13 @@
15
15
  "devDependencies": {
16
16
  "@types/chai": "4.3.11",
17
17
  "@types/mocha": "10.0.6",
18
- "@types/node": "20.11.3",
18
+ "@types/node": "20.11.7",
19
19
  "typescript": "5.3.3"
20
20
  },
21
21
  "dependencies": {
22
- "@weborigami/async-tree": "0.0.39",
23
- "@weborigami/language": "0.0.39",
24
- "@weborigami/types": "0.0.39",
22
+ "@weborigami/async-tree": "0.0.41",
23
+ "@weborigami/language": "0.0.41",
24
+ "@weborigami/types": "0.0.41",
25
25
  "graphviz-wasm": "3.0.1",
26
26
  "highlight.js": "11.9.0",
27
27
  "marked": "11.1.1",
@@ -34,7 +34,7 @@
34
34
  "scripts": {
35
35
  "build": "ori exports/buildExports.js src > exports/exports.js",
36
36
  "prepublishOnly": "npm run build",
37
- "test": "node --test --test-reporter=spec test/*/*.test.js test/*/*/*.test.js",
37
+ "test": "node --test --test-reporter=spec",
38
38
  "typecheck": "node node_modules/typescript/bin/tsc"
39
39
  }
40
40
  }
@@ -1,9 +1,8 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import { Scope, functionResultsMap } from "@weborigami/language";
3
2
  import builtins from "../../src/builtins/@builtins.js";
4
3
  import arrowFunctionsMap from "../common/arrowFunctionsMap.js";
5
4
  import { keySymbol } from "../common/utilities.js";
6
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
5
+ import getTreeArgument from "../misc/getTreeArgument.js";
7
6
 
8
7
  /**
9
8
  * Interpret arrow keys in the tree as function calls.
@@ -14,15 +13,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
14
13
  * @param {Treelike} [treelike]
15
14
  */
16
15
  export default async function arrows(treelike) {
17
- assertScopeIsDefined(this);
18
- treelike = treelike ?? (await this?.get("@current"));
19
- if (treelike === undefined) {
20
- throw TypeError(
21
- "@arrows requires a treelike argument, but received undefined"
22
- );
23
- }
24
- /** @type {AsyncTree} */
25
- const tree = Tree.from(treelike);
16
+ const tree = await getTreeArgument(this, arguments, treelike);
26
17
  const mapped = functionResultsMap(arrowFunctionsMap()(tree));
27
18
  const scope = this ?? builtins;
28
19
  const scoped = Scope.treeWithScope(mapped, scope);
@@ -2,7 +2,7 @@ import { Tree, isPlainObject } from "@weborigami/async-tree";
2
2
  import ExplorableSiteTransform from "../common/ExplorableSiteTransform.js";
3
3
  import { isTransformApplied, transformObject } from "../common/utilities.js";
4
4
  import OriCommandTransform from "../misc/OriCommandTransform.js";
5
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
5
+ import getTreeArgument from "../misc/getTreeArgument.js";
6
6
 
7
7
  /**
8
8
  * Add debugging features to the indicated tree.
@@ -14,15 +14,9 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
14
14
  * @param {Treelike} [treelike]
15
15
  */
16
16
  export default async function debug(treelike) {
17
- assertScopeIsDefined(this);
18
- treelike = treelike ?? (await this?.get("@current"));
19
- if (treelike === undefined) {
20
- return;
21
- }
22
-
23
17
  // The debug command leaves the tree's existing scope intact; it does not
24
18
  // apply its own scope to the tree.
25
- let tree = Tree.from(treelike);
19
+ let tree = await getTreeArgument(this, arguments, treelike);
26
20
 
27
21
  if (!isTransformApplied(ExplorableSiteTransform, tree)) {
28
22
  tree = transformObject(ExplorableSiteTransform, tree);
@@ -1,5 +1,5 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
- import { ObjectTree } from "@weborigami/async-tree";
2
+ import { ObjectTree, Tree } from "@weborigami/async-tree";
3
3
  import { OrigamiFiles, Scope } from "@weborigami/language";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -14,14 +14,8 @@ const miscFiles = Scope.treeWithScope(new OrigamiFiles(miscDir), builtins);
14
14
  /**
15
15
  * @this {AsyncTree|null}
16
16
  */
17
- export default async function explore() {
17
+ export default async function explore(...keys) {
18
18
  const scope = Scope.getScope(this);
19
- const templateFile = await miscFiles.get("explore.ori");
20
- const template = await templateFile.unpack();
21
-
22
- const data = await getScopeData(scope);
23
- const text = await template(data);
24
-
25
19
  const ambientsTree = new ObjectTree({
26
20
  "@current": this,
27
21
  });
@@ -29,8 +23,34 @@ export default async function explore() {
29
23
  const extendedScope = new Scope(ambientsTree, scope);
30
24
 
31
25
  /** @type {any} */
32
- const result = new String(text);
33
- result.unpack = () => debug.call(scope, extendedScope);
26
+ let result;
27
+ if (keys.length > 0) {
28
+ // Traverse the scope using the given keys.
29
+ const debugScope = await debug.call(scope, extendedScope);
30
+ if (!debugScope) {
31
+ return undefined;
32
+ }
33
+
34
+ // HACK: reproduce logic of ExplorableSiteTransform that turns a trailing
35
+ // slash into index.html. Calling `debug` applies that transform and the
36
+ // transform should handle that logic, but unfortunately the `traverse`
37
+ // operation has special casing to treat a trailing slash, and never gives
38
+ // ExplorableSiteTransform a chance.
39
+ if (keys.at(-1) === "") {
40
+ keys[keys.length - 1] = "index.html";
41
+ }
42
+ result = await Tree.traverse(debugScope, ...keys);
43
+ } else {
44
+ // Return the Explore page for the current scope.
45
+ const templateFile = await miscFiles.get("explore.ori");
46
+ const template = await templateFile.unpack();
47
+
48
+ const data = await getScopeData(scope);
49
+ const text = await template(data);
50
+
51
+ result = new String(text);
52
+ result.unpack = () => debug.call(scope, extendedScope);
53
+ }
34
54
 
35
55
  return result;
36
56
  }
@@ -1,6 +1,5 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import { keySymbol } from "../common/utilities.js";
3
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../misc/getTreeArgument.js";
4
3
 
5
4
  /**
6
5
  * Return a default index.html page for the current tree.
@@ -11,12 +10,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
10
  * @param {Treelike} [treelike]
12
11
  */
13
12
  export default async function index(treelike) {
14
- assertScopeIsDefined(this);
15
- treelike = treelike ?? (await this?.get("@current"));
16
- if (treelike === undefined) {
17
- return undefined;
18
- }
19
- const tree = Tree.from(treelike);
13
+ const tree = await getTreeArgument(this, arguments, treelike);
20
14
  const keys = Array.from(await tree.keys());
21
15
 
22
16
  // Skip system-ish files that start with a period. Also skip `index.html`.
@@ -1,5 +1,5 @@
1
+ import { compile } from "@weborigami/language";
1
2
  import unpackText from "../builtins/@loaders/txt.js";
2
- import * as utilities from "../common/utilities.js";
3
3
  import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
4
  import unpackOrigamiExpression from "./@loaders/ori.js";
5
5
 
@@ -28,13 +28,9 @@ export default async function inline(input) {
28
28
  inputDocument = await unpackText(input);
29
29
  }
30
30
 
31
- // Treat the input text as the body of an Origami template literal.
32
- const inputText = utilities.toString(inputDocument);
33
- const templateDocument = Object.assign({}, inputDocument, {
34
- "@text": `=\`${inputText}\``,
31
+ const templateFn = await unpackOrigamiExpression(inputDocument, {
32
+ compiler: compile.templateDocument,
35
33
  });
36
-
37
- const templateFn = await unpackOrigamiExpression(templateDocument);
38
34
  const templateResult = await templateFn(inputDocument);
39
35
  return inputDocument
40
36
  ? Object.assign({}, inputDocument, { "@text": String(templateResult) })
@@ -1,3 +1,4 @@
1
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
1
2
  import builtins from "./@builtins.js";
2
3
 
3
4
  /**
@@ -22,6 +23,13 @@ import builtins from "./@builtins.js";
22
23
  * @this {import("@weborigami/types").AsyncTree|null}
23
24
  */
24
25
  export default async function invoke(fn) {
26
+ assertScopeIsDefined(this);
27
+ // A fragment of the logic from getTreeArgument.js
28
+ if (arguments.length > 0 && fn === undefined) {
29
+ throw new Error(
30
+ "An Origami function was called with an initial argument, but its value is undefined."
31
+ );
32
+ }
25
33
  if (typeof fn !== "function" && fn.unpack) {
26
34
  fn = await fn.unpack();
27
35
  }
@@ -10,6 +10,12 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
10
10
  */
11
11
  export default async function json(obj) {
12
12
  assertScopeIsDefined(this);
13
+ // A fragment of the logic from getTreeArgument.js
14
+ if (arguments.length > 0 && obj === undefined) {
15
+ throw new Error(
16
+ "An Origami function was called with an initial argument, but its value is undefined."
17
+ );
18
+ }
13
19
  obj = obj ?? (await this?.get("@current"));
14
20
  if (obj === undefined) {
15
21
  return undefined;
@@ -17,10 +17,27 @@ export default async function unpackOrigamiExpression(
17
17
  options.parent ??
18
18
  /** @type {any} */ (inputDocument).parent ??
19
19
  /** @type {any} */ (inputDocument)[utilities.parentSymbol];
20
+ const compiler = options.compiler ?? compile.expression;
20
21
 
21
22
  // Compile the body text as an Origami expression and evaluate it.
22
23
  const inputText = utilities.toString(inputDocument);
23
- const fn = compile.expression(inputText);
24
+ let fn;
25
+ try {
26
+ fn = compiler(inputText);
27
+ } catch (/** @type {any} */ error) {
28
+ let location = "";
29
+ if (options.key) {
30
+ location += `${options.key}`;
31
+ }
32
+ if (error.location) {
33
+ const { start } = error.location;
34
+ location += `, line ${start.line}, column ${start.column}`;
35
+ }
36
+ if (location) {
37
+ error.message += ` (${location})`;
38
+ }
39
+ throw error;
40
+ }
24
41
  const parentScope = parent ? Scope.getScope(parent) : builtins;
25
42
  let content = await fn.call(parentScope);
26
43
 
@@ -19,8 +19,8 @@ import { toFunction } from "../common/utilities.js";
19
19
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
20
20
  *
21
21
  * @typedef {{ deep?: boolean, description?: string, extensions?: string,
22
- * inverseKeyMap?: KeyFn, keyMap?: ValueKeyFn, keyName?: string, valueMap?:
23
- * ValueKeyFn, valueName?: string }} TreeMapOptions
22
+ * inverseKeyMap?: KeyFn, keyMap?: ValueKeyFn, valueMap?: ValueKeyFn }}
23
+ * TreeMapOptions
24
24
  *
25
25
  * @this {import("@weborigami/types").AsyncTree|null}
26
26
  *
@@ -73,9 +73,7 @@ export default function treeMap(param1, param2) {
73
73
  extensions,
74
74
  inverseKeyMap,
75
75
  keyMap,
76
- keyName,
77
76
  needsSourceValue,
78
- valueName,
79
77
  } = options;
80
78
 
81
79
  description ??= `@map ${extensions ?? ""}`;
@@ -93,13 +91,7 @@ export default function treeMap(param1, param2) {
93
91
  if (valueMap) {
94
92
  const resolvedValueFn = toFunction(valueMap);
95
93
  extendedValueFn = function (sourceValue, sourceKey, tree) {
96
- const scope = addValueKeyToScope(
97
- baseScope,
98
- sourceValue,
99
- sourceKey,
100
- valueName,
101
- keyName
102
- );
94
+ const scope = addValueKeyToScope(baseScope, sourceValue, sourceKey);
103
95
  return resolvedValueFn.call(scope, sourceValue, sourceKey, tree);
104
96
  };
105
97
  }
@@ -119,13 +111,7 @@ export default function treeMap(param1, param2) {
119
111
  const resolvedKeyFn = toFunction(keyMap);
120
112
  async function scopedKeyFn(sourceKey, tree) {
121
113
  const sourceValue = await tree.get(sourceKey);
122
- const scope = addValueKeyToScope(
123
- baseScope,
124
- sourceValue,
125
- sourceKey,
126
- valueName,
127
- keyName
128
- );
114
+ const scope = addValueKeyToScope(baseScope, sourceValue, sourceKey);
129
115
  const resultKey = await resolvedKeyFn.call(
130
116
  scope,
131
117
  sourceValue,
@@ -15,7 +15,10 @@ const TypedArray = Object.getPrototypeOf(Uint8Array);
15
15
  * @this {AsyncTree|null}
16
16
  * @param {string} expression
17
17
  */
18
- export default async function ori(expression) {
18
+ export default async function ori(
19
+ expression,
20
+ options = { formatResult: true }
21
+ ) {
19
22
  assertScopeIsDefined(this);
20
23
  // In case expression is a Buffer, cast it to a string.
21
24
  expression = String(expression);
@@ -34,8 +37,7 @@ export default async function ori(expression) {
34
37
  result = await result.call(scope);
35
38
  }
36
39
 
37
- const formatted = await formatResult(result);
38
- return formatted;
40
+ return options.formatResult ? await formatResult(result) : result;
39
41
  }
40
42
 
41
43
  async function formatResult(result) {
@@ -4,25 +4,49 @@ import project from "./@project.js";
4
4
 
5
5
  /**
6
6
  * @this {import("@weborigami/types").AsyncTree|null}
7
- * @param {string[]} packageKeys
7
+ * @param {string[]} keys
8
8
  */
9
- export default async function packageBuiltin(...packageKeys) {
9
+ export default async function packageBuiltin(...keys) {
10
10
  let scope = this;
11
11
  if (!scope) {
12
12
  const projectRoot = await project.call(null);
13
13
  scope = Scope.getScope(projectRoot);
14
14
  }
15
+
16
+ const packageKeys = [keys.shift()];
17
+ if (packageKeys[0]?.startsWith("@")) {
18
+ // First key is an npm organization, get the next key too.
19
+ packageKeys.push(keys.shift());
20
+ }
21
+
15
22
  const packageRoot = await Tree.traverse(
16
23
  // @ts-ignore
17
24
  scope,
18
25
  "node_modules",
19
26
  ...packageKeys
20
27
  );
28
+ if (!packageRoot) {
29
+ throw new Error(`Can't find node_modules/${packageKeys.join("/")}`);
30
+ }
31
+
21
32
  const mainPath = await Tree.traverse(packageRoot, "package.json", "main");
33
+ if (!mainPath) {
34
+ throw new Error(
35
+ `node_modules/${keys.join(
36
+ "/"
37
+ )} doesn't contain a package.json with a "main" entry.`
38
+ );
39
+ }
40
+
22
41
  const mainKeys = keysFromPath(mainPath);
23
42
  const mainContainerKeys = mainKeys.slice(0, -1);
24
43
  const mainFileName = mainKeys[mainKeys.length - 1];
25
44
  const mainContainer = await Tree.traverse(packageRoot, ...mainContainerKeys);
26
45
  const packageExports = await mainContainer.import(mainFileName);
27
- return packageExports;
46
+
47
+ const result =
48
+ keys.length > 0
49
+ ? await Tree.traverse(packageExports, ...keys)
50
+ : packageExports;
51
+ return result;
28
52
  }
@@ -1,6 +1,5 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import graphviz from "graphviz-wasm";
3
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../misc/getTreeArgument.js";
4
3
  import dot from "./@tree/dot.js";
5
4
 
6
5
  let graphvizLoaded = false;
@@ -17,16 +16,11 @@ let graphvizLoaded = false;
17
16
  * @param {PlainObject} [options]
18
17
  */
19
18
  export default async function svg(treelike, options = {}) {
20
- assertScopeIsDefined(this);
21
19
  if (!graphvizLoaded) {
22
20
  await graphviz.loadWASM();
23
21
  graphvizLoaded = true;
24
22
  }
25
- treelike = treelike ?? (await this?.get("@current"));
26
- if (treelike === undefined) {
27
- return undefined;
28
- }
29
- const tree = Tree.from(treelike);
23
+ const tree = await getTreeArgument(this, arguments, treelike);
30
24
  const dotText = await dot.call(this, tree, options);
31
25
  if (dotText === undefined) {
32
26
  return undefined;
@@ -1,5 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
1
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Return the number of keys in the tree.
@@ -10,12 +9,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
9
  * @param {Treelike} [treelike]
11
10
  */
12
11
  export default async function count(treelike) {
13
- assertScopeIsDefined(this);
14
- treelike = treelike ?? (await this?.get("@current"));
15
- if (treelike === undefined) {
16
- return undefined;
17
- }
18
- const tree = await Tree.from(treelike);
12
+ const tree = await getTreeArgument(this, arguments, treelike);
19
13
  const keys = [...(await tree.keys())];
20
14
  return keys.length;
21
15
  }
@@ -1,6 +1,6 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
4
 
5
5
  /**
6
6
  * Return only the defined (not `undefined`) values in the tree.
@@ -12,14 +12,10 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
12
12
  * @param {Treelike} treelike
13
13
  */
14
14
  export default async function defineds(treelike) {
15
- assertScopeIsDefined(this);
16
- treelike = treelike ?? (await this?.get("@current"));
17
- if (treelike === undefined) {
18
- throw new TypeError("A treelike argument is required");
19
- }
15
+ const tree = await getTreeArgument(this, arguments, treelike);
20
16
 
21
17
  /** @type {AsyncTree} */
22
- let result = await Tree.mapReduce(treelike, null, async (values, keys) => {
18
+ let result = await Tree.mapReduce(tree, null, async (values, keys) => {
23
19
  const result = {};
24
20
  let someValuesExist = false;
25
21
  for (let i = 0; i < keys.length; i++) {
@@ -5,7 +5,7 @@ import {
5
5
  hasNonPrintableCharacters,
6
6
  keySymbol,
7
7
  } from "../../common/utilities.js";
8
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
8
+ import getTreeArgument from "../../misc/getTreeArgument.js";
9
9
 
10
10
  /**
11
11
  * Render a tree in DOT format.
@@ -19,12 +19,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
19
19
  * @param {PlainObject} [options]
20
20
  */
21
21
  export default async function dot(treelike, options = {}) {
22
- assertScopeIsDefined(this);
23
- treelike = treelike ?? (await this?.get("@current"));
24
- if (treelike === undefined) {
25
- return undefined;
26
- }
27
- const tree = Tree.from(treelike);
22
+ const tree = await getTreeArgument(this, arguments, treelike);
28
23
  const rootLabel = tree[keySymbol] ?? "";
29
24
  const treeArcs = await statements(tree, "", rootLabel, options);
30
25
  return `digraph g {
@@ -1,6 +1,6 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
4
  import defineds from "./defineds.js";
5
5
 
6
6
  /**
@@ -10,11 +10,10 @@ import defineds from "./defineds.js";
10
10
  * @param {Treelike} treelike
11
11
  */
12
12
  export default async function exceptions(treelike) {
13
- assertScopeIsDefined(this);
14
- treelike = treelike ?? (await this?.get("@current"));
13
+ const tree = await getTreeArgument(this, arguments, treelike);
15
14
 
16
15
  /** @type {AsyncTree} */
17
- let exceptionsTree = new ExceptionsTree(treelike);
16
+ let exceptionsTree = new ExceptionsTree(tree);
18
17
  exceptionsTree = Scope.treeWithScope(exceptionsTree, this);
19
18
  return defineds.call(this, exceptionsTree);
20
19
  }
@@ -23,8 +22,8 @@ export default async function exceptions(treelike) {
23
22
  * @implements {AsyncTree}
24
23
  */
25
24
  class ExceptionsTree {
26
- constructor(treelike) {
27
- this.tree = Tree.from(treelike);
25
+ constructor(tree) {
26
+ this.tree = tree;
28
27
  }
29
28
 
30
29
  async get(key) {
@@ -1,5 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
1
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Return the first value in the tree.
@@ -10,12 +9,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
9
  * @param {Treelike} [treelike]
11
10
  */
12
11
  export default async function first(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);
12
+ const tree = await getTreeArgument(this, arguments, treelike);
19
13
  for (const key of await tree.keys()) {
20
14
  // Just return first value immediately.
21
15
  const value = await tree.get(key);
@@ -15,6 +15,12 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
15
15
  */
16
16
  export default async function fn(invocable, keys = []) {
17
17
  assertScopeIsDefined(this);
18
+ // A fragment of the logic from getTreeArgument.js
19
+ if (arguments.length > 0 && invocable === undefined) {
20
+ throw new Error(
21
+ "An Origami function was called with an initial argument, but its value is undefined."
22
+ );
23
+ }
18
24
  invocable = invocable ?? (await this?.get("@current"));
19
25
  if (invocable === undefined) {
20
26
  return undefined;
@@ -1,6 +1,5 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
3
 
5
4
  /**
6
5
  * Cast the indicated treelike to a tree.
@@ -11,16 +10,9 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
11
10
  * @param {Treelike} [treelike]
12
11
  */
13
12
  export default async function tree(treelike) {
14
- assertScopeIsDefined(this);
15
- treelike = treelike ?? (await this?.get("@current"));
16
- if (treelike === undefined) {
17
- return undefined;
18
- }
19
-
20
- /** @type {AsyncTree} */
21
- let result = Tree.from(treelike);
22
- result = Scope.treeWithScope(result, this);
23
- return result;
13
+ let tree = await getTreeArgument(this, arguments, treelike);
14
+ tree = Scope.treeWithScope(tree, this);
15
+ return tree;
24
16
  }
25
17
 
26
18
  tree.usage = `from <treelike>\tConvert JSON, YAML, function, or plain object to a tree`;
@@ -1,8 +1,8 @@
1
- import { Tree, groupBy } from "@weborigami/async-tree";
1
+ import { groupBy } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import addValueKeyToScope from "../../common/addValueKeyToScope.js";
4
4
  import { toFunction } from "../../common/utilities.js";
5
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
5
+ import getTreeArgument from "../../misc/getTreeArgument.js";
6
6
 
7
7
  /**
8
8
  * Return a new tree with the values from the original tree in groups.
@@ -17,9 +17,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
17
17
  * @param {Invocable} groupKeyFn
18
18
  */
19
19
  export default async function groupByBuiltin(treelike, groupKeyFn) {
20
- assertScopeIsDefined(this);
21
- treelike = treelike ?? (await this?.get("@current"));
22
- const tree = Tree.from(treelike);
20
+ const tree = await getTreeArgument(this, arguments, treelike);
23
21
 
24
22
  const fn = toFunction(groupKeyFn);
25
23
  const baseScope = Scope.getScope(this);
@@ -1,6 +1,6 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
4
 
5
5
  /**
6
6
  * Return the source nodes of the tree: the nodes with children.
@@ -11,12 +11,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
11
11
  * @param {Treelike} [treelike]
12
12
  */
13
13
  export default async function inners(treelike) {
14
- assertScopeIsDefined(this);
15
- treelike = treelike ?? (await this?.get("@current"));
16
- if (treelike === undefined) {
17
- return undefined;
18
- }
19
- const tree = Tree.from(treelike);
14
+ const tree = await getTreeArgument(this, arguments, treelike);
20
15
 
21
16
  /** @type {AsyncTree} */
22
17
  let result = {
@@ -1,5 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
1
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Return the top-level keys in the tree as an array.
@@ -10,12 +9,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
9
  * @param {Treelike} [treelike]
11
10
  */
12
11
  export default async function keys(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);
12
+ const tree = await getTreeArgument(this, arguments, treelike);
19
13
  const keys = await tree.keys();
20
14
  return Array.from(keys);
21
15
  }
@@ -1,7 +1,7 @@
1
1
  import { Tree, keysJson } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import { transformObject } from "../../common/utilities.js";
4
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
4
+ import getTreeArgument from "../../misc/getTreeArgument.js";
5
5
 
6
6
  /**
7
7
  * Expose .keys.json for a tree.
@@ -12,12 +12,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
12
12
  * @param {Treelike} treelike
13
13
  */
14
14
  export default async function treeKeysJson(treelike) {
15
- assertScopeIsDefined(this);
16
- treelike = treelike ?? (await this?.get("@current"));
17
- if (treelike === undefined) {
18
- return undefined;
19
- }
20
- const tree = Tree.from(treelike);
15
+ const tree = await getTreeArgument(this, arguments, treelike);
21
16
  let result = transformObject(KeysJsonTransform, tree);
22
17
  result = Scope.treeWithScope(result, this);
23
18
  return result;
@@ -8,9 +8,7 @@ type TreeMapOptions = {
8
8
  extensions?: string;
9
9
  inverseKeyMap?: KeyFn;
10
10
  keyMap?: ValueKeyFn;
11
- keyName?: string;
12
11
  valueMap?: ValueKeyFn;
13
- valueName?: string
14
12
  };
15
13
 
16
14
  export default function treeMap(valueMap: ValueKeyFn): TreelikeTransform;
@@ -1,5 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
1
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
2
 
4
3
  /**
5
4
  * Returns the parent of the current tree.
@@ -11,12 +10,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
11
10
  * @param {Treelike} [treelike]
12
11
  */
13
12
  export default async function parent(treelike) {
14
- assertScopeIsDefined(this);
15
- treelike = treelike ?? (await this?.get("@current"));
16
- if (treelike === undefined) {
17
- return undefined;
18
- }
19
- const tree = Tree.from(treelike);
13
+ const tree = await getTreeArgument(this, arguments, treelike);
20
14
  return tree.parent;
21
15
  }
22
16
 
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
3
 
4
4
  /**
5
5
  * Return an array of paths to the values in the tree.
@@ -12,13 +12,8 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
12
12
  * @param {string} [prefix]
13
13
  */
14
14
  export default async function paths(treelike, prefix = "") {
15
- assertScopeIsDefined(this);
16
- treelike = treelike ?? (await this?.get("@current"));
17
- if (treelike === undefined) {
18
- return undefined;
19
- }
15
+ const tree = await getTreeArgument(this, arguments, treelike);
20
16
  const result = [];
21
- const tree = Tree.from(treelike);
22
17
  for (const key of await tree.keys()) {
23
18
  const valuePath = prefix ? `${prefix}/${key}` : key;
24
19
  const value = await tree.get(key);
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
3
 
4
4
  /**
5
5
  * Return the interior nodes of the tree.
@@ -10,12 +10,8 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
10
  * @param {Treelike} [treelike]
11
11
  */
12
12
  export default async function plain(treelike) {
13
- assertScopeIsDefined(this);
14
- treelike = treelike ?? (await this?.get("@current"));
15
- if (treelike === undefined) {
16
- return undefined;
17
- }
18
- return Tree.plain(treelike);
13
+ const tree = await getTreeArgument(this, arguments, treelike);
14
+ return Tree.plain(tree);
19
15
  }
20
16
 
21
17
  plain.usage = `plain <tree>\tA plain JavaScript object representation of the tree`;
@@ -1,6 +1,6 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
4
 
5
5
  /**
6
6
  * Reverse the order of the top-level keys in the tree.
@@ -14,13 +14,8 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
14
14
  * @param {PlainObject} [options]
15
15
  */
16
16
  export default async function reverse(treelike, options = {}) {
17
- assertScopeIsDefined(this);
18
- treelike = treelike ?? (await this?.get("@current"));
19
- if (treelike === undefined) {
20
- return undefined;
21
- }
17
+ const tree = await getTreeArgument(this, arguments, treelike);
22
18
  const scope = this;
23
- const tree = Tree.from(treelike);
24
19
  const deep = options.deep ?? false;
25
20
 
26
21
  /** @type {AsyncTree} */
@@ -1,8 +1,7 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import { Scope } from "@weborigami/language";
3
2
  import ShuffleTransform from "../../common/ShuffleTransform.js";
4
3
  import { transformObject } from "../../common/utilities.js";
5
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
4
+ import getTreeArgument from "../../misc/getTreeArgument.js";
6
5
 
7
6
  /**
8
7
  * Return a new tree with the original's keys shuffled
@@ -14,12 +13,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
14
13
  * @param {Treelike} [treelike]
15
14
  */
16
15
  export default async function shuffle(treelike) {
17
- assertScopeIsDefined(this);
18
- treelike = treelike ?? (await this?.get("@current"));
19
- if (treelike === undefined) {
20
- return undefined;
21
- }
22
- const tree = Tree.from(treelike);
16
+ const tree = await getTreeArgument(this, arguments, treelike);
23
17
 
24
18
  /** @type {AsyncTree} */
25
19
  let shuffled = transformObject(ShuffleTransform, tree);
@@ -1,5 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
1
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
2
  import builtins from "../@builtins.js";
4
3
  import unpackOrigamiExpression from "../@loaders/ori.js";
5
4
  import paths from "./paths.js";
@@ -23,9 +22,7 @@ const templateText = `=\`<?xml version="1.0" encoding="UTF-8"?>
23
22
  * @param {string} [baseHref ]
24
23
  */
25
24
  export default async function sitemap(treelike, baseHref = "") {
26
- assertScopeIsDefined(this);
27
- treelike = treelike ?? (await this?.get("@current"));
28
- const tree = Tree.from(treelike);
25
+ const tree = await getTreeArgument(this, arguments, treelike);
29
26
 
30
27
  // We're only interested in keys that end in .html or with no extension.
31
28
  function test(key) {
@@ -1,6 +1,6 @@
1
- import { Tree, sortNatural } from "@weborigami/async-tree";
1
+ import { sortNatural } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
3
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
4
 
5
5
  /**
6
6
  * Return a new tree with the original's keys sorted in natural sort order.
@@ -13,9 +13,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
13
13
  * @param {Treelike} [treelike]
14
14
  */
15
15
  export default async function sort(treelike) {
16
- assertScopeIsDefined(this);
17
- treelike = treelike ?? (await this?.get("@current"));
18
- const tree = Tree.from(treelike);
16
+ const tree = await getTreeArgument(this, arguments, treelike);
19
17
  const sorted = sortNatural()(tree);
20
18
  const scoped = Scope.treeWithScope(sorted, this);
21
19
  return scoped;
@@ -1,8 +1,8 @@
1
- import { Tree, sortBy } from "@weborigami/async-tree";
1
+ import { sortBy } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import addValueKeyToScope from "../../common/addValueKeyToScope.js";
4
4
  import { toFunction } from "../../common/utilities.js";
5
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
5
+ import getTreeArgument from "../../misc/getTreeArgument.js";
6
6
 
7
7
  /**
8
8
  * Return a new tree with the original's keys sorted using the given function to
@@ -17,9 +17,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
17
17
  * @param {Invocable} sortKeyFn
18
18
  */
19
19
  export default async function sortByBuiltin(treelike, sortKeyFn) {
20
- assertScopeIsDefined(this);
21
- treelike = treelike ?? (await this?.get("@current"));
22
- const tree = Tree.from(treelike);
20
+ const tree = await getTreeArgument(this, arguments, treelike);
23
21
 
24
22
  const fn = toFunction(sortKeyFn);
25
23
  const baseScope = Scope.getScope(this);
@@ -1,7 +1,7 @@
1
1
  import { Tree, keysJson } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import { transformObject } from "../../common/utilities.js";
4
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
4
+ import getTreeArgument from "../../misc/getTreeArgument.js";
5
5
  import index from "../@index.js";
6
6
 
7
7
  /**
@@ -13,12 +13,7 @@ import index from "../@index.js";
13
13
  * @param {Treelike} treelike
14
14
  */
15
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);
16
+ const tree = await getTreeArgument(this, arguments, treelike);
22
17
  let result = transformObject(StaticTransform, tree);
23
18
  result = Scope.treeWithScope(result, this);
24
19
  return result;
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
3
 
4
4
  /**
5
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -8,12 +8,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
8
8
  * @param {Treelike} treelike
9
9
  */
10
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);
11
+ const tree = await getTreeArgument(this, arguments, treelike);
17
12
  const firstValue = await valueForFirstKey(tree);
18
13
  if (Tree.isAsyncTree(firstValue)) {
19
14
  return fullTable(tree, firstValue);
@@ -1,6 +1,5 @@
1
- import { Tree } from "@weborigami/async-tree";
2
1
  import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
4
3
 
5
4
  /**
6
5
  * Given a tree, take the first n items from it.
@@ -12,12 +11,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
12
11
  * @param {number} n
13
12
  */
14
13
  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);
14
+ const tree = await getTreeArgument(this, arguments, treelike);
21
15
 
22
16
  /** @type {AsyncTree} */
23
17
  let takeTree = {
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
3
 
4
4
  /**
5
5
  * Return the interior nodes of the tree.
@@ -10,12 +10,7 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
10
  * @param {Treelike} [treelike]
11
11
  */
12
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);
13
+ const tree = await getTreeArgument(this, arguments, treelike);
19
14
  return Tree.values(tree);
20
15
  }
21
16
 
@@ -1,5 +1,5 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
2
+ import getTreeArgument from "../../misc/getTreeArgument.js";
3
3
 
4
4
  /**
5
5
  * Return the in-order exterior values of a tree as a flat array.
@@ -10,12 +10,8 @@ import assertScopeIsDefined from "../../misc/assertScopeIsDefined.js";
10
10
  * @param {Treelike} [treelike]
11
11
  */
12
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());
13
+ const tree = await getTreeArgument(this, arguments, treelike);
14
+ return Tree.mapReduce(tree, null, async (values) => values.flat());
19
15
  }
20
16
 
21
17
  valuesDeep.usage = `valuesDeep <tree>\tThe in-order tree values as a flat array`;
@@ -1,7 +1,7 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import ConstantTree from "../common/ConstantTree.js";
4
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+ import getTreeArgument from "../misc/getTreeArgument.js";
5
5
 
6
6
  /**
7
7
  * Let a tree (e.g., of files) respond to changes.
@@ -15,15 +15,10 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
15
15
  * @param {Invocable} [fn]
16
16
  */
17
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
- }
18
+ /** @type {any} */
19
+ const container = await getTreeArgument(this, arguments, treelike);
23
20
 
24
21
  // Watch the indicated tree.
25
- /** @type {any} */
26
- const container = Tree.from(treelike);
27
22
  await /** @type {any} */ (container).watch?.();
28
23
 
29
24
  // // Watch trees in scope.
@@ -11,6 +11,12 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
11
  */
12
12
  export default async function toYaml(obj) {
13
13
  assertScopeIsDefined(this);
14
+ // A fragment of the logic from getTreeArgument.js
15
+ if (arguments.length > 0 && obj === undefined) {
16
+ throw new Error(
17
+ "An Origami function was called with an initial argument, but its value is undefined."
18
+ );
19
+ }
14
20
  obj = obj ?? (await this?.get("@current"));
15
21
  if (obj === undefined) {
16
22
  return undefined;
@@ -1,4 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
1
+ import { DeepObjectTree, Tree, isPlainObject } from "@weborigami/async-tree";
2
2
 
3
3
  /**
4
4
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -7,7 +7,9 @@ import { Tree } from "@weborigami/async-tree";
7
7
  export default class FilterTree {
8
8
  constructor(tree, filter) {
9
9
  this.tree = Tree.from(tree);
10
- this.filter = Tree.from(filter);
10
+ this.filter = isPlainObject(filter)
11
+ ? new DeepObjectTree(filter)
12
+ : Tree.from(filter);
11
13
  }
12
14
 
13
15
  async get(key) {
@@ -1,4 +1,10 @@
1
- import { ObjectTree, Tree, merge } from "@weborigami/async-tree";
1
+ import {
2
+ DeepObjectTree,
3
+ ObjectTree,
4
+ Tree,
5
+ isPlainObject,
6
+ merge,
7
+ } from "@weborigami/async-tree";
2
8
 
3
9
  const globstar = "**";
4
10
 
@@ -8,7 +14,9 @@ const globstar = "**";
8
14
  */
9
15
  export default class GlobTree {
10
16
  constructor(globs) {
11
- this.globs = Tree.from(globs);
17
+ this.globs = isPlainObject(globs)
18
+ ? new DeepObjectTree(globs)
19
+ : Tree.from(globs);
12
20
  }
13
21
 
14
22
  async get(key) {
@@ -11,20 +11,12 @@ import { Scope } from "@weborigami/language";
11
11
  * @param {AsyncTree|null} scope
12
12
  * @param {any} value
13
13
  * @param {any} key
14
- * @param {string} [valueName]
15
- * @param {string} [keyName]
16
14
  */
17
- export default function addValueKeyToScope(
18
- scope,
19
- value,
20
- key,
21
- valueName = "_",
22
- keyName = "@key"
23
- ) {
15
+ export default function addValueKeyToScope(scope, value, key) {
24
16
  // Add the key and value to the scope as ambients.
25
17
  const ambients = {
26
- [keyName]: key,
27
- [valueName]: value,
18
+ "@key": key,
19
+ _: value,
28
20
  };
29
21
  return new Scope(ambients, scope);
30
22
  }
@@ -24,14 +24,12 @@ export default function processUnpackedContent(content, parent, inputDocument) {
24
24
  ? new Scope(inputDocument, parentScope)
25
25
  : parentScope;
26
26
 
27
- /** @this {AsyncTree|null} */
28
- async function extendScope(input, ...rest) {
29
- return fn.call(extendedScope, input, ...rest);
30
- }
31
-
32
- extendScope.code = fn.code;
33
- return extendScope;
34
- } else if (Tree.isAsyncTree(content)) {
27
+ const boundFn = fn.bind(extendedScope);
28
+ return boundFn;
29
+ } else if (
30
+ Tree.isAsyncTree(content) &&
31
+ !(/** @type {any} */ (content).scope)
32
+ ) {
35
33
  const result = Object.create(content);
36
34
  result.parent = parent;
37
35
  return result;
@@ -33,7 +33,7 @@ export default function OriCommandTransform(Base) {
33
33
  ambientsTree[keySymbol] = "ori command";
34
34
  const extendedScope = new Scope(ambientsTree, Scope.getScope(this));
35
35
  const source = key.slice(1).trim();
36
- value = await ori.call(extendedScope, source);
36
+ value = await ori.call(extendedScope, source, { formatResult: false });
37
37
 
38
38
  // Ensure this transform is applied to any subtree.
39
39
  if (Tree.isAsyncTree(value)) {
@@ -0,0 +1,41 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import assertScopeIsDefined from "./assertScopeIsDefined.js";
3
+
4
+ /**
5
+ * Many Origami built-in functions accept an optional treelike object as their
6
+ * first argument. If no tree is supplied, then the current context for the
7
+ * Origami command is used as the tree.
8
+ *
9
+ * So the argument is optional -- but if supplied, it must be defined. The
10
+ * caller should pass its `arguments` object to this function so that the actual
11
+ * number of supplied arguments can be checked.
12
+ *
13
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
14
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
15
+ *
16
+ * @param {AsyncTree|null} scope
17
+ * @param {IArguments} args
18
+ * @param {Treelike|undefined} treelike
19
+ * @returns {Promise<AsyncTree>}
20
+ */
21
+ export default async function getTreeArgument(scope, args, treelike) {
22
+ assertScopeIsDefined(scope);
23
+
24
+ if (treelike !== undefined) {
25
+ return Tree.from(treelike);
26
+ }
27
+
28
+ if (args.length === 0) {
29
+ if (!scope) {
30
+ // Should never happen because assertScopeIsDefined throws an exception.
31
+ throw new Error(
32
+ "An Origami tree function was called with no tree argument and no scope."
33
+ );
34
+ }
35
+ return scope.get("@current");
36
+ }
37
+
38
+ throw new Error(
39
+ "An Origami tree function was called with an initial argument, but its value is undefined."
40
+ );
41
+ }
@@ -53,21 +53,7 @@ export function treeRouter(tree) {
53
53
  export async function handleRequest(request, response, tree) {
54
54
  // For parsing purposes, we assume HTTPS -- it doesn't affect parsing.
55
55
  const url = new URL(request.url, `https://${request.headers.host}`);
56
-
57
- // Split on occurrences of `/!`, which represent Origami debug commands.
58
- // Command arguments can contain slashes; don't treat those as path keys.
59
- const parts = url.pathname.split(/\/!/);
60
- const keys = parts.flatMap((part, index) => {
61
- const decoded = decodeURIComponent(part);
62
- // Split keys that aren't commands; add back the `!` to commands.
63
- return index % 2 === 0 ? keysFromPath(decoded) : `!${decoded}`;
64
- });
65
-
66
- // If the path ends with a trailing slash, the final key will be an empty
67
- // string. Change that to "index.html".
68
- if (keys[keys.length - 1] === "") {
69
- keys[keys.length - 1] = "index.html";
70
- }
56
+ const keys = keysFromUrl(url);
71
57
 
72
58
  const extendedTree =
73
59
  url.searchParams && "parent" in tree
@@ -176,6 +162,28 @@ export async function handleRequest(request, response, tree) {
176
162
  return true;
177
163
  }
178
164
 
165
+ function keysFromUrl(url) {
166
+ // Split on occurrences of `/!`, which represent Origami debug commands.
167
+ // Command arguments can contain slashes; don't treat those as path keys.
168
+ const parts = url.pathname.split(/\/!/);
169
+
170
+ // Split everything before the first command by slashes and decode those.
171
+ const path = parts.shift();
172
+ const pathKeys = keysFromPath(path).map((key) => decodeURIComponent(key));
173
+
174
+ // If there are no commands, and the path ends with a trailing slash, the
175
+ // final key will be an empty string. Change that to "index.html".
176
+ if (parts.length === 0 && pathKeys[pathKeys.length - 1] === "") {
177
+ pathKeys[pathKeys.length - 1] = "index.html";
178
+ }
179
+
180
+ // Add back the `!` to commands.
181
+ const commandKeys = parts.map((command) => `!${command}`);
182
+
183
+ const keys = [...pathKeys, ...commandKeys];
184
+ return keys;
185
+ }
186
+
179
187
  /**
180
188
  * A request listener for use with the node http.createServer and
181
189
  * https.createServer calls, letting you serve an async tree as a set of pages.