@weborigami/origami 0.2.2 → 0.2.4
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/package.json +12 -12
- package/src/common/processUnpackedContent.js +2 -3
- package/src/common/serialize.d.ts +1 -1
- package/src/common/serialize.js +1 -1
- package/src/handlers/oridocument.handler.js +101 -16
- package/src/handlers/parseFrontMatter.js +21 -0
- package/src/handlers/txt.handler.js +16 -10
- package/src/help/help.js +3 -3
- package/src/origami/once.js +18 -2
- package/src/text/inline.js +20 -8
- package/src/text/mdHtml.js +1 -0
- package/src/text/text.js +1 -1
- package/src/tree/fromFn.js +4 -1
- package/src/tree/map.d.ts +1 -3
- package/src/tree/map.js +44 -26
- package/src/text/indent.js +0 -115
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/origami",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Web Origami language, CLI, framework, and server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
"main": "./main.js",
|
|
14
14
|
"types": "./index.ts",
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@types/node": "22.
|
|
17
|
-
"typescript": "5.
|
|
16
|
+
"@types/node": "22.10.2",
|
|
17
|
+
"typescript": "5.7.2"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@weborigami/async-tree": "0.2.
|
|
21
|
-
"@weborigami/language": "0.2.
|
|
22
|
-
"@weborigami/types": "0.2.
|
|
20
|
+
"@weborigami/async-tree": "0.2.4",
|
|
21
|
+
"@weborigami/language": "0.2.4",
|
|
22
|
+
"@weborigami/types": "0.2.4",
|
|
23
23
|
"exif-parser": "0.1.12",
|
|
24
24
|
"graphviz-wasm": "3.0.2",
|
|
25
|
-
"highlight.js": "11.
|
|
26
|
-
"marked": "
|
|
27
|
-
"marked-gfm-heading-id": "4.1.
|
|
28
|
-
"marked-highlight": "2.1
|
|
29
|
-
"marked-smartypants": "1.1.
|
|
25
|
+
"highlight.js": "11.11.0",
|
|
26
|
+
"marked": "15.0.4",
|
|
27
|
+
"marked-gfm-heading-id": "4.1.1",
|
|
28
|
+
"marked-highlight": "2.2.1",
|
|
29
|
+
"marked-smartypants": "1.1.9",
|
|
30
30
|
"sharp": "0.33.5",
|
|
31
|
-
"yaml": "2.
|
|
31
|
+
"yaml": "2.6.1"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"test": "node --test --test-reporter=spec",
|
|
@@ -16,9 +16,8 @@ export default function processUnpackedContent(content, parent) {
|
|
|
16
16
|
// Bind the function to the parent as the `this` context.
|
|
17
17
|
const target = parent ?? builtinsTree;
|
|
18
18
|
const result = content.bind(target);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
19
|
+
// Copy over any properties that were attached to the function
|
|
20
|
+
Object.assign(result, content);
|
|
22
21
|
return result;
|
|
23
22
|
} else if (Tree.isAsyncTree(content) && !content.parent) {
|
|
24
23
|
const result = Object.create(content);
|
|
@@ -2,6 +2,6 @@ import type { AsyncTree } from "@weborigami/types";
|
|
|
2
2
|
import type { JsonValue } from "../../index.ts";
|
|
3
3
|
|
|
4
4
|
export function evaluateYaml(text: string, parent?: AsyncTree|null): Promise<JsonValue>;
|
|
5
|
-
export function parseYaml(text: string): JsonValue
|
|
5
|
+
export function parseYaml(text: string): JsonValue;
|
|
6
6
|
export function toJson(obj: JsonValue | AsyncTree): Promise<string>;
|
|
7
7
|
export function toYaml(obj: JsonValue | AsyncTree): Promise<string>;
|
package/src/common/serialize.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
extension,
|
|
3
|
+
ObjectTree,
|
|
4
|
+
symbols,
|
|
5
|
+
trailingSlash,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
2
7
|
import { compile } from "@weborigami/language";
|
|
3
|
-
import
|
|
8
|
+
import { parseYaml } from "../common/serialize.js";
|
|
9
|
+
import { toString } from "../common/utilities.js";
|
|
4
10
|
import { processUnpackedContent } from "../internal.js";
|
|
11
|
+
import parseFrontMatter from "./parseFrontMatter.js";
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* An Origami template document: a plain text file that contains Origami
|
|
@@ -15,29 +22,107 @@ export default {
|
|
|
15
22
|
const parent =
|
|
16
23
|
options.parent ??
|
|
17
24
|
/** @type {any} */ (packed).parent ??
|
|
18
|
-
/** @type {any} */ (packed)[symbols.parent]
|
|
25
|
+
/** @type {any} */ (packed)[symbols.parent] ??
|
|
26
|
+
null;
|
|
19
27
|
|
|
20
|
-
//
|
|
21
|
-
const
|
|
28
|
+
// Unpack as a text document
|
|
29
|
+
const unpacked = toString(packed);
|
|
30
|
+
|
|
31
|
+
// See if we can construct a URL to use in error messages
|
|
32
|
+
const key = options.key;
|
|
22
33
|
let url;
|
|
23
|
-
if (
|
|
34
|
+
if (key && parent?.url) {
|
|
24
35
|
let parentHref = parent.url.href;
|
|
25
36
|
if (!parentHref.endsWith("/")) {
|
|
26
37
|
parentHref += "/";
|
|
27
38
|
}
|
|
28
|
-
url = new URL(
|
|
39
|
+
url = new URL(key, parentHref);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Determine the data (if present) and text content
|
|
43
|
+
let text;
|
|
44
|
+
let frontData = null;
|
|
45
|
+
let frontSource = null;
|
|
46
|
+
let extendedParent = parent;
|
|
47
|
+
const parsed = parseFrontMatter(unpacked);
|
|
48
|
+
if (!parsed) {
|
|
49
|
+
text = unpacked;
|
|
50
|
+
} else {
|
|
51
|
+
const { body, frontText, isOrigami } = parsed;
|
|
52
|
+
if (isOrigami) {
|
|
53
|
+
// Origami front matter
|
|
54
|
+
frontSource = { name: key, text: frontText, url };
|
|
55
|
+
} else {
|
|
56
|
+
// YAML front matter
|
|
57
|
+
frontData = parseYaml(frontText);
|
|
58
|
+
if (typeof frontData !== "object") {
|
|
59
|
+
throw new TypeError(`YAML or JSON front matter must be an object`);
|
|
60
|
+
}
|
|
61
|
+
extendedParent = new ObjectTree(frontData);
|
|
62
|
+
extendedParent.parent = parent;
|
|
63
|
+
}
|
|
64
|
+
text = body;
|
|
29
65
|
}
|
|
30
66
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
name: options.key,
|
|
34
|
-
url,
|
|
35
|
-
};
|
|
67
|
+
// Construct an object to represent the source code
|
|
68
|
+
const bodySource = { name: key, text, url };
|
|
36
69
|
|
|
37
|
-
// Compile the
|
|
38
|
-
const
|
|
39
|
-
const
|
|
70
|
+
// Compile the source as an Origami template document
|
|
71
|
+
const scopeCaching = frontSource ? false : true;
|
|
72
|
+
const defineTemplateFn = compile.templateDocument(bodySource, {
|
|
73
|
+
scopeCaching,
|
|
74
|
+
});
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
// Determine the result of the template
|
|
77
|
+
let result;
|
|
78
|
+
if (frontSource) {
|
|
79
|
+
// Result is the evaluated front source
|
|
80
|
+
const frontFn = compile.expression(frontSource, {
|
|
81
|
+
macros: {
|
|
82
|
+
"@template": defineTemplateFn.code,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
result = await frontFn.call(parent);
|
|
86
|
+
} else {
|
|
87
|
+
const templateFn = await defineTemplateFn.call(extendedParent);
|
|
88
|
+
if (frontData) {
|
|
89
|
+
// Result is a function that adds the front data to the template result
|
|
90
|
+
result = async (input) => {
|
|
91
|
+
const text = await templateFn.call(extendedParent, input);
|
|
92
|
+
const object = {
|
|
93
|
+
...frontData,
|
|
94
|
+
"@text": text,
|
|
95
|
+
};
|
|
96
|
+
object[symbols.parent] = extendedParent;
|
|
97
|
+
return object;
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
// Result is a function that calls the body template
|
|
101
|
+
result = templateFn;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resultExtension = key ? extension.extname(key) : null;
|
|
106
|
+
if (resultExtension && Object.isExtensible(result)) {
|
|
107
|
+
// Add sidecar function so this template can be used in a map.
|
|
108
|
+
result.key = addExtension(resultExtension);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return processUnpackedContent(result, parent);
|
|
42
112
|
},
|
|
43
113
|
};
|
|
114
|
+
|
|
115
|
+
// Return a function that adds the given extension
|
|
116
|
+
function addExtension(resultExtension) {
|
|
117
|
+
return (sourceKey) => {
|
|
118
|
+
if (sourceKey === undefined) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const normalizedKey = trailingSlash.remove(sourceKey);
|
|
122
|
+
const sourceExtension = extension.extname(normalizedKey);
|
|
123
|
+
const resultKey = sourceExtension
|
|
124
|
+
? extension.replace(normalizedKey, sourceExtension, resultExtension)
|
|
125
|
+
: normalizedKey + resultExtension;
|
|
126
|
+
return resultKey;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default function parseFrontMatter(text) {
|
|
2
|
+
const regex =
|
|
3
|
+
/^(---\r?\n(?<frontText>[\s\S]*?\r?\n?)---\r?\n)(?<body>[\s\S]*$)/;
|
|
4
|
+
const match = regex.exec(text);
|
|
5
|
+
if (!match?.groups) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const isOrigami = detectOrigami(match.groups.frontText);
|
|
9
|
+
return {
|
|
10
|
+
body: match.groups.body,
|
|
11
|
+
frontText: match.groups.frontText,
|
|
12
|
+
isOrigami,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function detectOrigami(text) {
|
|
17
|
+
// Find first character that's not whitespace, alphanumeric, or underscore
|
|
18
|
+
const first = text.match(/[^A-Za-z0-9_ \t\n\r]/)?.[0];
|
|
19
|
+
const origamiMarkers = ["(", ".", "/", "{"];
|
|
20
|
+
return origamiMarkers.includes(first);
|
|
21
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isPacked, symbols } from "@weborigami/async-tree";
|
|
2
|
+
import { compile } from "@weborigami/language";
|
|
2
3
|
import { parseYaml, toYaml } from "../common/serialize.js";
|
|
3
|
-
import
|
|
4
|
+
import { toString } from "../common/utilities.js";
|
|
5
|
+
import parseFrontMatter from "./parseFrontMatter.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* A text file with possible front matter
|
|
@@ -51,21 +53,25 @@ export default {
|
|
|
51
53
|
},
|
|
52
54
|
|
|
53
55
|
/** @type {import("@weborigami/language").UnpackFunction} */
|
|
54
|
-
unpack(packed, options = {}) {
|
|
56
|
+
async unpack(packed, options = {}) {
|
|
55
57
|
const parent = options.parent ?? null;
|
|
56
|
-
const text =
|
|
58
|
+
const text = toString(packed);
|
|
57
59
|
if (text === null) {
|
|
58
|
-
throw new Error("Tried to treat
|
|
60
|
+
throw new Error("Tried to treat a file as text but it wasn't text.");
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
const
|
|
62
|
-
/^(---\r?\n(?<frontText>[\s\S]*?\r?\n?)---\r?\n)(?<body>[\s\S]*$)/;
|
|
63
|
-
const match = regex.exec(text);
|
|
63
|
+
const parsed = parseFrontMatter(text);
|
|
64
64
|
let unpacked;
|
|
65
|
-
if (
|
|
65
|
+
if (parsed) {
|
|
66
66
|
// Document object with front matter
|
|
67
|
-
const { body, frontText } =
|
|
68
|
-
|
|
67
|
+
const { body, frontText, isOrigami } = parsed;
|
|
68
|
+
let frontData;
|
|
69
|
+
if (isOrigami) {
|
|
70
|
+
const compiled = compile.expression(frontText.trim());
|
|
71
|
+
frontData = await compiled.call(parent);
|
|
72
|
+
} else {
|
|
73
|
+
frontData = parseYaml(frontText);
|
|
74
|
+
}
|
|
69
75
|
unpacked = Object.assign({}, frontData, { "@text": body });
|
|
70
76
|
} else {
|
|
71
77
|
// Plain text
|
package/src/help/help.js
CHANGED
|
@@ -50,7 +50,7 @@ async function commandDescription(commandHelp, namespace, command) {
|
|
|
50
50
|
"",
|
|
51
51
|
formatCommandDescription(commandHelp, namespace, command),
|
|
52
52
|
"",
|
|
53
|
-
`For more information: https://weborigami.org/builtins/${url}`,
|
|
53
|
+
`For more information: https://weborigami.org/builtins/${url}\n`,
|
|
54
54
|
];
|
|
55
55
|
return text.join("\n");
|
|
56
56
|
}
|
|
@@ -82,7 +82,7 @@ async function namespaceCommands(namespaceHelp, namespace) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const url = namespaceHelp.collection ? `${namespace}.html` : `${namespace}/`;
|
|
85
|
-
text.push(`For more information: https://weborigami.org/builtins/${url}`);
|
|
85
|
+
text.push(`For more information: https://weborigami.org/builtins/${url}\n`);
|
|
86
86
|
return text.join("\n");
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -97,7 +97,7 @@ async function namespaceDescriptions(helpData) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
text.push(
|
|
100
|
-
`\nType "ori help:<namespace>" for more or visit https://weborigami.org/builtins`
|
|
100
|
+
`\nType "ori help:<namespace>" for more or visit https://weborigami.org/builtins\n`
|
|
101
101
|
);
|
|
102
102
|
return text.join("\n");
|
|
103
103
|
}
|
package/src/origami/once.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
|
2
2
|
|
|
3
|
-
const fnPromiseMap = new
|
|
3
|
+
const fnPromiseMap = new Map();
|
|
4
|
+
const codePromiseMap = new Map();
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Evaluate the given function only once and cache the result.
|
|
@@ -11,8 +12,23 @@ const fnPromiseMap = new WeakMap();
|
|
|
11
12
|
*/
|
|
12
13
|
export default async function once(fn) {
|
|
13
14
|
assertTreeIsDefined(this, "origami:once");
|
|
15
|
+
|
|
16
|
+
const code = /** @type {any} */ (fn).code;
|
|
17
|
+
if (code) {
|
|
18
|
+
// Origami function, cache by code
|
|
19
|
+
if (!codePromiseMap.has(code)) {
|
|
20
|
+
// Don't wait for promise to resolve
|
|
21
|
+
const promise = fn.call(this);
|
|
22
|
+
codePromiseMap.set(code, promise);
|
|
23
|
+
}
|
|
24
|
+
return codePromiseMap.get(code);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Regular function, cache by function
|
|
14
28
|
if (!fnPromiseMap.has(fn)) {
|
|
15
|
-
|
|
29
|
+
// Don't wait for promise to resolve
|
|
30
|
+
const promise = fn.call(this);
|
|
31
|
+
fnPromiseMap.set(fn, promise);
|
|
16
32
|
}
|
|
17
33
|
return fnPromiseMap.get(fn);
|
|
18
34
|
}
|
package/src/text/inline.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
isUnpackable,
|
|
3
|
+
ObjectTree,
|
|
4
|
+
symbols,
|
|
5
|
+
toString,
|
|
6
|
+
} from "@weborigami/async-tree";
|
|
3
7
|
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
|
4
8
|
import documentObject from "../common/documentObject.js";
|
|
5
|
-
import {
|
|
6
|
-
import { oriHandler } from "../internal.js";
|
|
9
|
+
import { oridocumentHandler } from "../internal.js";
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Inline any Origami expressions found inside ${...} placeholders in the input
|
|
@@ -29,10 +32,19 @@ export default async function inline(input) {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const parent =
|
|
32
|
-
/** @type {any} */ (input).parent ??
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
/** @type {any} */ (input).parent ??
|
|
36
|
+
/** @type {any} */ (input)[symbols.parent] ??
|
|
37
|
+
this;
|
|
38
|
+
|
|
39
|
+
let extendedParent = parent;
|
|
40
|
+
if (inputIsDocument) {
|
|
41
|
+
extendedParent = new ObjectTree(input);
|
|
42
|
+
extendedParent.parent = parent;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
const templateFn = await oridocumentHandler.unpack(input, {
|
|
47
|
+
parent: extendedParent,
|
|
36
48
|
});
|
|
37
49
|
|
|
38
50
|
const inputData = inputIsDocument ? input : null;
|
package/src/text/mdHtml.js
CHANGED
package/src/text/text.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
+
export { taggedTemplateIndent as indent } from "@weborigami/language";
|
|
1
2
|
export { default as document } from "./document.js";
|
|
2
|
-
export { default as indent } from "./indent.js";
|
|
3
3
|
export { default as inline } from "./inline.js";
|
|
4
4
|
export { default as mdHtml } from "./mdHtml.js";
|
package/src/tree/fromFn.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FunctionTree } from "@weborigami/async-tree";
|
|
1
|
+
import { FunctionTree, isUnpackable } from "@weborigami/async-tree";
|
|
2
2
|
import assertTreeIsDefined from "../common/assertTreeIsDefined.js";
|
|
3
3
|
import { toFunction } from "../common/utilities.js";
|
|
4
4
|
|
|
@@ -20,6 +20,9 @@ export default async function fromFn(invocable, keys = []) {
|
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
const fn = toFunction(invocable);
|
|
23
|
+
if (isUnpackable(keys)) {
|
|
24
|
+
keys = await keys.unpack();
|
|
25
|
+
}
|
|
23
26
|
const tree = new FunctionTree(fn, keys);
|
|
24
27
|
tree.parent = this;
|
|
25
28
|
return tree;
|
package/src/tree/map.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TreeMapOptions as AsyncTreeMapOptions, Treelike,
|
|
1
|
+
import { TreeMapOptions as AsyncTreeMapOptions, Treelike, ValueKeyFn } from "@weborigami/async-tree";
|
|
2
2
|
import { AsyncTree } from "@weborigami/types";
|
|
3
3
|
|
|
4
4
|
/* Add more properties to TreeMapOptions */
|
|
@@ -8,6 +8,4 @@ type TreeMapOptions = AsyncTreeMapOptions &{
|
|
|
8
8
|
needsSourceValue?: boolean;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export default function treeMap(options: ValueKeyFn | TreeMapOptions): TreeTransform;
|
|
12
11
|
export default function treeMap(treelike: Treelike, options: ValueKeyFn | TreeMapOptions): AsyncTree;
|
|
13
|
-
export default function treeMap(param1: Treelike | ValueKeyFn | TreeMapOptions, param2?: ValueKeyFn | TreeMapOptions): AsyncTree | TreeTransform;
|
package/src/tree/map.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cachedKeyFunctions,
|
|
3
3
|
isPlainObject,
|
|
4
|
+
isUnpackable,
|
|
4
5
|
keyFunctionsForExtensions,
|
|
5
6
|
map as mapTransform,
|
|
6
7
|
} from "@weborigami/async-tree";
|
|
@@ -25,7 +26,11 @@ export default async function map(treelike, operation) {
|
|
|
25
26
|
// The tree in which the map operation happens
|
|
26
27
|
const context = this;
|
|
27
28
|
|
|
29
|
+
if (isUnpackable(operation)) {
|
|
30
|
+
operation = await operation.unpack();
|
|
31
|
+
}
|
|
28
32
|
const options = extendedOptions(context, operation);
|
|
33
|
+
|
|
29
34
|
const mapped = mapTransform(source, options);
|
|
30
35
|
mapped.parent = context;
|
|
31
36
|
return mapped;
|
|
@@ -66,8 +71,8 @@ function extendedOptions(context, operation) {
|
|
|
66
71
|
|
|
67
72
|
const { deep, extension, needsSourceValue } = options;
|
|
68
73
|
const description = options.description ?? `map ${extension ?? ""}`;
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
let keyFn = options.key;
|
|
75
|
+
let inverseKeyFn = options.inverseKey;
|
|
71
76
|
|
|
72
77
|
if (extension && (keyFn || inverseKeyFn)) {
|
|
73
78
|
throw new TypeError(
|
|
@@ -75,47 +80,60 @@ function extendedOptions(context, operation) {
|
|
|
75
80
|
);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
let extendedValueFn;
|
|
79
83
|
if (valueFn) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
// @ts-ignore
|
|
85
|
+
valueFn = toFunction(valueFn);
|
|
86
|
+
// By default, run the value function in the context of this tree so that
|
|
87
|
+
// Origami builtins can be used as value functions.
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
const bound = valueFn.bind(context);
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
Object.assign(bound, valueFn);
|
|
92
|
+
valueFn = bound;
|
|
83
93
|
}
|
|
84
94
|
|
|
85
|
-
// Extend the key functions to run in this tree.
|
|
86
|
-
let extendedKeyFn;
|
|
87
|
-
let extendedInverseKeyFn;
|
|
88
95
|
if (extension) {
|
|
96
|
+
// Generate key/inverseKey functions from the extension
|
|
89
97
|
let { resultExtension, sourceExtension } = parseExtensions(extension);
|
|
90
98
|
const keyFns = keyFunctionsForExtensions({
|
|
91
99
|
resultExtension,
|
|
92
100
|
sourceExtension,
|
|
93
101
|
});
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
keyFn = keyFns.key;
|
|
103
|
+
inverseKeyFn = keyFns.inverseKey;
|
|
96
104
|
} else if (keyFn) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const sourceValue = await sourceTree.get(sourceKey);
|
|
100
|
-
const resultKey = await resolvedKeyFn(sourceValue, sourceKey, sourceTree);
|
|
101
|
-
return resultKey;
|
|
102
|
-
}
|
|
103
|
-
const keyFns = cachedKeyFunctions(keyWithValueFn, deep);
|
|
104
|
-
extendedKeyFn = keyFns.key;
|
|
105
|
-
extendedInverseKeyFn = keyFns.inverseKey;
|
|
105
|
+
// Extend the key function to include a value parameter
|
|
106
|
+
keyFn = extendKeyFn(keyFn);
|
|
106
107
|
} else {
|
|
107
|
-
// Use sidecar
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
// Use sidecar key/inverseKey functions if the valueFn defines them
|
|
109
|
+
keyFn = /** @type {any} */ (valueFn)?.key;
|
|
110
|
+
inverseKeyFn = /** @type {any} */ (valueFn)?.inverseKey;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (keyFn && !inverseKeyFn) {
|
|
114
|
+
// Only keyFn was provided, so we need to generate the inverseKeyFn
|
|
115
|
+
const keyFns = cachedKeyFunctions(keyFn, deep);
|
|
116
|
+
keyFn = keyFns.key;
|
|
117
|
+
inverseKeyFn = keyFns.inverseKey;
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
return {
|
|
113
121
|
deep,
|
|
114
122
|
description,
|
|
115
|
-
inverseKey:
|
|
116
|
-
key:
|
|
123
|
+
inverseKey: inverseKeyFn,
|
|
124
|
+
key: keyFn,
|
|
117
125
|
needsSourceValue,
|
|
118
|
-
value:
|
|
126
|
+
value: valueFn,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Extend the key function to include a value parameter
|
|
131
|
+
function extendKeyFn(keyFn) {
|
|
132
|
+
keyFn = toFunction(keyFn);
|
|
133
|
+
return async function keyWithValueFn(sourceKey, sourceTree) {
|
|
134
|
+
const sourceValue = await sourceTree.get(sourceKey);
|
|
135
|
+
const resultKey = await keyFn(sourceValue, sourceKey, sourceTree);
|
|
136
|
+
return resultKey;
|
|
119
137
|
};
|
|
120
138
|
}
|
|
121
139
|
|
package/src/text/indent.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
const lastLineWhitespaceRegex = /\n(?<indent>[ \t]*)$/;
|
|
2
|
-
|
|
3
|
-
const mapStringsToModifications = new Map();
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Normalize indentation in a tagged template string.
|
|
7
|
-
*
|
|
8
|
-
* @param {TemplateStringsArray} strings
|
|
9
|
-
* @param {...any} values
|
|
10
|
-
* @returns {string}
|
|
11
|
-
*/
|
|
12
|
-
export default function indent(strings, ...values) {
|
|
13
|
-
let modified = mapStringsToModifications.get(strings);
|
|
14
|
-
if (!modified) {
|
|
15
|
-
modified = modifyStrings(strings);
|
|
16
|
-
mapStringsToModifications.set(strings, modified);
|
|
17
|
-
}
|
|
18
|
-
const { blockIndentations, strings: modifiedStrings } = modified;
|
|
19
|
-
return joinBlocks(modifiedStrings, values, blockIndentations);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Join strings and values, applying the given block indentation to the lines of
|
|
23
|
-
// values for block placholders.
|
|
24
|
-
function joinBlocks(strings, values, blockIndentations) {
|
|
25
|
-
let result = strings[0];
|
|
26
|
-
for (let i = 0; i < values.length; i++) {
|
|
27
|
-
let text = values[i];
|
|
28
|
-
if (text) {
|
|
29
|
-
const blockIndentation = blockIndentations[i];
|
|
30
|
-
if (blockIndentation) {
|
|
31
|
-
const lines = text.split("\n");
|
|
32
|
-
text = "";
|
|
33
|
-
if (lines.at(-1) === "") {
|
|
34
|
-
// Drop empty last line
|
|
35
|
-
lines.pop();
|
|
36
|
-
}
|
|
37
|
-
for (let line of lines) {
|
|
38
|
-
text += blockIndentation + line + "\n";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
result += text;
|
|
42
|
-
}
|
|
43
|
-
result += strings[i + 1];
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Given an array of template boilerplate strings, return an object { modified,
|
|
49
|
-
// blockIndentations } where `strings` is the array of strings with indentation
|
|
50
|
-
// removed, and `blockIndentations` is an array of indentation strings for each
|
|
51
|
-
// block placeholder.
|
|
52
|
-
function modifyStrings(strings) {
|
|
53
|
-
// Phase one: Identify the indentation based on the first real line of the
|
|
54
|
-
// first string (skipping the initial newline), and remove this indentation
|
|
55
|
-
// from all lines of all strings.
|
|
56
|
-
let indent;
|
|
57
|
-
if (strings.length > 0 && strings[0].startsWith("\n")) {
|
|
58
|
-
// Look for indenttation
|
|
59
|
-
const firstLineWhitespaceRegex = /^\n(?<indent>[ \t]*)/;
|
|
60
|
-
const match = strings[0].match(firstLineWhitespaceRegex);
|
|
61
|
-
indent = match?.groups.indent;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Determine the modified strings. If this invoked as a JS tagged template
|
|
65
|
-
// literal, the `strings` argument will be an odd array-ish object that we'll
|
|
66
|
-
// want to convert to a real array.
|
|
67
|
-
let modified;
|
|
68
|
-
if (indent) {
|
|
69
|
-
// De-indent the strings.
|
|
70
|
-
const indentationRegex = new RegExp(`\n${indent}`, "g");
|
|
71
|
-
// The `replaceAll` also converts strings to a real array.
|
|
72
|
-
modified = strings.map((string) =>
|
|
73
|
-
string.replaceAll(indentationRegex, "\n")
|
|
74
|
-
);
|
|
75
|
-
// Remove indentation from last line of last string
|
|
76
|
-
modified[modified.length - 1] = modified
|
|
77
|
-
.at(-1)
|
|
78
|
-
.replace(lastLineWhitespaceRegex, "\n");
|
|
79
|
-
} else {
|
|
80
|
-
// No indentation; just copy the strings so we have a real array
|
|
81
|
-
modified = strings.slice();
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Phase two: Identify any block placholders, identify and remove their
|
|
85
|
-
// preceding indentation, and remove the following newline. Work backward from
|
|
86
|
-
// the end towards the start because we're modifying the strings in place and
|
|
87
|
-
// our pattern matching won't work going forward from start to end.
|
|
88
|
-
let blockIndentations = [];
|
|
89
|
-
for (let i = modified.length - 2; i >= 0; i--) {
|
|
90
|
-
// Get the modified before and after substitution with index `i`
|
|
91
|
-
const beforeString = modified[i];
|
|
92
|
-
const afterString = modified[i + 1];
|
|
93
|
-
const match = beforeString.match(lastLineWhitespaceRegex);
|
|
94
|
-
if (match && afterString.startsWith("\n")) {
|
|
95
|
-
// The substitution between these strings is a block substitution
|
|
96
|
-
let blockIndentation = match.groups.indent;
|
|
97
|
-
blockIndentations[i] = blockIndentation;
|
|
98
|
-
// Trim the before and after strings
|
|
99
|
-
if (blockIndentation) {
|
|
100
|
-
modified[i] = beforeString.slice(0, -blockIndentation.length);
|
|
101
|
-
}
|
|
102
|
-
modified[i + 1] = afterString.slice(1);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Remove newline from start of first string *after* removing indentation.
|
|
107
|
-
if (modified[0].startsWith("\n")) {
|
|
108
|
-
modified[0] = modified[0].slice(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
blockIndentations,
|
|
113
|
-
strings: modified,
|
|
114
|
-
};
|
|
115
|
-
}
|