@weborigami/language 0.0.43 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { AsyncTree } from "@weborigami/types";
2
- import { StringLike } from "../async-tree/index.js";
3
1
 
4
2
  export * from "./main.js";
5
3
 
@@ -19,15 +17,12 @@ export type Code = ArrayWithSource;
19
17
  export type Constructor<T> = new (...args: any[]) => T;
20
18
 
21
19
  /**
22
- * A function that can convert a string-like input value into some live object.
20
+ * A function that can convert a value from some persistent form into some kind
21
+ * of live value.
23
22
  */
24
23
  export type FileUnpackFunction = (
25
- input: StringLike,
26
- options?: {
27
- compiler?: any,
28
- key?: any,
29
- parent?: AsyncTree | null
30
- }
24
+ input: any,
25
+ options?: any
31
26
  ) => any;
32
27
 
33
28
  /**
package/main.js CHANGED
@@ -19,3 +19,4 @@ export * as expressionFunction from "./src/runtime/expressionFunction.js";
19
19
  export { default as extname } from "./src/runtime/extname.js";
20
20
  export { default as formatError } from "./src/runtime/formatError.js";
21
21
  export { default as functionResultsMap } from "./src/runtime/functionResultsMap.js";
22
+ export * as symbols from "./src/runtime/symbols.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/language",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "Web Origami expression language compiler and runtime",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -10,8 +10,8 @@
10
10
  "typescript": "5.3.3"
11
11
  },
12
12
  "dependencies": {
13
- "@weborigami/async-tree": "0.0.43",
14
- "@weborigami/types": "0.0.43",
13
+ "@weborigami/async-tree": "0.0.45",
14
+ "@weborigami/types": "0.0.45",
15
15
  "peggy": "3.0.2",
16
16
  "watcher": "2.3.0"
17
17
  },
@@ -1,6 +1,6 @@
1
- import { Tree, isStringLike } from "@weborigami/async-tree";
1
+ import { isStringLike } from "@weborigami/async-tree";
2
2
  import Scope from "./Scope.js";
3
- import extname from "./extname.js";
3
+ import attachFileLoader from "./attachFileLoader.js";
4
4
 
5
5
  /**
6
6
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
@@ -14,27 +14,11 @@ export default function FileLoadersTransform(Base) {
14
14
  async get(key) {
15
15
  let value = await super.get(key);
16
16
 
17
- // If the value is string-like and the key has an extension, look for a
18
- // loader that handles that extension and call it. The value will
19
- // typically be a Buffer loaded from the file system, but could also be a
20
- // string-like object defined by a user function.
21
- if (isStringLike(value) && isStringLike(key)) {
22
- const extension = extname(String(key)).toLowerCase().slice(1);
23
- if (extension) {
24
- /** @type {any} */
25
- const scope = Scope.getScope(this);
26
- /** @type {FileUnpackFunction} */
27
- const unpackFn = await Tree.traverse(scope, "@loaders", extension);
28
- if (unpackFn) {
29
- const input = value;
30
- // If the input is a plain string, convert it to a String so we can
31
- // attach data to it.
32
- value = new String(input);
33
- const parent = this;
34
- value.parent = parent;
35
- value.unpack = unpackFn.bind(null, input, { key, parent });
36
- }
37
- }
17
+ // If the key is string-like and has an extension, attach a loader (if one
18
+ // exists) that handles that extension.
19
+ if (value && isStringLike(key)) {
20
+ const scope = Scope.getScope(this);
21
+ value = await attachFileLoader(scope, String(key), value, this);
38
22
  }
39
23
 
40
24
  return value;
