@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.
@@ -917,6 +917,17 @@ function readFormatSource(format) {
917
917
  import { basename } from "path";
918
918
  import { statSync as statSync3 } from "fs";
919
919
 
920
+ // src/types.ts
921
+ var ItemType = {
922
+ Error: 0,
923
+ EOF: 1,
924
+ Header: 2,
925
+ Name: 3,
926
+ Tags: 4,
927
+ Metadata: 5,
928
+ Content: 6
929
+ };
930
+
920
931
  // src/lexer.ts
921
932
  var EOF = -1;
922
933
  var HEADER_DELIM = "::";
@@ -949,10 +960,16 @@ var LexerContext = class {
949
960
  throw new Error("backup would leave pos < start");
950
961
  }
951
962
  }
963
+ // Line-counting invariant: next() increments this.line for each newline during
964
+ // character-by-character scanning. However, Content items are produced by jumping
965
+ // this.pos directly (e.g. via indexOf) without calling next(), so those newlines
966
+ // haven't been counted yet. emit() re-counts newlines in [start, pos) for Content
967
+ // items to compensate. This is safe because this.start is reset to this.pos after
968
+ // every emit(), so no range is ever counted twice.
952
969
  emit(type) {
953
970
  const val = this.input.slice(this.start, this.pos);
954
971
  this.items.push({ type, line: this.line, pos: this.start, val });
955
- if (type === 6 /* Content */) {
972
+ if (type === ItemType.Content) {
956
973
  for (let i = this.start; i < this.pos; i++) {
957
974
  if (this.input.charCodeAt(i) === 10) this.line++;
958
975
  }
@@ -973,7 +990,7 @@ var LexerContext = class {
973
990
  }
974
991
  errorf(message) {
975
992
  this.items.push({
976
- type: 0 /* Error */,
993
+ type: ItemType.Error,
977
994
  line: this.line,
978
995
  pos: this.start,
979
996
  val: message
@@ -1015,7 +1032,7 @@ function lexProlog(ctx) {
1015
1032
  ctx.ignore();
1016
1033
  return lexHeaderDelim;
1017
1034
  }
1018
- ctx.emit(1 /* EOF */);
1035
+ ctx.emit(ItemType.EOF);
1019
1036
  return null;
1020
1037
  }
1021
1038
  function lexContent(ctx) {
@@ -1025,19 +1042,19 @@ function lexContent(ctx) {
1025
1042
  const i = ctx.input.indexOf(NEWLINE_HEADER_DELIM, ctx.pos);
1026
1043
  if (i > -1) {
1027
1044
  ctx.pos = i + 1;
1028
- ctx.emit(6 /* Content */);
1045
+ ctx.emit(ItemType.Content);
1029
1046
  return lexHeaderDelim;
1030
1047
  }
1031
1048
  ctx.pos = ctx.input.length;
1032
1049
  if (ctx.pos > ctx.start) {
1033
- ctx.emit(6 /* Content */);
1050
+ ctx.emit(ItemType.Content);
1034
1051
  }
1035
- ctx.emit(1 /* EOF */);
1052
+ ctx.emit(ItemType.EOF);
1036
1053
  return null;
1037
1054
  }
1038
1055
  function lexHeaderDelim(ctx) {
1039
1056
  ctx.pos += HEADER_DELIM.length;
1040
- ctx.emit(2 /* Header */);
1057
+ ctx.emit(ItemType.Header);
1041
1058
  return lexName;
1042
1059
  }
1043
1060
  function lexName(ctx) {
@@ -1066,7 +1083,7 @@ function lexName(ctx) {
1066
1083
  break outer;
1067
1084
  }
1068
1085
  }
1069
- ctx.emit(3 /* Name */);
1086
+ ctx.emit(ItemType.Name);
1070
1087
  switch (r) {
1071
1088
  case 91:
1072
1089
  return lexTags;
@@ -1083,7 +1100,7 @@ function lexName(ctx) {
1083
1100
  ctx.ignore();
1084
1101
  return lexContent;
1085
1102
  }
1086
- ctx.emit(1 /* EOF */);
1103
+ ctx.emit(ItemType.EOF);
1087
1104
  return null;
1088
1105
  }
1089
1106
  function lexNextOptionalBlock(ctx) {
@@ -1106,7 +1123,7 @@ function lexNextOptionalBlock(ctx) {
1106
1123
  ctx.ignore();
1107
1124
  return lexContent;
1108
1125
  case EOF:
1109
- ctx.emit(1 /* EOF */);
1126
+ ctx.emit(ItemType.EOF);
1110
1127
  return null;
1111
1128
  }
1112
1129
  return ctx.errorf(`illegal character '${String.fromCharCode(r)}' amid the optional blocks`);
@@ -1126,7 +1143,7 @@ function lexTags(ctx) {
1126
1143
  if (r === 10) ctx.backup();
1127
1144
  return ctx.errorf("unterminated tag block");
1128
1145
  case 93:
1129
- if (ctx.pos > ctx.start) ctx.emit(4 /* Tags */);
1146
+ if (ctx.pos > ctx.start) ctx.emit(ItemType.Tags);
1130
1147
  return lexNextOptionalBlock;
1131
1148
  case 91:
1132
1149
  return ctx.errorf(`unexpected left square bracket '['`);
@@ -1159,7 +1176,7 @@ function lexMetadata(ctx) {
1159
1176
  case 125:
1160
1177
  depth--;
1161
1178
  if (depth === 0) {
1162
- if (ctx.pos > ctx.start) ctx.emit(5 /* Metadata */);
1179
+ if (ctx.pos > ctx.start) ctx.emit(ItemType.Metadata);
1163
1180
  return lexNextOptionalBlock;
1164
1181
  }
1165
1182
  break;
@@ -1210,10 +1227,10 @@ function parseTwee(source, options = {}) {
1210
1227
  const passages = [];
1211
1228
  let current = null;
1212
1229
  let pCount = 0;
1213
- let lastType = 1 /* EOF */;
1230
+ let lastType = ItemType.EOF;
1214
1231
  for (const item of tweeLexer(source)) {
1215
1232
  switch (item.type) {
1216
- case 0 /* Error */:
1233
+ case ItemType.Error:
1217
1234
  diagnostics.push({
1218
1235
  level: "error",
1219
1236
  message: `line ${item.line}: Malformed twee source; ${item.val}.`,
@@ -1221,19 +1238,19 @@ function parseTwee(source, options = {}) {
1221
1238
  line: item.line
1222
1239
  });
1223
1240
  return { passages, diagnostics };
1224
- case 1 /* EOF */:
1241
+ case ItemType.EOF:
1225
1242
  if (pCount > 0 && current) {
1226
1243
  passages.push(current);
1227
1244
  }
1228
1245
  return { passages, diagnostics };
1229
- case 2 /* Header */:
1246
+ case ItemType.Header:
1230
1247
  pCount++;
1231
1248
  if (pCount > 1 && current) {
1232
1249
  passages.push(current);
1233
1250
  }
1234
1251
  current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
1235
1252
  break;
1236
- case 3 /* Name */: {
1253
+ case ItemType.Name: {
1237
1254
  if (!current) break;
1238
1255
  const name = tweeUnescape(item.val).trim();
1239
1256
  if (name.length === 0) {
@@ -1248,9 +1265,9 @@ function parseTwee(source, options = {}) {
1248
1265
  current.name = name;
1249
1266
  break;
1250
1267
  }
1251
- case 4 /* Tags */: {
1268
+ case ItemType.Tags: {
1252
1269
  if (!current) break;
1253
- if (lastType !== 3 /* Name */) {
1270
+ if (lastType !== ItemType.Name) {
1254
1271
  diagnostics.push({
1255
1272
  level: "error",
1256
1273
  message: `line ${item.line}: Malformed twee source; optional tags block must immediately follow the passage name.`,
@@ -1263,9 +1280,9 @@ function parseTwee(source, options = {}) {
1263
1280
  current.tags = tweeUnescape(inner).split(/\s+/).filter(Boolean);
1264
1281
  break;
1265
1282
  }
1266
- case 5 /* Metadata */: {
1283
+ case ItemType.Metadata: {
1267
1284
  if (!current) break;
1268
- if (lastType !== 3 /* Name */ && lastType !== 4 /* Tags */) {
1285
+ if (lastType !== ItemType.Name && lastType !== ItemType.Tags) {
1269
1286
  diagnostics.push({
1270
1287
  level: "error",
1271
1288
  message: `line ${item.line}: Malformed twee source; optional metadata block must immediately follow the passage name or tags block.`,
@@ -1297,7 +1314,7 @@ function parseTwee(source, options = {}) {
1297
1314
  }
1298
1315
  break;
1299
1316
  }
1300
- case 6 /* Content */: {
1317
+ case ItemType.Content: {
1301
1318
  if (!current) break;
1302
1319
  if (trim) {
1303
1320
  current.text = item.val.trim();
@@ -4427,21 +4444,20 @@ var VERSION = "1.2.0";
4427
4444
 
4428
4445
  // src/output-twine2.ts
4429
4446
  var CREATOR_NAME = "Twee-ts";
4430
- function toTwine2Archive(story, startName, diagnostics, options) {
4431
- return getTwine2DataChunk(story, startName, diagnostics, options) + "\n";
4447
+ function toTwine2Archive(story, startName, options) {
4448
+ return getTwine2DataChunk(story, startName, options) + "\n";
4432
4449
  }
4433
- function toTwine2HTML(story, format, startName, diagnostics, options) {
4450
+ function toTwine2HTML(story, format, startName, options) {
4434
4451
  let template = readFormatSource(format);
4435
4452
  if (template.includes("{{STORY_NAME}}")) {
4436
4453
  template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
4437
4454
  }
4438
4455
  if (template.includes("{{STORY_DATA}}")) {
4439
- template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics, options));
4456
+ template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, options));
4440
4457
  }
4441
4458
  return template;
4442
4459
  }
4443
- function getTwine2DataChunk(story, startName, diagnostics, options) {
4444
- ensureIFID(story, diagnostics);
4460
+ function getTwine2DataChunk(story, startName, options) {
4445
4461
  const parts = [];
4446
4462
  let startID = "";
4447
4463
  let pid = 0;
@@ -4510,23 +4526,6 @@ function getTwine2DataChunk(story, startName, diagnostics, options) {
4510
4526
  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>`;
4511
4527
  return wrapper + parts.join("") + "</tw-storydata>";
4512
4528
  }
4513
- function ensureIFID(story, diagnostics) {
4514
- if (story.ifid !== "") return;
4515
- if (story.legacyIFID !== "") {
4516
- story.ifid = story.legacyIFID;
4517
- diagnostics.push({
4518
- level: "warning",
4519
- message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
4520
- });
4521
- } else {
4522
- const ifid = generateIFID();
4523
- story.ifid = ifid;
4524
- diagnostics.push({
4525
- level: "error",
4526
- message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
4527
- });
4528
- }
4529
- }
4530
4529
 
4531
4530
  // src/output-twine1.ts
4532
4531
  import { readFileSync as readFileSync2 } from "fs";
@@ -4537,7 +4536,7 @@ function toTwine1Archive(story, _startName) {
4537
4536
  return `<div id="storeArea" data-size="${count}">${data}</div>
4538
4537
  `;
4539
4538
  }
4540
- function toTwine1HTML(story, format, startName, _diagnostics) {
4539
+ function toTwine1HTML(story, format, startName) {
4541
4540
  const formatDir = dirname(format.filename);
4542
4541
  const parentDir = dirname(formatDir);
4543
4542
  let template = readFormatSource(format);
@@ -5091,6 +5090,7 @@ async function buildOutput(options, cache, changedFiles) {
5091
5090
  if (testMode) {
5092
5091
  story.twine2.options.set("debug", true);
5093
5092
  }
5093
+ ensureIFID(story, diagnostics);
5094
5094
  let output;
5095
5095
  switch (outputMode) {
5096
5096
  case "twee3":
@@ -5098,7 +5098,7 @@ async function buildOutput(options, cache, changedFiles) {
5098
5098
  output = toTwee(story, outputMode);
5099
5099
  break;
5100
5100
  case "twine2-archive":
5101
- output = toTwine2Archive(story, startName, diagnostics, { sourceInfo });
5101
+ output = toTwine2Archive(story, startName, { sourceInfo });
5102
5102
  break;
5103
5103
  case "twine1-archive":
5104
5104
  output = toTwine1Archive(story, startName);
@@ -5117,7 +5117,7 @@ async function buildOutput(options, cache, changedFiles) {
5117
5117
  throw new TweeTsError("No story format available for HTML output.", diagnostics);
5118
5118
  }
5119
5119
  if (format.isTwine2) {
5120
- output = toTwine2HTML(story, format, startName, diagnostics, { sourceInfo });
5120
+ output = toTwine2HTML(story, format, startName, { sourceInfo });
5121
5121
  } else {
5122
5122
  if (story.name === "" && !storyHas(story, "StoryTitle")) {
5123
5123
  diagnostics.push({
@@ -5125,7 +5125,7 @@ async function buildOutput(options, cache, changedFiles) {
5125
5125
  message: 'Special passage "StoryTitle" not found.'
5126
5126
  });
5127
5127
  }
5128
- output = toTwine1HTML(story, format, startName, diagnostics);
5128
+ output = toTwine1HTML(story, format, startName);
5129
5129
  }
5130
5130
  const modulePaths = options.modules ? getFilenames(options.modules) : [];
5131
5131
  output = modifyHead(output, modulePaths, options.headFile, diagnostics);
@@ -5142,6 +5142,23 @@ async function buildOutput(options, cache, changedFiles) {
5142
5142
  };
5143
5143
  return { output, story, format, diagnostics, stats };
5144
5144
  }
5145
+ function ensureIFID(story, diagnostics) {
5146
+ if (story.ifid !== "") return;
5147
+ if (story.legacyIFID !== "") {
5148
+ story.ifid = story.legacyIFID;
5149
+ diagnostics.push({
5150
+ level: "warning",
5151
+ message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
5152
+ });
5153
+ } else {
5154
+ const ifid = generateIFID();
5155
+ story.ifid = ifid;
5156
+ diagnostics.push({
5157
+ level: "error",
5158
+ message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
5159
+ });
5160
+ }
5161
+ }
5145
5162
  function storyToJSON(story) {
5146
5163
  const scripts = [];
5147
5164
  const stylesheets = [];