@rohal12/twee-ts 1.16.1 → 1.17.0
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/README.md +1 -1
- package/dist/bin/twee-ts.js +191 -85
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{compiler-Bmpbelqj.js → diagnostics-DZNS7W41.js} +114 -37
- package/dist/diagnostics-DZNS7W41.js.map +1 -0
- package/dist/index.cjs +200 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -10
- package/dist/index.d.ts +67 -10
- package/dist/index.js +200 -67
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.d.ts +14 -1
- package/dist/plugins/rollup.js +20 -9
- package/dist/plugins/rollup.js.map +1 -1
- package/dist/plugins/vite.js +1 -28
- package/dist/plugins/vite.js.map +1 -1
- package/package.json +2 -1
- package/dist/compiler-Bmpbelqj.js.map +0 -1
|
@@ -4419,14 +4419,16 @@ function parseDocument(data, options) {
|
|
|
4419
4419
|
*/
|
|
4420
4420
|
/**
|
|
4421
4421
|
* Parse a Twine 2 or Twine 1 compiled HTML file back into a Story model.
|
|
4422
|
+
* Tweego always trims passage text; here `trim: false` keeps it exactly, as for Twee sources.
|
|
4422
4423
|
*/
|
|
4423
|
-
function decompileHTML(html) {
|
|
4424
|
+
function decompileHTML(html, options = {}) {
|
|
4424
4425
|
const diagnostics = [];
|
|
4425
4426
|
const story = createStory();
|
|
4427
|
+
const passageText = options.trim === false ? (text) => text : (text) => text.trim();
|
|
4426
4428
|
const doc = parseDocument(html);
|
|
4427
4429
|
const twine2Data = findElement(doc, "tw-storydata");
|
|
4428
4430
|
if (twine2Data) {
|
|
4429
|
-
decompileTwine2(twine2Data, story, diagnostics);
|
|
4431
|
+
decompileTwine2(twine2Data, story, passageText, diagnostics);
|
|
4430
4432
|
return {
|
|
4431
4433
|
story,
|
|
4432
4434
|
diagnostics
|
|
@@ -4434,7 +4436,7 @@ function decompileHTML(html) {
|
|
|
4434
4436
|
}
|
|
4435
4437
|
const twine1Data = findElementByIdPattern(doc, /^store(?:-a|A)rea$/);
|
|
4436
4438
|
if (twine1Data) {
|
|
4437
|
-
decompileTwine1(twine1Data, story, diagnostics);
|
|
4439
|
+
decompileTwine1(twine1Data, story, passageText, diagnostics);
|
|
4438
4440
|
return {
|
|
4439
4441
|
story,
|
|
4440
4442
|
diagnostics
|
|
@@ -4449,7 +4451,7 @@ function decompileHTML(html) {
|
|
|
4449
4451
|
diagnostics
|
|
4450
4452
|
};
|
|
4451
4453
|
}
|
|
4452
|
-
function decompileTwine2(storyData, story, diagnostics) {
|
|
4454
|
+
function decompileTwine2(storyData, story, passageText, diagnostics) {
|
|
4453
4455
|
let startnode = 0;
|
|
4454
4456
|
const attrs = storyData.attribs ?? {};
|
|
4455
4457
|
if (attrs["name"]) story.name = attrs["name"];
|
|
@@ -4479,8 +4481,9 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4479
4481
|
switch (node.name) {
|
|
4480
4482
|
case "style":
|
|
4481
4483
|
case "script": {
|
|
4482
|
-
const
|
|
4483
|
-
if (
|
|
4484
|
+
const content = getTextContent(node);
|
|
4485
|
+
if (content.trim().length === 0) continue;
|
|
4486
|
+
const text = passageText(content);
|
|
4484
4487
|
storyAdd(story, {
|
|
4485
4488
|
name: node.name === "style" ? "Story Stylesheet" : "Story JavaScript",
|
|
4486
4489
|
tags: node.name === "style" ? ["stylesheet"] : ["script"],
|
|
@@ -4515,7 +4518,7 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4515
4518
|
const passage = {
|
|
4516
4519
|
name,
|
|
4517
4520
|
tags,
|
|
4518
|
-
text: getTextContent(node)
|
|
4521
|
+
text: passageText(getTextContent(node))
|
|
4519
4522
|
};
|
|
4520
4523
|
if (metadata.position || metadata.size) passage.metadata = metadata;
|
|
4521
4524
|
storyAdd(story, passage, diagnostics);
|
|
@@ -4529,8 +4532,8 @@ function decompileTwine2(storyData, story, diagnostics) {
|
|
|
4529
4532
|
text: marshalStoryData(story)
|
|
4530
4533
|
}, diagnostics);
|
|
4531
4534
|
}
|
|
4532
|
-
function decompileTwine1(storeArea, story, diagnostics) {
|
|
4533
|
-
const passages = (storeArea.children ?? []).filter(isTiddler).map(tiddlerToPassage);
|
|
4535
|
+
function decompileTwine1(storeArea, story, passageText, diagnostics) {
|
|
4536
|
+
const passages = (storeArea.children ?? []).filter(isTiddler).map((node) => tiddlerToPassage(node, passageText));
|
|
4534
4537
|
const obfuscated = isRot13Obfuscated(passages);
|
|
4535
4538
|
for (const p of passages) storyAdd(story, obfuscated && p.name !== "StorySettings" ? {
|
|
4536
4539
|
...p,
|
|
@@ -4540,12 +4543,12 @@ function decompileTwine1(storeArea, story, diagnostics) {
|
|
|
4540
4543
|
function isTiddler(node) {
|
|
4541
4544
|
return node.type === "tag" && node.name === "div" && node.attribs !== void 0 && "tiddler" in node.attribs;
|
|
4542
4545
|
}
|
|
4543
|
-
function tiddlerToPassage(node) {
|
|
4546
|
+
function tiddlerToPassage(node, passageText) {
|
|
4544
4547
|
const nodeAttrs = node.attribs ?? {};
|
|
4545
4548
|
const passage = {
|
|
4546
4549
|
name: nodeAttrs["tiddler"] ?? "",
|
|
4547
4550
|
tags: nodeAttrs["tags"] ? nodeAttrs["tags"].split(/\s+/).filter((s) => s.length > 0) : [],
|
|
4548
|
-
text: tiddlerUnescape(getTextContent(node))
|
|
4551
|
+
text: passageText(tiddlerUnescape(getTextContent(node)))
|
|
4549
4552
|
};
|
|
4550
4553
|
const position = nodeAttrs["twine-position"];
|
|
4551
4554
|
if (position) passage.metadata = { position };
|
|
@@ -4610,7 +4613,7 @@ function loadSources(story, filenames, opts, diagnostics, processedFiles) {
|
|
|
4610
4613
|
break;
|
|
4611
4614
|
case "htm":
|
|
4612
4615
|
case "html":
|
|
4613
|
-
loadHTML(story, filename, diagnostics);
|
|
4616
|
+
loadHTML(story, filename, opts, diagnostics);
|
|
4614
4617
|
break;
|
|
4615
4618
|
case "css":
|
|
4616
4619
|
loadTagged(story, "stylesheet", filename, diagnostics);
|
|
@@ -4764,8 +4767,8 @@ function parseFontFile(filename) {
|
|
|
4764
4767
|
diagnostics: []
|
|
4765
4768
|
};
|
|
4766
4769
|
}
|
|
4767
|
-
function parseHTMLFile(filename) {
|
|
4768
|
-
const { story, diagnostics } = decompileHTML(readUTF8(filename));
|
|
4770
|
+
function parseHTMLFile(filename, opts) {
|
|
4771
|
+
const { story, diagnostics } = decompileHTML(readUTF8(filename), { trim: opts.trim ?? true });
|
|
4769
4772
|
return {
|
|
4770
4773
|
passages: story.name !== "" && !storyHas(story, "StoryTitle") ? [{
|
|
4771
4774
|
name: "StoryTitle",
|
|
@@ -4777,14 +4780,17 @@ function parseHTMLFile(filename) {
|
|
|
4777
4780
|
}
|
|
4778
4781
|
/**
|
|
4779
4782
|
* Identity of the options that affect how a file parses, stored on its cache entry.
|
|
4780
|
-
*
|
|
4783
|
+
* Twee files depend on `trim` and `twee2Compat`, HTML files on `trim`; every other file type
|
|
4784
|
+
* yields the same key for all options.
|
|
4781
4785
|
*/
|
|
4782
4786
|
function parseOptionsKey(filename, opts) {
|
|
4783
4787
|
const ext = normalizedFileExt(filename);
|
|
4788
|
+
const trim = opts.trim ?? true;
|
|
4789
|
+
if (ext === "htm" || ext === "html") return JSON.stringify({ trim });
|
|
4784
4790
|
const twee2File = ext === "tw2" || ext === "twee2";
|
|
4785
4791
|
if (!twee2File && ext !== "tw" && ext !== "twee") return "";
|
|
4786
4792
|
return JSON.stringify({
|
|
4787
|
-
trim
|
|
4793
|
+
trim,
|
|
4788
4794
|
twee2Compat: twee2File || (opts.twee2Compat ?? false)
|
|
4789
4795
|
});
|
|
4790
4796
|
}
|
|
@@ -4798,7 +4804,7 @@ function parseFile(filename, opts) {
|
|
|
4798
4804
|
twee2Compat: true
|
|
4799
4805
|
});
|
|
4800
4806
|
case "htm":
|
|
4801
|
-
case "html": return parseHTMLFile(filename);
|
|
4807
|
+
case "html": return parseHTMLFile(filename, opts);
|
|
4802
4808
|
case "css": return parseTaggedFile("stylesheet", filename);
|
|
4803
4809
|
case "js": return parseTaggedFile("script", filename);
|
|
4804
4810
|
case "otf":
|
|
@@ -4830,8 +4836,8 @@ function parseFile(filename, opts) {
|
|
|
4830
4836
|
default: return;
|
|
4831
4837
|
}
|
|
4832
4838
|
}
|
|
4833
|
-
function loadHTML(story, filename, diagnostics) {
|
|
4834
|
-
const result = parseHTMLFile(filename);
|
|
4839
|
+
function loadHTML(story, filename, opts, diagnostics) {
|
|
4840
|
+
const result = parseHTMLFile(filename, opts);
|
|
4835
4841
|
diagnostics.push(...result.diagnostics);
|
|
4836
4842
|
for (const p of result.passages) storyAdd(story, p, diagnostics);
|
|
4837
4843
|
}
|
|
@@ -4870,7 +4876,8 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
4870
4876
|
const optionsKey = parseOptionsKey(filename, opts);
|
|
4871
4877
|
const entry = cache.get(filename);
|
|
4872
4878
|
const cached = entry?.parseOptionsKey === optionsKey ? entry : void 0;
|
|
4873
|
-
|
|
4879
|
+
const changed = changedFiles?.has(filename) ?? false;
|
|
4880
|
+
if (changedFiles && cached && !changed) {
|
|
4874
4881
|
diagnostics.push(...cached.diagnostics);
|
|
4875
4882
|
for (const p of cached.passages) storyAdd(story, p, diagnostics);
|
|
4876
4883
|
processedFiles.add(filename);
|
|
@@ -4882,7 +4889,7 @@ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles,
|
|
|
4882
4889
|
} catch {
|
|
4883
4890
|
continue;
|
|
4884
4891
|
}
|
|
4885
|
-
if (cached && cached.mtimeMs === mtimeMs) {
|
|
4892
|
+
if (cached && !changed && cached.mtimeMs === mtimeMs) {
|
|
4886
4893
|
diagnostics.push(...cached.diagnostics);
|
|
4887
4894
|
for (const p of cached.passages) storyAdd(story, p, diagnostics);
|
|
4888
4895
|
processedFiles.add(filename);
|
|
@@ -5130,10 +5137,39 @@ function tryReplaceComponent(template, placeholder, componentPath, required) {
|
|
|
5130
5137
|
}
|
|
5131
5138
|
//#endregion
|
|
5132
5139
|
//#region src/output-twee.ts
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5140
|
+
/**
|
|
5141
|
+
* Serialize the story as Twee. The StoryData passage is written from the story model rather than
|
|
5142
|
+
* from its loaded text, so changes made after loading (a start passage override, test mode) are kept.
|
|
5143
|
+
* Tweego writes the loaded text; its own normalization makes the two the same for an unchanged story.
|
|
5144
|
+
*
|
|
5145
|
+
* `addStoryData`: add a StoryData passage when the story has none, so that StoryData fields the
|
|
5146
|
+
* compile options set are recorded. Default: false.
|
|
5147
|
+
*/
|
|
5148
|
+
function toTwee(story, outMode, options = {}) {
|
|
5149
|
+
return withEffectiveStoryData(story, options.addStoryData ?? false).map((p) => passageToTwee(p, outMode)).join("");
|
|
5150
|
+
}
|
|
5151
|
+
function withEffectiveStoryData(story, addStoryData) {
|
|
5152
|
+
const storyData = {
|
|
5153
|
+
name: "StoryData",
|
|
5154
|
+
tags: [],
|
|
5155
|
+
text: marshalStoryData(story)
|
|
5156
|
+
};
|
|
5157
|
+
const index = story.passages.findIndex((p) => p.name === "StoryData");
|
|
5158
|
+
if (index !== -1) return story.passages.map((p, i) => i === index && isStoryDataJSON(p.text) ? {
|
|
5159
|
+
...p,
|
|
5160
|
+
text: storyData.text
|
|
5161
|
+
} : p);
|
|
5162
|
+
if (!addStoryData) return story.passages;
|
|
5163
|
+
const at = story.passages[0]?.name === "StoryTitle" ? 1 : 0;
|
|
5164
|
+
return [
|
|
5165
|
+
...story.passages.slice(0, at),
|
|
5166
|
+
storyData,
|
|
5167
|
+
...story.passages.slice(at)
|
|
5168
|
+
];
|
|
5169
|
+
}
|
|
5170
|
+
/** Whether the text loaded as StoryData. Text that did not is kept as written, for the author to fix. */
|
|
5171
|
+
function isStoryDataJSON(text) {
|
|
5172
|
+
return unmarshalStoryData(createStory(), text) === null;
|
|
5137
5173
|
}
|
|
5138
5174
|
//#endregion
|
|
5139
5175
|
//#region src/modules.ts
|
|
@@ -5205,7 +5241,19 @@ function modifyHead(html, modulePaths, headFile, diagnostics) {
|
|
|
5205
5241
|
return html;
|
|
5206
5242
|
}
|
|
5207
5243
|
//#endregion
|
|
5208
|
-
//#region src/
|
|
5244
|
+
//#region src/passage-omission.ts
|
|
5245
|
+
/**
|
|
5246
|
+
* Why the `target` output leaves `passage` out of its passage elements, or `undefined` when it
|
|
5247
|
+
* emits the passage (and so a link or the start can name it).
|
|
5248
|
+
*/
|
|
5249
|
+
function passageOmission(story, passage, target) {
|
|
5250
|
+
switch (target) {
|
|
5251
|
+
case "twine2": return twine2PassageOmission(story, passage);
|
|
5252
|
+
case "twine1": return twine1PassageOmission(passage);
|
|
5253
|
+
default: throw new Error(`unhandled passage output target: ${String(target)}`);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
/** Why a passage is left out, as a predicate: `Passage "X" ${describeOmission(o)}`. */
|
|
5209
5257
|
function describeOmission(omission) {
|
|
5210
5258
|
switch (omission.kind) {
|
|
5211
5259
|
case "special-name": return `is the special "${omission.name}" passage`;
|
|
@@ -5214,13 +5262,8 @@ function describeOmission(omission) {
|
|
|
5214
5262
|
default: throw new Error(`unhandled passage omission: ${JSON.stringify(omission)}`);
|
|
5215
5263
|
}
|
|
5216
5264
|
}
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
case "twine2": return twine2PassageOmission(story, passage);
|
|
5220
|
-
case "twine1": return twine1PassageOmission(passage);
|
|
5221
|
-
default: throw new Error(`unhandled start passage target: ${String(target)}`);
|
|
5222
|
-
}
|
|
5223
|
-
}
|
|
5265
|
+
//#endregion
|
|
5266
|
+
//#region src/start-passage.ts
|
|
5224
5267
|
/**
|
|
5225
5268
|
* Check that `startName` names a passage the `target` output emits.
|
|
5226
5269
|
* Returns the error diagnostics to report (empty when the start is usable).
|
|
@@ -5231,7 +5274,7 @@ function startPassageDiagnostics(story, startName, target) {
|
|
|
5231
5274
|
level: "error",
|
|
5232
5275
|
message: `Starting passage "${startName}" not found.`
|
|
5233
5276
|
}];
|
|
5234
|
-
const omission =
|
|
5277
|
+
const omission = passageOmission(story, passage, target);
|
|
5235
5278
|
if (omission === void 0) return [];
|
|
5236
5279
|
return [{
|
|
5237
5280
|
level: "error",
|
|
@@ -5260,6 +5303,11 @@ async function compile(options) {
|
|
|
5260
5303
|
/**
|
|
5261
5304
|
* Compile with incremental caching support.
|
|
5262
5305
|
* Plugins and advanced users can manage their own cache and changed-file tracking.
|
|
5306
|
+
*
|
|
5307
|
+
* Without `changedFiles`, a cached file is reused while its modification time is unchanged.
|
|
5308
|
+
* With it, a file it names (by the path source discovery gives it, relative to the working
|
|
5309
|
+
* directory) is always reparsed, whatever its modification time, and every other cached
|
|
5310
|
+
* file is reused as it is.
|
|
5263
5311
|
*/
|
|
5264
5312
|
async function compileIncremental(options, cache, changedFiles) {
|
|
5265
5313
|
return buildOutput(options, cache, changedFiles);
|
|
@@ -5311,7 +5359,7 @@ async function buildOutput(options, cache, changedFiles, outFile) {
|
|
|
5311
5359
|
switch (outputMode) {
|
|
5312
5360
|
case "twee3":
|
|
5313
5361
|
case "twee1":
|
|
5314
|
-
output = toTwee(story, outputMode);
|
|
5362
|
+
output = toTwee(story, outputMode, { addStoryData: Boolean(options.startPassage) || testMode });
|
|
5315
5363
|
break;
|
|
5316
5364
|
case "twine2-archive":
|
|
5317
5365
|
output = toTwine2Archive(story, startName, { sourceInfo });
|
|
@@ -5411,6 +5459,35 @@ function storyToJSON(story) {
|
|
|
5411
5459
|
return JSON.stringify(obj, null, 2);
|
|
5412
5460
|
}
|
|
5413
5461
|
//#endregion
|
|
5414
|
-
|
|
5462
|
+
//#region src/plugins/diagnostics.ts
|
|
5463
|
+
function formatDiagnostic(d) {
|
|
5464
|
+
return `${d.file ? `${d.file}${d.line ? `:${d.line}` : ""}: ` : ""}${d.file && d.line ? d.message.replace(/^line \d+: /, "") : d.message}`;
|
|
5465
|
+
}
|
|
5466
|
+
/** Returns the warnings; throws a LocatedError when the compile reported errors. */
|
|
5467
|
+
function splitDiagnostics(result) {
|
|
5468
|
+
const errors = result.diagnostics.filter((d) => d.level === "error");
|
|
5469
|
+
if (errors.length === 0) return result.diagnostics.filter((d) => d.level === "warning");
|
|
5470
|
+
const error = new Error(errors.map(formatDiagnostic).join("\n"));
|
|
5471
|
+
const [first] = errors;
|
|
5472
|
+
if (first?.file) {
|
|
5473
|
+
error.id = first.file;
|
|
5474
|
+
error.loc = {
|
|
5475
|
+
file: first.file,
|
|
5476
|
+
line: first.line ?? 1,
|
|
5477
|
+
column: 1
|
|
5478
|
+
};
|
|
5479
|
+
}
|
|
5480
|
+
throw error;
|
|
5481
|
+
}
|
|
5482
|
+
/** An error thrown by the compiler or bundler, with a TweeTsError's error diagnostics folded into its message. */
|
|
5483
|
+
function fatalError(e) {
|
|
5484
|
+
if (e instanceof TweeTsError) {
|
|
5485
|
+
const errors = e.diagnostics.filter((d) => d.level === "error").map(formatDiagnostic);
|
|
5486
|
+
return new Error([...errors, e.message].join("\n"));
|
|
5487
|
+
}
|
|
5488
|
+
return e instanceof Error ? e : new Error(String(e));
|
|
5489
|
+
}
|
|
5490
|
+
//#endregion
|
|
5491
|
+
export { compileIncremental as a, mediaTypeFromFilename as c, compile as i, formatDiagnostic as n, getFilenames as o, splitDiagnostics as r, isExcluded as s, fatalError as t };
|
|
5415
5492
|
|
|
5416
|
-
//# sourceMappingURL=
|
|
5493
|
+
//# sourceMappingURL=diagnostics-DZNS7W41.js.map
|