@weborigami/origami 0.0.48 → 0.0.50

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 (72) hide show
  1. package/exports/buildExports.js +2 -2
  2. package/exports/exports.js +29 -13
  3. package/index.ts +0 -3
  4. package/package.json +10 -10
  5. package/src/builtins/@addNextPrevious.js +58 -0
  6. package/src/builtins/{@arrows.js → @arrowsMap.js} +7 -7
  7. package/src/builtins/@changes.js +46 -0
  8. package/src/builtins/@clean.js +19 -0
  9. package/src/builtins/@constructor.js +17 -0
  10. package/src/builtins/@crawl.js +2 -2
  11. package/src/builtins/@debug.js +17 -19
  12. package/src/builtins/@deepMap.js +19 -0
  13. package/src/builtins/@deepMapFn.js +25 -0
  14. package/src/builtins/{@mergeDeep.js → @deepMerge.js} +7 -7
  15. package/src/builtins/@deepTake.js +21 -0
  16. package/src/builtins/@deepTakeFn.js +22 -0
  17. package/src/builtins/{@valuesDeep.js → @deepValues.js} +6 -5
  18. package/src/builtins/@document.js +1 -2
  19. package/src/builtins/@files.js +14 -1
  20. package/src/builtins/@group.js +20 -0
  21. package/src/builtins/@groupFn.js +30 -0
  22. package/src/builtins/@if.js +2 -1
  23. package/src/builtins/@image/format.js +10 -31
  24. package/src/builtins/@image/formatFn.js +15 -0
  25. package/src/builtins/@image/resize.js +7 -28
  26. package/src/builtins/@image/resizeFn.js +14 -0
  27. package/src/builtins/@inline.js +8 -2
  28. package/src/builtins/@invoke.js +1 -1
  29. package/src/builtins/@json.js +5 -1
  30. package/src/builtins/@jsonParse.js +9 -0
  31. package/src/builtins/@map.js +10 -170
  32. package/src/builtins/@mapFn.js +143 -0
  33. package/src/builtins/@mdHtml.js +2 -0
  34. package/src/builtins/@mdTree.js +69 -0
  35. package/src/builtins/@naturalOrder.js +1 -0
  36. package/src/builtins/@ori.js +1 -1
  37. package/src/builtins/@paginate.js +18 -0
  38. package/src/builtins/@paginateFn.js +61 -0
  39. package/src/builtins/@perf.js +1 -1
  40. package/src/builtins/@redirect.js +10 -1
  41. package/src/builtins/@regexParse.js +5 -0
  42. package/src/builtins/@regexParseFn.js +9 -0
  43. package/src/builtins/@rss.js +8 -4
  44. package/src/builtins/@sitemap.js +4 -4
  45. package/src/builtins/@slug.js +15 -0
  46. package/src/builtins/@sort.js +10 -7
  47. package/src/builtins/@sortFn.js +58 -0
  48. package/src/builtins/@take.js +3 -17
  49. package/src/builtins/@takeFn.js +21 -0
  50. package/src/builtins/@tree.js +2 -14
  51. package/src/builtins/@yaml.js +4 -0
  52. package/src/builtins/@yamlParse.js +10 -0
  53. package/src/builtins/map.d.ts +6 -7
  54. package/src/common/ExplorableSiteTransform.js +16 -10
  55. package/src/common/ShuffleTransform.js +3 -3
  56. package/src/common/{arrowFunctionsMap.js → arrowsMapFn.js} +3 -3
  57. package/src/common/documentObject.js +18 -9
  58. package/src/common/serialize.js +1 -10
  59. package/src/common/utilities.js +5 -2
  60. package/src/misc/OriCommandTransform.js +2 -7
  61. package/src/misc/explore.ori +7 -7
  62. package/src/server/constructResponse.js +3 -9
  63. package/src/server/server.js +5 -2
  64. package/src/builtins/@apply.js +0 -6
  65. package/src/builtins/@groupBy.js +0 -37
  66. package/src/builtins/@isAsyncTree.js +0 -17
  67. package/src/builtins/@mapDeep.js +0 -22
  68. package/src/builtins/@new.js +0 -6
  69. package/src/builtins/@parse/json.js +0 -6
  70. package/src/builtins/@parse/yaml.js +0 -8
  71. package/src/builtins/@sortBy.js +0 -37
  72. package/src/builtins/@with.js +0 -22
