@weborigami/origami 0.3.3-jse.3 → 0.3.3

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 (45) hide show
  1. package/main.js +3 -1
  2. package/package.json +4 -4
  3. package/src/builtins.js +58 -0
  4. package/src/builtinsTree.js +36 -0
  5. package/src/calc/calc.js +81 -0
  6. package/src/common/documentObject.js +1 -7
  7. package/src/{handlers → common}/processUnpackedContent.js +3 -1
  8. package/src/dev/crawler/crawlResources.js +1 -1
  9. package/src/dev/crawler/pathsInHtml.js +0 -2
  10. package/src/dev/explore.js +8 -3
  11. package/src/handlers/css.handler.js +2 -2
  12. package/src/handlers/handlerExports.js +16 -0
  13. package/src/handlers/handlers.js +40 -36
  14. package/src/handlers/htm.handler.js +1 -1
  15. package/src/handlers/html.handler.js +2 -2
  16. package/src/handlers/jpg.handler.js +1 -1
  17. package/src/handlers/js.handler.js +1 -1
  18. package/src/handlers/md.handler.js +2 -2
  19. package/src/handlers/mjs.handler.js +1 -1
  20. package/src/handlers/ori.handler.js +5 -13
  21. package/src/handlers/oridocument.handler.js +2 -15
  22. package/src/handlers/ts.handler.js +1 -1
  23. package/src/handlers/txt.handler.js +1 -9
  24. package/src/handlers/wasm.handler.js +1 -1
  25. package/src/handlers/yaml.handler.js +1 -1
  26. package/src/handlers/yml.handler.js +1 -1
  27. package/src/internal.js +24 -0
  28. package/src/js.js +38 -0
  29. package/src/origami/config.js +4 -2
  30. package/src/origami/ori.js +4 -13
  31. package/src/origami/origami.js +5 -0
  32. package/src/origami/project.js +56 -49
  33. package/src/protocols/files.js +1 -2
  34. package/src/protocols/inherited.js +3 -6
  35. package/src/protocols/scope.js +3 -3
  36. package/src/site/sitemap.js +3 -2
  37. package/src/text/inline.js +1 -1
  38. package/src/tree/map.js +1 -1
  39. package/src/BuiltinsTree.js +0 -18
  40. package/src/builtinsJse.js +0 -67
  41. package/src/builtinsShell.js +0 -83
  42. package/src/cli/getConfig.js +0 -10
  43. package/src/handlers/handlerBuiltins.js +0 -26
  44. package/src/handlers/jse.handler.js +0 -17
  45. package/src/handlers/jsedocument.handler.js +0 -17
package/main.js CHANGED
@@ -1,9 +1,11 @@
1
+ export * from "./src/calc/calc.js";
1
2
  export { default as documentObject } from "./src/common/documentObject.js";
2
3
  export { toString } from "./src/common/utilities.js";
3
4
  export * from "./src/dev/dev.js";
4
- export { default as handlerBuiltins } from "./src/handlers/handlerBuiltins.js";
5
+ export * from "./src/handlers/handlerExports.js";
5
6
  export * from "./src/handlers/handlers.js";
6
7
  export * from "./src/image/image.js";
8
+ export { builtinsTree } from "./src/internal.js";
7
9
  export * from "./src/origami/origami.js";
8
10
  export { default as packageBuiltin } from "./src/protocols/package.js";
9
11
  export * from "./src/server/server.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.3.3-jse.3",
3
+ "version": "0.3.3",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -17,10 +17,10 @@
17
17
  "typescript": "5.8.2"
18
18
  },
