@rohal12/twee-ts 1.12.0 → 1.13.1

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.
@@ -828,6 +828,17 @@ function readFormatSource(format) {
828
828
  import { basename } from "path";
829
829
  import { statSync as statSync3 } from "fs";
830
830
 
831
+ // src/types.ts
832
+ var ItemType = {
833
+ Error: 0,
834
+ EOF: 1,
835
+ Header: 2,
836
+ Name: 3,
837
+ Tags: 4,
838
+ Metadata: 5,
839
+ Content: 6
840
+ };
841
+
831
842
  // src/lexer.ts
832
843
  var EOF = -1;
833
844
  var HEADER_DELIM = "::";
@@ -860,10 +871,16 @@ var LexerContext = class {
860
871
  throw new Error("backup would leave pos < start");
861
872
  }
862
873
  }
874
+ // Line-counting invariant: next() increments this.line for each newline during
875
+ // character-by-character scanning. However, Content items are produced by jumping
876
+ // this.pos directly (e.g. via indexOf) without calling next(), so those newlines
877
+ // haven't been counted yet. emit() re-counts newlines in [start, pos) for Content
878
+ // items to compensate. This is safe because this.start is reset to this.pos after
879
+ // every emit(), so no range is ever counted twice.
863
880
  emit(type) {
864
881
  const val = this.input.slice(this.start, this.pos);
865
882
  this.items.push({ type, line: this.line, pos: this.start, val });
866
- if (type === 6 /* Content */) {
883
+ if (type === ItemType.Content) {
867
884
  for (let i = this.start; i < this.pos; i++) {
868
885
  if (this.input.charCodeAt(i) === 10) this.line++;
869
886
  }
@@ -884,7 +901,7 @@ var LexerContext = class {
884
901
  }
885
902
  errorf(message) {
886
903
  this.items.push({
887
- type: 0 /* Error */,
904
+ type: ItemType.Error,
888
905
  line: this.line,
889
906
  pos: this.start,
890
907
  val: message
@@ -926,7 +943,7 @@ function lexProlog(ctx) {
926
943
  ctx.ignore();
927
944
  return lexHeaderDelim;
928
945
  }
929
- ctx.emit(1 /* EOF */);
946
+ ctx.emit(ItemType.EOF);
930
947
  return null;
931
948
  }
932
949
  function lexContent(ctx) {
@@ -936,19 +953,19 @@ function lexContent(ctx) {
936
953
  const i = ctx.input.indexOf(NEWLINE_HEADER_DELIM, ctx.pos);
937
954
  if (i > -1) {
938
955
  ctx.pos = i + 1;
939
- ctx.emit(6 /* Content */);
956
+ ctx.emit(ItemType.Content);
940
957
  return lexHeaderDelim;
941
958
  }
942
959
  ctx.pos = ctx.input.length;
943
960
  if (ctx.pos > ctx.start) {
944
- ctx.emit(6 /* Content */);
961
+ ctx.emit(ItemType.Content);
945
962
  }
946
- ctx.emit(1 /* EOF */);
963
+ ctx.emit(ItemType.EOF);
947
964
  return null;
948
965
  }
949
966
  function lexHeaderDelim(ctx) {
950
967
  ctx.pos += HEADER_DELIM.length;
951
- ctx.emit(2 /* Header */);
968
+ ctx.emit(ItemType.Header);
952
969
  return lexName;
953
970
  }
954
971
  function lexName(ctx) {
@@ -977,7 +994,7 @@ function lexName(ctx) {
977
994
  break outer;
978
995
  }
979
996
  }
980
- ctx.emit(3 /* Name */);
997
+ ctx.emit(ItemType.Name);
981
998
  switch (r) {
982
999
  case 91:
983
1000
  return lexTags;
@@ -994,7 +1011,7 @@ function lexName(ctx) {
994
1011
  ctx.ignore();
995
1012
  return lexContent;
996
1013
  }
997
- ctx.emit(1 /* EOF */);
1014
+ ctx.emit(ItemType.EOF);
998
1015
  return null;
999
1016
  }
1000
1017
  function lexNextOptionalBlock(ctx) {
@@ -1017,7 +1034,7 @@ function lexNextOptionalBlock(ctx) {
1017
1034
  ctx.ignore();
1018
1035
  return lexContent;
1019
1036
  case EOF:
1020
- ctx.emit(1 /* EOF */);
1037
+ ctx.emit(ItemType.EOF);
1021
1038
  return null;
1022
1039
  }
1023
1040
  return ctx.errorf(`illegal character '${String.fromCharCode(r)}' amid the optional blocks`);
@@ -1037,7 +1054,7 @@ function lexTags(ctx) {
1037
1054
  if (r === 10) ctx.backup();
1038
1055
  return ctx.errorf("unterminated tag block");
1039
1056
  case 93:
1040
- if (ctx.pos > ctx.start) ctx.emit(4 /* Tags */);
1057
+ if (ctx.pos > ctx.start) ctx.emit(ItemType.Tags);
1041
1058
  return lexNextOptionalBlock;
1042
1059
  case 91:
1043
1060
  return ctx.errorf(`unexpected left square bracket '['`);
@@ -1070,7 +1087,7 @@ function lexMetadata(ctx) {
1070
1087
  case 125:
1071
1088
  depth--;
1072
1089
  if (depth === 0) {
1073
- if (ctx.pos > ctx.start) ctx.emit(5 /* Metadata */);
1090
+ if (ctx.pos > ctx.start) ctx.emit(ItemType.Metadata);
1074
1091
  return lexNextOptionalBlock;
1075
1092
  }
1076
1093
  break;
@@ -1121,10 +1138,10 @@ function parseTwee(source, options = {}) {
1121
1138
  const passages = [];
1122
1139
  let current = null;
1123
1140
  let pCount = 0;
1124
- let lastType = 1 /* EOF */;
1141
+ let lastType = ItemType.EOF;
1125
1142
  for (const item of tweeLexer(source)) {
1126
1143
  switch (item.type) {
1127
- case 0 /* Error */:
1144
+ case ItemType.Error:
1128
1145
  diagnostics.push({
1129
1146
  level: "error",
1130
1147
  message: `line ${item.line}: Malformed twee source; ${item.val}.`,
@@ -1132,19 +1149,19 @@ function parseTwee(source, options = {}) {
1132
1149
  line: item.line
1133
1150
  });
1134
1151
  return { passages, diagnostics };
1135
- case 1 /* EOF */:
1152
+ case ItemType.EOF:
1136
1153
  if (pCount > 0 && current) {
1137
1154
  passages.push(current);
1138
1155
  }
1139
1156
  return { passages, diagnostics };
1140
- case 2 /* Header */:
1157
+ case ItemType.Header:
1141
1158
  pCount++;
1142
1159
  if (pCount > 1 && current) {
1143
1160
  passages.push(current);
1144
1161
  }
1145
1162
  current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
1146
1163
  break;
1147
- case 3 /* Name */: {
1164
+ case ItemType.Name: {
1148
1165
  if (!current) break;
1149
1166
  const name = tweeUnescape(item.val).trim();
1150
1167
  if (name.length === 0) {
@@ -1159,9 +1176,9 @@ function parseTwee(source, options = {}) {
1159
1176
  current.name = name;
1160
1177
  break;
1161
1178
  }
1162
- case 4 /* Tags */: {
1179
+ case ItemType.Tags: {
1163
1180
  if (!current) break;
1164
- if (lastType !== 3 /* Name */) {
1181
+ if (lastType !== ItemType.Name) {
1165
1182
  diagnostics.push({
1166
1183
  level: "error",
1167
1184
  message: `line ${item.line}: Malformed twee source; optional tags block must immediately follow the passage name.`,
@@ -1174,9 +1191,9 @@ function parseTwee(source, options = {}) {
1174
1191
  current.tags = tweeUnescape(inner).split(/\s+/).filter(Boolean);
1175
1192
  break;
1176
1193
  }
1177
- case 5 /* Metadata */: {
1194
+ case ItemType.Metadata: {
1178
1195
  if (!current) break;
1179
- if (lastType !== 3 /* Name */ && lastType !== 4 /* Tags */) {
1196
+ if (lastType !== ItemType.Name && lastType !== ItemType.Tags) {
1180
1197
  diagnostics.push({
1181
1198
  level: "error",
1182
1199
  message: `line ${item.line}: Malformed twee source; optional metadata block must immediately follow the passage name or tags block.`,
@@ -1208,7 +1225,7 @@ function parseTwee(source, options = {}) {
1208
1225
  }
1209
1226
  break;
1210
1227
  }
1211
- case 6 /* Content */: {
1228
+ case ItemType.Content: {
1212
1229
  if (!current) break;
1213
1230
  if (trim) {
1214
1231
  current.text = item.val.trim();
@@ -4338,21 +4355,22 @@ var VERSION = "1.2.0";
4338
4355
 
4339
4356
  // src/output-twine2.ts
4340
4357
  var CREATOR_NAME = "Twee-ts";
4341
- function toTwine2Archive(story, startName, diagnostics, options) {
4342
- return getTwine2DataChunk(story, startName, diagnostics, options) + "\n";
4358
+ function toTwine2Archive(story, startName, options) {
4359
+ return getTwine2DataChunk(story, startName, options) + "\n";
4343
4360
  }
4344
- function toTwine2HTML(story, format, startName, diagnostics, options) {
4361
+ function toTwine2HTML(story, format, startName, options) {
4345
4362
  let template = readFormatSource(format);
4346
4363
  if (template.includes("{{STORY_NAME}}")) {
4347
- template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
4364
+ const name = htmlEscape(story.name);
4365
+ template = template.replaceAll("{{STORY_NAME}}", () => name);
4348
4366
  }
4349
4367
  if (template.includes("{{STORY_DATA}}")) {
4350
- template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics, options));
4368
+ const data = getTwine2DataChunk(story, startName, options);
4369
+ template = template.replace("{{STORY_DATA}}", () => data);
4351
4370
  }
4352
4371
  return template;
4353
4372
  }
4354
- function getTwine2DataChunk(story, startName, diagnostics, options) {
4355
- ensureIFID(story, diagnostics);
4373
+ function getTwine2DataChunk(story, startName, options) {
4356
4374
  const parts = [];
4357
4375
  let startID = "";
4358
4376
  let pid = 0;
@@ -4421,23 +4439,6 @@ function getTwine2DataChunk(story, startName, diagnostics, options) {
4421
4439
  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>`;
4422
4440
  return wrapper + parts.join("") + "</tw-storydata>";
4423
4441
  }
4424
- function ensureIFID(story, diagnostics) {
4425
- if (story.ifid !== "") return;
4426
- if (story.legacyIFID !== "") {
4427
- story.ifid = story.legacyIFID;
4428
- diagnostics.push({
4429
- level: "warning",
4430
- message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
4431
- });
4432
- } else {
4433
- const ifid = generateIFID();
4434
- story.ifid = ifid;
4435
- diagnostics.push({
4436
- level: "error",
4437
- message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
4438
- });
4439
- }
4440
- }
4441
4442
 
4442
4443
  // src/output-twine1.ts
4443
4444
  import { readFileSync as readFileSync2 } from "fs";
@@ -4448,7 +4449,7 @@ function toTwine1Archive(story, _startName) {
4448
4449
  return `<div id="storeArea" data-size="${count}">${data}</div>
4449
4450
  `;
4450
4451
  }
4451
- function toTwine1HTML(story, format, startName, _diagnostics) {
4452
+ function toTwine1HTML(story, format, startName) {
4452
4453
  const formatDir = dirname(format.filename);
4453
4454
  const parentDir = dirname(formatDir);
4454
4455
  let template = readFormatSource(format);
@@ -4466,10 +4467,11 @@ function toTwine1HTML(story, format, startName, _diagnostics) {
4466
4467
  const displayStart = startName === "Start" ? "" : startName;
4467
4468
  template = template.replace('"VERSION"', `Compiled with ${CREATOR_NAME2}, ${VERSION}`);
4468
4469
  template = template.replace('"TIME"', `Built on ${(/* @__PURE__ */ new Date()).toUTCString()}`);
4469
- template = template.replace('"START_AT"', `"${jsStringEscape(displayStart)}"`);
4470
+ const startAtValue = `"${jsStringEscape(displayStart)}"`;
4471
+ template = template.replace('"START_AT"', () => startAtValue);
4470
4472
  template = template.replace('"STORY_SIZE"', `"${count}"`);
4471
4473
  if (template.includes('"STORY"')) {
4472
- template = template.replace('"STORY"', data);
4474
+ template = template.replace('"STORY"', () => data);
4473
4475
  } else {
4474
4476
  let footer;
4475
4477
  try {
@@ -4505,7 +4507,7 @@ function tryReplaceComponent(template, placeholder, componentPath, required) {
4505
4507
  try {
4506
4508
  let content = readFileSync2(componentPath, "utf-8");
4507
4509
  if (content.charCodeAt(0) === 65279) content = content.slice(1);
4508
- return template.replace(placeholder, content);
4510
+ return template.replace(placeholder, () => content);
4509
4511
  } catch (e) {
4510
4512
  if (required) {
4511
4513
  throw new Error(
@@ -4593,7 +4595,8 @@ function modifyHead(html, modulePaths, headFile, diagnostics) {
4593
4595
  }
4594
4596
  if (parts.length > 0) {
4595
4597
  parts.push("</head>");
4596
- return html.replace("</head>", parts.join("\n"));
4598
+ const replacement = parts.join("\n");
4599
+ return html.replace("</head>", () => replacement);
4597
4600
  }
4598
4601
  return html;
4599
4602
  }
@@ -4878,6 +4881,7 @@ async function buildOutput(options, cache, changedFiles) {
4878
4881
  if (testMode) {
4879
4882
  story.twine2.options.set("debug", true);
4880
4883
  }
4884
+ ensureIFID(story, diagnostics);
4881
4885
  let output;
4882
4886
  switch (outputMode) {
4883
4887
  case "twee3":
@@ -4885,7 +4889,7 @@ async function buildOutput(options, cache, changedFiles) {
4885
4889
  output = toTwee(story, outputMode);
4886
4890
  break;
4887
4891
  case "twine2-archive":
4888
- output = toTwine2Archive(story, startName, diagnostics, { sourceInfo });
4892
+ output = toTwine2Archive(story, startName, { sourceInfo });
4889
4893
  break;
4890
4894
  case "twine1-archive":
4891
4895
  output = toTwine1Archive(story, startName);
@@ -4904,7 +4908,7 @@ async function buildOutput(options, cache, changedFiles) {
4904
4908
  throw new TweeTsError("No story format available for HTML output.", diagnostics);
4905
4909
  }
4906
4910
  if (format.isTwine2) {
4907
- output = toTwine2HTML(story, format, startName, diagnostics, { sourceInfo });
4911
+ output = toTwine2HTML(story, format, startName, { sourceInfo });
4908
4912
  } else {
4909
4913
  if (story.name === "" && !storyHas(story, "StoryTitle")) {
4910
4914
  diagnostics.push({
@@ -4912,7 +4916,7 @@ async function buildOutput(options, cache, changedFiles) {
4912
4916
  message: 'Special passage "StoryTitle" not found.'
4913
4917
  });
4914
4918
  }
4915
- output = toTwine1HTML(story, format, startName, diagnostics);
4919
+ output = toTwine1HTML(story, format, startName);
4916
4920
  }
4917
4921
  const modulePaths = options.modules ? getFilenames(options.modules) : [];
4918
4922
  output = modifyHead(output, modulePaths, options.headFile, diagnostics);
@@ -4929,6 +4933,23 @@ async function buildOutput(options, cache, changedFiles) {
4929
4933
  };
4930
4934
  return { output, story, format, diagnostics, stats };
4931
4935
  }
4936
+ function ensureIFID(story, diagnostics) {
4937
+ if (story.ifid !== "") return;
4938
+ if (story.legacyIFID !== "") {
4939
+ story.ifid = story.legacyIFID;
4940
+ diagnostics.push({
4941
+ level: "warning",
4942
+ message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
4943
+ });
4944
+ } else {
4945
+ const ifid = generateIFID();
4946
+ story.ifid = ifid;
4947
+ diagnostics.push({
4948
+ level: "error",
4949
+ message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
4950
+ });
4951
+ }
4952
+ }
4932
4953
  function storyToJSON(story) {
4933
4954
  const scripts = [];
4934
4955
  const stylesheets = [];
@@ -4979,4 +5000,4 @@ export {
4979
5000
  compile,
4980
5001
  compileIncremental
4981
5002
  };
4982
- //# sourceMappingURL=chunk-TYLUAJTM.js.map
5003
+ //# sourceMappingURL=chunk-EQAUFATA.js.map