@@ -103,11 +103,11 @@ function exportStatements(src) {
103
103
  const withPaths = transformObject(PathTransform, src);
104
104
 
105
105
  // Map each source file to an export statement.
106
- const mapped = map({
106
+ const mapped = map(withPaths, {
107
107
  deep: true,
108
108
  value: exportStatementForCode,
109
109
  ...keyFunctionsForExtensions({ sourceExtension: "js" }),
110
- })(withPaths);
110
+ });
111
111
 
112
112
  return mapped;
113
113
  }
@@ -1,15 +1,24 @@
1
1
  // This file is generated by running buildExports.js -- do not edit by hand.
2
- export { default as apply } from "../src/builtins/@apply.js";
3
- export { default as arrows } from "../src/builtins/@arrows.js";
2
+ export { default as addNextPrevious } from "../src/builtins/@addNextPrevious.js";
3
+ export { default as arrowsMap } from "../src/builtins/@arrowsMap.js";
4
4
  export { default as basename } from "../src/builtins/@basename.js";
5
5
  export { default as builtins } from "../src/builtins/@builtins.js";
6
6
  export { default as cache } from "../src/builtins/@cache.js";
7
+ export { default as changes } from "../src/builtins/@changes.js";
8
+ export { default as clean } from "../src/builtins/@clean.js";
7
9
  export { default as concat } from "../src/builtins/@concat.js";
8
10
  export { default as config } from "../src/builtins/@config.js";
11
+ export { default as constructor } from "../src/builtins/@constructor.js";
9
12
  export { default as copy } from "../src/builtins/@copy.js";
10
13
  export { default as count } from "../src/builtins/@count.js";
11
14
  export { default as crawl } from "../src/builtins/@crawl.js";
12
15
  export { default as debug } from "../src/builtins/@debug.js";
16
+ export { default as deepMap } from "../src/builtins/@deepMap.js";
17
+ export { default as deepMapFn } from "../src/builtins/@deepMapFn.js";
18
+ export { default as deepMerge } from "../src/builtins/@deepMerge.js";
19
+ export { default as deepTake } from "../src/builtins/@deepTake.js";
20
+ export { default as deepTakeFn } from "../src/builtins/@deepTakeFn.js";
21
+ export { default as deepValues } from "../src/builtins/@deepValues.js";
13
22
  export { default as defineds } from "../src/builtins/@defineds.js";
14
23
  export { default as document } from "../src/builtins/@document.js";
15
24
  export { default as equals } from "../src/builtins/@equals.js";
@@ -21,30 +30,34 @@ export { default as filter } from "../src/builtins/@filter.js";
21
30
  export { default as first } from "../src/builtins/@first.js";
22
31
  export { default as fnTree } from "../src/builtins/@fnTree.js";
23
32
  export { default as globs } from "../src/builtins/@globs.js";
24
- export { default as groupBy } from "../src/builtins/@groupBy.js";
33
+ export { default as group } from "../src/builtins/@group.js";
34
+ export { default as groupFn } from "../src/builtins/@groupFn.js";
25
35
  export { default as help } from "../src/builtins/@help.js";
26
36
  export { default as http } from "../src/builtins/@http.js";
27
37
  export { default as https } from "../src/builtins/@https.js";
28
38
  export { default as if } from "../src/builtins/@if.js";
29
39
  export { default as imageFormat } from "../src/builtins/@image/format.js";
40
+ export { default as imageFormatFn } from "../src/builtins/@image/formatFn.js";
30
41
  export { default as imageResize } from "../src/builtins/@image/resize.js";
42
+ export { default as imageResizeFn } from "../src/builtins/@image/resizeFn.js";
31
43
  export { default as index } from "../src/builtins/@index.js";
32
44
  export { default as inherited } from "../src/builtins/@inherited.js";
33
45
  export { default as inline } from "../src/builtins/@inline.js";
34
46
  export { default as inners } from "../src/builtins/@inners.js";
35
47
  export { default as invoke } from "../src/builtins/@invoke.js";
36
- export { default as isAsyncTree } from "../src/builtins/@isAsyncTree.js";
37
48
  export { default as js } from "../src/builtins/@js.js";
38
49
  export { default as json } from "../src/builtins/@json.js";
50
+ export { default as jsonParse } from "../src/builtins/@jsonParse.js";
39
51
  export { default as keys } from "../src/builtins/@keys.js";
40
52
  export { default as keysJson } from "../src/builtins/@keysJson.js";