19
19
  "dependencies": {
20
- "@weborigami/async-tree": "0.3.3-jse.3",
20
+ "@weborigami/async-tree": "0.3.3",
21
21
  "@weborigami/json-feed-to-rss": "1.0.0",
22
- "@weborigami/language": "0.3.3-jse.3",
23
- "@weborigami/types": "0.3.3-jse.3",
22
+ "@weborigami/language": "0.3.3",
23
+ "@weborigami/types": "0.3.3",
24
24
  "css-tree": "3.1.0",
25
25
  "exif-parser": "0.1.12",
26
26
  "graphviz-wasm": "3.0.2",
@@ -0,0 +1,58 @@
1
+ import * as calc from "./calc/calc.js";
2
+ import * as dev from "./dev/dev.js";
3
+ import * as handlers from "./handlers/handlers.js";
4
+ import help from "./help/help.js";
5
+ import * as image from "./image/image.js";
6
+ import js from "./js.js";
7
+ import node from "./node.js";
8
+ import * as origami from "./origami/origami.js";
9
+ import explore from "./protocols/explore.js";
10
+ import files from "./protocols/files.js";
11
+ import http from "./protocols/http.js";
12
+ import https from "./protocols/https.js";
13
+ import httpstree from "./protocols/httpstree.js";
14
+ import httptree from "./protocols/httptree.js";
15
+ import inherited from "./protocols/inherited.js";
16
+ import instantiate from "./protocols/new.js";
17
+ import packageNamespace from "./protocols/package.js";
18
+ import scope from "./protocols/scope.js";
19
+ import * as site from "./site/site.js";
20
+ import * as text from "./text/text.js";
21
+ import * as tree from "./tree/tree.js";
22
+
23
+ /** @type {any} */
24
+ export default {
25
+ "calc:": adjustReservedWords(calc),
26
+ "dev:": dev,
27
+ "explore:": explore,
28
+ "files:": files,
29
+ "help:": help,
30
+ "http:": http,
31
+ "https:": https,
32
+ "httpstree:": httpstree,
33
+ "httptree:": httptree,
34
+ "image:": image,
35
+ "inherited:": inherited,
36
+ "js:": js,
37
+ "new:": instantiate,
38
+ "node:": node,
39
+ "origami:": origami,
40
+ "package:": packageNamespace,
41
+ "scope:": scope,
42
+ "site:": adjustReservedWords(site),
43
+ "text:": text,
44
+ "tree:": tree,
45
+
46
+ // Some builtins need to be exposed at top level
47
+ ...handlers.default,
48
+ };
49
+
50
+ // Handle cases where a builtin name conflicts with a JS reserved word
51
+ function adjustReservedWords(obj) {
52
+ const result = {};
53
+ for (const [key, value] of Object.entries(obj)) {
54
+ const name = value.key ?? key;
55
+ result[name] = value;
56
+ }
57
+ return result;
58
+ }
@@ -0,0 +1,36 @@
1
+ import { trailingSlash } from "@weborigami/async-tree";
2
+ import builtins from "./builtins.js";
3
+
4
+ const expanded = { ...builtins };
5
+
6
+ // For all builtins like `tree:keys`, add a shorthand `keys`.
7
+ for (const [key, value] of Object.entries(expanded)) {
8
+ const isNamespace = key.endsWith(":");
9
+ if (isNamespace) {
10
+ for (const [subKey, subValue] of Object.entries(value)) {
11
+ // HACK: Skip description keys until we can make them all non-enumerable.
12
+ if (subKey === "description") {
13
+ continue;
14
+ }
15
+ if (subKey in expanded) {
16
+ throw new Error(`Internal Origami error: Duplicate key: ${subKey}`);
17
+ }
18
+ expanded[subKey] = subValue;
19
+ }
20
+ }
21
+ }
22
+
23
+ // We create our own tree instead of using ObjectTree, since that binds the
24
+ // functions would be bound to the object. We want to leave them unbound.
25
+ class BuiltinsTree {
26
+ async get(key) {
27
+ const normalizedKey = trailingSlash.remove(key);
28
+ return expanded[normalizedKey];
29
+ }
30
+
31
+ async keys() {
32
+ return Object.keys(expanded);
33
+ }
34
+ }
35
+
36
+ export default new BuiltinsTree();
@@ -0,0 +1,81 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
3
+
4
+ export function add(...args) {
5
+ console.warn(`Warning: "add" is deprecated. Use the "+" operator instead.`);
6
+ const numbers = args.map((arg) => Number(arg));
7
+ return numbers.reduce((acc, val) => acc + val, 0);
8
+ }
9
+
10
+ export function and(...args) {
11
+ console.warn(`Warning: "and" is deprecated. Use the "&&" operator instead.`);
12
+ return args.every((arg) => arg);
13
+ }
14
+
15
+ export function divide(a, b) {
16
+ console.warn(
17
+ `Warning: "divide" is deprecated. Use the "/" operator instead.`
18
+ );
19
+ return Number(a) / Number(b);
20
+ }
21
+
22
+ export function equals(a, b) {
23
+ console.warn(
24
+ `Warning: "equals" is deprecated. Use the "===" operator instead.`
25
+ );
26
+ return a === b;
27
+ }
28
+
29
+ /**
30
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
31
+ *
32
+ * @this {AsyncTree|null}
33
+ * @param {any} value
34
+ * @param {any} trueResult
35
+ * @param {any} [falseResult]
36
+ */
37
+ export async function ifBuiltin(value, trueResult, falseResult) {
38
+ console.warn(
39
+ `Warning: "if" is deprecated. Use the conditional "a ? b : c" operator instead.`
40
+ );
41
+
42
+ assertTreeIsDefined(this, "calc:if");
43
+ let condition = await value;
44
+ if (Tree.isAsyncTree(condition)) {
45
+ const keys = Array.from(await condition.keys());
46
+ condition = keys.length > 0;
47
+ }
48
+
49
+ // 0 is true, null/undefined/false is false
50
+ let result = condition || condition === 0 ? trueResult : falseResult;
51
+ if (typeof result === "function") {
52
+ result = await result.call(this);
53
+ }
54
+ return result;
55
+ }
56
+ ifBuiltin.key = "if";
57
+
58
+ export function multiply(...args) {
59
+ console.warn(
60
+ `Warning: "multiply" is deprecated. Use the "*" operator instead.`
61
+ );
62
+ const numbers = args.map((arg) => Number(arg));
63
+ return numbers.reduce((acc, val) => acc * val, 1);
64
+ }
65
+
66
+ export function not(value) {
67
+ console.warn(`Warning: "not" is deprecated. Use the "!" operator instead.`);
68
+ return !value;
69
+ }
70
+
71
+ export function or(...args) {
72
+ console.warn(`Warning: "or" is deprecated. Use the "||" operator instead.`);
73
+ return args.find((arg) => arg);
74
+ }
75
+
76
+ export function subtract(a, b) {
77
+ console.warn(
78
+ `Warning: "subtract" is deprecated. Use the "-" operator instead.`
79
+ );
80
+ return Number(a) - Number(b);
81
+ }
@@ -1,4 +1,5 @@
1
1
  import { isPlainObject, isUnpackable, toString } from "@weborigami/async-tree";
2
+ // import txtHandler from "../builtins/txt.handler.js";
2
3
 
3
4
  /**
4
5
  * In Origami, a text document object is any object with a `@text` property and
@@ -36,13 +37,6 @@ export default async function documentObject(input, data) {
36
37
  // };
37
38
  // const result = Object.create(base);
38
39
  const result = {};
39
- // TODO: Deprecate @text
40
40
  Object.assign(result, inputData, data, { "@text": text });
41
- Object.defineProperty(result, "_body", {
42
- configurable: true,
43
- value: text,
44
- enumerable: false, // TODO: Make enumerable
45
- writable: true,
46
- });
47
41
  return result;
48
42
  }
@@ -1,4 +1,5 @@
1
1
  import { symbols, Tree } from "@weborigami/async-tree";
2
+ import { builtinsTree } from "../internal.js";
2
3
 
3
4
  /**
4
5
  * Perform any necessary post-processing on the unpacked content of a file. This
@@ -13,7 +14,8 @@ import { symbols, Tree } from "@weborigami/async-tree";
13
14
  export default function processUnpackedContent(content, parent) {
14
15
  if (typeof content === "function") {
15
16
  // Bind the function to the parent as the `this` context.
16
- const result = content.bind(parent);
17
+ const target = parent ?? builtinsTree;
18
+ const result = content.bind(target);
17
19
  // Copy over any properties that were attached to the function
18
20
  Object.assign(result, content);
19
21
  return result;
@@ -130,7 +130,7 @@ async function processPath(tree, path, baseUrl) {
130
130
 
131
131
  // Traverse tree to get value.
132
132
  let value;
133
- let normalizedKeys = [];
133
+ let normalizedKeys;
134
134
  let normalizedPath;
135
135
  try {
136
136
  value = await Tree.traverse(tree, ...keys);
@@ -5,9 +5,7 @@ import { addHref } from "./utilities.js";
5
5
 
6
6
  export default function pathsInHtml(html) {
7
7
  const paths = {
8
- /** @type {string[]} */
9
8
  crawlablePaths: [],
10
- /** @type {string[]} */
11
9
  resourcePaths: [],
12
10
  };
