@weborigami/origami 0.0.48 → 0.0.49
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/exports/exports.js +3 -0
- package/index.ts +0 -3
- package/package.json +10 -10
- package/src/builtins/@addNextPrevious.js +47 -0
- package/src/builtins/@debug.js +17 -19
- package/src/builtins/@document.js +1 -2
- package/src/builtins/@inline.js +8 -2
- package/src/builtins/@makeParser.js +9 -0
- package/src/builtins/@map.js +45 -34
- package/src/builtins/@mapDeep.js +64 -15
- package/src/builtins/@ori.js +1 -1
- package/src/builtins/@parse/json.js +4 -1
- package/src/builtins/@parse/yaml.js +4 -2
- package/src/builtins/@perf.js +1 -1
- package/src/builtins/@rss.js +8 -4
- package/src/builtins/@slug.js +15 -0
- package/src/builtins/map.d.ts +5 -6
- package/src/common/ExplorableSiteTransform.js +16 -10
- package/src/common/documentObject.js +18 -9
- package/src/common/utilities.js +5 -2
- package/src/misc/OriCommandTransform.js +2 -7
- package/src/server/constructResponse.js +3 -9
- package/src/server/server.js +5 -2
package/exports/exports.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// This file is generated by running buildExports.js -- do not edit by hand.
|
|
2
|
+
export { default as addNextPrevious } from "../src/builtins/@addNextPrevious.js";
|
|
2
3
|
export { default as apply } from "../src/builtins/@apply.js";
|
|
3
4
|
export { default as arrows } from "../src/builtins/@arrows.js";
|
|
4
5
|
export { default as basename } from "../src/builtins/@basename.js";
|
|
@@ -38,6 +39,7 @@ export { default as js } from "../src/builtins/@js.js";
|
|
|
38
39
|
export { default as json } from "../src/builtins/@json.js";
|
|
39
40
|
export { default as keys } from "../src/builtins/@keys.js";
|
|
40
41
|
export { default as keysJson } from "../src/builtins/@keysJson.js";
|
|
42
|
+
export { default as makeParser } from "../src/builtins/@makeParser.js";
|
|
41
43
|
export { default as map } from "../src/builtins/@map.js";
|
|
42
44
|
export { default as mapDeep } from "../src/builtins/@mapDeep.js";
|
|
43
45
|
export { default as match } from "../src/builtins/@match.js";
|
|
@@ -72,6 +74,7 @@ export { default as setDeep } from "../src/builtins/@setDeep.js";
|
|
|
72
74
|
export { default as shell } from "../src/builtins/@shell.js";
|
|
73
75
|
export { default as shuffle } from "../src/builtins/@shuffle.js";
|
|
74
76
|
export { default as sitemap } from "../src/builtins/@sitemap.js";
|
|
77
|
+
export { default as slug } from "../src/builtins/@slug.js";
|
|
75
78
|
export { default as sort } from "../src/builtins/@sort.js";
|
|
76
79
|
export { default as sortBy } from "../src/builtins/@sortBy.js";
|
|
77
80
|
export { default as static } from "../src/builtins/@static.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.
|
|
3
|
+
"version": "0.0.49",
|
|
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.
|
|
17
|
-
"typescript": "5.4.
|
|
16
|
+
"@types/node": "20.12.7",
|
|
17
|
+
"typescript": "5.4.5"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@weborigami/async-tree": "0.0.
|
|
21
|
-
"@weborigami/language": "0.0.
|
|
22
|
-
"@weborigami/types": "0.0.
|
|
20
|
+
"@weborigami/async-tree": "0.0.49",
|
|
21
|
+
"@weborigami/language": "0.0.49",
|
|
22
|
+
"@weborigami/types": "0.0.49",
|
|
23
23
|
"exif-parser": "0.1.12",
|
|
24
|
-
"graphviz-wasm": "3.0.
|
|
24
|
+
"graphviz-wasm": "3.0.2",
|
|
25
25
|
"highlight.js": "11.9.0",
|
|
26
|
-
"marked": "12.0.
|
|
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.
|
|
31
|
-
"yaml": "2.4.
|
|
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,47 @@
|
|
|
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 sequence(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 (Tree.isTreelike(value)) {
|
|
21
|
+
value = await Tree.plain(value);
|
|
22
|
+
} else if (typeof value === "object") {
|
|
23
|
+
// Clone value to avoid modifying the original object.
|
|
24
|
+
value = { ...value };
|
|
25
|
+
} else {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (keys === undefined) {
|
|
30
|
+
keys = Array.from(await tree.keys());
|
|
31
|
+
}
|
|
32
|
+
const index = keys.indexOf(key);
|
|
33
|
+
if (index === -1) {
|
|
34
|
+
// Key is supported but not published in `keys`
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Extend value with nextKey/previousKey properties.
|
|
39
|
+
return Object.assign(value, {
|
|
40
|
+
nextKey: keys[index + 1],
|
|
41
|
+
previousKey: keys[index - 1],
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
writable: true,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
package/src/builtins/@debug.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { 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
|
|
41
|
-
//
|
|
42
|
-
if (
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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(
|
|
68
|
-
tree = transformObject(
|
|
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
|
}
|
|
@@ -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
|
}
|
package/src/builtins/@inline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isUnpackable, symbols } from "@weborigami/async-tree";
|
|
1
|
+
import { ObjectTree, isUnpackable, symbols } from "@weborigami/async-tree";
|
|
2
2
|
import { compile } from "@weborigami/language";
|
|
3
3
|
import documentObject from "../common/documentObject.js";
|
|
4
4
|
import { toString } from "../common/utilities.js";
|
|
@@ -24,7 +24,13 @@ export default async function inline(input) {
|
|
|
24
24
|
}
|
|
25
25
|
const inputIsDocument = input["@text"] !== undefined;
|
|
26
26
|
const origami = inputIsDocument ? input["@text"] : toString(input);
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
let parent = /** @type {any} */ (input).parent ?? input[symbols.parent];
|
|
29
|
+
if (!parent) {
|
|
30
|
+
// Construct a temporary parent that has the right scope.
|
|
31
|
+
parent = new ObjectTree({});
|
|
32
|
+
parent.scope = this;
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
// If the input document is a plain object, include it in scope for the
|
|
30
36
|
// evaluated expression.
|
package/src/builtins/@map.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cachedKeyFunctions,
|
|
3
|
+
isPlainObject,
|
|
3
4
|
keyFunctionsForExtensions,
|
|
4
5
|
map,
|
|
5
6
|
} from "@weborigami/async-tree";
|
|
6
7
|
import { Scope } from "@weborigami/language";
|
|
7
8
|
import addValueKeyToScope from "../common/addValueKeyToScope.js";
|
|
8
9
|
import { toFunction } from "../common/utilities.js";
|
|
10
|
+
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Map a hierarchical tree of keys and values to a new tree of keys and values.
|
|
@@ -14,38 +16,41 @@ import { toFunction } from "../common/utilities.js";
|
|
|
14
16
|
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
15
17
|
* @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
|
|
16
18
|
* @typedef {import("@weborigami/async-tree").TreeTransform} TreeTransform
|
|
17
|
-
* @typedef {import("../../index.ts").TreelikeTransform} TreelikeTransform
|
|
18
19
|
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
19
20
|
*
|
|
20
21
|
* @typedef {{ deep?: boolean, description?: string, extension?: string,
|
|
21
22
|
* extensions?: string, inverseKey?: KeyFn, key?: ValueKeyFn, keyMap?:
|
|
22
|
-
* ValueKeyFn,
|
|
23
|
+
* ValueKeyFn, needsSourceValue?: boolean, value?: ValueKeyFn, valueMap?:
|
|
24
|
+
* ValueKeyFn }} MapOptionsDictionary
|
|
23
25
|
*
|
|
24
|
-
* @
|
|
26
|
+
* @typedef {ValueKeyFn|MapOptionsDictionary} OptionsOrValueFn
|
|
25
27
|
*
|
|
26
28
|
* @overload
|
|
27
|
-
* @param {
|
|
28
|
-
* @
|
|
29
|
-
*
|
|
30
|
-
* @overload
|
|
31
|
-
* @param {TreeMapOptions} param1
|
|
32
|
-
* @returns {TreelikeTransform}
|
|
33
|
-
*
|
|
34
|
-
* @overload
|
|
35
|
-
* @param {Treelike} param1
|
|
36
|
-
* @param {ValueKeyFn} param2
|
|
29
|
+
* @param {Treelike} source
|
|
30
|
+
* @param {OptionsOrValueFn} instructions
|
|
37
31
|
* @returns {AsyncTree}
|
|
38
32
|
*
|
|
39
33
|
* @overload
|
|
40
|
-
* @param {
|
|
41
|
-
* @
|
|
42
|
-
*
|
|
34
|
+
* @param {OptionsOrValueFn} instructions
|
|
35
|
+
* @returns {TreeTransform}
|
|
36
|
+
*
|
|
37
|
+
* @this {AsyncTree|null}
|
|
38
|
+
* @param {Treelike|OptionsOrValueFn} param1
|
|
39
|
+
* @param {OptionsOrValueFn} [param2]
|
|
43
40
|
*/
|
|
44
41
|
export default function treeMap(param1, param2) {
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
assertScopeIsDefined(this, "map");
|
|
43
|
+
|
|
44
|
+
// Identify whether the map instructions are in the first parameter or the
|
|
45
|
+
// second. If present, identify the function to apply to values.
|
|
46
|
+
|
|
47
|
+
/** @type {Treelike|undefined} */
|
|
47
48
|
let source;
|
|
48
|
-
|
|
49
|
+
/** @type {OptionsOrValueFn} */
|
|
50
|
+
let instructions;
|
|
51
|
+
/** @type {ValueKeyFn|undefined} */
|
|
52
|
+
let valueFn;
|
|
53
|
+
|
|
49
54
|
if (arguments.length === 0) {
|
|
50
55
|
throw new TypeError(
|
|
51
56
|
`@map: You must give @map a function or a dictionary of options.`
|
|
@@ -53,29 +58,35 @@ export default function treeMap(param1, param2) {
|
|
|
53
58
|
} else if (!param1) {
|
|
54
59
|
throw new TypeError(`@map: The first argument was undefined.`);
|
|
55
60
|
} else if (arguments.length === 1) {
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
// One argument
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
instructions = param1;
|
|
64
|
+
} else if (param2 === undefined) {
|
|
58
65
|
throw new TypeError(`@map: The second argument was undefined.`);
|
|
59
66
|
} else {
|
|
67
|
+
// Two arguments
|
|
60
68
|
source = param1;
|
|
61
|
-
|
|
69
|
+
instructions = param2;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
// Identify whether the
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
// Identify whether the map instructions take the form of a value function or
|
|
73
|
+
// a dictionary of options.
|
|
74
|
+
/** @type {MapOptionsDictionary} */
|
|
75
|
+
let options;
|
|
76
|
+
if (isPlainObject(instructions)) {
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
options = instructions;
|
|
79
|
+
valueFn = options?.value ?? options?.valueMap;
|
|
80
|
+
} else if (
|
|
81
|
+
typeof instructions === "function" ||
|
|
82
|
+
typeof (/** @type {any} */ (instructions)?.unpack) === "function"
|
|
70
83
|
) {
|
|
71
|
-
valueFn =
|
|
84
|
+
valueFn = instructions;
|
|
72
85
|
options = {};
|
|
73
|
-
} else
|
|
86
|
+
} else {
|
|
74
87
|
throw new TypeError(
|
|
75
88
|
`@map: You must specify a value function or options dictionary.`
|
|
76
89
|
);
|
|
77
|
-
} else {
|
|
78
|
-
valueFn = options.value ?? options.valueMap;
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
let { deep, description, inverseKey, needsSourceValue } = options;
|
|
@@ -131,8 +142,8 @@ export default function treeMap(param1, param2) {
|
|
|
131
142
|
extendedInverseKeyFn = keyFns.inverseKey;
|
|
132
143
|
} else {
|
|
133
144
|
// Use sidecar keyFn/inverseKey functions if the valueFn defines them.
|
|
134
|
-
extendedKeyFn = valueFn?.key;
|
|
135
|
-
extendedInverseKeyFn = valueFn?.inverseKey;
|
|
145
|
+
extendedKeyFn = /** @type {any} */ (valueFn)?.key;
|
|
146
|
+
extendedInverseKeyFn = /** @type {any} */ (valueFn)?.inverseKey;
|
|
136
147
|
}
|
|
137
148
|
|
|
138
149
|
const transform = function mapTreelike(treelike) {
|
package/src/builtins/@mapDeep.js
CHANGED
|
@@ -1,22 +1,71 @@
|
|
|
1
1
|
import { isPlainObject } from "@weborigami/async-tree";
|
|
2
|
+
import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
2
3
|
import treeMap from "./@map.js";
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Map a hierarchical tree of keys and values to a new tree of keys and values.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {import("@weborigami/async-tree").KeyFn} KeyFn
|
|
9
|
+
* @typedef {import("@weborigami/async-tree").Treelike} Treelike
|
|
10
|
+
* @typedef {import("@weborigami/async-tree").ValueKeyFn} ValueKeyFn
|
|
11
|
+
* @typedef {import("@weborigami/async-tree").TreeTransform} TreeTransform
|
|
12
|
+
* @typedef {import("@weborigami/types").AsyncTree} AsyncTree
|
|
13
|
+
*
|
|
14
|
+
* @typedef {{ deep?: boolean, description?: string, extension?: string,
|
|
15
|
+
* extensions?: string, inverseKey?: KeyFn, key?: ValueKeyFn, keyMap?:
|
|
16
|
+
* ValueKeyFn, needsSourceValue?: boolean, value?: ValueKeyFn, valueMap?:
|
|
17
|
+
* ValueKeyFn }} MapOptionsDictionary
|
|
18
|
+
*
|
|
19
|
+
* @typedef {ValueKeyFn|MapOptionsDictionary} OptionsOrValueFn
|
|
20
|
+
*
|
|
21
|
+
* @overload
|
|
22
|
+
* @param {Treelike} source
|
|
23
|
+
* @param {OptionsOrValueFn} instructions
|
|
24
|
+
* @returns {AsyncTree}
|
|
25
|
+
*
|
|
26
|
+
* @overload
|
|
27
|
+
* @param {OptionsOrValueFn} instructions
|
|
28
|
+
* @returns {TreeTransform}
|
|
29
|
+
*
|
|
30
|
+
* @this {AsyncTree|null}
|
|
31
|
+
* @param {Treelike|OptionsOrValueFn} param1
|
|
32
|
+
* @param {OptionsOrValueFn} [param2]
|
|
33
|
+
*/
|
|
4
34
|
export default function mapDeep(param1, param2) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (
|
|
10
|
-
|
|
35
|
+
assertScopeIsDefined(this, "mapDeep");
|
|
36
|
+
|
|
37
|
+
// Identify whether the map instructions are the first parameter or the
|
|
38
|
+
// second.
|
|
39
|
+
if (arguments.length === 1) {
|
|
40
|
+
// One argument, which is a dictionary or function.
|
|
41
|
+
/** @type {MapOptionsDictionary} */
|
|
42
|
+
const options = isPlainObject(param1)
|
|
43
|
+
? // Dictionary
|
|
44
|
+
{ ...param1, deep: true }
|
|
45
|
+
: // Function
|
|
46
|
+
{ deep: true, value: param1 };
|
|
47
|
+
const transform = treeMap.call(this, options);
|
|
48
|
+
return transform;
|
|
11
49
|
} else {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
50
|
+
// Two arguments, second is a dictionary or function.
|
|
51
|
+
/** @type {Treelike} */
|
|
52
|
+
const source = param1;
|
|
53
|
+
/** @type {MapOptionsDictionary} */
|
|
54
|
+
const options = isPlainObject(param2)
|
|
55
|
+
? // Dictionary
|
|
56
|
+
{ ...param2, deep: true }
|
|
57
|
+
: // Function
|
|
58
|
+
{ deep: true, value: param2 };
|
|
19
59
|
|
|
20
|
-
|
|
21
|
-
|
|
60
|
+
// We go through some type gymnastics to convince TypeScript that the return
|
|
61
|
+
// value is an AsyncTree. Using `.call()` with the overloaded `@map`
|
|
62
|
+
// function seems to confuse TypeScript into thinking the call will return a
|
|
63
|
+
// TreeTransform.
|
|
64
|
+
|
|
65
|
+
/** @type {AsyncTree} */
|
|
66
|
+
let tree;
|
|
67
|
+
// @ts-ignore
|
|
68
|
+
tree = treeMap.call(this, source, options);
|
|
69
|
+
return tree;
|
|
70
|
+
}
|
|
22
71
|
}
|
package/src/builtins/@ori.js
CHANGED
|
@@ -62,7 +62,7 @@ async function formatResult(result) {
|
|
|
62
62
|
} else if (
|
|
63
63
|
!(result instanceof Array) &&
|
|
64
64
|
(typeof result !== "object" ||
|
|
65
|
-
result.toString !== getRealmObjectPrototype(result)
|
|
65
|
+
result.toString !== getRealmObjectPrototype(result)?.toString)
|
|
66
66
|
) {
|
|
67
67
|
text = result.toString();
|
|
68
68
|
} else if (typeof result === "object") {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as serialize from "../../common/serialize.js";
|
|
2
|
+
import { toString } from "../../common/utilities.js";
|
|
2
3
|
|
|
3
|
-
export default async function parseYaml(
|
|
4
|
-
|
|
4
|
+
export default async function parseYaml(input) {
|
|
5
|
+
const text = toString(input);
|
|
6
|
+
return text ? serialize.parseYaml(text) : undefined;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
9
|
parseYaml.usage = `parseYaml <text>\tParse text as YAML (including JSON)`;
|
package/src/builtins/@perf.js
CHANGED
|
@@ -7,7 +7,7 @@ import assertScopeIsDefined from "../misc/assertScopeIsDefined.js";
|
|
|
7
7
|
* @this {import("@weborigami/types").AsyncTree|null}
|
|
8
8
|
* @param {Function} fn
|
|
9
9
|
*/
|
|
10
|
-
export default async function perf(fn, count =
|
|
10
|
+
export default async function perf(fn, count = 1) {
|
|
11
11
|
assertScopeIsDefined(this, "perf");
|
|
12
12
|
const start = performance.now();
|
|
13
13
|
for (let i = 0; i < count; i++) {
|
package/src/builtins/@rss.js
CHANGED
|
@@ -24,7 +24,7 @@ export default async function rss(jsonFeedTree) {
|
|
|
24
24
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
|
25
25
|
<channel>
|
|
26
26
|
<atom:link href="${rssUrl}" rel="self" type="application/rss+xml"/>
|
|
27
|
-
<title
|
|
27
|
+
<title><![CDATA[${title}]]></title>
|
|
28
28
|
<link>${home_page_url}</link>
|
|
29
29
|
<description>${description}</description>
|
|
30
30
|
${itemsRss}</channel>
|
|
@@ -33,15 +33,19 @@ ${itemsRss}</channel>
|
|
|
33
33
|
|
|
34
34
|
function itemRss(jsonFeedItem) {
|
|
35
35
|
const { content_html, date_published, id, title, url } = jsonFeedItem;
|
|
36
|
-
// RSS wants dates in RFC-822.
|
|
37
|
-
const date =
|
|
36
|
+
// RSS wants dates in RFC-822, essentially the same as ISO 8601.
|
|
37
|
+
const date =
|
|
38
|
+
date_published instanceof Date
|
|
39
|
+
? date_published.toUTCString()
|
|
40
|
+
: date_published;
|
|
38
41
|
const dateElement = date ? ` <pubDate>${date}</pubDate>\n` : "";
|
|
39
42
|
const guidElement = id ? ` <guid>${id}</guid>\n` : "";
|
|
40
43
|
const contentElement = content_html
|
|
41
44
|
? ` <description><![CDATA[${content_html}]]></description>\n`
|
|
42
45
|
: "";
|
|
46
|
+
const titleElement = `<![CDATA[${title}]]>`;
|
|
43
47
|
return ` <item>
|
|
44
|
-
${dateElement} <title>${
|
|
48
|
+
${dateElement} <title>${titleElement}</title>
|
|
45
49
|
<link>${url}</link>
|
|
46
50
|
${guidElement}${contentElement} </item>
|
|
47
51
|
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default function slug(filename) {
|
|
2
|
+
let slug = filename.toLowerCase();
|
|
3
|
+
|
|
4
|
+
// Convert spaces to dashes
|
|
5
|
+
slug = slug.replace(/\s+/g, "-");
|
|
6
|
+
|
|
7
|
+
// Remove special characters except dashes, letters, numbers, and periods.
|
|
8
|
+
slug = slug.replace(/[^\w\-\.]/g, "");
|
|
9
|
+
|
|
10
|
+
// Trim leading or trailing dashes.
|
|
11
|
+
slug = slug.replace(/^-+/, "");
|
|
12
|
+
slug = slug.replace(/-+$/, "");
|
|
13
|
+
|
|
14
|
+
return slug;
|
|
15
|
+
}
|
package/src/builtins/map.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { KeyFn, Treelike, ValueKeyFn } from "@weborigami/async-tree";
|
|
1
|
+
import { KeyFn, Treelike, TreeTransform, ValueKeyFn } from "@weborigami/async-tree";
|
|
2
2
|
import { AsyncTree } from "@weborigami/types";
|
|
3
|
-
import { TreelikeTransform } from "../../index.ts";
|
|
4
3
|
|
|
5
4
|
type TreeMapOptions = {
|
|
6
5
|
deep?: boolean;
|
|
@@ -8,10 +7,10 @@ type TreeMapOptions = {
|
|
|
8
7
|
extensions?: string;
|
|
9
8
|
inverseKey?: KeyFn;
|
|
10
9
|
key?: ValueKeyFn;
|
|
10
|
+
needsSourceValue?: boolean;
|
|
11
11
|
value?: ValueKeyFn;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export default function treeMap(
|
|
15
|
-
export default function treeMap(options: TreeMapOptions):
|
|
16
|
-
export default function treeMap(
|
|
17
|
-
export default function treeMap(treelike: Treelike, options: TreeMapOptions): AsyncTree;
|
|
14
|
+
export default function treeMap(options: ValueKeyFn | TreeMapOptions): TreeTransform;
|
|
15
|
+
export default function treeMap(treelike: Treelike, options: ValueKeyFn | TreeMapOptions): AsyncTree;
|
|
16
|
+
export default function treeMap(param1: Treelike | ValueKeyFn | TreeMapOptions, param2?: ValueKeyFn | TreeMapOptions): AsyncTree | TreeTransform;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Tree, keysJson } from "@weborigami/async-tree";
|
|
2
2
|
import { Scope } from "@weborigami/language";
|
|
3
3
|
import index from "../builtins/@index.js";
|
|
4
|
-
import { transformObject } from "../common/utilities.js";
|
|
4
|
+
import { isTransformApplied, transformObject } from "../common/utilities.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Wraps a tree (typically a SiteTree) to turn a standard site into an
|
|
@@ -33,10 +33,10 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
33
33
|
|
|
34
34
|
// Ask the tree if it has the key.
|
|
35
35
|
let value = await super.get(key);
|
|
36
|
+
const scope = Scope.getScope(this);
|
|
36
37
|
|
|
37
38
|
if (value === undefined) {
|
|
38
39
|
// The tree doesn't have the key; try the defaults.
|
|
39
|
-
const scope = Scope.getScope(this);
|
|
40
40
|
if (scope) {
|
|
41
41
|
if (key === "index.html") {
|
|
42
42
|
value = await index.call(scope, this);
|
|
@@ -46,15 +46,19 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// Ensure this transform is applied to any explorable result. This lets
|
|
50
|
-
// the user browse into data and explorable trees of types other than the
|
|
51
|
-
// current class.
|
|
52
49
|
if (Tree.isAsyncTree(value)) {
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// Ensure this transform is applied to any tree result so the user
|
|
51
|
+
// browse into data and trees of classes other than the current class.
|
|
52
|
+
if (!isTransformApplied(ExplorableSiteTransform, value)) {
|
|
53
|
+
value = transformObject(ExplorableSiteTransform, value);
|
|
54
|
+
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
if (key.endsWith?.("/")) {
|
|
57
|
+
// Instead of return the tree directly, return an index for it.
|
|
58
|
+
value = await index.call(scope, value);
|
|
59
|
+
}
|
|
60
|
+
} else if (value?.unpack) {
|
|
61
|
+
// If the value isn't a tree, but has a tree attached via an `unpack`
|
|
58
62
|
// method, wrap the unpack method to add this transform.
|
|
59
63
|
const original = value.unpack.bind(value);
|
|
60
64
|
value.unpack = async () => {
|
|
@@ -68,7 +72,9 @@ export default function ExplorableSiteTransform(Base) {
|
|
|
68
72
|
const scope = Scope.getScope(this);
|
|
69
73
|
tree = Scope.treeWithScope(tree, scope);
|
|
70
74
|
}
|
|
71
|
-
|
|
75
|
+
if (!isTransformApplied(ExplorableSiteTransform, tree)) {
|
|
76
|
+
tree = transformObject(ExplorableSiteTransform, tree);
|
|
77
|
+
}
|
|
72
78
|
return tree;
|
|
73
79
|
};
|
|
74
80
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isPlainObject } from "@weborigami/async-tree";
|
|
2
|
-
import txtHandler from "../builtins/txt_handler.js";
|
|
1
|
+
import { isPlainObject, isUnpackable } from "@weborigami/async-tree";
|
|
2
|
+
// import txtHandler from "../builtins/txt_handler.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* In Origami, a text document object is any object with a `@text` property and
|
|
@@ -12,9 +12,15 @@ import txtHandler from "../builtins/txt_handler.js";
|
|
|
12
12
|
* @param {StringLike|PlainObject} input
|
|
13
13
|
* @param {any} [data]
|
|
14
14
|
*/
|
|
15
|
-
export default function documentObject(input, data) {
|
|
15
|
+
export default async function documentObject(input, data) {
|
|
16
16
|
let text;
|
|
17
17
|
let inputData;
|
|
18
|
+
|
|
19
|
+
if (isUnpackable(input)) {
|
|
20
|
+
// Unpack the input first, might already be a document object.
|
|
21
|
+
input = await input.unpack();
|
|
22
|
+
}
|
|
23
|
+
|
|
18
24
|
if (isPlainObject(input)) {
|
|
19
25
|
text = input["@text"];
|
|
20
26
|
inputData = input;
|
|
@@ -22,12 +28,15 @@ export default function documentObject(input, data) {
|
|
|
22
28
|
text = String(input);
|
|
23
29
|
inputData = null;
|
|
24
30
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
// TODO: Either restore this code, or move responsibility for packing a
|
|
32
|
+
// document to HandleExtensionsTransform set().
|
|
33
|
+
// const base = {
|
|
34
|
+
// pack() {
|
|
35
|
+
// return txtHandler.pack(this);
|
|
36
|
+
// },
|
|
37
|
+
// };
|
|
38
|
+
// const result = Object.create(base);
|
|
39
|
+
const result = {};
|
|
31
40
|
Object.assign(result, inputData, data, { "@text": text });
|
|
32
41
|
return result;
|
|
33
42
|
}
|
package/src/common/utilities.js
CHANGED
|
@@ -45,10 +45,13 @@ export const keySymbol = Symbol("key");
|
|
|
45
45
|
* @param {string} resultExtension
|
|
46
46
|
*/
|
|
47
47
|
export function replaceExtension(key, sourceExtension, resultExtension) {
|
|
48
|
-
if (!key
|
|
48
|
+
if (!key) {
|
|
49
|
+
return undefined;
|
|
50
|
+
} else if (!key.endsWith(sourceExtension)) {
|
|
49
51
|
return key;
|
|
52
|
+
} else {
|
|
53
|
+
return key.slice(0, -sourceExtension.length) + resultExtension;
|
|
50
54
|
}
|
|
51
|
-
return key.slice(0, -sourceExtension.length) + resultExtension;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @typedef {import("@weborigami/types").AsyncTree} AsyncTree */
|
|
2
|
-
import { ObjectTree
|
|
2
|
+
import { ObjectTree } from "@weborigami/async-tree";
|
|
3
3
|
import { Scope } from "@weborigami/language";
|
|
4
4
|
import ori from "../builtins/@ori.js";
|
|
5
|
-
import { keySymbol
|
|
5
|
+
import { keySymbol } from "../common/utilities.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Add support for commands prefixed with `!`.
|
|
@@ -34,11 +34,6 @@ export default function OriCommandTransform(Base) {
|
|
|
34
34
|
const extendedScope = new Scope(ambientsTree, Scope.getScope(this));
|
|
35
35
|
const source = key.slice(1).trim();
|
|
36
36
|
value = await ori.call(extendedScope, source, { formatResult: false });
|
|
37
|
-
|
|
38
|
-
// Ensure this transform is applied to any subtree.
|
|
39
|
-
if (Tree.isAsyncTree(value)) {
|
|
40
|
-
value = transformObject(OriCommandTransform, value);
|
|
41
|
-
}
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
return value;
|
|
@@ -25,7 +25,7 @@ export default async function constructResponse(request, resource) {
|
|
|
25
25
|
if (resource instanceof Response) {
|
|
26
26
|
// Already a Response, return as is.
|
|
27
27
|
return resource;
|
|
28
|
-
} else if (
|
|
28
|
+
} else if (resource == null) {
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -42,14 +42,8 @@ export default async function constructResponse(request, resource) {
|
|
|
42
42
|
mediaType = extension ? mediaTypeForExtension[extension] : undefined;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
!url.pathname.endsWith("/") &&
|
|
48
|
-
(Tree.isAsyncTree(resource) ||
|
|
49
|
-
isPlainObject(resource) ||
|
|
50
|
-
resource instanceof Array)
|
|
51
|
-
) {
|
|
52
|
-
// Redirect to an index page for the result.
|
|
45
|
+
if (!url.pathname.endsWith("/") && Tree.isTreelike(resource)) {
|
|
46
|
+
// Treelike resource: redirect to its index page.
|
|
53
47
|
const Location = `${request.url}/`;
|
|
54
48
|
return new Response("ok", {
|
|
55
49
|
headers: {
|
package/src/server/server.js
CHANGED
|
@@ -120,8 +120,11 @@ function keysFromUrl(url) {
|
|
|
120
120
|
pathKeys[pathKeys.length - 1] = "index.html";
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
//
|
|
124
|
-
|
|
123
|
+
// Decode the text of the commands, prefix spaces with a backslash, and add
|
|
124
|
+
// back the `!` character.
|
|
125
|
+
const commandKeys = parts.map(
|
|
126
|
+
(command) => `!${decodeURIComponent(command).replace(/ /g, "\\ ")}`
|
|
127
|
+
);
|
|
125
128
|
|
|
126
129
|
const keys = [...pathKeys, ...commandKeys];
|
|
127
130
|
return keys;
|