41
53
  export { default as map } from "../src/builtins/@map.js";
42
- export { default as mapDeep } from "../src/builtins/@mapDeep.js";
54
+ export { default as mapFn } from "../src/builtins/@mapFn.js";
43
55
  export { default as match } from "../src/builtins/@match.js";
44
56
  export { default as mdHtml } from "../src/builtins/@mdHtml.js";
57
+ export { default as mdTree } from "../src/builtins/@mdTree.js";
45
58
  export { default as merge } from "../src/builtins/@merge.js";
46
- export { default as mergeDeep } from "../src/builtins/@mergeDeep.js";
47
59
  export { default as mkdir } from "../src/builtins/@mkdir.js";
60
+ export * from "../src/builtins/@naturalOrder.js";
48
61
  export { default as node } from "../src/builtins/@node.js";
49
62
  export { default as not } from "../src/builtins/@not.js";
50
63
  export { default as once } from "../src/builtins/@once.js";
@@ -52,14 +65,16 @@ export { default as or } from "../src/builtins/@or.js";
52
65
  export { default as ori } from "../src/builtins/@ori.js";
53
66
  export { default as pack } from "../src/builtins/@pack.js";
54
67
  export { default as package } from "../src/builtins/@package.js";
68
+ export { default as paginate } from "../src/builtins/@paginate.js";
69
+ export { default as paginateFn } from "../src/builtins/@paginateFn.js";
55
70
  export { default as parent } from "../src/builtins/@parent.js";
56
- export { default as parseJson } from "../src/builtins/@parse/json.js";
57
- export { default as parseYaml } from "../src/builtins/@parse/yaml.js";
58
71
  export { default as paths } from "../src/builtins/@paths.js";
59
72
  export { default as perf } from "../src/builtins/@perf.js";
60
73
  export { default as plain } from "../src/builtins/@plain.js";
61
74
  export { default as project } from "../src/builtins/@project.js";
62
75
  export { default as redirect } from "../src/builtins/@redirect.js";
76
+ export { default as regexParse } from "../src/builtins/@regexParse.js";
77
+ export { default as regexParseFn } from "../src/builtins/@regexParseFn.js";
63
78
  export { default as repeat } from "../src/builtins/@repeat.js";
64
79
  export { default as reverse } from "../src/builtins/@reverse.js";
65
80
  export { default as rss } from "../src/builtins/@rss.js";
@@ -72,23 +87,24 @@ export { default as setDeep } from "../src/builtins/@setDeep.js";
72
87
  export { default as shell } from "../src/builtins/@shell.js";
73
88
  export { default as shuffle } from "../src/builtins/@shuffle.js";
74
89
  export { default as sitemap } from "../src/builtins/@sitemap.js";
90
+ export { default as slug } from "../src/builtins/@slug.js";
75
91
  export { default as sort } from "../src/builtins/@sort.js";
76
- export { default as sortBy } from "../src/builtins/@sortBy.js";
92
+ export { default as sortFn } from "../src/builtins/@sortFn.js";
77
93
  export { default as static } from "../src/builtins/@static.js";
78
94
  export { default as stdin } from "../src/builtins/@stdin.js";
79
95
  export { default as string } from "../src/builtins/@string.js";
80
96
  export { default as svg } from "../src/builtins/@svg.js";
81
97
  export { default as table } from "../src/builtins/@table.js";
82
98
  export { default as take } from "../src/builtins/@take.js";
83
- export { default as tree } from "../src/builtins/@tree.js";
99
+ export { default as takeFn } from "../src/builtins/@takeFn.js";
100
+ export * from "../src/builtins/@tree.js";
84
101
  export { default as treeHttp } from "../src/builtins/@treeHttp.js";
85
102
  export { default as treeHttps } from "../src/builtins/@treeHttps.js";
86
103
  export { default as unpack } from "../src/builtins/@unpack.js";
87
104
  export { default as values } from "../src/builtins/@values.js";
88
- export { default as valuesDeep } from "../src/builtins/@valuesDeep.js";
89
105
  export { default as watch } from "../src/builtins/@watch.js";
90
- export { default as with } from "../src/builtins/@with.js";
91
106
  export { default as yaml } from "../src/builtins/@yaml.js";
107
+ export { default as yamlParse } from "../src/builtins/@yamlParse.js";
92
108
  export { default as homeFiles } from "../src/builtins/~.js";
