@weborigami/origami 0.0.59 → 0.0.61

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 (82) hide show
  1. package/exports/exports.js +3 -7
  2. package/package.json +4 -4
  3. package/src/builtins/@breakpoint.js +12 -0
  4. package/src/builtins/@cache.js +3 -6
  5. package/src/builtins/@concat.js +3 -4
  6. package/src/builtins/@config.js +3 -12
  7. package/src/builtins/@constructor.js +3 -7
  8. package/src/builtins/@copy.js +2 -2
  9. package/src/builtins/@crawl.js +4 -6
  10. package/src/builtins/@debug.js +5 -6
  11. package/src/builtins/@deepMap.js +2 -2
  12. package/src/builtins/@deepMapFn.js +2 -2
  13. package/src/builtins/@deepMerge.js +3 -18
  14. package/src/builtins/@deepReverse.js +1 -3
  15. package/src/builtins/@deepTakeFn.js +5 -6
  16. package/src/builtins/@defineds.js +4 -7
  17. package/src/builtins/@document.js +2 -2
  18. package/src/builtins/@exceptions.js +1 -5
  19. package/src/builtins/@explore.js +15 -14
  20. package/src/builtins/@files.js +4 -6
  21. package/src/builtins/@filter.js +3 -6
  22. package/src/builtins/@fnTree.js +8 -22
  23. package/src/builtins/@globs.js +3 -6
  24. package/src/builtins/@groupFn.js +7 -8
  25. package/src/builtins/@help.js +2 -2
  26. package/src/builtins/@http.js +2 -2
  27. package/src/builtins/@https.js +2 -2
  28. package/src/builtins/@if.js +2 -2
  29. package/src/builtins/@image/format.js +2 -2
  30. package/src/builtins/@image/formatFn.js +6 -3
  31. package/src/builtins/@image/resize.js +2 -2
  32. package/src/builtins/@image/resizeFn.js +6 -3
  33. package/src/builtins/@inherited.js +2 -2
  34. package/src/builtins/@inline.js +5 -9
  35. package/src/builtins/@inners.js +0 -1
  36. package/src/builtins/@invoke.js +4 -7
  37. package/src/builtins/@json.js +3 -3
  38. package/src/builtins/@map.js +2 -2
  39. package/src/builtins/@mapFn.js +9 -10
  40. package/src/builtins/@match.js +17 -16
  41. package/src/builtins/@mdHtml.js +7 -1
  42. package/src/builtins/@merge.js +3 -18
  43. package/src/builtins/@once.js +2 -2
  44. package/src/builtins/@ori.js +12 -8
  45. package/src/builtins/@pack.js +2 -2
  46. package/src/builtins/@package.js +4 -8
  47. package/src/builtins/@paginateFn.js +1 -6
  48. package/src/builtins/@perf.js +2 -2
  49. package/src/builtins/@project.js +31 -22
  50. package/src/builtins/@reverse.js +1 -3
  51. package/src/builtins/@rss.js +78 -24
  52. package/src/builtins/@serve.js +2 -2
  53. package/src/builtins/@sitemap.js +4 -1
  54. package/src/builtins/@sortFn.js +6 -7
  55. package/src/builtins/@string.js +2 -2
  56. package/src/builtins/@takeFn.js +5 -6
  57. package/src/builtins/@treeHttp.js +2 -2
  58. package/src/builtins/@treeHttps.js +2 -2
  59. package/src/builtins/@unpack.js +2 -2
  60. package/src/builtins/@watch.js +5 -12
  61. package/src/builtins/@yaml.js +3 -3
  62. package/src/builtins/ori_handler.js +3 -3
  63. package/src/builtins/~.js +3 -3
  64. package/src/cli/cli.js +6 -19
  65. package/src/common/ConstantTree.js +1 -0
  66. package/src/common/ExplorableSiteTransform.js +9 -12
  67. package/src/common/documentObject.js +2 -2
  68. package/src/common/processUnpackedContent.js +12 -18
  69. package/src/common/utilities.js +2 -3
  70. package/src/misc/OriCommandTransform.js +1 -9
  71. package/src/misc/assertTreeIsDefined.d.ts +1 -0
  72. package/src/misc/assertTreeIsDefined.js +7 -0
  73. package/src/misc/getTreeArgument.js +11 -12
  74. package/src/misc/origamiHighlightDefinition.js +36 -0
  75. package/src/server/server.js +3 -10
  76. package/src/builtins/@arrowsMap.js +0 -25
  77. package/src/builtins/@scope/extend.js +0 -22
  78. package/src/builtins/@scope/get.js +0 -25
  79. package/src/builtins/@scope/invoke.js +0 -22
  80. package/src/builtins/@scope/set.js +0 -25
  81. package/src/common/addValueKeyToScope.js +0 -23
  82. package/src/misc/assertScopeIsDefined.js +0 -7
