@weborigami/language 0.7.0-beta.1 → 0.7.0-beta.3
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/main.js +1 -0
- package/package.json +3 -2
- package/src/compiler/compile.js +0 -3
- package/src/compiler/optimize.js +56 -23
- package/src/compiler/parserHelpers.js +11 -8
- package/src/handlers/addExtensionKeyFn.js +18 -0
- package/src/handlers/epub_handler.js +54 -0
- package/src/handlers/getSource.js +1 -0
- package/src/handlers/handlers.js +2 -0
- package/src/handlers/ori_handler.js +2 -2
- package/src/handlers/oridocument_handler.js +5 -21
- package/src/handlers/{processOriExport.js → processOrigamiExport.js} +1 -1
- package/src/handlers/zip_handler.js +112 -0
- package/src/runtime/AsyncCacheTransform.d.ts +5 -1
- package/src/runtime/AsyncCacheTransform.js +22 -25
- package/src/runtime/OrigamiFileMap.js +3 -3
- package/src/runtime/SyncCacheTransform.d.ts +2 -1
- package/src/runtime/SyncCacheTransform.js +31 -34
- package/src/runtime/SystemCacheMap.js +28 -17
- package/src/runtime/counters.js +3 -0
- package/src/runtime/enableValueCaching.js +8 -7
- package/src/runtime/errors.js +33 -1
- package/src/runtime/execute.js +3 -0
- package/src/runtime/explainReferenceError.js +11 -24
- package/src/runtime/explainTraverseError.js +15 -6
- package/src/runtime/expressionObject.js +53 -34
- package/src/runtime/handleExtension.js +18 -9
- package/src/runtime/ops.js +4 -3
- package/test/compiler/compile.test.js +0 -1
- package/test/compiler/optimize.test.js +60 -15
- package/test/compiler/parse.test.js +38 -9
- package/test/handlers/epub_handler.test.js +27 -0
- package/test/handlers/fixtures/test.zip +0 -0
- package/test/handlers/zip_handler.test.js +45 -0
- package/test/runtime/AsyncCacheTransform.test.js +9 -4
- package/test/runtime/SyncCacheTransform.test.js +4 -0
- package/test/runtime/asyncCalcs.js +26 -4
- package/test/runtime/errors.test.js +4 -4
- package/test/runtime/expressionObject.test.js +23 -12
- package/test/runtime/handleExtension.test.js +0 -2
- package/test/runtime/ops.test.js +5 -3
- package/test/runtime/syncCalcs.js +26 -3
- package/test/runtime/systemCache.test.js +4 -0
package/main.js
CHANGED
|
@@ -15,6 +15,7 @@ export { default as projectRoot } from "./src/project/projectRoot.js";
|
|
|
15
15
|
export { default as projectRootFromPath } from "./src/project/projectRootFromPath.js";
|
|
16
16
|
export { default as fetchAndHandleExtension } from "./src/protocols/fetchAndHandleExtension.js";
|
|
17
17
|
export * as Protocols from "./src/protocols/protocols.js";
|
|
18
|
+
export { default as counters } from "./src/runtime/counters.js";
|
|
18
19
|
export { formatError, highlightError, lineInfo } from "./src/runtime/errors.js";
|
|
19
20
|
export { default as evaluate } from "./src/runtime/evaluate.js";
|
|
20
21
|
export { default as EventTargetMixin } from "./src/runtime/EventTargetMixin.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/language",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.3",
|
|
4
4
|
"description": "Web Origami expression language compiler and runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"typescript": "6.0.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/async-tree": "0.7.0-beta.
|
|
14
|
+
"@weborigami/async-tree": "0.7.0-beta.3",
|
|
15
|
+
"adm-zip": "0.5.17",
|
|
15
16
|
"exif-parser": "0.1.12",
|
|
16
17
|
"watcher": "2.3.1",
|
|
17
18
|
"yaml": "2.9.0"
|
package/src/compiler/compile.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
1
|
import { createExpressionFunction } from "../runtime/expressionFunction.js";
|
|
3
2
|
import optimize from "./optimize.js";
|
|
4
3
|
import { parse } from "./parse.js";
|
|
@@ -33,13 +32,11 @@ function compile(source, options) {
|
|
|
33
32
|
|
|
34
33
|
// Select a path the code will use for caching
|
|
35
34
|
const cachePath = source.cachePath ?? `_compile${count++}`;
|
|
36
|
-
const objectCachePath = cachePath ? trailingSlash.add(cachePath) : null;
|
|
37
35
|
|
|
38
36
|
// Optimize the code
|
|
39
37
|
const optimized = optimize(code, {
|
|
40
38
|
cachePath,
|
|
41
39
|
globals,
|
|
42
|
-
objectCachePath,
|
|
43
40
|
});
|
|
44
41
|
|
|
45
42
|
// Create a function that executes the optimized code.
|
package/src/compiler/optimize.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { trailingSlash } from "@weborigami/async-tree";
|
|
2
2
|
import jsGlobals from "../project/jsGlobals.js";
|
|
3
3
|
import {
|
|
4
4
|
KEY_TYPE,
|
|
@@ -31,9 +31,13 @@ export const REFERENCE_EXTERNAL = 4;
|
|
|
31
31
|
* @returns {AnnotatedCode}
|
|
32
32
|
*/
|
|
33
33
|
export default function optimize(code, options = {}) {
|
|
34
|
-
// Cache path for this source file
|
|
35
|
-
const cachePath = options.cachePath ?? null;
|
|
36
34
|
const globals = options.globals ?? jsGlobals;
|
|
35
|
+
const cachePath = options.cachePath; // top-level cache path for this code
|
|
36
|
+
// Cache path for attached objects
|
|
37
|
+
const objectCachePath =
|
|
38
|
+
options.objectCachePath ??
|
|
39
|
+
(cachePath ? trailingSlash.add(cachePath) : null);
|
|
40
|
+
const attached = options.attached ?? true;
|
|
37
41
|
|
|
38
42
|
// The locals is an array, one item for each function or object context that
|
|
39
43
|
// has been entered. The array grows to the right. Array items are objects
|
|
@@ -66,7 +70,8 @@ export default function optimize(code, options = {}) {
|
|
|
66
70
|
return inlineLiteral(code);
|
|
67
71
|
|
|
68
72
|
case ops.object:
|
|
69
|
-
const
|
|
73
|
+
const entries = args.slice(1);
|
|
74
|
+
const propertyNames = getPropertyNames(entries);
|
|
70
75
|
locals.push({
|
|
71
76
|
type: REFERENCE_INHERITED,
|
|
72
77
|
names: propertyNames,
|
|
@@ -80,14 +85,24 @@ export default function optimize(code, options = {}) {
|
|
|
80
85
|
if (op === ops.object) {
|
|
81
86
|
if (index === 0) {
|
|
82
87
|
return child; // return op as is
|
|
88
|
+
} else if (index === 1) {
|
|
89
|
+
// Replace null cache path with actual value
|
|
90
|
+
return objectCachePath;
|
|
83
91
|
} else {
|
|
84
92
|
// Object entry
|
|
85
93
|
const [key, value] = child;
|
|
86
94
|
const adjustedLocals = avoidLocalRecursion(locals, key);
|
|
87
95
|
const isStringKey = typeof key === "string";
|
|
96
|
+
const cachePathKey = isStringKey
|
|
97
|
+
? baseKeyName(key)
|
|
98
|
+
: `_arg${index - 2}`;
|
|
99
|
+
const childObjectCachePath = attached
|
|
100
|
+
? SystemCacheMap.joinPath(objectCachePath, cachePathKey + "/")
|
|
101
|
+
: objectCachePath;
|
|
88
102
|
const childOptions = {
|
|
89
103
|
...options,
|
|
90
104
|
locals: adjustedLocals,
|
|
105
|
+
objectCachePath: childObjectCachePath,
|
|
91
106
|
};
|
|
92
107
|
const optimizedKey = isStringKey
|
|
93
108
|
? key
|
|
@@ -98,12 +113,30 @@ export default function optimize(code, options = {}) {
|
|
|
98
113
|
);
|
|
99
114
|
return annotate([optimizedKey, optimizedValue], child.location);
|
|
100
115
|
}
|
|
116
|
+
} else if (op === ops.getter) {
|
|
117
|
+
if (index === 0) {
|
|
118
|
+
return child; // return op as is
|
|
119
|
+
} else {
|
|
120
|
+
// Already set up to optimize the (single) child
|
|
121
|
+
return optimize(/** @type {AnnotatedCode} */ (child), options);
|
|
122
|
+
}
|
|
101
123
|
} else if (Array.isArray(child) && "location" in child) {
|
|
102
124
|
// Review: Aside from ops.object (above), what non-instruction arrays
|
|
103
125
|
// does this descend into?
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
let childObjectCachePath = objectCachePath;
|
|
127
|
+
if (attached) {
|
|
128
|
+
// Objects below this point are detached, use `_objects` cache path
|
|
129
|
+
childObjectCachePath = SystemCacheMap.joinPath(
|
|
130
|
+
objectCachePath,
|
|
131
|
+
`_objects/`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const childOptions = {
|
|
135
|
+
...options,
|
|
136
|
+
attached: false,
|
|
137
|
+
locals,
|
|
138
|
+
objectCachePath: childObjectCachePath,
|
|
139
|
+
};
|
|
107
140
|
return optimize(/** @type {AnnotatedCode} */ (child), childOptions);
|
|
108
141
|
} else {
|
|
109
142
|
return child;
|
|
@@ -124,23 +157,20 @@ export default function optimize(code, options = {}) {
|
|
|
124
157
|
// remove any local variable with that name from the stack of locals to avoid a
|
|
125
158
|
// recursive reference.
|
|
126
159
|
function avoidLocalRecursion(locals, key) {
|
|
127
|
-
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
128
|
-
// Non-enumerable property, remove parentheses
|
|
129
|
-
key = key.slice(1, -1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
160
|
const currentFrameIndex = locals.length - 1;
|
|
133
161
|
if (locals[currentFrameIndex]?.type !== REFERENCE_INHERITED) {
|
|
134
162
|
// Not an inherited context, nothing to do
|
|
135
163
|
return locals;
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
const baseKey = baseKeyName(key);
|
|
167
|
+
|
|
138
168
|
// See if the key matches any of the local variable names in the current
|
|
139
169
|
// context (ignoring trailing slashes)
|
|
140
170
|
const matchingKeyIndex = locals[currentFrameIndex].names.findIndex(
|
|
141
171
|
(name) =>
|
|
142
172
|
// Ignore trailing slashes when comparing keys
|
|
143
|
-
trailingSlash.remove(name) === trailingSlash.remove(
|
|
173
|
+
trailingSlash.remove(name) === trailingSlash.remove(baseKey),
|
|
144
174
|
);
|
|
145
175
|
|
|
146
176
|
if (matchingKeyIndex >= 0) {
|
|
@@ -158,11 +188,12 @@ function avoidLocalRecursion(locals, key) {
|
|
|
158
188
|
}
|
|
159
189
|
}
|
|
160
190
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
191
|
+
function baseKeyName(key) {
|
|
192
|
+
if (key[0] === "(" && key[key.length - 1] === ")") {
|
|
193
|
+
// Non-enumerable property, remove parentheses
|
|
194
|
+
key = key.slice(1, -1);
|
|
195
|
+
}
|
|
196
|
+
return key;
|
|
166
197
|
}
|
|
167
198
|
|
|
168
199
|
// A reference with periods like x.y.z
|
|
@@ -366,6 +397,13 @@ function resolvePath(code, globals, locals, cachePath) {
|
|
|
366
397
|
|
|
367
398
|
let { type, result } = reference(head, globals, locals);
|
|
368
399
|
|
|
400
|
+
if (type === REFERENCE_EXTERNAL && cachePath) {
|
|
401
|
+
// External references should be cached
|
|
402
|
+
const headKey = keyFromCode(head);
|
|
403
|
+
const refCachePath = SystemCacheMap.joinPath(cachePath, "_refs", headKey);
|
|
404
|
+
result = annotate([ops.cache, refCachePath, result], head.location);
|
|
405
|
+
}
|
|
406
|
+
|
|
369
407
|
if (tail.length > 0) {
|
|
370
408
|
// If the result is a traversal, we can safely extend it
|
|
371
409
|
const extendResult =
|
|
@@ -383,10 +421,5 @@ function resolvePath(code, globals, locals, cachePath) {
|
|
|
383
421
|
}
|
|
384
422
|
}
|
|
385
423
|
|
|
386
|
-
if (type === REFERENCE_EXTERNAL && cachePath) {
|
|
387
|
-
// External references should be cached
|
|
388
|
-
return cacheExternalPath(result, cachePath);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
424
|
return result;
|
|
392
425
|
}
|
|
@@ -272,7 +272,7 @@ export function makeDocument(front, body, location) {
|
|
|
272
272
|
entries.push(annotate(["_body", body], location));
|
|
273
273
|
|
|
274
274
|
// Return the code for the document object
|
|
275
|
-
return annotate([ops.object, ...entries], location);
|
|
275
|
+
return annotate([ops.object, null, ...entries], location);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/**
|
|
@@ -341,7 +341,7 @@ function makeMerge(spreads, location) {
|
|
|
341
341
|
|
|
342
342
|
for (const spread of spreads) {
|
|
343
343
|
if (spread[0] === ops.object) {
|
|
344
|
-
const spreadEntries = spread.slice(
|
|
344
|
+
const spreadEntries = spread.slice(2);
|
|
345
345
|
topEntries.push(...spreadEntries);
|
|
346
346
|
// Also add an object to the result with indirect references
|
|
347
347
|
const indirectEntries = spreadEntries.map((entry) => {
|
|
@@ -352,7 +352,7 @@ function makeMerge(spreads, location) {
|
|
|
352
352
|
return annotate([key, getter], entry.location);
|
|
353
353
|
});
|
|
354
354
|
const indirectObject = annotate(
|
|
355
|
-
[ops.object, ...indirectEntries],
|
|
355
|
+
[ops.object, null, ...indirectEntries],
|
|
356
356
|
location,
|
|
357
357
|
);
|
|
358
358
|
resultEntries.push(indirectObject);
|
|
@@ -368,7 +368,7 @@ function makeMerge(spreads, location) {
|
|
|
368
368
|
topEntries.push(annotate(["_result", result], location));
|
|
369
369
|
|
|
370
370
|
// Construct the top-level object
|
|
371
|
-
const topObject = annotate([ops.object, ...topEntries], location);
|
|
371
|
+
const topObject = annotate([ops.object, null, ...topEntries], location);
|
|
372
372
|
|
|
373
373
|
// Get the _result property
|
|
374
374
|
const code = annotate([topObject, "_result"], location);
|
|
@@ -390,14 +390,17 @@ export function makeObject(entries, location) {
|
|
|
390
390
|
if (key === markers.spread) {
|
|
391
391
|
if (value[0] === ops.object) {
|
|
392
392
|
// Spread of an object; fold into current object
|
|
393
|
-
const spreadEntries = value.slice(
|
|
393
|
+
const spreadEntries = value.slice(2);
|
|
394
394
|
currentEntries.push(...spreadEntries);
|
|
395
395
|
} else {
|
|
396
396
|
// Spread of a tree; accumulate
|
|
397
397
|
if (currentEntries.length > 0) {
|
|
398
398
|
const location = { ...currentEntries[0].location };
|
|
399
399
|
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
400
|
-
const spread = annotate(
|
|
400
|
+
const spread = annotate(
|
|
401
|
+
[ops.object, null, ...currentEntries],
|
|
402
|
+
location,
|
|
403
|
+
);
|
|
401
404
|
spreads.push(spread);
|
|
402
405
|
currentEntries = [];
|
|
403
406
|
}
|
|
@@ -424,7 +427,7 @@ export function makeObject(entries, location) {
|
|
|
424
427
|
if (currentEntries.length > 0) {
|
|
425
428
|
const location = { ...currentEntries[0].location };
|
|
426
429
|
location.end = currentEntries[currentEntries.length - 1].location.end;
|
|
427
|
-
const spread = annotate([ops.object, ...currentEntries], location);
|
|
430
|
+
const spread = annotate([ops.object, null, ...currentEntries], location);
|
|
428
431
|
spreads.push(spread);
|
|
429
432
|
currentEntries = [];
|
|
430
433
|
}
|
|
@@ -442,7 +445,7 @@ export function makeObject(entries, location) {
|
|
|
442
445
|
}
|
|
443
446
|
} else {
|
|
444
447
|
// Empty object
|
|
445
|
-
code = [ops.object];
|
|
448
|
+
code = [ops.object, null];
|
|
446
449
|
}
|
|
447
450
|
|
|
448
451
|
return annotate(code, location);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { extension, trailingSlash } from "@weborigami/async-tree";
|
|
2
|
+
|
|
3
|
+
// Return a function that adds the given extension
|
|
4
|
+
export default function addExtensionKeyFn(resultExtension) {
|
|
5
|
+
const keyFn = (sourceValue, sourceKey) => {
|
|
6
|
+
if (sourceKey === undefined) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const normalizedKey = trailingSlash.remove(sourceKey);
|
|
10
|
+
const sourceExtension = extension.extname(normalizedKey);
|
|
11
|
+
const resultKey = sourceExtension
|
|
12
|
+
? extension.replace(normalizedKey, sourceExtension, resultExtension)
|
|
13
|
+
: normalizedKey + resultExtension;
|
|
14
|
+
return resultKey;
|
|
15
|
+
};
|
|
16
|
+
keyFn.needsSourceValue = false;
|
|
17
|
+
return keyFn;
|
|
18
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AsyncMap, isUnpackable, Tree } from "@weborigami/async-tree";
|
|
2
|
+
import addExtensionKeyFn from "./addExtensionKeyFn.js";
|
|
3
|
+
import zip_handler from "./zip_handler.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handler for EPUB files
|
|
7
|
+
*/
|
|
8
|
+
const epub_handler = {
|
|
9
|
+
mediaType: "application/epub+zip",
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Package a tree of files as an EPUB file in Buffer form.
|
|
13
|
+
*
|
|
14
|
+
* This calls the pack() method for ZIP files, but ensures the `mimetype` file
|
|
15
|
+
* is the first file in the package -- a requirement for EPUB files.
|
|
16
|
+
*
|
|
17
|
+
* @param {import("@weborigami/async-tree").Maplike} maplike
|
|
18
|
+
*/
|
|
19
|
+
async pack(maplike) {
|
|
20
|
+
if (isUnpackable(maplike)) {
|
|
21
|
+
maplike = await maplike.unpack();
|
|
22
|
+
}
|
|
23
|
+
const tree = Tree.from(maplike, { deep: true });
|
|
24
|
+
return zip_handler.pack(mimetypeFirst(tree));
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async unpack(buffer, options) {
|
|
28
|
+
return zip_handler.unpack(buffer, options);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** @type {any} */ (epub_handler.pack).key = addExtensionKeyFn(".epub");
|
|
33
|
+
|
|
34
|
+
export default epub_handler;
|
|
35
|
+
|
|
36
|
+
// A tree with its `mimetype` file first
|
|
37
|
+
function mimetypeFirst(tree) {
|
|
38
|
+
return Object.assign(new AsyncMap(), {
|
|
39
|
+
async get(key) {
|
|
40
|
+
return tree.get(key);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async *keys() {
|
|
44
|
+
const keys = await Tree.keys(tree);
|
|
45
|
+
// Move `mimetype` (if present) to the front of the list.
|
|
46
|
+
const index = keys.indexOf("mimetype");
|
|
47
|
+
if (index >= 0) {
|
|
48
|
+
keys.splice(index, 1);
|
|
49
|
+
keys.unshift("mimetype");
|
|
50
|
+
}
|
|
51
|
+
yield* keys;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -30,6 +30,7 @@ export default function getSource(packed, options = {}) {
|
|
|
30
30
|
const parentPath = /** @type {any} */ (parent).path;
|
|
31
31
|
cachePath = SystemCacheMap.joinPath(parentPath, sourceName);
|
|
32
32
|
} else {
|
|
33
|
+
// TODO: Avoid cache path collisions in this case
|
|
33
34
|
cachePath = sourceName;
|
|
34
35
|
}
|
|
35
36
|
}
|
package/src/handlers/handlers.js
CHANGED
|
@@ -20,6 +20,7 @@ export { default as txt_handler } from "./txt_handler.js";
|
|
|
20
20
|
|
|
21
21
|
export { default as css_handler } from "./css_handler.js";
|
|
22
22
|
export { default as csv_handler } from "./csv_handler.js";
|
|
23
|
+
export { default as epub_handler } from "./epub_handler.js";
|
|
23
24
|
export { default as htm_handler } from "./htm_handler.js";
|
|
24
25
|
export { default as html_handler } from "./html_handler.js";
|
|
25
26
|
export { default as jpeg_handler } from "./jpeg_handler.js";
|
|
@@ -33,3 +34,4 @@ export { default as wasm_handler } from "./wasm_handler.js";
|
|
|
33
34
|
export { default as xhtml_handler } from "./xhtml_handler.js";
|
|
34
35
|
export { default as yaml_handler } from "./yaml_handler.js";
|
|
35
36
|
export { default as yml_handler } from "./yml_handler.js";
|
|
37
|
+
export { default as zip_handler } from "./zip_handler.js";
|
|
@@ -3,7 +3,7 @@ import * as compile from "../compiler/compile.js";
|
|
|
3
3
|
import coreGlobals from "../project/coreGlobals.js";
|
|
4
4
|
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
5
5
|
import getSource from "./getSource.js";
|
|
6
|
-
import
|
|
6
|
+
import processOrigamiExport from "./processOrigamiExport.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* An Origami expression file
|
|
@@ -31,7 +31,7 @@ export default {
|
|
|
31
31
|
// Evaluate the program
|
|
32
32
|
let result = await fn();
|
|
33
33
|
|
|
34
|
-
result =
|
|
34
|
+
result = processOrigamiExport(result, source, parent);
|
|
35
35
|
|
|
36
36
|
return result;
|
|
37
37
|
},
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { extension, getParent
|
|
1
|
+
import { extension, getParent } from "@weborigami/async-tree";
|
|
2
2
|
import * as compile from "../compiler/compile.js";
|
|
3
3
|
import coreGlobals from "../project/coreGlobals.js";
|
|
4
4
|
import getGlobalsForTree from "../project/getGlobalsForTree.js";
|
|
5
|
+
import addExtensionKeyFn from "./addExtensionKeyFn.js";
|
|
5
6
|
import getSource from "./getSource.js";
|
|
6
|
-
import
|
|
7
|
+
import processOrigamiExport from "./processOrigamiExport.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* An Origami template document: a plain text file that contains Origami
|
|
@@ -30,34 +31,17 @@ export default {
|
|
|
30
31
|
// Invoke the definition to get back the template function or object
|
|
31
32
|
let result = await fn();
|
|
32
33
|
|
|
33
|
-
result =
|
|
34
|
+
result = processOrigamiExport(result, source, parent);
|
|
34
35
|
|
|
35
36
|
if (result instanceof Function) {
|
|
36
37
|
const key = options.key;
|
|
37
38
|
const resultExtension = key ? extension.extname(key) : null;
|
|
38
39
|
if (resultExtension && Object.isExtensible(result)) {
|
|
39
40
|
// Add sidecar function so this template can be used in a map.
|
|
40
|
-
result.key =
|
|
41
|
+
result.key = addExtensionKeyFn(resultExtension);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
return result;
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
|
-
|
|
48
|
-
// Return a function that adds the given extension
|
|
49
|
-
function addExtension(resultExtension) {
|
|
50
|
-
const keyFn = (sourceValue, sourceKey) => {
|
|
51
|
-
if (sourceKey === undefined) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
const normalizedKey = trailingSlash.remove(sourceKey);
|
|
55
|
-
const sourceExtension = extension.extname(normalizedKey);
|
|
56
|
-
const resultKey = sourceExtension
|
|
57
|
-
? extension.replace(normalizedKey, sourceExtension, resultExtension)
|
|
58
|
-
: normalizedKey + resultExtension;
|
|
59
|
-
return resultKey;
|
|
60
|
-
};
|
|
61
|
-
keyFn.needsSourceValue = false;
|
|
62
|
-
return keyFn;
|
|
63
|
-
}
|
|
@@ -5,7 +5,7 @@ import enableValueCaching from "../runtime/enableValueCaching.js";
|
|
|
5
5
|
* Given an object that's the top-level result of an Origami file, perform any
|
|
6
6
|
* necessary processing.
|
|
7
7
|
*/
|
|
8
|
-
export default function
|
|
8
|
+
export default function processOrigamiExport(value, source, parent) {
|
|
9
9
|
setParent(value, parent);
|
|
10
10
|
|
|
11
11
|
if (source.cachePath) {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isUnpackable, SyncMap, Tree } from "@weborigami/async-tree";
|
|
2
|
+
import {
|
|
3
|
+
getGlobalsForTree,
|
|
4
|
+
HandleExtensionsTransform,
|
|
5
|
+
} from "@weborigami/language";
|
|
6
|
+
import Zip from "adm-zip";
|
|
7
|
+
import addExtensionKeyFn from "./addExtensionKeyFn.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handler for ZIP files
|
|
11
|
+
*/
|
|
12
|
+
const zip_handler = {
|
|
13
|
+
mediaType: "application/zip",
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Pack a tree of files as a ZIP file in Buffer form.
|
|
17
|
+
*
|
|
18
|
+
* @param {import("@weborigami/async-tree").Maplike} maplike
|
|
19
|
+
*/
|
|
20
|
+
async pack(maplike) {
|
|
21
|
+
// The ZIP file should leave the files in tree order.
|
|
22
|
+
const zip = new Zip({ noSort: true });
|
|
23
|
+
|
|
24
|
+
if (isUnpackable(maplike)) {
|
|
25
|
+
maplike = await maplike.unpack();
|
|
26
|
+
}
|
|
27
|
+
const tree = Tree.from(maplike, { deep: true });
|
|
28
|
+
const deflated = await Tree.deflatePaths(tree);
|
|
29
|
+
for (let [path, value] of deflated) {
|
|
30
|
+
if (typeof value === "function") {
|
|
31
|
+
value = value();
|
|
32
|
+
}
|
|
33
|
+
if (value instanceof Promise) {
|
|
34
|
+
value = await value;
|
|
35
|
+
} else if (value instanceof String) {
|
|
36
|
+
value = value.toString(); // adm-zip wants simple strings
|
|
37
|
+
}
|
|
38
|
+
zip.addFile(path, value);
|
|
39
|
+
|
|
40
|
+
// Special case for EPUB files, where `mimetype` must be uncompressed.
|
|
41
|
+
if (path === "mimetype") {
|
|
42
|
+
const entry = zip.getEntry(path);
|
|
43
|
+
entry.header.method = 0; // STORE (not DEFLATE)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const buffer = zip.toBuffer();
|
|
47
|
+
return buffer;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Unpack a ZIP file
|
|
52
|
+
*/
|
|
53
|
+
async unpack(buffer, options) {
|
|
54
|
+
// Origami generally prefers keeping things as an Uint8Array or ArrayBuffer,
|
|
55
|
+
// but adm-zip only accepts a Buffer.
|
|
56
|
+
if (buffer instanceof Uint8Array || buffer instanceof ArrayBuffer) {
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
buffer = Buffer.from(buffer);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const zip = new Zip(buffer);
|
|
62
|
+
|
|
63
|
+
const entries = zip.getEntries();
|
|
64
|
+
const filtered = entries.filter(
|
|
65
|
+
(entry) =>
|
|
66
|
+
!entry.entryName.startsWith("__MACOSX/") &&
|
|
67
|
+
!entry.entryName.endsWith("/"),
|
|
68
|
+
);
|
|
69
|
+
const deflated = Object.fromEntries(
|
|
70
|
+
filtered.map((entry) => [entry.entryName, () => entry.getData()]),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// The final tree will include extension handlers and have functions invoked
|
|
74
|
+
// to retrieve data from the ZIP file. While the base map is a SyncMap, the
|
|
75
|
+
// final tree will be async.
|
|
76
|
+
const classFn = HandleExtensionsTransform(
|
|
77
|
+
InvokeFunctionsTransform(SyncMap),
|
|
78
|
+
);
|
|
79
|
+
const result = await Tree.inflatePaths(deflated, { classFn });
|
|
80
|
+
|
|
81
|
+
const parent = options?.parent;
|
|
82
|
+
const globals = parent ? getGlobalsForTree(parent) : null;
|
|
83
|
+
if (globals) {
|
|
84
|
+
result.globals = globals;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** @type {any} */ (zip_handler.pack).key = addExtensionKeyFn(".zip");
|
|
92
|
+
|
|
93
|
+
export default zip_handler;
|
|
94
|
+
|
|
95
|
+
function InvokeFunctionsTransform(Base) {
|
|
96
|
+
return class InvokeFunctions extends Base {
|
|
97
|
+
delete(key) {
|
|
98
|
+
return super.delete(key);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get(key) {
|
|
102
|
+
const value = super.get(key);
|
|
103
|
+
return typeof value === "function" ? value() : value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
set(key, value) {
|
|
107
|
+
return super.set(key, value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
trailingSlashKeys = true;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Mixin } from "../../index.ts";
|
|
2
2
|
|
|
3
|
-
declare const AsyncCacheTransform: Mixin<{
|
|
3
|
+
declare const AsyncCacheTransform: Mixin<{
|
|
4
|
+
cachePathForKey(key: string): string;
|
|
5
|
+
onKeysChange(key: string): void;
|
|
6
|
+
onValueChange(key: string): void;
|
|
7
|
+
}>
|
|
4
8
|
|
|
5
9
|
export default AsyncCacheTransform;
|
|
@@ -45,24 +45,21 @@ export default function AsyncCacheTransform(Base) {
|
|
|
45
45
|
this.cache = systemCache;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
get cachePath() {
|
|
49
|
-
// @ts-ignore
|
|
50
|
-
return this[cachePathSymbol];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
48
|
cachePathForKey(key) {
|
|
54
49
|
return key === "."
|
|
55
|
-
?
|
|
56
|
-
|
|
50
|
+
? // @ts-ignore
|
|
51
|
+
this[cachePathSymbol]
|
|
52
|
+
: // @ts-ignore
|
|
53
|
+
SystemCacheMap.joinPath(this[cachePathSymbol], key);
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
async delete(key) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
56
|
+
// async delete(key) {
|
|
57
|
+
// const deleted = await super.delete(key);
|
|
58
|
+
// if (typeof key === "string") {
|
|
59
|
+
// systemCache.delete(this.cachePathForKey(key));
|
|
60
|
+
// }
|
|
61
|
+
// return deleted;
|
|
62
|
+
// }
|
|
66
63
|
|
|
67
64
|
async get(key) {
|
|
68
65
|
if (typeof key !== "string" || key.length === 0) {
|
|
@@ -119,16 +116,16 @@ export default function AsyncCacheTransform(Base) {
|
|
|
119
116
|
systemCache.delete(this.cachePathForKey(key));
|
|
120
117
|
}
|
|
121
118
|
|
|
122
|
-
async set(key, value) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
119
|
+
// async set(key, value) {
|
|
120
|
+
// if (typeof key !== "string") {
|
|
121
|
+
// return super.set(key, value);
|
|
122
|
+
// }
|
|
123
|
+
// systemCache.delete(this.cachePathForKey(key));
|
|
124
|
+
// if (!this.has(key)) {
|
|
125
|
+
// // Adding a new key, need to invalidate cached keys
|
|
126
|
+
// this.invalidateKeys();
|
|
127
|
+
// }
|
|
128
|
+
// super.set(key, value);
|
|
129
|
+
// }
|
|
133
130
|
};
|
|
134
131
|
}
|
|
@@ -4,15 +4,15 @@ import HandleExtensionsTransform from "./HandleExtensionsTransform.js";
|
|
|
4
4
|
import ImportModulesMixin from "./ImportModulesMixin.js";
|
|
5
5
|
import SyncCacheTransform from "./SyncCacheTransform.js";
|
|
6
6
|
import WatchFilesMixin from "./WatchFilesMixin.js";
|
|
7
|
-
import { noCacheSymbol } from "./symbols.js";
|
|
7
|
+
import { cachePathSymbol, noCacheSymbol } from "./symbols.js";
|
|
8
8
|
|
|
9
9
|
export default class OrigamiFileMap extends SyncCacheTransform(
|
|
10
10
|
HandleExtensionsTransform(
|
|
11
11
|
ImportModulesMixin(WatchFilesMixin(EventTargetMixin(FileMap))),
|
|
12
12
|
),
|
|
13
13
|
) {
|
|
14
|
-
get
|
|
15
|
-
return
|
|
14
|
+
get [cachePathSymbol]() {
|
|
15
|
+
return this.path;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
// Workaround to register file paths in the system cache without trailing
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Mixin } from "../../index.ts";
|
|
2
2
|
|
|
3
3
|
declare const SyncCacheTransform: Mixin<{
|
|
4
|
-
get cachePath(): string;
|
|
5
4
|
cachePathForKey(key: string): string;
|
|
5
|
+
onKeysChange(key: string): void;
|
|
6
|
+
onValueChange(key: string): void;
|
|
6
7
|
}>
|
|
7
8
|
|
|
8
9
|
export default SyncCacheTransform;
|