93
109
  export { default as csshandler } from "../src/builtins/css_handler.js";
94
110
  export { default as htmhandler } from "../src/builtins/htm_handler.js";
@@ -108,7 +124,7 @@ export { default as ymlhandler } from "../src/builtins/yml_handler.js";
108
124
  export { default as defaultModuleExport } from "../src/cli/defaultModuleExport.js";
109
125
  export { default as showUsage } from "../src/cli/showUsage.js";
110
126
  export { default as addValueKeyToScope } from "../src/common/addValueKeyToScope.js";
111
- export { default as arrowFunctionsMap } from "../src/common/arrowFunctionsMap.js";
127
+ export { default as arrowsMapFn } from "../src/common/arrowsMapFn.js";
112
128
  export { default as CommandModulesTransform } from "../src/common/CommandModulesTransform.js";
113
129
  export { default as ConstantTree } from "../src/common/ConstantTree.js";
114
130
  export { default as documentObject } from "../src/common/documentObject.js";
package/index.ts CHANGED
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  import { Treelike, Unpackable } from "@weborigami/async-tree";
7
- import { AsyncTree } from "@weborigami/types";
8
7
 
9
8
  /**
10
9
  * A class constructor is an object with a `new` method that returns an
@@ -21,5 +20,3 @@ export interface JsonObject {
21
20
  export type JsonValue = boolean | number | string | Date | JsonObject | JsonValue[] | null;
22
21
 
23
22
  export type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array;
24
-
25
- export type TreelikeTransform = (value: Treelike) => AsyncTree;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/origami",
3
- "version": "0.0.48",
3
+ "version": "0.0.50",
4
4
  "description": "Web Origami language, CLI, framework, and server",
5
5
  "type": "module",
6
6
  "repository": {
@@ -13,22 +13,22 @@
13
13
  "main": "./exports/exports.js",
14
14
  "types": "./index.ts",
15
15
  "devDependencies": {
16
- "@types/node": "20.11.27",
17
- "typescript": "5.4.2"
16
+ "@types/node": "20.12.7",
17
+ "typescript": "5.4.5"
18
18
  },
19
19
  "dependencies": {
20
- "@weborigami/async-tree": "0.0.48",
21
- "@weborigami/language": "0.0.48",
22
- "@weborigami/types": "0.0.48",
20
+ "@weborigami/async-tree": "0.0.50",
21
+ "@weborigami/language": "0.0.50",
22
+ "@weborigami/types": "0.0.50",
23
23
  "exif-parser": "0.1.12",
24
- "graphviz-wasm": "3.0.1",
24
+ "graphviz-wasm": "3.0.2",
25
25
  "highlight.js": "11.9.0",
26
- "marked": "12.0.1",
26
+ "marked": "12.0.2",
27
27
  "marked-gfm-heading-id": "3.1.3",
28
28
  "marked-highlight": "2.1.1",
29
29
  "marked-smartypants": "1.1.6",
30
- "sharp": "0.33.2",
31
- "yaml": "2.4.1"
30
+ "sharp": "0.33.3",
31
+ "yaml": "2.4.2"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "ori exports/buildExports.js src > exports/exports.js",
@@ -0,0 +1,58 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import getTreeArgument from "../misc/getTreeArgument.js";
3
+
4
+ /**
5
+ * Add nextKey/previousKey properties to values.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ *
9
+ * @this {AsyncTree|null}
10
+ * @param {import("@weborigami/async-tree").Treelike} treelike
11
+ */
12
+ export default async function addNextPrevious(treelike) {
13
+ const tree = await getTreeArgument(this, arguments, treelike, "@sequence");
14
+ let keys;
15
+ return Object.create(tree, {
16
+ get: {
17
+ value: async (key) => {
18
+ let value = await tree.get(key);
19
+
20
+ if (value === undefined) {
21
+ return undefined;
22
+ } else if (Tree.isTreelike(value)) {
23
+ value = await Tree.plain(value);
24
+ } else if (typeof value === "object") {
25
+ // Clone value to avoid modifying the original object.
26
+ value = { ...value };
27
+ } else if (typeof value === "string") {
28
+ // Upgrade text nodes to objects.
29
+ value = { "@text": value };
30
+ } else {
31
+ // Upgrade other scalar types to objects.
32
+ value = { "@data": value };
33
+ }
34
+
35
+ if (keys === undefined) {
36
+ keys = Array.from(await tree.keys());
37
+ }
38
+ const index = keys.indexOf(key);
39
+ if (index === -1) {
40
+ // Key is supported but not published in `keys`
41
+ return value;
42
+ }
43
+
44
+ // Extend value with nextKey/previousKey properties.
45
+ const nextKey = keys[index + 1];
46
+ if (nextKey) {
47
+ value.nextKey = nextKey;
48
+ }
49
+ const previousKey = keys[index - 1];
50
+ if (previousKey) {
51
+ value.previousKey = previousKey;
52
+ }
53
+ return value;
54
+ },
55
+ writable: true,
56
+ },
57
+ });
58
+ }
@@ -1,5 +1,5 @@
1
1
  import { Scope, functionResultsMap } from "@weborigami/language";
