@weborigami/origami 0.5.8 → 0.6.1

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/src/dev/help.yaml CHANGED
@@ -167,8 +167,8 @@ Tree:
167
167
  description: Work with trees of files and data
168
168
  commands:
169
169
  addNextPrevious:
170
- args: (tree)
171
- description: Add next/previous fields to the tree's values
170
+ args: (map)
171
+ description: Add next/previous fields to the map's values
172
172
  assign:
173
173
  args: (target, source)
174
174
  description: Apply key/values from source to target
@@ -178,9 +178,12 @@ Tree:
178
178
  calendar:
179
179
  args: (options)
180
180
  description: Return a tree structure for years/months/days
181
+ child:
182
+ args: (tree, key)
183
+ description: Returns the indicated child node, creating it if necessary
181
184
  clear:
182
- args: (tree)
183
- description: Remove all values from the tree
185
+ args: (map)
186
+ description: Remove all values from the map
184
187
  constant:
185
188
  args: (value)
186
189
  description: Return a deep tree with a single constant value
@@ -203,68 +206,68 @@ Tree:
203
206
  args: (tree)
204
207
  description: The in-order leaf values of the tree
205
208
  delete:
206
- args: (tree, key)
207
- description: Delete the value for the key from tree
209
+ args: (map, key)
210
+ description: Delete the value for the key from map
208
211
  entries:
209
- args: (tree)
210
- description: The tree's [key, value] pairs
212
+ args: (map)
213
+ description: The map's [key, value] pairs
211
214
  filter:
212
- args: (source, options)
213
- description: Filter the source tree
215
+ args: (tree, options)
216
+ description: Filter a tree by a condition
214
217
  first:
215
- args: (tree)
216
- description: The first value in the tree
218
+ args: (map)
219
+ description: The first value in the map
217
220
  forEach:
218
- args: (tree, fn)
221
+ args: (map, fn)
219
222
  description: Apply fn to each (value, key)
220
223
  from:
221
224
  args: (object, options)
222
- description: Create a tree from an object
225
+ description: Create a map from an object
223
226
  globKeys:
224
227
  args: (patterns)
225
228
  description: A tree whose keys can include glob wildcard patterns
226
229
  groupBy:
227
- args: (tree, fn)
228
- description: A new tree with values grouped by the function
230
+ args: (map, fn)
231
+ description: A new map with values grouped by the function
229
232
  has:
230
- args: (tree, key)
231
- description: True if key exists in tree
233
+ args: (map, key)
234
+ description: True if key exists in map
232
235
  indent:
233
236
  args: "`…`"
234
237
  description: Tagged template literal for normalizing indentation
235
238
  inners:
236
239
  args: (tree)
237
240
  description: The tree's interior nodes
238
- isAsyncMutableTree:
241
+ invokeFunctions:
242
+ args: (map)
243
+ description: Getting a map value invokes it if it's a function
244
+ isMap:
239
245
  args: (object)
240
- description: True if object is an async mutable tree
241
- isAsyncTree:
246
+ description: True if object is a map
247
+ isMaplike:
242
248
  args: (object)
243
- description: True if object is an async tree
249
+ description: True if object can be coerced to a tree
250
+ isReadOnlyMap:
251
+ args: (object)
252
+ description: True if object is a read-only map
244
253
  isTraversable:
245
254
  args: (object)
246
255
  description: True if object is traversable
247
- isTreelike:
248
- args: (object)
249
- description: True if object can be coerced to a tree
250
256
  json:
251
257
  args: (tree)
252
258
  description: Render the tree in JSON format
253
259
  keys:
254
- args: (tree)
255
- description: The keys of the tree
256
- length:
257
- args: (tree)
258
- description: The tree's size (number of keys)
260
+ args: (map)
261
+ description: The keys of the map
259
262
  map:
260
- args: (tree, options)
261
- description: Create a new tree by mapping keys and/or values
263
+ args: (source, options)
264
+ description: Create a new map by transforming keys and/or values
262
265
  mapExtension:
263
- args: (tree, ext, options)
264
- description: Map extensions and values
266
+ args: (source, ext, options)
267
+ description: Create a new map by transforming extensions
265
268
  mapReduce:
266
269
  args: (tree, mapFn, reduceFn)
267
- description: Map values and reduce them
270
+ description: Map the keys and/or values in a tree and reduce them
268
271
  mask:
269
272
  args: (source, mask)
