@rohal12/twee-ts 1.12.0 → 1.13.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/dist/bin/twee-ts.js +66 -49
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-TYLUAJTM.js → chunk-ORMESTR7.js} +67 -50
- package/dist/chunk-ORMESTR7.js.map +1 -0
- package/dist/index.cjs +97 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -19
- package/dist/index.d.ts +66 -19
- package/dist/index.js +94 -50
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TYLUAJTM.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
CONFIG_FILENAME: () => CONFIG_FILENAME,
|
|
24
|
+
ItemType: () => ItemType,
|
|
25
|
+
StoryBuilder: () => StoryBuilder,
|
|
24
26
|
TweeLexer: () => TweeLexer,
|
|
25
27
|
TweeTsError: () => TweeTsError,
|
|
26
28
|
applyTagAliases: () => applyTagAliases,
|
|
@@ -28,6 +30,7 @@ __export(src_exports, {
|
|
|
28
30
|
compile: () => compile,
|
|
29
31
|
compileIncremental: () => compileIncremental,
|
|
30
32
|
compileToFile: () => compileToFile,
|
|
33
|
+
createIFID: () => createIFID,
|
|
31
34
|
decompileHTML: () => decompileHTML,
|
|
32
35
|
discoverCachedFormats: () => discoverCachedFormats,
|
|
33
36
|
discoverFormats: () => discoverFormats,
|
|
@@ -64,6 +67,11 @@ var import_node_crypto = require("crypto");
|
|
|
64
67
|
function generateIFID() {
|
|
65
68
|
return (0, import_node_crypto.randomUUID)().toUpperCase();
|
|
66
69
|
}
|
|
70
|
+
function createIFID(value) {
|
|
71
|
+
const err = validateIFID(value);
|
|
72
|
+
if (err) throw new Error(`Invalid IFID: ${err}`);
|
|
73
|
+
return value.toUpperCase();
|
|
74
|
+
}
|
|
67
75
|
function validateIFID(ifid) {
|
|
68
76
|
let uuid = ifid;
|
|
69
77
|
switch (ifid.length) {
|
|
@@ -545,6 +553,25 @@ function getStoryStats(story, wordCountMethod = "tweego") {
|
|
|
545
553
|
}
|
|
546
554
|
return { passages: story.passages.length, storyPassages, words };
|
|
547
555
|
}
|
|
556
|
+
var StoryBuilder = class {
|
|
557
|
+
/** The mutable story, accessible during construction for loader functions. */
|
|
558
|
+
story;
|
|
559
|
+
constructor() {
|
|
560
|
+
this.story = createStory();
|
|
561
|
+
}
|
|
562
|
+
/** Add a passage, handling special passages (StoryData, StoryTitle, etc.). */
|
|
563
|
+
add(passage, diagnostics) {
|
|
564
|
+
storyAdd(this.story, passage, diagnostics);
|
|
565
|
+
}
|
|
566
|
+
/** Check if a passage name exists. */
|
|
567
|
+
has(name) {
|
|
568
|
+
return storyHas(this.story, name);
|
|
569
|
+
}
|
|
570
|
+
/** Finalize and return the story as read-only. */
|
|
571
|
+
build() {
|
|
572
|
+
return this.story;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
548
575
|
|
|
549
576
|
// src/filesystem.ts
|
|
550
577
|
var import_node_fs = require("fs");
|
|
@@ -968,6 +995,17 @@ function readFormatSource(format) {
|
|
|
968
995
|
var import_node_path5 = require("path");
|
|
969
996
|
var import_node_fs4 = require("fs");
|
|
970
997
|
|
|
998
|
+
// src/types.ts
|
|
999
|
+
var ItemType = {
|
|
1000
|
+
Error: 0,
|
|
1001
|
+
EOF: 1,
|
|
1002
|
+
Header: 2,
|
|
1003
|
+
Name: 3,
|
|
1004
|
+
Tags: 4,
|
|
1005
|
+
Metadata: 5,
|
|
1006
|
+
Content: 6
|
|
1007
|
+
};
|
|
1008
|
+
|
|
971
1009
|
// src/lexer.ts
|
|
972
1010
|
var EOF = -1;
|
|
973
1011
|
var HEADER_DELIM = "::";
|
|
@@ -1000,10 +1038,16 @@ var LexerContext = class {
|
|
|
1000
1038
|
throw new Error("backup would leave pos < start");
|
|
1001
1039
|
}
|
|
1002
1040
|
}
|
|
1041
|
+
// Line-counting invariant: next() increments this.line for each newline during
|
|
1042
|
+
// character-by-character scanning. However, Content items are produced by jumping
|
|
1043
|
+
// this.pos directly (e.g. via indexOf) without calling next(), so those newlines
|
|
1044
|
+
// haven't been counted yet. emit() re-counts newlines in [start, pos) for Content
|
|
1045
|
+
// items to compensate. This is safe because this.start is reset to this.pos after
|
|
1046
|
+
// every emit(), so no range is ever counted twice.
|
|
1003
1047
|
emit(type) {
|
|
1004
1048
|
const val = this.input.slice(this.start, this.pos);
|
|
1005
1049
|
this.items.push({ type, line: this.line, pos: this.start, val });
|
|
1006
|
-
if (type ===
|
|
1050
|
+
if (type === ItemType.Content) {
|
|
1007
1051
|
for (let i = this.start; i < this.pos; i++) {
|
|
1008
1052
|
if (this.input.charCodeAt(i) === 10) this.line++;
|
|
1009
1053
|
}
|
|
@@ -1024,7 +1068,7 @@ var LexerContext = class {
|
|
|
1024
1068
|
}
|
|
1025
1069
|
errorf(message) {
|
|
1026
1070
|
this.items.push({
|
|
1027
|
-
type:
|
|
1071
|
+
type: ItemType.Error,
|
|
1028
1072
|
line: this.line,
|
|
1029
1073
|
pos: this.start,
|
|
1030
1074
|
val: message
|
|
@@ -1066,7 +1110,7 @@ function lexProlog(ctx) {
|
|
|
1066
1110
|
ctx.ignore();
|
|
1067
1111
|
return lexHeaderDelim;
|
|
1068
1112
|
}
|
|
1069
|
-
ctx.emit(
|
|
1113
|
+
ctx.emit(ItemType.EOF);
|
|
1070
1114
|
return null;
|
|
1071
1115
|
}
|
|
1072
1116
|
function lexContent(ctx) {
|
|
@@ -1076,19 +1120,19 @@ function lexContent(ctx) {
|
|
|
1076
1120
|
const i = ctx.input.indexOf(NEWLINE_HEADER_DELIM, ctx.pos);
|
|
1077
1121
|
if (i > -1) {
|
|
1078
1122
|
ctx.pos = i + 1;
|
|
1079
|
-
ctx.emit(
|
|
1123
|
+
ctx.emit(ItemType.Content);
|
|
1080
1124
|
return lexHeaderDelim;
|
|
1081
1125
|
}
|
|
1082
1126
|
ctx.pos = ctx.input.length;
|
|
1083
1127
|
if (ctx.pos > ctx.start) {
|
|
1084
|
-
ctx.emit(
|
|
1128
|
+
ctx.emit(ItemType.Content);
|
|
1085
1129
|
}
|
|
1086
|
-
ctx.emit(
|
|
1130
|
+
ctx.emit(ItemType.EOF);
|
|
1087
1131
|
return null;
|
|
1088
1132
|
}
|
|
1089
1133
|
function lexHeaderDelim(ctx) {
|
|
1090
1134
|
ctx.pos += HEADER_DELIM.length;
|
|
1091
|
-
ctx.emit(
|
|
1135
|
+
ctx.emit(ItemType.Header);
|
|
1092
1136
|
return lexName;
|
|
1093
1137
|
}
|
|
1094
1138
|
function lexName(ctx) {
|
|
@@ -1117,7 +1161,7 @@ function lexName(ctx) {
|
|
|
1117
1161
|
break outer;
|
|
1118
1162
|
}
|
|
1119
1163
|
}
|
|
1120
|
-
ctx.emit(
|
|
1164
|
+
ctx.emit(ItemType.Name);
|
|
1121
1165
|
switch (r) {
|
|
1122
1166
|
case 91:
|
|
1123
1167
|
return lexTags;
|
|
@@ -1134,7 +1178,7 @@ function lexName(ctx) {
|
|
|
1134
1178
|
ctx.ignore();
|
|
1135
1179
|
return lexContent;
|
|
1136
1180
|
}
|
|
1137
|
-
ctx.emit(
|
|
1181
|
+
ctx.emit(ItemType.EOF);
|
|
1138
1182
|
return null;
|
|
1139
1183
|
}
|
|
1140
1184
|
function lexNextOptionalBlock(ctx) {
|
|
@@ -1157,7 +1201,7 @@ function lexNextOptionalBlock(ctx) {
|
|
|
1157
1201
|
ctx.ignore();
|
|
1158
1202
|
return lexContent;
|
|
1159
1203
|
case EOF:
|
|
1160
|
-
ctx.emit(
|
|
1204
|
+
ctx.emit(ItemType.EOF);
|
|
1161
1205
|
return null;
|
|
1162
1206
|
}
|
|
1163
1207
|
return ctx.errorf(`illegal character '${String.fromCharCode(r)}' amid the optional blocks`);
|
|
@@ -1177,7 +1221,7 @@ function lexTags(ctx) {
|
|
|
1177
1221
|
if (r === 10) ctx.backup();
|
|
1178
1222
|
return ctx.errorf("unterminated tag block");
|
|
1179
1223
|
case 93:
|
|
1180
|
-
if (ctx.pos > ctx.start) ctx.emit(
|
|
1224
|
+
if (ctx.pos > ctx.start) ctx.emit(ItemType.Tags);
|
|
1181
1225
|
return lexNextOptionalBlock;
|
|
1182
1226
|
case 91:
|
|
1183
1227
|
return ctx.errorf(`unexpected left square bracket '['`);
|
|
@@ -1210,7 +1254,7 @@ function lexMetadata(ctx) {
|
|
|
1210
1254
|
case 125:
|
|
1211
1255
|
depth--;
|
|
1212
1256
|
if (depth === 0) {
|
|
1213
|
-
if (ctx.pos > ctx.start) ctx.emit(
|
|
1257
|
+
if (ctx.pos > ctx.start) ctx.emit(ItemType.Metadata);
|
|
1214
1258
|
return lexNextOptionalBlock;
|
|
1215
1259
|
}
|
|
1216
1260
|
break;
|
|
@@ -1240,7 +1284,7 @@ var TweeLexer = class {
|
|
|
1240
1284
|
nextItem() {
|
|
1241
1285
|
const result = this.gen.next();
|
|
1242
1286
|
if (result.done) {
|
|
1243
|
-
return { item: { type:
|
|
1287
|
+
return { item: { type: ItemType.EOF, line: 0, pos: 0, val: "" }, done: true };
|
|
1244
1288
|
}
|
|
1245
1289
|
return { item: result.value, done: false };
|
|
1246
1290
|
}
|
|
@@ -1277,10 +1321,10 @@ function parseTwee(source, options = {}) {
|
|
|
1277
1321
|
const passages = [];
|
|
1278
1322
|
let current = null;
|
|
1279
1323
|
let pCount = 0;
|
|
1280
|
-
let lastType =
|
|
1324
|
+
let lastType = ItemType.EOF;
|
|
1281
1325
|
for (const item of tweeLexer(source)) {
|
|
1282
1326
|
switch (item.type) {
|
|
1283
|
-
case
|
|
1327
|
+
case ItemType.Error:
|
|
1284
1328
|
diagnostics.push({
|
|
1285
1329
|
level: "error",
|
|
1286
1330
|
message: `line ${item.line}: Malformed twee source; ${item.val}.`,
|
|
@@ -1288,19 +1332,19 @@ function parseTwee(source, options = {}) {
|
|
|
1288
1332
|
line: item.line
|
|
1289
1333
|
});
|
|
1290
1334
|
return { passages, diagnostics };
|
|
1291
|
-
case
|
|
1335
|
+
case ItemType.EOF:
|
|
1292
1336
|
if (pCount > 0 && current) {
|
|
1293
1337
|
passages.push(current);
|
|
1294
1338
|
}
|
|
1295
1339
|
return { passages, diagnostics };
|
|
1296
|
-
case
|
|
1340
|
+
case ItemType.Header:
|
|
1297
1341
|
pCount++;
|
|
1298
1342
|
if (pCount > 1 && current) {
|
|
1299
1343
|
passages.push(current);
|
|
1300
1344
|
}
|
|
1301
1345
|
current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
|
|
1302
1346
|
break;
|
|
1303
|
-
case
|
|
1347
|
+
case ItemType.Name: {
|
|
1304
1348
|
if (!current) break;
|
|
1305
1349
|
const name = tweeUnescape(item.val).trim();
|
|
1306
1350
|
if (name.length === 0) {
|
|
@@ -1315,9 +1359,9 @@ function parseTwee(source, options = {}) {
|
|
|
1315
1359
|
current.name = name;
|
|
1316
1360
|
break;
|
|
1317
1361
|
}
|
|
1318
|
-
case
|
|
1362
|
+
case ItemType.Tags: {
|
|
1319
1363
|
if (!current) break;
|
|
1320
|
-
if (lastType !==
|
|
1364
|
+
if (lastType !== ItemType.Name) {
|
|
1321
1365
|
diagnostics.push({
|
|
1322
1366
|
level: "error",
|
|
1323
1367
|
message: `line ${item.line}: Malformed twee source; optional tags block must immediately follow the passage name.`,
|
|
@@ -1330,9 +1374,9 @@ function parseTwee(source, options = {}) {
|
|
|
1330
1374
|
current.tags = tweeUnescape(inner).split(/\s+/).filter(Boolean);
|
|
1331
1375
|
break;
|
|
1332
1376
|
}
|
|
1333
|
-
case
|
|
1377
|
+
case ItemType.Metadata: {
|
|
1334
1378
|
if (!current) break;
|
|
1335
|
-
if (lastType !==
|
|
1379
|
+
if (lastType !== ItemType.Name && lastType !== ItemType.Tags) {
|
|
1336
1380
|
diagnostics.push({
|
|
1337
1381
|
level: "error",
|
|
1338
1382
|
message: `line ${item.line}: Malformed twee source; optional metadata block must immediately follow the passage name or tags block.`,
|
|
@@ -1364,7 +1408,7 @@ function parseTwee(source, options = {}) {
|
|
|
1364
1408
|
}
|
|
1365
1409
|
break;
|
|
1366
1410
|
}
|
|
1367
|
-
case
|
|
1411
|
+
case ItemType.Content: {
|
|
1368
1412
|
if (!current) break;
|
|
1369
1413
|
if (trim) {
|
|
1370
1414
|
current.text = item.val.trim();
|
|
@@ -4494,21 +4538,20 @@ var VERSION = "1.2.0";
|
|
|
4494
4538
|
|
|
4495
4539
|
// src/output-twine2.ts
|
|
4496
4540
|
var CREATOR_NAME = "Twee-ts";
|
|
4497
|
-
function toTwine2Archive(story, startName,
|
|
4498
|
-
return getTwine2DataChunk(story, startName,
|
|
4541
|
+
function toTwine2Archive(story, startName, options) {
|
|
4542
|
+
return getTwine2DataChunk(story, startName, options) + "\n";
|
|
4499
4543
|
}
|
|
4500
|
-
function toTwine2HTML(story, format, startName,
|
|
4544
|
+
function toTwine2HTML(story, format, startName, options) {
|
|
4501
4545
|
let template = readFormatSource(format);
|
|
4502
4546
|
if (template.includes("{{STORY_NAME}}")) {
|
|
4503
4547
|
template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
|
|
4504
4548
|
}
|
|
4505
4549
|
if (template.includes("{{STORY_DATA}}")) {
|
|
4506
|
-
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName,
|
|
4550
|
+
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, options));
|
|
4507
4551
|
}
|
|
4508
4552
|
return template;
|
|
4509
4553
|
}
|
|
4510
|
-
function getTwine2DataChunk(story, startName,
|
|
4511
|
-
ensureIFID(story, diagnostics);
|
|
4554
|
+
function getTwine2DataChunk(story, startName, options) {
|
|
4512
4555
|
const parts = [];
|
|
4513
4556
|
let startID = "";
|
|
4514
4557
|
let pid = 0;
|
|
@@ -4577,23 +4620,6 @@ function getTwine2DataChunk(story, startName, diagnostics, options) {
|
|
|
4577
4620
|
const wrapper = `<!-- UUID://${htmlCommentSanitize(story.ifid)}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${attrEscape(startID)}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(optionsStr)}" tags="${attrEscape(story.twine2.tags)}" hidden>`;
|
|
4578
4621
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
4579
4622
|
}
|
|
4580
|
-
function ensureIFID(story, diagnostics) {
|
|
4581
|
-
if (story.ifid !== "") return;
|
|
4582
|
-
if (story.legacyIFID !== "") {
|
|
4583
|
-
story.ifid = story.legacyIFID;
|
|
4584
|
-
diagnostics.push({
|
|
4585
|
-
level: "warning",
|
|
4586
|
-
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
4587
|
-
});
|
|
4588
|
-
} else {
|
|
4589
|
-
const ifid = generateIFID();
|
|
4590
|
-
story.ifid = ifid;
|
|
4591
|
-
diagnostics.push({
|
|
4592
|
-
level: "error",
|
|
4593
|
-
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
4594
|
-
});
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
4623
|
|
|
4598
4624
|
// src/output-twine1.ts
|
|
4599
4625
|
var import_node_fs5 = require("fs");
|
|
@@ -4604,7 +4630,7 @@ function toTwine1Archive(story, _startName) {
|
|
|
4604
4630
|
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
4605
4631
|
`;
|
|
4606
4632
|
}
|
|
4607
|
-
function toTwine1HTML(story, format, startName
|
|
4633
|
+
function toTwine1HTML(story, format, startName) {
|
|
4608
4634
|
const formatDir = (0, import_node_path6.dirname)(format.filename);
|
|
4609
4635
|
const parentDir = (0, import_node_path6.dirname)(formatDir);
|
|
4610
4636
|
let template = readFormatSource(format);
|
|
@@ -5161,6 +5187,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5161
5187
|
if (testMode) {
|
|
5162
5188
|
story.twine2.options.set("debug", true);
|
|
5163
5189
|
}
|
|
5190
|
+
ensureIFID(story, diagnostics);
|
|
5164
5191
|
let output;
|
|
5165
5192
|
switch (outputMode) {
|
|
5166
5193
|
case "twee3":
|
|
@@ -5168,7 +5195,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5168
5195
|
output = toTwee(story, outputMode);
|
|
5169
5196
|
break;
|
|
5170
5197
|
case "twine2-archive":
|
|
5171
|
-
output = toTwine2Archive(story, startName,
|
|
5198
|
+
output = toTwine2Archive(story, startName, { sourceInfo });
|
|
5172
5199
|
break;
|
|
5173
5200
|
case "twine1-archive":
|
|
5174
5201
|
output = toTwine1Archive(story, startName);
|
|
@@ -5187,7 +5214,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5187
5214
|
throw new TweeTsError("No story format available for HTML output.", diagnostics);
|
|
5188
5215
|
}
|
|
5189
5216
|
if (format.isTwine2) {
|
|
5190
|
-
output = toTwine2HTML(story, format, startName,
|
|
5217
|
+
output = toTwine2HTML(story, format, startName, { sourceInfo });
|
|
5191
5218
|
} else {
|
|
5192
5219
|
if (story.name === "" && !storyHas(story, "StoryTitle")) {
|
|
5193
5220
|
diagnostics.push({
|
|
@@ -5195,7 +5222,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5195
5222
|
message: 'Special passage "StoryTitle" not found.'
|
|
5196
5223
|
});
|
|
5197
5224
|
}
|
|
5198
|
-
output = toTwine1HTML(story, format, startName
|
|
5225
|
+
output = toTwine1HTML(story, format, startName);
|
|
5199
5226
|
}
|
|
5200
5227
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
5201
5228
|
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
@@ -5212,6 +5239,23 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5212
5239
|
};
|
|
5213
5240
|
return { output, story, format, diagnostics, stats };
|
|
5214
5241
|
}
|
|
5242
|
+
function ensureIFID(story, diagnostics) {
|
|
5243
|
+
if (story.ifid !== "") return;
|
|
5244
|
+
if (story.legacyIFID !== "") {
|
|
5245
|
+
story.ifid = story.legacyIFID;
|
|
5246
|
+
diagnostics.push({
|
|
5247
|
+
level: "warning",
|
|
5248
|
+
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
5249
|
+
});
|
|
5250
|
+
} else {
|
|
5251
|
+
const ifid = generateIFID();
|
|
5252
|
+
story.ifid = ifid;
|
|
5253
|
+
diagnostics.push({
|
|
5254
|
+
level: "error",
|
|
5255
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5215
5259
|
function storyToJSON(story) {
|
|
5216
5260
|
const scripts = [];
|
|
5217
5261
|
const stylesheets = [];
|
|
@@ -5533,6 +5577,8 @@ function scaffoldConfig() {
|
|
|
5533
5577
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5534
5578
|
0 && (module.exports = {
|
|
5535
5579
|
CONFIG_FILENAME,
|
|
5580
|
+
ItemType,
|
|
5581
|
+
StoryBuilder,
|
|
5536
5582
|
TweeLexer,
|
|
5537
5583
|
TweeTsError,
|
|
5538
5584
|
applyTagAliases,
|
|
@@ -5540,6 +5586,7 @@ function scaffoldConfig() {
|
|
|
5540
5586
|
compile,
|
|
5541
5587
|
compileIncremental,
|
|
5542
5588
|
compileToFile,
|
|
5589
|
+
createIFID,
|
|
5543
5590
|
decompileHTML,
|
|
5544
5591
|
discoverCachedFormats,
|
|
5545
5592
|
discoverFormats,
|