13
11
 
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
7
7
  import { getDescriptor } from "../common/utilities.js";
8
- import oriHandler from "../handlers/ori.handler.js";
8
+ import { builtinsTree } from "../internal.js";
9
9
  import debug from "./debug.js";
10
10
 
11
11
  let templatePromise;
@@ -44,7 +44,7 @@ export default async function explore(...keys) {
44
44
  const data = await getScopeData(scope(tree));
45
45
  templatePromise ??= loadTemplate();
46
46
  const template = await templatePromise;
47
- const text = await template.call(this, data);
47
+ const text = await template(data);
48
48
 
49
49
  result = new String(text);
50
50
  result.unpack = () => debug.call(tree, tree);
@@ -57,6 +57,10 @@ async function getScopeData(scope) {
57
57
  const trees = scope.trees ?? [scope];
58
58
  const data = [];
59
59
  for (const tree of trees) {
60
+ if (tree.parent === undefined) {
61
+ // Skip builtins.
62
+ continue;
63
+ }
60
64
  const name = getDescriptor(tree);
61
65
  const treeKeys = Array.from(await tree.keys());
62
66
  // Skip system-ish files that start with a period.
@@ -69,7 +73,8 @@ async function getScopeData(scope) {
69
73
  async function loadTemplate() {
70
74
  const folderPath = path.resolve(fileURLToPath(import.meta.url), "..");
71
75
  const folder = new OrigamiFiles(folderPath);
76
+ folder.parent = builtinsTree;
72
77
  const templateFile = await folder.get("explore.ori");
73
- const template = await oriHandler.unpack(templateFile, { parent: folder });
78
+ const template = await templateFile.unpack();
74
79
  return template;
75
80
  }
@@ -1,7 +1,7 @@
1
1
  // .css files use the .txt loader
2
- import { txtHandler } from "./handlers.js";
2
+ import fileTypeText from "./txt.handler.js";
3
3
 
4
4
  export default {
5
- ...txtHandler,
5
+ ...fileTypeText,
6
6
  mediaType: "text/css",
7
7
  };
@@ -0,0 +1,16 @@
1
+ export { default as cssHandler } from "./css.handler.js";
2
+ export { default as htmHandler } from "./htm.handler.js";
3
+ export { default as htmlHandler } from "./html.handler.js";
4
+ export { default as jpegHandler } from "./jpeg.handler.js";
5
+ export { default as jpgHandler } from "./jpg.handler.js";
6
+ export { default as jsHandler } from "./js.handler.js";
7
+ export { default as jsonHandler } from "./json.handler.js";
8
+ export { default as mdHandler } from "./md.handler.js";
9
+ export { default as mjsHandler } from "./mjs.handler.js";
10
+ export { default as oriHandler } from "./ori.handler.js";
11
+ export { default as oridocumentHandler } from "./oridocument.handler.js";
12
+ export { default as txtHandler } from "./txt.handler.js";
13
+ export { default as wasmHandler } from "./wasm.handler.js";
14
+ export { default as xhtmlHandler } from "./xhtml.handler.js";
15
+ export { default as yamlHandler } from "./yaml.handler.js";
16
+ export { default as ymlHandler } from "./yml.handler.js";
@@ -1,37 +1,41 @@
1
- //
2
- // This library includes a number of modules with circular dependencies. This
3
- // module exists to explicitly set the loading order for those modules. To
4
- // enforce use of this loading order, other modules should only load the modules
5
- // below via this module.
6
- //
7
- // About this pattern:
8
- // https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
9
- //
10
- // Note: to avoid having VS Code auto-sort the imports, keep lines between them.
1
+ import {
2
+ jsHandler,
3
+ oriHandler,
4
+ oridocumentHandler,
5
+ wasmHandler,
6
+ yamlHandler,
7
+ } from "../internal.js";
8
+ import cssHandler from "./css.handler.js";
9
+ import csvHandler from "./csv.handler.js";
10
+ import htmHandler from "./htm.handler.js";
11
+ import htmlHandler from "./html.handler.js";
12
+ import jpegHandler from "./jpeg.handler.js";
13
+ import jpgHandler from "./jpg.handler.js";
14
+ import jsonHandler from "./json.handler.js";
15
+ import mdHandler from "./md.handler.js";
16
+ import mjsHandler from "./mjs.handler.js";
17
+ import tsHandler from "./ts.handler.js";
18
+ import txtHandler from "./txt.handler.js";
19
+ import xhtmlHandler from "./xhtml.handler.js";
20
+ import ymlHandler from "./yml.handler.js";
11
21
 
12
- export { default as jsHandler } from "./js.handler.js";
13
- export { default as tsHandler } from "./ts.handler.js";
14
-
15
- export { default as oriHandler } from "./ori.handler.js";
16
-
17
- export { default as jseHandler } from "./jse.handler.js";
18
-
19
- export { default as oridocumentHandler } from "./oridocument.handler.js";
20
-
21
- export { default as jsedocumentHandler } from "./jsedocument.handler.js";
22
-
23
- export { default as txtHandler } from "./txt.handler.js";
24
-
25
- export { default as cssHandler } from "./css.handler.js";
26
- export { default as csvHandler } from "./csv.handler.js";
27
- export { default as htmHandler } from "./htm.handler.js";
28
- export { default as htmlHandler } from "./html.handler.js";
29
- export { default as jpegHandler } from "./jpeg.handler.js";
30
- export { default as jpgHandler } from "./jpg.handler.js";
31
- export { default as jsonHandler } from "./json.handler.js";
32
- export { default as mdHandler } from "./md.handler.js";
33
- export { default as mjsHandler } from "./mjs.handler.js";
34
- export { default as wasmHandler } from "./wasm.handler.js";
35
- export { default as xhtmlHandler } from "./xhtml.handler.js";
36
- export { default as yamlHandler } from "./yaml.handler.js";
37
- export { default as ymlHandler } from "./yml.handler.js";
22
+ export default {
23
+ "css.handler": cssHandler,
24
+ "csv.handler": csvHandler,
25
+ "htm.handler": htmHandler,
26
+ "html.handler": htmlHandler,
27
+ "jpeg.handler": jpegHandler,
28
+ "jpg.handler": jpgHandler,
29
+ "js.handler": jsHandler,
30
+ "json.handler": jsonHandler,
31
+ "md.handler": mdHandler,
32
+ "mjs.handler": mjsHandler,
33
+ "ori.handler": oriHandler,
34
+ "oridocument.handler": oridocumentHandler,
35
+ "ts.handler": tsHandler,
36
+ "txt.handler": txtHandler,
37
+ "wasm.handler": wasmHandler,
38
+ "xhtml.handler": xhtmlHandler,
39
+ "yaml.handler": yamlHandler,
40
+ "yml.handler": ymlHandler,
41
+ };
@@ -1,2 +1,2 @@
1
1
  // .htm is a synonynm for .html
2
- export { htmlHandler as default } from "./handlers.js";
2
+ export { default } from "./html.handler.js";
@@ -1,7 +1,7 @@
1
1
  // .html files use the .txt loader
2
- import { txtHandler } from "./handlers.js";
2
+ import fileTypeText from "./txt.handler.js";
3
3
 
4
4
  export default {
5
- ...txtHandler,
5
+ ...fileTypeText,
6
6
  mediaType: "text/html",
7
7
  };
@@ -1,2 +1,2 @@
1
1
  // .jpg is a synonym for .jpeg
2
- export { jpegHandler as default } from "./handlers.js";
2
+ export { default } from "./jpeg.handler.js";
@@ -1,4 +1,4 @@
1
- import processUnpackedContent from "./processUnpackedContent.js";
1
+ import { processUnpackedContent } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * A JavaScript file
@@ -1,7 +1,7 @@
1
1
  // .md files use the .txt loader
2
- import { txtHandler } from "./handlers.js";
2
+ import fileTypeText from "./txt.handler.js";
3
3
 
4
4
  export default {
5
- ...txtHandler,
5
+ ...fileTypeText,
6
6
  mediaType: "text/markdown",
7
7
  };
@@ -1,2 +1,2 @@
1
1
  // .mjs is a synonynm for .js
2
- export { jsHandler as default } from "./handlers.js";
2
+ export { jsHandler as default } from "../internal.js";
@@ -1,10 +1,7 @@
1
- import { merge } from "@weborigami/async-tree";
2
1
  import { compile } from "@weborigami/language";
3
- import builtinsShell from "../builtinsShell.js";
4
- import getConfig from "../cli/getConfig.js";
5
2
  import * as utilities from "../common/utilities.js";
3
+ import { builtinsTree, processUnpackedContent } from "../internal.js";
6
4
  import getParent from "./getParent.js";
7
- import processUnpackedContent from "./processUnpackedContent.js";
8
5
 
9
6
  /**
10
7
  * An Origami expression file
@@ -37,15 +34,10 @@ export default {
37
34
 
38
35
  // Compile the source code as an Origami program and evaluate it.
39
36
  const compiler = options.compiler ?? compile.program;
37
+ const fn = compiler(source);
38
+ const target = parent ?? builtinsTree;
39
+ let content = await fn.call(target);
40
40
 
41
- const config = getConfig(parent);
42
- const globals = merge(options.globals ?? builtinsShell(), config);
43
-
44
- const mode = options.mode ?? "shell";
45
- const fn = compiler(source, { globals, mode });
46
-
47
- let result = await fn.call(parent);
48
-
49
- return processUnpackedContent(result, parent);
41
+ return processUnpackedContent(content, parent);
50
42
  },
51
43
  };
@@ -1,9 +1,8 @@
1
1
  import { extension, trailingSlash } from "@weborigami/async-tree";
2
2
  import { compile } from "@weborigami/language";
3
- import builtinsShell from "../builtinsShell.js";
4
3
  import { toString } from "../common/utilities.js";
4
+ import { processUnpackedContent } from "../internal.js";
5
5
  import getParent from "./getParent.js";
6
- import processUnpackedContent from "./processUnpackedContent.js";
7
6
 
8
7
  /**
9
8
  * An Origami template document: a plain text file that contains Origami
@@ -36,19 +35,7 @@ export default {
36
35
  text,
37
36
  url,
38
37
  };
39
-
40
- let globals;
41
- if (options.globals) {
42
- globals = options.globals;
43
- } else {
44
- globals = builtinsShell();
45
- }
46
-
47
- const mode = options.mode ?? "shell";
48
- const defineFn = compile.templateDocument(source, {
49
- globals,
50
- mode,
51
- });
38
+ const defineFn = compile.templateDocument(source);
52
39
 
53
40
  // Invoke the definition to get back the template function
54
41
  const result = await defineFn.call(parent);
@@ -1 +1 @@
1
- export { jsHandler as default } from "./handlers.js";
1
+ export { default as default } from "./js.handler.js";
@@ -39,8 +39,7 @@ export default {
39
39
  throw new TypeError("The input to pack must be a JavaScript object.");
40
40
  }
41
41
 
42
- // TODO: Deprecate @text
43
- const text = object._body ?? object["@text"] ?? "";
42
+ const text = object["@text"] ?? "";
44
43
 
45
44
  /** @type {any} */
46
45
  const dataWithoutText = Object.assign({}, object);
@@ -73,14 +72,7 @@ export default {
73
72
  } else {
74
73
  frontData = parseYaml(frontText);
75
74
  }
76
- // TODO: Deprecate @text
77
75
  unpacked = Object.assign({}, frontData, { "@text": body });
78
- Object.defineProperty(unpacked, "_body", {
79
- configurable: true,
80
- value: body,
81
- enumerable: false, // TODO: Make enumerable
82
- writable: true,
83
- });
84
76
  } else {
85
77
  // Plain text
86
78
  unpacked = new String(text);
@@ -1,4 +1,4 @@
1
- import processUnpackedContent from "./processUnpackedContent.js";
1
+ import { processUnpackedContent } from "../internal.js";
2
2
 
3
3
  /**
4
4
  * A WebAssembly module
@@ -2,7 +2,7 @@ import { symbols } from "@weborigami/async-tree";
2
2
  import * as YAMLModule from "yaml";
3
3
  import { parseYaml } from "../common/serialize.js";
4
4
  import * as utilities from "../common/utilities.js";
5
- import processUnpackedContent from "./processUnpackedContent.js";
5
+ import { processUnpackedContent } from "../internal.js";
6
6
 
7
7
  // See notes at serialize.js
8
8
  // @ts-ignore
@@ -1,2 +1,2 @@
1
1
  // .yml is a synonym for .yaml
2
- export { default } from "./yaml.handler.js";
2
+ export { yamlHandler as default } from "../internal.js";
@@ -0,0 +1,24 @@
1
+ //
2
+ // This library includes a number of modules with circular dependencies. This
3
+ // module exists to explicitly set the loading order for those modules. To
4
+ // enforce use of this loading order, other modules should only load the modules
5
+ // below via this module.
6
+ //
7
+ // About this pattern:
8
+ // https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
9
+ //
10
+ // Note: to avoid having VS Code auto-sort the imports, keep lines between them.
11
+
12
+ export { default as jsHandler } from "./handlers/js.handler.js";
13
+
14
+ export { default as oriHandler } from "./handlers/ori.handler.js";
15
+
16
+ export { default as oridocumentHandler } from "./handlers/oridocument.handler.js";
17
+
18
+ export { default as processUnpackedContent } from "./common/processUnpackedContent.js";
19
+
20
+ export { default as wasmHandler } from "./handlers/wasm.handler.js";
21
+
22
+ export { default as yamlHandler } from "./handlers/yaml.handler.js";
23
+
24
+ export { default as builtinsTree } from "./builtinsTree.js";
package/src/js.js ADDED
@@ -0,0 +1,38 @@
1
+ async function fetchWrapper(resource, options) {
2
+ const response = await fetch(resource, options);
3
+ return response.ok ? await response.arrayBuffer() : undefined;
4
+ }
5
+
6
+ export default {
7
+ Array,
8
+ BigInt,
9
+ Boolean,
10
+ Date,
11
+ Error,
12
+ Infinity,
13
+ Intl,
14
+ JSON,
15
+ Map,
16
+ Math,
17
+ NaN,
18
+ Number,
19
+ Object,
20
+ RegExp,
21
+ Response,
22
+ Set,
23
+ String,
24
+ Symbol,
25
+ decodeURI,
26
+ decodeURIComponent,
27
+ encodeURI,
28
+ encodeURIComponent,
29
+ false: false,
30
+ fetch: fetchWrapper,
31
+ isFinite,
32
+ isNaN,
33
+ null: null,
34
+ parseFloat,
35
+ parseInt,
36
+ true: true,
37
+ undefined: undefined,
38
+ };
@@ -13,6 +13,8 @@ import project from "./project.js";
13
13
  */
14
14
  export default async function config(key) {
15
15
  const projectTree = await project.call(this);
16
- const projectConfig = projectTree.config;
17
- return key === undefined ? projectConfig : projectConfig.get(key);
16
+ // HACK: We use specific knowledge of how @project returns a tree to get the
17
+ // config. The config is always the parent of the project folder.
18
+ const parent = projectTree.parent;
19
+ return key === undefined ? parent : parent.get(key);
18
20
  }
@@ -1,12 +1,10 @@
1
1
  import {
2
2
  Tree,
3
3
  getRealmObjectPrototype,
4
- merge,
5
4
  toString,
6
5
  } from "@weborigami/async-tree";
7
6
  import { compile } from "@weborigami/language";
8
- import builtinsShell from "../builtinsShell.js";
9
- import getConfig from "../cli/getConfig.js";
7
+ import builtinsTree from "../builtinsTree.js";
10
8
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
11
9
  import { toYaml } from "../common/serialize.js";
12
10
 
@@ -30,20 +28,13 @@ export default async function ori(
30
28
  // In case expression has come from a file, cast it to a string.
31
29
  expression = toString(expression);
32
30
 
33
- // Run in the context of `this` if defined
34
- const tree = this;
35
-
36
- const config = getConfig(tree);
37
- const globals = merge(builtinsShell(), config);
31
+ // Run in the context of `this` if defined, otherwise use the builtins.
32
+ const tree = this ?? builtinsTree;
38
33
 
39
34
  // Compile the expression. Avoid caching scope references so that, e.g.,
40
35
  // passing a function to the `watch` builtin will always look the current
41
36
  // value of things in scope.
42
- const fn = compile.expression(expression, {
43
- globals,
44
- mode: "shell",
45
- scopeCaching: false,
46
- });
37
+ const fn = compile.expression(expression, { scopeCaching: false });
47
38
 
48
39
  // Execute
49
40
  let result = await fn.call(tree);
@@ -1,3 +1,8 @@
1
+ // Use a dynamic import to avoid circular dependencies
2
+ export const builtins = import("../internal.js").then(
3
+ (internal) => internal.builtinsTree
4
+ );
5
+
1
6
  export { extension } from "@weborigami/async-tree";
2
7
  export { toFunction } from "../common/utilities.js";
3
8
 
@@ -1,17 +1,13 @@
1
1
  /** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
2
- import { merge } from "@weborigami/async-tree";
2
+ import { Tree } from "@weborigami/async-tree";
3
3
  import { OrigamiFiles } from "@weborigami/language";
4
4
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
5
- import handlerBuiltins from "../handlers/handlerBuiltins.js";
6
- import { oriHandler } from "../handlers/handlers.js";
5
+ import { builtinsTree, oriHandler } from "../internal.js";
7
6
 
8
7
  const configFileName = "config.ori";
9
8
 
10
9
  /**
11
- * Return an object for the current project including
12
- *
13
- * `config`: the evaluated config.ori file
14
- * `root`: the project's root folder
10
+ * Return the tree for the current project's root folder.
15
11
  *
16
12
  * This searches the current directory and its ancestors for an Origami file
17
13
  * called `config.ori`. If an Origami configuration file is found, the
@@ -23,70 +19,81 @@ const configFileName = "config.ori";
23
19
  * returned as a tree, with the builtins as its parent.
24
20
  *
25
21
  * @this {AsyncTree|null}
22
+ * @param {any} [key]
26
23
  */
27
- export default async function project() {
24
+ export default async function project(key) {
28
25
  assertTreeIsDefined(this, "origami:project");
29
26
 
30
27
  const dirname = process.cwd();
31
28
  const currentTree = new OrigamiFiles(dirname);
29
+ currentTree.parent = builtinsTree;
32
30
 
33
31
  // Search up the tree for the configuration file or package.json to determine
34
32
  // the project root.
35
- let root;
36
- const foundConfig = await findAncestorFile(currentTree, configFileName);
37
- if (foundConfig) {
38
- root = foundConfig.container;
39
- // Unpack Origami configuration file
40
- const buffer = foundConfig.value;
41
- root.config = await oriHandler.unpack(buffer, {
42
- key: configFileName,
43
- parent: root,
44
- });
33
+ const configContainer =
34
+ (await findAncestorFile(currentTree, configFileName)) ??
35
+ (await findAncestorFile(currentTree, "package.json"));
36
+
37
+ let projectRoot;
38
+ if (!configContainer) {
39
+ // No configuration file or package.json found; use the current directory.
40
+ projectRoot = currentTree;
45
41
  } else {
46
- // No Origami configuration file, look for package.json
47
- const foundPackageJson = await findAncestorFile(
48
- currentTree,
49
- "package.json"
50
- );
51
- if (foundPackageJson) {
52
- // Found package.json; use its parent as the project root
53
- root = foundPackageJson.container;
42
+ // Load the configuration file if one exists.
43
+ const buffer = await configContainer.get(configFileName);
44
+ if (!buffer) {
45
+ // Project root defined by package.json
46
+ projectRoot = configContainer;
54
47
  } else {
55
- // No package.json found; use the current directory as root
56
- root = currentTree;
48
+ // Load Origami configuration file
49
+ const config = await oriHandler.unpack(buffer, {
50
+ key: configFileName,
51
+ parent: configContainer,
52
+ });
53
+ if (!config) {
54
+ const configPath = /** @type {any} */ (configContainer).path;
55
+ throw new Error(
56
+ `Couldn't load the Origami configuration in ${configPath}/${configFileName}`
57
+ );
58
+ }
59
+
60
+ // The config tree may refer to the container tree *and vice versa*. To
61
+ // support this, we put the container in the tree twice. The chain will
62
+ // be: projectRoot -> configTree -> configContainer -> builtins, where
63
+ // the projectRoot and configContainer are the same folder.
64
+ const configTree = Tree.from(config);
65
+ projectRoot = new OrigamiFiles(configContainer.path);
66
+ projectRoot.parent = configTree;
67
+ configTree.parent = configContainer;
68
+ configContainer.parent = builtinsTree;
57
69
  }
58
70
  }
59
71
 
60
- // Merge config if present into handlers
61
- root.handlers = merge(handlerBuiltins(), root.config);
62
-
63
- return root;
72
+ return key === undefined ? projectRoot : projectRoot.get(key);
64
73
  }
65
74
 
66
- // Find the first ancestor of the given folder that contains a file with the
67
- // given name. Return the container and the file contents.
75
+ // Return the first ancestor of the given tree that contains a file with the
76
+ // given name.
68
77
  async function findAncestorFile(start, fileName) {
69
- let container = start;
70
- while (container) {
71
- const value = await container.get(fileName);
78
+ let current = start;
79
+ while (current) {
80
+ const value = await current.get(fileName);
72
81
  if (value) {
73
- // Found the desired file
74
- return {
75
- container,
76
- value,
77
- };
82
+ // Found the desired file; its container is the project root. Set the
83
+ // parent to the builtins; in the context of this project, there's nothing
84
+ // higher up.
85
+ current.parent = builtinsTree;
86
+ return current;
78
87
  }
79
- // Not found; try the parent
80
- const parent = await container.get("..");
88
+ // Not found; try the parent.
89
+ const parent = await current.get("..");
81
90
  if (
82
91
  !parent ||
83
- (parent.path && container.path && parent.path === container.path)
92
+ (parent.path && current.path && parent.path === current.path)
84
93
  ) {
85
94
  break;
86
95
  }
87
- container = parent;
96
+ current = parent;
88
97
  }
89
-
90
- // Not found
91
- return null;
98
+ return undefined;
92
99
  }
@@ -1,4 +1,4 @@
1
- import { OrigamiFiles, getHandlers } from "@weborigami/language";
1
+ import { OrigamiFiles } from "@weborigami/language";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
@@ -26,6 +26,5 @@ export default async function files(...keys) {
26
26
  const resolved = path.resolve(basePath, relativePath);
27
27
 
28
28
  const result = new OrigamiFiles(resolved);
29
- result.handlers = getHandlers(this);
30
29
  return result;
31
30
  }
@@ -1,4 +1,5 @@
1
- import { scope, Tree } from "@weborigami/async-tree";
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import { ops } from "@weborigami/language";
2
3
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
3
4
 
4
5
  /**
@@ -12,10 +13,6 @@ import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
12
13
  export default async function inherited(...keys) {
13
14
  assertTreeIsDefined(this, "inherited:");
14
15
  const key = keys.shift();
15
- if (!this?.parent) {
16
- return undefined;
17
- }
18
- const parentScope = scope(this.parent);
19
- const value = await parentScope.get(key);
16
+ const value = await ops.inherited.call(this, key);
20
17
  return keys.length > 0 ? await Tree.traverse(value, ...keys) : value;
21
18
  }
@@ -1,4 +1,5 @@
1
- import { Tree, scope as scopeFn } from "@weborigami/async-tree";
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import { ops } from "@weborigami/language";
2
3
 
3
4
  /**
4
5
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -11,8 +12,7 @@ export default async function scope(...keys) {
11
12
  let value;
12
13
  try {
13
14
  // Look up key in scope but don't throw if it's undefined
14
- const thisScope = scopeFn(this);
15
- value = await thisScope.get(key);
15
+ value = await ops.scope.call(this, key);
16
16
  } catch (error) {
17
17
  if (error instanceof ReferenceError) {
18
18
  value = undefined;
@@ -1,7 +1,7 @@
1
1
  import { Tree } from "@weborigami/async-tree";
2
2
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
3
3
  import getTreeArgument from "../common/getTreeArgument.js";
4
- import { oriHandler } from "../handlers/handlers.js";
4
+ import { builtinsTree, oriHandler } from "../internal.js";
5
5
 
6
6
  const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
7
7
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@@ -48,6 +48,7 @@ export default async function sitemap(treelike, baseHref = "") {
48
48
  .map((path) => (path.endsWith("index.html") ? path.slice(0, -10) : path));
49
49
 
50
50
  const templateFn = await oriHandler.unpack(templateText);
51
- const templateResult = await templateFn.call(this, htmlPaths);
51
+ const target = this ?? builtinsTree;
52
+ const templateResult = await templateFn.call(target, htmlPaths);
52
53
  return String(templateResult);
53
54
  }
@@ -6,7 +6,7 @@ import {
6
6
  } from "@weborigami/async-tree";
7
7
  import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
8
8
  import documentObject from "../common/documentObject.js";
9
- import { oridocumentHandler } from "../handlers/handlers.js";
9
+ import { oridocumentHandler } from "../internal.js";
10
10
 
11
11
  /**
12
12
  * Inline any Origami expressions found inside ${...} placeholders in the input
package/src/tree/map.js CHANGED
@@ -69,7 +69,7 @@ function extendedOptions(context, operation) {
69
69
  options = {};
70
70
  } else {
71
71
  throw new TypeError(
72
- `map: You must specify a value function or options dictionary as the second parameter.`
72
+ `map: You must specify a value function or options dictionary as the first parameter.`
73
73
  );
74
74
  }
75
75
 
@@ -1,18 +0,0 @@
1
- import { trailingSlash } from "@weborigami/async-tree";
2
-
3
- // We create our own tree instead of using ObjectTree, since that binds the
4
- // functions would be bound to the object. We want to leave them unbound.
5
- export default class BuiltinsTree {
6
- constructor(object) {
7
- this.object = object;
8
- }
9
-
10
- async get(key) {
11
- const normalizedKey = trailingSlash.remove(key);
12
- return this.object[normalizedKey];
13
- }
14
-
15
- async keys() {
16
- return Object.keys(this.object);
17
- }
18
- }
@@ -1,67 +0,0 @@
1
- import { text as treeText } from "@weborigami/async-tree";
2
- import { jsGlobals } from "@weborigami/language";
3
- import BuiltinsTree from "./BuiltinsTree.js";
4
- import * as dev from "./dev/dev.js";
5
- import handlerBuiltins from "./handlers/handlerBuiltins.js";
6
- import help from "./help/help.js";
7
- import * as image from "./image/image.js";
8
- import node from "./node.js";
9
- import * as origami from "./origami/origami.js";
10
- import explore from "./protocols/explore.js";
11
- import files from "./protocols/files.js";
12
- import http from "./protocols/http.js";
13
- import https from "./protocols/https.js";
14
- import httpstree from "./protocols/httpstree.js";
15
- import httptree from "./protocols/httptree.js";
16
- import inherited from "./protocols/inherited.js";
17
- import packageNamespace from "./protocols/package.js";
18
- import scope from "./protocols/scope.js";
19
- import * as site from "./site/site.js";
20
- import * as text from "./text/text.js";
21
- import * as tree from "./tree/tree.js";
22
-
23
- let result;
24
-
25
- export default function builtinsJse() {
26
- if (!result) {
27
- const Tree = new BuiltinsTree({
28
- ...tree,
29
- indent: text.indent,
30
- json: origami.json,
31
- text: treeText,
32
- });
33
-
34
- const Origami = new BuiltinsTree({
35
- ...dev,
36
- image,
37
- ...origami,
38
- ...site,
39
- ...text,
40
- });
41
-
42
- /** @type {any} */
43
- result = new BuiltinsTree({
44
- ...jsGlobals,
45
-
46
- "explore:": explore,
47
- "files:": files,
48
- "help:": help,
49
- "http:": http,
50
- "https:": https,
51
- "httpstree:": httpstree,
52
- "httptree:": httptree,
53
- "inherited:": inherited,
54
- "node:": node,
55
- "package:": packageNamespace,
56
- "scope:": scope,
57
-
58
- Tree,
59
- Origami,
60
-
61
- // Handlers need to be exposed at top level
62
- ...handlerBuiltins(),
63
- });
64
- }
65
-
66
- return result;
67
- }
@@ -1,83 +0,0 @@
1
- import { jsGlobals } from "@weborigami/language";
2
- import BuiltinsTree from "./BuiltinsTree.js";
3
- import * as dev from "./dev/dev.js";
4
- import handlerBuiltins from "./handlers/handlerBuiltins.js";
5
- import help from "./help/help.js";
6
- import * as image from "./image/image.js";
7
- import node from "./node.js";
8
- import * as origami from "./origami/origami.js";
9
- import explore from "./protocols/explore.js";
10
- import files from "./protocols/files.js";
11
- import http from "./protocols/http.js";
12
- import https from "./protocols/https.js";
13
- import httpstree from "./protocols/httpstree.js";
14
- import httptree from "./protocols/httptree.js";
15
- import inherited from "./protocols/inherited.js";
16
- import instantiate from "./protocols/new.js";
17
- import packageNamespace from "./protocols/package.js";
18
- import scope from "./protocols/scope.js";
19
- import * as site from "./site/site.js";
20
- import * as text from "./text/text.js";
21
- import * as tree from "./tree/tree.js";
22
-
23
- let result;
24
-
25
- export default function builtinsShell() {
26
- if (!result) {
27
- const builtins = {
28
- "dev:": dev,
29
- "explore:": explore,
30
- "files:": files,
31
- "help:": help,
32
- "http:": http,
33
- "https:": https,
34
- "httpstree:": httpstree,
35
- "httptree:": httptree,
36
- "image:": image,
37
- "inherited:": inherited,
38
- "js:": jsGlobals,
39
- "new:": instantiate,
40
- "node:": node,
41
- "origami:": origami,
42
- "package:": packageNamespace,
43
- "scope:": scope,
44
- "site:": adjustReservedWords(site),
45
- "text:": text,
46
- "tree:": tree,
47
-
48
- // Handlers need to be exposed at top level
49
- ...handlerBuiltins(),
50
- };
51
-
52
- // For all builtins like `tree:keys`, add a shorthand `keys`.
53
- for (const [key, value] of Object.entries(builtins)) {
54
- const isNamespace = key.endsWith(":");
55
- if (isNamespace) {
56
- for (const [subKey, subValue] of Object.entries(value)) {
57
- // HACK: Skip description keys until we can make them all non-enumerable.
58
- if (subKey === "description") {
59
- continue;
60
- }
61
- if (subKey in builtins) {
62
- throw new Error(`Internal Origami error: Duplicate key: ${subKey}`);
63
- }
64
- builtins[subKey] = subValue;
65
- }
66
- }
67
- }
68
-
69
- result = new BuiltinsTree(builtins);
70
- }
71
-
72
- return result;
73
- }
74
-
75
- // Handle cases where a builtin name conflicts with a JS reserved word
76
- function adjustReservedWords(obj) {
77
- const result = {};
78
- for (const [key, value] of Object.entries(obj)) {
79
- const name = value.key ?? key;
80
- result[name] = value;
81
- }
82
- return result;
83
- }
@@ -1,10 +0,0 @@
1
- import { Tree } from "@weborigami/async-tree";
2
-
3
- // Return the config for the given tree
4
- export default function getConfig(tree) {
5
- if (!tree) {
6
- return null;
7
- }
8
- const root = Tree.root(tree);
9
- return root.config;
10
- }
@@ -1,26 +0,0 @@
1
- import * as handlers from "./handlers.js";
2
-
3
- export default function handlerBuiltins() {
4
- return {
5
- "css.handler": handlers.cssHandler,
6
- "csv.handler": handlers.csvHandler,
7
- "htm.handler": handlers.htmHandler,
8
- "html.handler": handlers.htmlHandler,
9
- "jpeg.handler": handlers.jpegHandler,
10
- "jpg.handler": handlers.jpgHandler,
11
- "js.handler": handlers.jsHandler,
12
- "jse.handler": handlers.jseHandler,
13
- "jsedocument.handler": handlers.jsedocumentHandler,
14
- "json.handler": handlers.jsonHandler,
15
- "md.handler": handlers.mdHandler,
16
- "mjs.handler": handlers.mjsHandler,
17
- "ori.handler": handlers.oriHandler,
18
- "oridocument.handler": handlers.oridocumentHandler,
19
- "ts.handler": handlers.tsHandler,
20
- "txt.handler": handlers.txtHandler,
21
- "wasm.handler": handlers.wasmHandler,
22
- "xhtml.handler": handlers.xhtmlHandler,
23
- "yaml.handler": handlers.yamlHandler,
24
- "yml.handler": handlers.ymlHandler,
25
- };
26
- }
@@ -1,17 +0,0 @@
1
- import builtinsJse from "../builtinsJse.js";
2
- import getParent from "./getParent.js";
3
- import { oriHandler } from "./handlers.js";
4
-
5
- export default {
6
- ...oriHandler,
7
-
8
- async unpack(packed, options = {}) {
9
- const parent = getParent(packed, options);
10
- return oriHandler.unpack(packed, {
11
- ...options,
12
- globals: builtinsJse(),
13
- mode: "jse",
14
- parent,
15
- });
16
- },
17
- };
@@ -1,17 +0,0 @@
1
- import builtinsJse from "../builtinsJse.js";
2
- import getParent from "./getParent.js";
3
- import { oridocumentHandler } from "./handlers.js";
4
-
5
- export default {
6
- ...oridocumentHandler,
7
-
8
- async unpack(packed, options = {}) {
9
- const parent = getParent(packed, options);
10
- return oridocumentHandler.unpack(packed, {
11
- ...options,
12
- globals: builtinsJse(),
13
- mode: "jse",
14
- parent,
15
- });
16
- },
17
- };