270
273
  description: Return the source tree with only the keys in the mask
@@ -272,11 +275,11 @@ Tree:
272
275
  args: (pattern, fn, [keys])
273
276
  description: Matches simple patterns or regular expressions
274
277
  merge:
275
- args: (...trees)
276
- description: Return a new tree merging the given trees
278
+ args: (...maps)
279
+ description: Return a new tree merging the given maps
277
280
  paginate:
278
- args: (tree, [n])
279
- description: Group the tree's values into fixed-size sets
281
+ args: (map, [n])
282
+ description: Group the map's values into fixed-size sets
280
283
  parent:
281
284
  args: (tree)
282
285
  description: The parent of the given tree node
@@ -286,27 +289,39 @@ Tree:
286
289
  plain:
287
290
  args: (tree)
288
291
  description: Render the tree as a plain JavaScript object
292
+ reduce:
293
+ args: (tree, reduceFn)
294
+ description: Reduce the tree to a single value
289
295
  regExpKeys:
290
296
  args: (tree)
291
297
  description: A tree whose keys are regular expression strings
292
298
  reverse:
293
- args: (tree)
294
- description: Reverse the order of the tree's keys
299
+ args: (map)
300
+ description: Reverse the order of the map's keys
295
301
  root:
296
302
  args: (tree)
297
303
  description: The root node of the given tree
298
- setDeep:
299
- args: (target, source)
300
- description: Applies the source tree to the target
301
- shuffle:
304
+ scope:
302
305
  args: (tree)
303
- description: Shuffle the keys of the tree
306
+ description: A merged view of the tree and its ancestors
307
+ set:
308
+ args: (map, key, value)
309
+ description: Set the value for the key in the map
310
+ shuffle:
311
+ args: (map)
312
+ description: Shuffle the keys of the map
313
+ size:
314
+ args: (map)
315
+ description: The map's size (number of keys)
304
316
  sort:
305
- args: (tree, options)
306
- description: A new tree with its keys sorted
317
+ args: (map, options)
318
+ description: A new map with its keys sorted
319
+ sync:
320
+ args: (tree)
321
+ description: Awaits all asynchronous values in the tree
307
322
  take:
308
- args: (tree, n)
309
- description: The first n values in the tree
323
+ args: (map, n)
324
+ description: The first n values in the map
310
325
  text:
311
326
  args: "`…`"
312
327
  description: Tagged template literal for rendering trees
@@ -320,8 +335,8 @@ Tree:
320
335
  args: (tree, path)
321
336
  description: Traverse a slash-separated path
322
337
  values:
323
- args: (tree)
324
- description: The tree's values
338
+ args: (map)
339
+ description: The map's values
325
340
  withKeys:
326
- args: (tree, keys)
327
- description: Use the given keys for the tree
341
+ args: (map, keys)
342
+ description: Use the given keys for the map
package/src/dev/serve.js CHANGED
@@ -11,14 +11,13 @@ const defaultPort = 5000;
11
11
  /**
12
12
  * Start a local web server for the indicated tree.
13
13
  *
14
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
15
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
14
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
16
15
  *
17
- * @param {Treelike} treelike
16
+ * @param {Maplike} maplike
18
17
  * @param {number} [port]
19
18
  */