@@ -0,0 +1,31 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import extname from "./extname.js";
3
+ import * as symbols from "./symbols.js";
4
+
5
+ export default async function attachFileLoader(scope, key, value, parent) {
6
+ const extension = extname(key);
7
+ let result = value;
8
+ if (extension) {
9
+ const loaderName = extension.slice(1);
10
+ const loader = await Tree.traverse(scope, "@loaders", loaderName);
11
+ if (loader) {
12
+ const input = value;
13
+
14
+ // If the result is a plain string, box it as a String so we can attach
15
+ // data to it.
16
+ if (typeof result === "string") {
17
+ result = new String(result);
18
+ }
19
+ result[symbols.parent] = parent;
20
+
21
+ // Wrap the loader with a function that will only be called once per
22
+ // value.
23
+ let loaded;
24
+ result.unpack = async () => {
25
+ loaded ??= await loader(input, { key, parent });
26
+ return loaded;
27
+ };
28
+ }
29
+ }
30
+ return result;
31
+ }
@@ -81,9 +81,13 @@ export default async function evaluate(code) {
81
81
  Object.isExtensible(result) &&
82
82
  !isPlainObject(result)
83
83
  ) {
84
- result[codeSymbol] = code;
85
- if (/** @type {any} */ (code).location) {
86
- result[sourceSymbol] = codeFragment(code);
84
+ try {
85
+ result[codeSymbol] = code;
86
+ if (/** @type {any} */ (code).location) {
87
+ result[sourceSymbol] = codeFragment(code);
88
+ }
89
+ } catch (/** @type {any} */ error) {
90
+ // Ignore errors.
87
91
  }
88
92
  }
89
93
 
@@ -7,6 +7,7 @@ import { SiteTree, Tree } from "@weborigami/async-tree";
7
7
  import FileLoadersTransform from "./FileLoadersTransform.js";
8
8
  import OrigamiFiles from "./OrigamiFiles.js";
9
9
  import Scope from "./Scope.js";
10
+ import attachFileLoader from "./attachFileLoader.js";
10
11
  import concatTreeValues from "./concatTreeValues.js";
11
12
  import { OrigamiTree, evaluate, expressionFunction } from "./internal.js";
12
13
 
@@ -60,11 +61,24 @@ function constructHref(protocol, host, ...keys) {
60
61
  /**
61
62
  * Fetch the resource at the given href.
62
63
  *
64
+ * @this {AsyncTree|null}
63
65
  * @param {string} href
64
66
  */
65
67
  async function fetchResponse(href) {
66
68
  const response = await fetch(href);
67
- return response.ok ? await response.arrayBuffer() : undefined;
69
+ if (!response.ok) {
70
+ return undefined;
71
+ }
72
+ let buffer = await response.arrayBuffer();
73
+
74
+ // Attach any loader defined for the file type.
75
+ const url = new URL(href);
76
+ const filename = url.pathname.split("/").pop();
77
+ if (filename) {
78
+ buffer = await attachFileLoader(this, filename, buffer, null);
79
+ }
80
+
81
+ return buffer;
68
82
  }
69
83
 
70
84
  /**
@@ -93,7 +107,7 @@ export async function filesRoot() {
93
107
  */
94
108
  export async function http(host, ...keys) {
95
109
  const href = constructHref("http:", host, ...keys);
96
- return fetchResponse(href);
110
+ return fetchResponse.call(this, href);
97
111
  }
98
112
  http.toString = () => "«ops.http»";
99
113
 
@@ -106,7 +120,7 @@ http.toString = () => "«ops.http»";
106
120
  */
107
121
  export function https(host, ...keys) {
108
122
  const href = constructHref("https:", host, ...keys);
109
- return fetchResponse(href);
123
+ return fetchResponse.call(this, href);
110
124
  }
111
125
  https.toString = () => "«ops.https»";
112
126
 
@@ -0,0 +1 @@
1
+ export const parent = Symbol("parent");
@@ -33,7 +33,7 @@ function createFixture() {
33
33
  /** @type {any} */
34
34
  const scope = {
35
35
  "@loaders": {
36
- json: (input) => JSON.parse(input),
36
+ json: (buffer) => JSON.parse(String(buffer)),
37
37
  },
38
38
  };
39
39
  tree = Scope.treeWithScope(tree, scope);