@@ -1,6 +1,7 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
- import { OrigamiFiles, Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import { merge } from "@weborigami/async-tree";
3
+ import { OrigamiFiles } from "@weborigami/language";
4
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
4
5
  import builtins from "./@builtins.js";
5
6
  import fileTypeOrigami from "./ori_handler.js";
6
7
 
@@ -22,43 +23,48 @@ const configFileName = "config.ori";
22
23
  * @param {any} [key]
23
24
  */
24
25
  export default async function project(key) {
25
- assertScopeIsDefined(this, "project");
26
+ assertTreeIsDefined(this, "project");
26
27
 
27
28
  const dirname = process.cwd();
28
29
  const currentTree = new OrigamiFiles(dirname);
30
+ currentTree.parent = builtins;
29
31
 
30
32
  // Search up the tree for the configuration file or package.json to determine
31
33
  // the project root.
32
- let rootTree =
34
+ const configContainer =
33
35
  (await findAncestorFile(currentTree, configFileName)) ??
34
36
  (await findAncestorFile(currentTree, "package.json"));
35
37
 
36
- let config = null;
37
- if (rootTree) {
38
- // Load the configuration.
39
- const configParent = Scope.treeWithScope(rootTree, builtins);
40
- const buffer = await configParent.get(configFileName);
41
- if (buffer) {
42
- // Project has configuration file
43
- config = await fileTypeOrigami.unpack(buffer, {
38
+ let projectRoot;
39
+ if (!configContainer) {
40
+ // No configuration file or package.json found; use the current directory.
41
+ projectRoot = currentTree;
42
+ } else {
43
+ // Load the configuration file if one exists.
44
+ const buffer = await configContainer.get(configFileName);
45
+ if (!buffer) {
46
+ // Project root defined by package.json
47
+ projectRoot = configContainer;
48
+ } else {
49
+ // Load Origami configuration file
50
+ const configTree = await fileTypeOrigami.unpack(buffer, {
44
51
  key: configFileName,
45
- parent: configParent,
52
+ parent: configContainer,
46
53
  });
47
- if (!config) {
48
- const configPath = /** @type {any} */ (configParent).path;
54
+ if (!configTree) {
55
+ const configPath = /** @type {any} */ (configContainer).path;
49
56
  throw new Error(
50
57
  `Couldn't load the Origami configuration in ${configPath}/${configFileName}`
51
58
  );
52
59
  }
60
+ projectRoot = merge(configTree, configContainer);
61
+ // HACK so cli.js can get a path
62
+ /** @type {any} */ (projectRoot).path = configContainer.path;
63
+ projectRoot.parent = builtins;
53
64
  }
54
- } else {
55
- rootTree = currentTree;
56
65
  }
57
66
 
58
- // Add the configuration as the context for the project root.
59
- const scope = new Scope(config, builtins);
60
- const result = Scope.treeWithScope(rootTree, scope);
61
- return key === undefined ? result : result.get(key);
67
+ return key === undefined ? projectRoot : projectRoot.get(key);
62
68
  }
63
69
 
64
70
  // Return the first ancestor of the given tree that contains a file with the
@@ -68,7 +74,10 @@ async function findAncestorFile(start, fileName) {
68
74
  while (current) {
69
75
  const value = await current.get(fileName);
70
76
  if (value) {
71
- // Found the desired file; its container is the project root.
77
+ // Found the desired file; its container is the project root. Set the
78
+ // parent to the builtins; in the context of this project, there's nothing
79
+ // higher up.
80
+ current.parent = builtins;
72
81
  return current;
73
82
  }
74
83
  // Not found; try the parent.
@@ -1,5 +1,4 @@
1
1
  import { reverse } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
2
  import getTreeArgument from "../misc/getTreeArgument.js";
4
3
 
5
4
  /**
@@ -13,8 +12,7 @@ import getTreeArgument from "../misc/getTreeArgument.js";
13
12
  */
14
13
  export default async function reverseBuiltin(treelike) {
15
14
  const tree = await getTreeArgument(this, arguments, treelike, "@reverse");
16
- let reversed = reverse(tree);
17
- reversed = Scope.treeWithScope(reversed, this);
15
+ const reversed = reverse(tree);
18
16
  return reversed;
19
17
  }
20
18
 
@@ -1,55 +1,109 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
3
+
4
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
5
+ const months = [
6
+ "Jan",
7
+ "Feb",
8
+ "Mar",
9
+ "Apr",
10
+ "May",
11
+ "Jun",
12
+ "Jul",
13
+ "Aug",
14
+ "Sep",
15
+ "Oct",
16
+ "Nov",
17
+ "Dec",
18
+ ];
3
19
 
4
20
  /**
5
21
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
22
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
7
23
  * @this {AsyncTree|null}
8
24
  * @param {Treelike} jsonFeedTree
25
+ * @param {any} options
9
26
  */
10
- export default async function rss(jsonFeedTree) {
11
- assertScopeIsDefined(this, "rss");
27
+ export default async function rss(jsonFeedTree, options = {}) {
28
+ assertTreeIsDefined(this, "rss");
12
29
  const jsonFeed = await Tree.plain(jsonFeedTree);
13
- const { description, home_page_url, items, feed_url, title } = jsonFeed;
30
+ const { description, home_page_url, items, title } = jsonFeed;
14
31
 
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("/");
32
+ let { feed_url, language } = options;
33
+ if (!feed_url && jsonFeed.feed_url) {
34
+ // Presume that the RSS feed lives in same location as feed_url
35
+ // but with a .xml extension.
36
+ feed_url = jsonFeed.feed_url;
37
+ if (feed_url.endsWith(".json")) {
38
+ feed_url = feed_url.replace(".json", ".xml");
39
+ }
40
+ }
20
41
 
21
42
  const itemsRss = items?.map((story) => itemRss(story)).join("") ?? [];
22
43
 
44
+ const titleElement = title ? ` <title>![CDATA[${title}]]</title>\n` : "";
45
+ const descriptionElement = description
46
+ ? ` <description>${description}</description>\n`
47
+ : "";
48
+ const linkElement = home_page_url
49
+ ? ` <link>${home_page_url}</link>\n`
50
+ : "";
51
+ const languageElement = language
52
+ ? ` <language>${language}</language>\n`
53
+ : "";
54
+ const feedLinkElement = ` <atom:link href="${feed_url}" rel="self" type="application/rss+xml"/>\n`;
55
+ // const feedLinkElement = ` <link rel="self" type="application/atom+xml" href="${feed_url}"/>\n`;
56
+
23
57
  return `<?xml version="1.0" ?>
24
- <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
58
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
25
59
  <channel>
26
- <atom:link href="${rssUrl}" rel="self" type="application/rss+xml"/>
27
- <title><![CDATA[${title}]]></title>
28
- <link>${home_page_url}</link>
29
- <description>${description}</description>
30
- ${itemsRss}</channel>
60
+ ${titleElement}${descriptionElement}${linkElement}${languageElement}${feedLinkElement}${itemsRss} </channel>
31
61
  </rss>`;
32
62
  }
33
63
 
34
64
  function itemRss(jsonFeedItem) {
35
- const { content_html, date_published, id, title, url } = jsonFeedItem;
36
- // RSS wants dates in RFC-822, essentially the same as ISO 8601.
65
+ const { content_html, id, summary, title, url } = jsonFeedItem;
66
+ let { date_published } = jsonFeedItem;
67
+ if (typeof date_published === "string") {
68
+ // Parse as ISO 8601 date.
69
+ date_published = new Date(date_published);
70
+ }
37
71
  const date =
38
72
  date_published instanceof Date
39
- ? date_published.toUTCString()
73
+ ? toRFC822Date(date_published)
40
74
  : date_published;
75
+
41
76
  const dateElement = date ? ` <pubDate>${date}</pubDate>\n` : "";
42
- const guidElement = id ? ` <guid>${id}</guid>\n` : "";
77
+ const isPermaLink =
78
+ id !== undefined && !URL.canParse(id) ? ` isPermaLink="false"` : "";
79
+ const guidElement = id ? ` <guid${isPermaLink}>${id}</guid>\n` : "";
80
+ const descriptionElement = summary
81
+ ? ` <description><![CDATA[${summary}]]></description>\n`
82
+ : "";
43
83
  const contentElement = content_html
44
- ? ` <description><![CDATA[${content_html}]]></description>\n`
84
+ ? ` <content:encoded><![CDATA[${content_html}]]></content:encoded>\n`
85
+ : "";
86
+ const titleElement = title
87
+ ? ` <title><![CDATA[${title}]]></title>\n`
45
88
  : "";
46
- const titleElement = `<![CDATA[${title}]]>`;
89
+ const linkElement = url ? ` <link>${url}</link>\n` : "";
90
+
47
91
  return ` <item>
48
- ${dateElement} <title>${titleElement}</title>
49
- <link>${url}</link>
50
- ${guidElement}${contentElement} </item>
92
+ ${dateElement}${titleElement}${linkElement}${guidElement}${descriptionElement}${contentElement} </item>
51
93
  `;
52
94
  }
53
95
 
96
+ // RSS wants dates in RFC-822.
97
+ function toRFC822Date(date) {
98
+ const day = days[date.getUTCDay()];
99
+ const dayOfMonth = date.getUTCDate().toString().padStart(2, "0");
100
+ const month = months[date.getUTCMonth()];
101
+ const year = date.getUTCFullYear();
102
+ const hours = date.getUTCHours().toString().padStart(2, "0");
103
+ const minutes = date.getUTCMinutes().toString().padStart(2, "0");
104
+ const seconds = date.getUTCSeconds().toString().padStart(2, "0");
105
+ return `${day}, ${dayOfMonth} ${month} ${year} ${hours}:${minutes}:${seconds} GMT`;
106
+ }
107
+
54
108
  rss.usage = `@rss <feed>\tTransforms a JSON Feed tree to RSS XML`;
55
109
  rss.documentation = "https://weborigami.org/language/@rss.html";
@@ -4,7 +4,7 @@ import { createServer } from "node:net";
4
4
  import process from "node:process";
5
5
  import ExplorableSiteTransform from "../common/ExplorableSiteTransform.js";
6
6
  import { isTransformApplied, transformObject } from "../common/utilities.js";
7
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
7
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
8
8
  import { requestListener } from "../server/server.js";
9
9
  import debug from "./@debug.js";
10
10
  import watch from "./@watch.js";
@@ -22,7 +22,7 @@ const defaultPort = 5000;
22
22
  * @this {AsyncTree|null}
23
23
  */
24
24
  export default async function serve(treelike, port) {
25
- assertScopeIsDefined(this, "serve");
25
+ assertTreeIsDefined(this, "serve");
26
26
  let tree;
27
27
  if (treelike) {
28
28
  tree = Tree.from(treelike);
@@ -1,3 +1,4 @@
1
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
1
2
  import getTreeArgument from "../misc/getTreeArgument.js";
2
3
  import builtins from "./@builtins.js";
3
4
  import paths from "./@paths.js";
@@ -22,6 +23,7 @@ const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
22
23
  * @param {string} [baseHref ]
23
24
  */
24
25
  export default async function sitemap(treelike, baseHref = "") {
26
+ assertTreeIsDefined(this, "sitemap");
25
27
  const tree = await getTreeArgument(this, arguments, treelike, "@sitemap");
26
28
 
27
29
  // We're only interested in keys that end in .html or with no extension.
@@ -49,7 +51,8 @@ export default async function sitemap(treelike, baseHref = "") {
49
51
  .map((path) => (path.endsWith("index.html") ? path.slice(0, -10) : path));
50
52
 
51
53
  const templateFn = await fileTypeOrigami.unpack(templateText);
52
- const templateResult = await templateFn.call(builtins, htmlPaths);
54
+ const target = this ?? builtins;
55
+ const templateResult = await templateFn.call(target, htmlPaths);
53
56
  return String(templateResult);
54
57
  }
55
58
 
@@ -1,7 +1,6 @@
1
1
  import { sortFn } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
2
  import { toFunction } from "../common/utilities.js";
4
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
3
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
5
4
 
6
5
  /**
7
6
  * Return a transform function that sorts a tree's keys using a comparison
@@ -23,8 +22,8 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
23
22
  * @param {ValueKeyFn|SortOptions} [options]
24
23
  */
25
24
  export default function sortFnBuiltin(options) {
26
- assertScopeIsDefined(this);
27
- const scope = this;
25
+ assertTreeIsDefined(this, "sortFn");
26
+ const parent = this;
28
27
 
29
28
  if (typeof options === "function") {
30
29
  // Take the function as the `sortKey` option
@@ -37,7 +36,7 @@ export default function sortFnBuiltin(options) {
37
36
  const originalSortKey = toFunction(options?.sortKey);
38
37
  extendedSortKeyFn = async (key, tree) => {
39
38
  const value = await tree.get(key);
40
- const sortKey = await originalSortKey.call(scope, value, key);
39
+ const sortKey = await originalSortKey.call(parent, value, key);
41
40
  return sortKey;
42
41
  };
43
42
  }
@@ -49,8 +48,8 @@ export default function sortFnBuiltin(options) {
49
48
 
50
49
  return (treelike) => {
51
50
  const sorted = fn(treelike);
52
- const scoped = Scope.treeWithScope(sorted, scope);
53
- return scoped;
51
+ sorted.parent = parent;
52
+ return sorted;
54
53
  };
55
54
  }
56
55
 
@@ -1,5 +1,5 @@
1
1
  import { toString } from "../common/utilities.js";
2
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
3
3
 
4
4
  /**
5
5
  * Convert an object to a string.
@@ -9,6 +9,6 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
9
9
  * @param {any} object
10
10
  */
11
11
  export default function stringBuiltin(object) {
12
- assertScopeIsDefined(this, "string");
12
+ assertTreeIsDefined(this, "string");
13
13
  return toString(object);
14
14
  }
@@ -1,6 +1,5 @@
1
1
  import { takeFn } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
4
3
 
5
4
  /**
6
5
  * Limit the number of keys to the indicated count.
@@ -11,11 +10,11 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
10
  * @param {number} count
12
11
  */
13
12
  export default function takeFnBuiltin(count) {
14
- assertScopeIsDefined(this, "takeFn");
15
- const scope = this;
13
+ assertTreeIsDefined(this, "takeFn");
14
+ const parent = this;
16
15
  return (treelike) => {
17
16
  const taken = takeFn(count)(treelike);
18
- const scoped = Scope.treeWithScope(taken, scope);
19
- return scoped;
17
+ taken.parent = parent;
18
+ return taken;
20
19
  };
21
20
  }
@@ -1,5 +1,5 @@
1
1
  import { ops } from "@weborigami/language";
2
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
3
3
 
4
4
  /**
5
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -11,7 +11,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
11
  * @param {...string|Symbol} keys
12
12
  */
13
13
  export default function treeHttp(host, ...keys) {
14
- assertScopeIsDefined(this, "treeHttp");
14
+ assertTreeIsDefined(this, "treeHttp");
15
15
  return ops.treeHttp.call(this, host, ...keys);
16
16
  }
17
17
 
@@ -1,5 +1,5 @@
1
1
  import { ops } from "@weborigami/language";
2
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
3
3
 
4
4
  /**
5
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -11,7 +11,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
11
  * @param {...string|Symbol} keys
12
12
  */
13
13
  export default function treeHttps(host, ...keys) {
14
- assertScopeIsDefined(this, "treeHttps");
14
+ assertTreeIsDefined(this, "treeHttps");
15
15
  return ops.treeHttps.call(this, host, ...keys);
16
16
  }
17
17
 
@@ -1,4 +1,4 @@
1
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
1
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
2
2
 
3
3
  /**
4
4
  * Unpack a packed format like a Uint8Array or ArrayBuffer to a usable form like
@@ -9,6 +9,6 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
9
9
  * @param {any} obj
10
10
  */
11
11
  export default function unpack(obj) {
12
- assertScopeIsDefined(this, "unpack");
12
+ assertTreeIsDefined(this, "unpack");
13
13
  return obj?.unpack?.();
14
14
  }
@@ -1,5 +1,4 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
2
  import ConstantTree from "../common/ConstantTree.js";
4
3
  import getTreeArgument from "../misc/getTreeArgument.js";
5
4
 
@@ -21,16 +20,12 @@ export default async function watch(treelike, fn) {
21
20
  // Watch the indicated tree.
22
21
  await /** @type {any} */ (container).watch?.();
23
22
 
24
- // // Watch trees in scope.
25
- // const scope = /** @type {any} */ (container).scope;
26
- // await scope?.watch?.();
27
-
28
23
  if (fn === undefined) {
29
24
  return container;
30
25
  }
31
26
 
32
27
  // The caller supplied a function to reevaluate whenever the tree changes.
33
- let tree = await evaluateTree(container.scope, fn);
28
+ let tree = await evaluateTree(container, fn);
34
29
 
35
30
  // We want to return a stable reference to the tree, so we'll use a prototype
36
31
  // chain that will always point to the latest tree. We'll extend the tree's
@@ -40,19 +35,19 @@ export default async function watch(treelike, fn) {
40
35
 
41
36
  // Reevaluate the function whenever the tree changes.
42
37
  container.addEventListener?.("change", async () => {
43
- const tree = await evaluateTree(container.scope, fn);
38
+ const tree = await evaluateTree(container, fn);
44
39
  updateIndirectPointer(handle, tree);
45
40
  });
46
41
 
47
42
  return handle;
48
43
  }
49
44
 
50
- async function evaluateTree(scope, fn) {
45
+ async function evaluateTree(container, fn) {
51
46
  let tree;
52
47
  let message;
53
48
  let result;
54
49
  try {
55
- result = await fn.call(scope);
50
+ result = await fn.call(container);
56
51
  } catch (error) {
57
52
  message = messageForError(error);
58
53
  }
@@ -65,9 +60,7 @@ async function evaluateTree(scope, fn) {
65
60
  }
66
61
  console.warn(message);
67
62
  tree = new ConstantTree(message);
68
- if (scope) {
69
- tree = Scope.treeWithScope(tree, scope);
70
- }
63
+ tree.parent = container;
71
64
  return tree;
72
65
  }
73
66
 
@@ -1,7 +1,7 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
2
  import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
3
3
  import YAML from "yaml";
4
- import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+ import assertTreeIsDefined from "../misc/assertTreeIsDefined.js";
5
5
 
6
6
  /**
7
7
  * Render the object as text in YAML format.
@@ -10,14 +10,14 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
10
10
  * @param {any} [obj]
11
11
  */
12
12
  export default async function toYaml(obj) {
13
- assertScopeIsDefined(this, "yaml");
13
+ assertTreeIsDefined(this, "yaml");
14
14
  // A fragment of the logic from getTreeArgument.js
15
15
  if (arguments.length > 0 && obj === undefined) {
16
16
  throw new Error(
17
17
  "An Origami function was called with an initial argument, but its value is undefined."
18
18
  );
19
19
  }
20
- obj = obj ?? (await this?.get("@current"));
20
+ obj = obj ?? this;
21
21
  if (obj === undefined) {
22
22
  return undefined;
23
23
  }
@@ -1,5 +1,5 @@
1
1
  import { symbols } from "@weborigami/async-tree";
2
- import { Scope, compile } from "@weborigami/language";
2
+ import { compile } from "@weborigami/language";
3
3
  import processUnpackedContent from "../common/processUnpackedContent.js";
4
4
  import * as utilities from "../common/utilities.js";
5
5
  import builtins from "./@builtins.js";
@@ -40,8 +40,8 @@ export default {
40
40
  // Compile the source code as an Origami expression and evaluate it.
41
41
  const compiler = options.compiler ?? compile.expression;
42
42
  const fn = compiler(source);
43
- const parentScope = parent ? Scope.getScope(parent) : builtins;
44
- let content = await fn.call(parentScope);
43
+ const target = parent ?? builtins;
44
+ let content = await fn.call(target);
45
45
 
46
46
  return processUnpackedContent(content, parent, attachedData);
47
47
  },
package/src/builtins/~.js CHANGED
@@ -1,9 +1,9 @@
1
- import { OrigamiFiles, Scope } from "@weborigami/language";
1
+ import { OrigamiFiles } from "@weborigami/language";
2
2
  import os from "node:os";
3
3
  import builtins from "./@builtins.js";
4
4
 
5
5
  /** @type {import("@weborigami/types").AsyncTree} */
6
- let tree = new OrigamiFiles(os.homedir());
7
- tree = Scope.treeWithScope(tree, builtins);
6
+ const tree = new OrigamiFiles(os.homedir());
7
+ tree.parent = builtins;
8
8
 
9
9
  export default tree;
package/src/cli/cli.js CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { ObjectTree, Tree } from "@weborigami/async-tree";
4
- import { Scope, formatError } from "@weborigami/language";
3
+ import { Tree } from "@weborigami/async-tree";
4
+ import { formatError } from "@weborigami/language";
5
5
  import path from "node:path";
6
6
  import process, { stdout } from "node:process";
7
7
  import ori from "../builtins/@ori.js";
8
8
  import project from "../builtins/@project.js";
9
- import { keySymbol } from "../common/utilities.js";
10
9
  import showUsage from "./showUsage.js";
11
10
 
12
11
  const TypedArray = Object.getPrototypeOf(Uint8Array);
@@ -17,32 +16,20 @@ async function main(...args) {
17
16
  // Find the project root.
18
17
  const projectTree = await project.call(null);
19
18
 
20
- // HACK: get the configuration via the project.
21
- const config = new Scope(...projectTree.scope.trees.slice(1));
22
-
23
19
  // If no arguments were passed, show usage.
24
20
  if (!expression) {
21
+ // HACK: the config is the parent of the project tree.
22
+ const config = projectTree.parent;
25
23
  await showUsage(config);
26
24
  return;
27
25
  }
28
26
 
29
- // Splice ambients tree into project tree scope.
30
- const ambients = new ObjectTree({});
31
- ambients[keySymbol] = "Origami CLI";
32
- let tree = Scope.treeWithScope(projectTree, new Scope(ambients, config));
33
-
34
27
  // Traverse from the project root to the current directory.
35
28
  const currentDirectory = process.cwd();
36
29
  const relative = path.relative(projectTree.path, currentDirectory);
37
- if (relative !== "") {
38
- tree = await Tree.traversePath(tree, relative);
39
- }
40
-
41
- // Add ambient property for the current tree.
42
- await ambients.set("@current", tree);
30
+ const tree = await Tree.traversePath(projectTree, relative);
43
31
 
44
- const scope = Scope.getScope(tree);
45
- const result = await ori.call(scope, expression);
32
+ const result = await ori.call(tree, expression);
46
33
  if (result !== undefined) {
47
34
  const output =
48
35
  result instanceof ArrayBuffer
@@ -5,6 +5,7 @@
5
5
  export default class ConstantTree {
6
6
  constructor(value) {
7
7
  this.value = value;
8
+ this.parent = null;
8
9
  }
9
10
 
10
11
  async get(key) {
@@ -1,5 +1,4 @@
1
1
  import { Tree, keysJson } from "@weborigami/async-tree";
2
- import { Scope } from "@weborigami/language";
3
2
  import index from "../builtins/@index.js";
4
3
  import { isTransformApplied, transformObject } from "../common/utilities.js";
5
4
 
@@ -33,16 +32,14 @@ export default function ExplorableSiteTransform(Base) {
33
32
 
34
33
  // Ask the tree if it has the key.
35
34
  let value = await super.get(key);
36
- const scope = Scope.getScope(this);
37
35
 
38
36
  if (value === undefined) {
39
37
  // The tree doesn't have the key; try the defaults.
40
- if (scope) {
41
- if (key === "index.html") {
42
- value = await index.call(scope, this);
43
- } else if (key === ".keys.json") {
44
- value = await keysJson.stringify(this);
45
- }
38
+ if (key === "index.html") {
39
+ // This tree is both the function call target and the parameter.
40
+ value = await index.call(this, this);
41
+ } else if (key === ".keys.json") {
42
+ value = await keysJson.stringify(this);
46
43
  }
47
44
  }
48
45
 
@@ -55,12 +52,13 @@ export default function ExplorableSiteTransform(Base) {
55
52
 
56
53
  if (key.endsWith?.("/")) {
57
54
  // Instead of return the tree directly, return an index for it.
58
- value = await index.call(scope, value);
55
+ value = await index.call(this, value);
59
56
  }
60
57
  } else if (value?.unpack) {
61
58
  // If the value isn't a tree, but has a tree attached via an `unpack`
62
59
  // method, wrap the unpack method to add this transform.
63
60
  const original = value.unpack.bind(value);
61
+ const parent = this;
64
62
  value.unpack = async () => {
65
63
  const content = await original();
66
64
  if (!Tree.isTraversable(content)) {
@@ -68,9 +66,8 @@ export default function ExplorableSiteTransform(Base) {
68
66
  }
69
67
  /** @type {any} */
70
68
  let tree = Tree.from(content);
71
- if (!tree.parent && !tree.scope) {
72
- const scope = Scope.getScope(this);
73
- tree = Scope.treeWithScope(tree, scope);
69
+ if (!tree.parent) {
70
+ tree.parent = parent;
74
71
  }
75
72
  if (!isTransformApplied(ExplorableSiteTransform, tree)) {
76
73
  tree = transformObject(ExplorableSiteTransform, tree);
@@ -1,4 +1,4 @@
1
- import { isPlainObject, isUnpackable } from "@weborigami/async-tree";
1
+ import { isPlainObject, isUnpackable, toString } from "@weborigami/async-tree";
2
2
  // import txtHandler from "../builtins/txt_handler.js";
3
3
 
4
4
  /**
@@ -25,7 +25,7 @@ export default async function documentObject(input, data) {
25
25
  text = input["@text"];
26
26
  inputData = input;
27
27
  } else {
28
- text = String(input);
28
+ text = toString(input);
29
29
  inputData = null;
30
30
  }
31
31
  // TODO: Either restore this code, or move responsibility for packing a