2
- import arrowFunctionsMap from "../common/arrowFunctionsMap.js";
2
+ import arrowsMapFn from "../common/arrowsMapFn.js";
3
3
  import { keySymbol } from "../common/utilities.js";
4
4
  import getTreeArgument from "../misc/getTreeArgument.js";
5
5
  import builtins from "./@builtins.js";
@@ -12,14 +12,14 @@ import builtins from "./@builtins.js";
12
12
  * @this {AsyncTree|null}
13
13
  * @param {Treelike} [treelike]
14
14
  */
15
- export default async function arrows(treelike) {
16
- const tree = await getTreeArgument(this, arguments, treelike, "@arrows");
17
- const mapped = functionResultsMap(arrowFunctionsMap()(tree));
15
+ export default async function arrowsMap(treelike) {
16
+ const tree = await getTreeArgument(this, arguments, treelike, "arrowsMap");
17
+ const mapped = functionResultsMap(arrowsMapFn()(tree));
18
18
  const scope = this ?? builtins;
19
19
  const scoped = Scope.treeWithScope(mapped, scope);
20
- scoped[keySymbol] = "@arrows";
20
+ scoped[keySymbol] = "@arrowsMap";
21
21
  return scoped;
22
22
  }
23
23
 
24
- arrows.usage = `@arrows <obj>\tInterpret arrow keys in the tree as function calls`;
25
- arrows.documentation = "https://weborigami.org/language/@arrows.html";
24
+ arrowsMap.usage = `@arrowsMap <obj>\tInterpret arrow keys in the tree as function calls`;
25
+ arrowsMap.documentation = "https://weborigami.org/language/@arrowsMap.html";
@@ -0,0 +1,46 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+
3
+ // Given an old tree and a new tree, return a tree of changes indicated
4
+ // by the values: "added", "changed", or "deleted".
5
+ export default async function changes(oldTreelike, newTreelike) {
6
+ const oldTree = Tree.from(oldTreelike);
7
+ const newTree = Tree.from(newTreelike);
8
+
9
+ const oldKeys = Array.from(await oldTree.keys());
10
+ const newKeys = Array.from(await newTree.keys());
11
+
12
+ const result = {};
13
+
14
+ for (const key of oldKeys) {
15
+ if (!newKeys.includes(key)) {
16
+ result[key] = "deleted";
17
+ continue;
18
+ }
19
+
20
+ const oldValue = await oldTree.get(key);
21
+ const newValue = await newTree.get(key);
22
+
23
+ if (Tree.isAsyncTree(oldValue) && Tree.isAsyncTree(newValue)) {
24
+ const treeChanges = await changes(oldValue, newValue);
25
+ if (Object.keys(treeChanges).length > 0) {
26
+ result[key] = treeChanges;
27
+ }
28
+ } else if (oldValue?.toString && newValue?.toString) {
29
+ const oldText = oldValue.toString();
30
+ const newText = newValue.toString();
31
+ if (oldText !== newText) {
32
+ result[key] = "changed";
33
+ }
34
+ } else {
35
+ result[key] = "changed";
36
+ }
37
+ }
38
+
39
+ for (const key of newKeys) {
40
+ if (!oldKeys.includes(key)) {
41
+ result[key] = "added";
42
+ }
43
+ }
44
+
45
+ return result;
46
+ }
@@ -0,0 +1,19 @@
1
+ import { Tree } from "@weborigami/async-tree";
2
+ import getTreeArgument from "../misc/getTreeArgument.js";
3
+
4
+ /**
5
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
+ *
7
+ * @this {AsyncTree|null}
8
+ * @param {import("@weborigami/async-tree").Treelike} treelike
9
+ */
10
+ export default async function clean(treelike) {
11
+ const tree = await getTreeArgument(this, arguments, treelike, "@sequence");
12
+ if (!Tree.isAsyncMutableTree(tree)) {
13
+ throw new TypeError("@clean: the given tree is read-only.");
14
+ }
15
+ const keys = Array.from(await tree.keys());
16
+ const promises = keys.map((key) => tree.set(key, undefined));
17
+ await Promise.all(promises);
18
+ return tree;
19
+ }
@@ -0,0 +1,17 @@
1
+ import { ops } from "@weborigami/language";
2
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
3
+
4
+ /**
5
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
6
+ *
7
+ * @this {AsyncTree|null}
8
+ * @param {...any} keys
9
+ */
10
+ export default function constructor(...keys) {
11
+ assertScopeIsDefined(this, "constructor");
12
+ const scope = this;
13
+ if (!scope) {
14
+ throw new Error("@constructor requires a non-null scope.");
15
+ }
16
+ return ops.constructor.call(scope, ...keys);
17
+ }
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  DeepObjectTree,
3
3
  Tree,
4
+ deepMerge,
4
5
  isPlainObject,
5
6
  keysFromPath,
6
- mergeDeep,
7
7
  } from "@weborigami/async-tree";