20
- export default async function serve(treelike, port) {
21
- let tree = await getTreeArgument(treelike, "serve");
19
+ export default async function serve(maplike, port) {
20
+ let tree = await getTreeArgument(maplike, "serve");
22
21
 
23
22
  if (!isTransformApplied(ExplorableSiteTransform, tree)) {
24
23
  tree = transformObject(ExplorableSiteTransform, tree);
package/src/dev/svg.js CHANGED
@@ -8,19 +8,18 @@ let graphvizLoaded = false;
8
8
  /**
9
9
  * Render a tree visually in SVG format.
10
10
  *
11
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
12
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
11
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
13
12
  * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
14
13
  *
15
- * @param {Treelike} treelike
14
+ * @param {Maplike} maplike
16
15
  * @param {PlainObject} [options]
17
16
  */
18
- export default async function svg(treelike, options = {}) {
17
+ export default async function svg(maplike, options = {}) {
19
18
  if (!graphvizLoaded) {
20
19
  await graphviz.loadWASM();
21
20
  graphvizLoaded = true;
22
21
  }
23
- const tree = await getTreeArgument(treelike, "svg", { deep: true });
22
+ const tree = await getTreeArgument(maplike, "svg", { deep: true });
24
23
  const dotText = await dot(tree, options);
25
24
  if (dotText === undefined) {
26
25
  return undefined;
@@ -12,14 +12,14 @@ import { getDescriptor } from "../common/utilities.js";
12
12
  /**
13
13
  * Render a tree in DOT format.
14
14
  *
15
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
15
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
16
16
  * @typedef {import("@weborigami/async-tree").PlainObject} PlainObject
17
17
  *
18
- * @param {Treelike} treelike
18
+ * @param {Maplike} maplike
19
19
  * @param {PlainObject} [options]
20
20
  */
21
- export default async function dot(treelike, options = {}) {
22
- const tree = await getTreeArgument(treelike, "treeDot", { deep: true });
21
+ export default async function dot(maplike, options = {}) {
22
+ const tree = await getTreeArgument(maplike, "treeDot", { deep: true });
23
23
  const rootLabel = getDescriptor(tree) ?? "";
24
24
  const treeArcs = await statements(tree, "", rootLabel, options);
25
25
  return `digraph g {
@@ -48,7 +48,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
48
48
 
49
49
  // Draw edges and collect labels for the nodes they lead to.
50
50
  let nodes = new Map();
51
- for (const key of await tree.keys()) {
51
+ for (const key of await Tree.keys(tree)) {
52
52
  const destPath = nodePath ? `${trailingSlash.add(nodePath)}${key}` : key;
53
53
  const labelUrl = createLinks ? `; labelURL="${destPath}"` : "";
54
54
  const arc = ` "${nodePath}" -> "${destPath}" [label="${key}"${labelUrl}];`;
@@ -67,7 +67,7 @@ async function statements(tree, nodePath, nodeLabel, options) {
67
67
  }
68
68
 
69
69
  const expandable =
70
- value instanceof Array || isPlainObject(value) || Tree.isAsyncTree(value);
70
+ value instanceof Array || isPlainObject(value) || Tree.isMap(value);
71
71
  if (expandable) {
72
72
  const subtree = Tree.from(value);
73
73
  const subStatements = await statements(subtree, destPath, null, options);
package/src/dev/watch.js CHANGED
@@ -1,19 +1,19 @@
1
- import { constant, getTreeArgument, Tree } from "@weborigami/async-tree";
1
+ import { ConstantMap, getTreeArgument, Tree } from "@weborigami/async-tree";
2
2
  import { formatError, moduleCache } from "@weborigami/language";
3
3
 
4
4
  /**
5
5
  * Let a tree (e.g., of files) respond to changes.
6
6
  *
7
+ * @typedef {import("@weborigami/async-tree").AsyncMap} AsyncMap
7
8
  * @typedef {import("@weborigami/async-tree").Invocable} Invocable
8
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
10
10
  *
11
- * @param {Treelike} treelike
11
+ * @param {Maplike} maplike
12
12
  * @param {Invocable} [fn]
13
- * @returns {Promise<AsyncTree>}
13
+ * @returns {Promise<Map|AsyncMap>}
14
14
  */
15
- export default async function watch(treelike, fn) {
16
- const container = await getTreeArgument(treelike, "watch");
15
+ export default async function watch(maplike, fn) {
16
+ const container = await getTreeArgument(maplike, "watch");
17
17
 
18
18
  // Watch the indicated tree.
19
19
  await /** @type {any} */ (container).watch?.();
@@ -58,7 +58,7 @@ async function evaluateTree(parent, fn) {
58
58
  message = `Warning: watch expression did not return a tree`;
59
59
  }
60
60
  console.warn(message);
61
- tree = constant(message);
61
+ tree = new ConstantMap(message);
62
62
  return tree;
63
63
  }
64
64
 
@@ -3,7 +3,7 @@ import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
3
3
  /**
4
4
  * Render the object as text in CSV format.
5
5
  *
6
- * The object should a treelike object such as an array. The output will include
6
+ * The object should a maplike object such as an array. The output will include
7
7
  * a header row with field names taken from the first item in the tree/array.
8
8
  *
9
9
  * @param {any} object
@@ -1,7 +1,6 @@
1
1
  import documentObject from "../common/documentObject.js";
2
2
 
3
3
  /**
4
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
5
4
  * @typedef {import("@weborigami/async-tree").Stringlike} Stringlike
6
5
  *
7
6
  * @param {Stringlike} text
@@ -1,21 +1,20 @@
1
- import { getTreeArgument } from "@weborigami/async-tree";
1
+ import { getTreeArgument, Tree } from "@weborigami/async-tree";
2
2
  import { getDescriptor } from "../common/utilities.js";
3
3
 
4
4
  /**
5
5
  * Return a default index.html page for the current tree.
6
6
  *
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
7
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
9
8
  *
10
- * @param {Treelike} treelike
9
+ * @param {Maplike} maplike
11
10
  * @param {string} [basePath]
12
11
  */
13
- export default async function indexPage(treelike, basePath) {
14
- const tree = await getTreeArgument(treelike, "svg");
15
- const keys = Array.from(await tree.keys());
12
+ export default async function indexPage(maplike, basePath) {
13
+ const tree = await getTreeArgument(maplike, "svg");
14
+ const treeKeys = await Tree.keys(tree);
16
15
 
17
16
  // Skip system-ish files that start with a period. Also skip `index.html`.
18
- const filtered = keys.filter(
17
+ const filtered = treeKeys.filter(
19
18
  (key) => !(key.startsWith?.(".") || key === "index.html")
20
19
  );
21
20
 
@@ -6,7 +6,6 @@ import documentObject from "../common/documentObject.js";
6
6
  * Inline any Origami expressions found inside ${...} placeholders in the input
7
7
  * text.
8
8
  *
9
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
9
  *
11
10
  * @param {any} input
12
11
  */
@@ -3,7 +3,6 @@ import { isUnpackable, toPlainValue } from "@weborigami/async-tree";
3
3
  /**
4
4
  * Render the given object in JSON format.
5
5
  *
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
6
  *
8
7
  * @param {any} [obj]
9
8
  */
@@ -1,36 +1,49 @@
1
- import { Tree, getTreeArgument, jsonKeys } from "@weborigami/async-tree";
1
+ import {
2
+ AsyncMap,
3
+ Tree,
4
+ getTreeArgument,
5
+ jsonKeys,
6
+ } from "@weborigami/async-tree";
2
7
 
3
8
  /**
4
9
  * Expose .keys.json for a tree.
5
10
  *
6
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
7
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
11
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
8
12
  *
9
- * @param {Treelike} treelike
10
- * @returns {Promise<AsyncTree>}
13
+ * @param {Maplike} maplike
14
+ * @returns {Promise<AsyncMap>}
11
15
  */
12
- export default async function jsonKeysBuiltin(treelike) {
13
- const tree = await getTreeArgument(treelike, "jsonKeys");
14
- return jsonKeysTree(tree);
16
+ export default async function jsonKeysBuiltin(maplike) {
17
+ const source = await getTreeArgument(maplike, "jsonKeys");
18
+ return jsonKeysMap(source);
15
19
  }
16
20
 
17
- function jsonKeysTree(tree) {
18
- return Object.assign(Object.create(tree), {
21
+ function jsonKeysMap(source) {
22
+ const result = Object.assign(new AsyncMap(), {
23
+ description: "jsonKeys",
24
+
19
25
  async get(key) {
20
- let value = await tree.get(key);
26
+ let value = await source.get(key);
21
27
  if (value === undefined && key === ".keys.json") {
22
28
  value = await jsonKeys.stringify(this);
23
- } else if (Tree.isTreelike(value)) {
24
- const subtree = Tree.from(value, { deep: true, parent: this });
25
- value = jsonKeysTree(subtree);
29
+ } else if (Tree.isMaplike(value)) {
30
+ const subtree = Tree.from(value, { deep: true, parent: result });
31
+ value = jsonKeysMap(subtree);
26
32
  }
27
33
  return value;
28
34
  },
29
35
 
30
- async keys() {
31
- const keys = new Set(await tree.keys());
32
- keys.add(".keys.json");
33
- return keys;
36
+ async *keys() {
37
+ const treeKeys = new Set(await Tree.keys(source));
38
+ treeKeys.add(".keys.json");
39
+ yield* treeKeys;
40
+ },
41
+
42
+ source: source,
43
+
44
+ get trailingSlashKeys() {
45
+ return source.trailingSlashKeys;
34
46
  },
35
47
  });
48
+ return result;
36
49
  }
@@ -4,7 +4,6 @@ const codePromiseMap = new Map();
4
4
  /**
5
5
  * Evaluate the given function only once and cache the result.
6
6
  *
7
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
7
  *
9
8
  * @param {Function} fn
10
9
  */
@@ -15,7 +15,6 @@ const TypedArray = Object.getPrototypeOf(Uint8Array);
15
15
  * Parse an Origami expression, evaluate it in the context of a tree (provided
16
16
  * by `this`), and return the result as text.
17
17
  *
18
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
19
18
  *
20
19
  * @param {string} expression
21
20
  */
@@ -49,6 +48,17 @@ export default async function ori(expression, options = {}) {
49
48
  // Execute
50
49
  let result = await fn();
51
50
 
51
+ // if (result === undefined) {
52
+ // // Was the code a path traversal?
53
+ // const wasTraversal =
54
+ // fn.code[0] === ops.unpack ||
55
+ // (fn.code[0] instanceof Array && fn.code[0][0] === ops.scope);
56
+ // if (wasTraversal) {
57
+ // // Yes, probably an error
58
+ // console.warn(`ori: warning: undefined ${highlightError(expression)}`);
59
+ // }
60
+ // }
61
+
52
62
  // If result was a function, execute it.
53
63
  if (typeof result === "function") {
54
64
  result = await result();
@@ -90,8 +100,8 @@ async function format(result) {
90
100
  text = result;
91
101
  }
92
102
 
93
- // If the result is treelike, attach it to the text output.
94
- if (Tree.isTreelike(result)) {
103
+ // If the result is maplike, attach it to the text output.
104
+ if (Tree.isMaplike(result)) {
95
105
  if (typeof text === "string") {
96
106
  // @ts-ignore
97
107
  text = new String(text);
@@ -1,5 +1,4 @@
1
1
  /**
2
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
3
2
  *
4
3
  * @param {any} obj
5
4
  */
@@ -18,7 +18,7 @@ export default async function post(url, data) {
18
18
  if (isUnpackable(data)) {
19
19
  data = await data.unpack();
20
20
  }
21
- if (Tree.isTreelike(data)) {
21
+ if (Tree.isMaplike(data)) {
22
22
  const value = await toPlainValue(data);
23
23
  body = JSON.stringify(value);
24
24
  headers = {
@@ -2,10 +2,9 @@ import { getTreeArgument, Tree } from "@weborigami/async-tree";
2
2
  import jsonFeedToRss from "@weborigami/json-feed-to-rss";
3
3
 
4
4
  /**
5
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
5
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
7
6
  *
8
- * @param {Treelike} jsonFeed
7
+ * @param {Maplike} jsonFeed
9
8
  * @param {any} options
10
9
  */
11
10
  export default async function rss(jsonFeed, options = {}) {
@@ -11,32 +11,22 @@ const templateText = `(urls) => \`<?xml version="1.0" encoding="UTF-8"?>
11
11
  `;
12
12
 
13
13
  /**
14
- * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
15
- * @typedef {import("@weborigami/async-tree").Treelike} Treelike
14
+ * @typedef {import("@weborigami/async-tree").Maplike} Maplike
16
15
  *
17
- * @param {Treelike} treelike
18
- * @param {{ assumeSlashes?: boolean, base?: string }} options
16
+ * @param {Maplike} maplike
17
+ * @param {{ base?: string }} options
19
18
  * @returns {Promise<string>}
20
19
  */
21
- export default async function sitemap(treelike, options = {}) {
22
- const tree = await getTreeArgument(treelike, "sitemap");
20
+ export default async function sitemap(maplike, options = {}) {
21
+ const tree = await getTreeArgument(maplike, "sitemap");
23
22
 
24
23
  // We're only interested in keys that end in .html or with no extension.
25
- function test(key) {
26
- return key.endsWith?.(".html") || !key.includes?.(".");
27
- }
28
- const filterTree = {
29
- async keys() {
30
- const keys = Array.from(await tree.keys());
31
- return keys.filter((key) => test(key));
32
- },
24
+ const filtered = await Tree.filter(
25
+ tree,
26
+ (value, key) => key.endsWith?.(".html") || !key.includes?.(".")
27
+ );
33
28
 
34
- async get(key) {
35
- return test(key) ? tree.get(key) : undefined;
36
- },
37
- };
38
-
39
- const treePaths = await Tree.paths(filterTree, options);
29
+ const treePaths = await Tree.paths(filtered, options);
40
30
 
41
31
  // For simplicity, we assume that HTML pages will end in .html.
42
32
  // If the page is named index.html, we remove index.html from