8
8
  import { InvokeFunctionsTransform, Scope, extname } from "@weborigami/language";
9
9
  import * as utilities from "../common/utilities.js";
@@ -93,7 +93,7 @@ export default async function crawl(treelike, baseHref) {
93
93
  // for something already, that's better than a function that will get that
94
94
  // value.
95
95
  /** @type {AsyncTree} */
96
- let result = mergeDeep(
96
+ let result = deepMerge(
97
97
  new DeepObjectTree(cache),
98
98
  new (InvokeFunctionsTransform(DeepObjectTree))(resources)
99
99
  );
@@ -1,4 +1,4 @@
1
- import { Tree, isPlainObject } from "@weborigami/async-tree";
1
+ import { Tree } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import ExplorableSiteTransform from "../common/ExplorableSiteTransform.js";
4
4
  import { isTransformApplied, transformObject } from "../common/utilities.js";
@@ -19,12 +19,13 @@ export default async function debug(treelike) {
19
19
  // apply its own scope to the tree.
20
20
  let tree = await getTreeArgument(this, arguments, treelike, "@debug");
21
21
 
22
+ if (!isTransformApplied(DebugTransform, tree)) {
23
+ tree = transformObject(DebugTransform, tree);
24
+ }
22
25
  if (!isTransformApplied(ExplorableSiteTransform, tree)) {
23
26
  tree = transformObject(ExplorableSiteTransform, tree);
24
27
  }
25
28
 
26
- tree = transformObject(DebugTransform, tree);
27
-
28
29
  return tree;
29
30
  }
30
31
 
@@ -36,20 +37,19 @@ function DebugTransform(Base) {
36
37
  return class Debug extends OriCommandTransform(Base) {
37
38
  async get(key) {
38
39
  let value = await super.get(key);
40
+ const scope = Scope.getScope(this);
39
41
 
40
- // Since this transform is for diagnostic purposes, cast arrays
41
- // or plain objects to trees so we can debug them too.
42
- if (value instanceof Array || isPlainObject(value)) {
42
+ // Since this transform is for diagnostic purposes, cast any treelike
43
+ // result to a tree so we can debug the result too.
44
+ if (Tree.isTreelike(value)) {
43
45
  value = Tree.from(value);
44
- }
45
-
46
- // Ensure debug transforms are applied to explorable results.
47
- if (Tree.isAsyncTree(value)) {
48
- value = transformObject(ExplorableSiteTransform, value);
49
- value = transformObject(DebugTransform, value);
50
- }
51
-
52
- if (value?.unpack) {
46
+ if (!value.parent && !value.scope) {
47
+ value = Scope.treeWithScope(value, scope);
48
+ }
49
+ if (!isTransformApplied(DebugTransform, value)) {
50
+ value = transformObject(DebugTransform, value);
51
+ }
52
+ } else if (value?.unpack) {
53
53
  // If the value isn't a tree, but has a tree attached via an `unpack`
54
54
  // method, wrap the unpack method to provide debug support for it.
55
55
  const original = value.unpack.bind(value);
@@ -61,13 +61,11 @@ function DebugTransform(Base) {
61
61
  /** @type {any} */
62
62
  let tree = Tree.from(content);
63
63
  if (!tree.parent && !tree.scope) {
64
- const scope = Scope.getScope(this);
65
64
  tree = Scope.treeWithScope(tree, scope);
66
65
  }
67
- if (!isTransformApplied(ExplorableSiteTransform, tree)) {
68
- tree = transformObject(ExplorableSiteTransform, tree);
66
+ if (!isTransformApplied(DebugTransform, tree)) {
67
+ tree = transformObject(DebugTransform, tree);
69
68
  }
70
- tree = transformObject(DebugTransform, tree);
71
69
  return tree;
72
70
  };
73
71
  }
@@ -0,0 +1,19 @@
1
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
2
+ import deepMapFn from "./@deepMapFn.js";
3
+
4
+ /**
5
+ * Shorthand for calling `@map` with `deep: true` option.
6
+ *
7
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
+ * @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
10
+ * @typedef {import("./map.d.ts").TreeMapOptions} TreeMapOptions
11
+ *
12
+ * @this {AsyncTree|null}
13
+ * @param {Treelike} source
14
+ * @param {ValueKeyFn|TreeMapOptions} operation
15
+ */
16
+ export default function deepMap(source, operation) {
17
+ assertScopeIsDefined(this, "deepMap");
18
+ return deepMapFn.call(this, operation)(source);
19
+ }
@@ -0,0 +1,25 @@
1
+ import { isPlainObject } from "@weborigami/async-tree";
2
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
3
+ import mapFn from "./@mapFn.js";
4
+
5
+ /**
6
+ * Shorthand for calling `@mapFn` with `deep: true` option.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ * @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
11
+ * @typedef {import("./map.d.ts").TreeMapOptions} TreeMapOptions
12
+ *
13
+ * @this {AsyncTree|null}
14
+ * @param {ValueKeyFn|TreeMapOptions} operation
15
+ */
16
+ export default function deepMapFn(operation) {
17
+ assertScopeIsDefined(this, "deepMap");
18
+ /** @type {TreeMapOptions} */
19
+ const options = isPlainObject(operation)
20
+ ? // Dictionary
21
+ { ...operation, deep: true }
22
+ : // Function
23
+ { deep: true, value: operation };
24
+ return mapFn.call(this, options);
25
+ }
@@ -1,4 +1,4 @@
1
- import { mergeDeep } from "@weborigami/async-tree";
1
+ import { deepMerge } from "@weborigami/async-tree";
2
2
  import { Scope } from "@weborigami/language";
3
3
  import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
4
 
@@ -10,8 +10,8 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
10
10
  * @this {AsyncTree|null}
11
11
  * @param {Treelike[]} trees
12
12
  */
13
- export default async function treeMergeDeep(...trees) {
14
- assertScopeIsDefined(this, "mergeDeep");
13
+ export default async function treedeepMerge(...trees) {
14
+ assertScopeIsDefined(this, "deepMerge");
15
15
  // Filter out null or undefined trees.
16
16
  const filtered = trees.filter((tree) => tree);
17
17
 
@@ -30,7 +30,7 @@ export default async function treeMergeDeep(...trees) {
30
30
  });
31
31
 
32
32
  // Merge the trees.
33
- const result = mergeDeep(...scopedTrees);
33
+ const result = deepMerge(...scopedTrees);
34
34
 
35
35
  // Give the overall mixed tree a scope that includes the component trees and
36
36
  // the current scope.
@@ -39,6 +39,6 @@ export default async function treeMergeDeep(...trees) {
39
39
  return result;
40
40
  }
41
41
 
42
- treeMergeDeep.usage = `@mergeDeep <...trees>\tMerge the given trees deeply`;
43
- treeMergeDeep.documentation =
44
- "https://weborigami.org/cli/builtins.html#mergeDeep";
42
+ treedeepMerge.usage = `@deepMerge <...trees>\tMerge the given trees deeply`;
43
+ treedeepMerge.documentation =
44
+ "https://weborigami.org/cli/builtins.html#deepMerge";
@@ -0,0 +1,21 @@
1
+ import getTreeArgument from "../misc/getTreeArgument.js";
2
+ import deepTakeFn from "./@deepTakeFn.js";
3
+
4
+ /**
5
+ * Returns a function that traverses a tree deeply and returns the values of the
6
+ * first `count` keys.
7
+ *
8
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
9
+ * @typedef {import("@weborigami/async-tree").Treelike} Treelike
10
+ *
11
+ * @this {AsyncTree|null}
12
+ * @param {Treelike} treelike
13
+ * @param {number} n
14
+ */
15
+ export default async function deepTake(treelike, n) {
16
+ const tree = await getTreeArgument(this, arguments, treelike, "@deepTake");
17
+ return deepTakeFn.call(this, n)(tree);
18
+ }
19
+
20
+ deepTake.usage = `@deepTake tree, n\tReturn the first n values from the deep tree`;
21
+ deepTake.documentation = "https://weborigami.org/builtins/deepTake.html";
@@ -0,0 +1,22 @@
1
+ import { deepTakeFn } from "@weborigami/async-tree";
2
+ import { Scope } from "@weborigami/language";
3
+ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
4
+
5
+ /**
6
+ * Returns a function that traverses a tree deeply and returns the values of the
7
+ * first `count` keys.
8
+ *
9
+ * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
10
+ *
11
+ * @this {AsyncTree|null}
12
+ * @param {number} count
13
+ */
14
+ export default function deepTakeFnBuiltin(count) {
15
+ assertScopeIsDefined(this, "deepTakeFn");
16
+ const scope = this;
17
+ return async (treelike) => {
18
+ const taken = await deepTakeFn(count)(treelike);
19
+ const scoped = Scope.treeWithScope(taken, scope);
20
+ return scoped;
21
+ };
22
+ }
@@ -1,4 +1,4 @@
1
- import { Tree } from "@weborigami/async-tree";
1
+ import { deepValues } from "@weborigami/async-tree";
2
2
  import getTreeArgument from "../misc/getTreeArgument.js";
3
3
 
4
4
  /**
@@ -6,14 +6,15 @@ import getTreeArgument from "../misc/getTreeArgument.js";
6
6
  *
7
7
  * @typedef {import("@weborigami/types").AsyncTree} AsyncTree
8
8
  * @typedef {import("@weborigami/async-tree").Treelike} Treelike
9
+ *
9
10
  * @this {AsyncTree|null}
10
11
  * @param {Treelike} [treelike]
11
12
  */
12
- export default async function valuesDeep(treelike) {
13
+ export default async function deepValuesBuiltin(treelike) {
13
14
  const tree = await getTreeArgument(this, arguments, treelike, "@valuesDeep");
14
- return Tree.mapReduce(tree, null, async (values) => values.flat());
15
+ return deepValues(tree);
15
16
  }
16
17
 
17
- valuesDeep.usage = `@valuesDeep <tree>\tThe in-order tree values as a flat array`;
18
- valuesDeep.documentation =
18
+ deepValuesBuiltin.usage = `@valuesDeep <tree>\tThe in-order tree values as a flat array`;
19
+ deepValuesBuiltin.documentation =
19
20
  "https://weborigami.org/cli/builtins.html#valuesDeep";
@@ -8,9 +8,8 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
8
8
  * @this {AsyncTree|null}
9
9
  * @param {StringLike} text
10
10
  * @param {any} [data]
11
- * @returns
12
11
  */
13
- export default function documentBuiltin(text, data) {
12
+ export default async function documentBuiltin(text, data) {
14
13
  assertScopeIsDefined(this, "document");
15
14
  return documentObject(text, data);
16
15
  }
@@ -1,4 +1,5 @@
1
1
  import { OrigamiFiles, Scope } from "@weborigami/language";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import process from "node:process";
4
5
  import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
@@ -11,7 +12,19 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
11
12
  */
12
13
  export default async function files(...keys) {
13
14
  assertScopeIsDefined(this, "files");
14
- const resolved = path.resolve(process.cwd(), ...keys);
15
+
16
+ // If path begins with `~`, treat it relative to the home directory.
17
+ // Otherwise, treat it relative to the current working directory.
18
+ let relativePath = keys.join(path.sep);
19
+ let basePath;
20
+ if (relativePath.startsWith("~")) {
21
+ basePath = os.homedir();
22
+ relativePath = relativePath.slice(2);
23
+ } else {
24
+ basePath = process.cwd();
25
+ }
26
+ const resolved = path.resolve(basePath, relativePath);
27
+
15
28
  /** @type {AsyncTree} */
16
29
  let result = new OrigamiFiles(resolved);
17
30
  result = Scope.treeWithScope(result, this);