@rohal12/twee-ts 1.4.0 → 1.6.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.
@@ -187,7 +187,7 @@ function passageToTwee(p, outMode) {
187
187
  output += "\n\n";
188
188
  return output;
189
189
  }
190
- function passageToPassagedata(p, pid) {
190
+ function passageToPassagedata(p, pid, options) {
191
191
  let position;
192
192
  let size;
193
193
  if (hasMetadataPosition(p)) {
@@ -204,7 +204,11 @@ function passageToPassagedata(p, pid) {
204
204
  } else {
205
205
  size = "100,100";
206
206
  }
207
- return `<tw-passagedata pid="${pid}" name=${quote(fullAttrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} position=${quote(attrEscape(position))} size=${quote(attrEscape(size))}>${htmlEscape(p.text)}</tw-passagedata>`;
207
+ let attrs = `<tw-passagedata pid="${pid}" name=${quote(fullAttrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} position=${quote(attrEscape(position))} size=${quote(attrEscape(size))}`;
208
+ if (options?.sourceInfo && p.source) {
209
+ attrs += ` data-source-file=${quote(attrEscape(p.source.file))} data-source-line="${p.source.line}"`;
210
+ }
211
+ return `${attrs}>${htmlEscape(p.text)}</tw-passagedata>`;
208
212
  }
209
213
  function passageToTiddler(p, pid, obfuscateRot13 = false) {
210
214
  let position;
@@ -251,8 +255,21 @@ function quote(s) {
251
255
  }
252
256
 
253
257
  // src/story.ts
258
+ var passageIndex = /* @__PURE__ */ new WeakMap();
259
+ function getIndex(story) {
260
+ let index = passageIndex.get(story);
261
+ if (!index) {
262
+ index = /* @__PURE__ */ new Map();
263
+ for (let i = 0; i < story.passages.length; i++) {
264
+ const passage = story.passages[i];
265
+ if (passage) index.set(passage.name, i);
266
+ }
267
+ passageIndex.set(story, index);
268
+ }
269
+ return index;
270
+ }
254
271
  function createStory() {
255
- return {
272
+ const story = {
256
273
  name: "",
257
274
  ifid: "",
258
275
  passages: [],
@@ -268,16 +285,17 @@ function createStory() {
268
285
  zoom: 1
269
286
  }
270
287
  };
288
+ passageIndex.set(story, /* @__PURE__ */ new Map());
289
+ return story;
271
290
  }
272
291
  function storyHas(story, name) {
273
- return story.passages.some((p) => p.name === name);
274
- }
275
- function storyIndex(story, name) {
276
- return story.passages.findIndex((p) => p.name === name);
292
+ return getIndex(story).has(name);
277
293
  }
278
294
  function storyAppend(story, p, diagnostics) {
279
- const i = storyIndex(story, p.name);
295
+ const index = getIndex(story);
296
+ const i = index.get(p.name) ?? -1;
280
297
  if (i === -1) {
298
+ index.set(p.name, story.passages.length);
281
299
  story.passages.push(p);
282
300
  } else {
283
301
  diagnostics.push({
@@ -288,9 +306,15 @@ function storyAppend(story, p, diagnostics) {
288
306
  }
289
307
  }
290
308
  function storyPrepend(story, p, diagnostics) {
291
- const i = storyIndex(story, p.name);
309
+ const index = getIndex(story);
310
+ const i = index.get(p.name) ?? -1;
292
311
  if (i === -1) {
293
312
  story.passages.unshift(p);
313
+ index.clear();
314
+ for (let j = 0; j < story.passages.length; j++) {
315
+ const passage = story.passages[j];
316
+ if (passage) index.set(passage.name, j);
317
+ }
294
318
  } else {
295
319
  diagnostics.push({
296
320
  level: "warning",
@@ -781,6 +805,7 @@ function readFormatSource(format) {
781
805
 
782
806
  // src/loader.ts
783
807
  import { basename } from "path";
808
+ import { statSync as statSync3 } from "fs";
784
809
 
785
810
  // src/lexer.ts
786
811
  var EOF = -1;
@@ -1096,7 +1121,7 @@ function parseTwee(source, options = {}) {
1096
1121
  if (pCount > 1 && current) {
1097
1122
  passages.push(current);
1098
1123
  }
1099
- current = { name: "", tags: [], text: "" };
1124
+ current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
1100
1125
  break;
1101
1126
  case 3 /* Name */: {
1102
1127
  if (!current) break;
@@ -1288,54 +1313,182 @@ function loadInlineSources(story, sources, opts, diagnostics) {
1288
1313
  }
1289
1314
  }
1290
1315
  }
1291
- function loadTwee(story, filename, opts, diagnostics) {
1316
+ function parseTweeFile(filename, opts) {
1292
1317
  const source = readUTF8(filename);
1293
1318
  const result = parseTwee(source, {
1294
1319
  filename,
1295
1320
  trim: opts.trim ?? true,
1296
1321
  twee2Compat: opts.twee2Compat ?? false
1297
1322
  });
1298
- diagnostics.push(...result.diagnostics);
1299
- for (const p of result.passages) {
1300
- storyAdd(story, p, diagnostics);
1301
- }
1323
+ return { passages: result.passages, diagnostics: [...result.diagnostics] };
1302
1324
  }
1303
- function loadTagged(story, tag, filename, diagnostics) {
1325
+ function parseTaggedFile(tag, filename) {
1304
1326
  const source = readUTF8(filename);
1305
- storyAdd(story, { name: basename(filename), tags: [tag], text: source }, diagnostics);
1327
+ return { passages: [{ name: basename(filename), tags: [tag], text: source }], diagnostics: [] };
1306
1328
  }
1307
- function loadMedia(story, tag, filename, diagnostics) {
1329
+ function parseMediaFile(tag, filename) {
1308
1330
  const source = readBase64(filename);
1309
1331
  const name = baseNameWithoutExt(filename);
1310
- storyAdd(
1311
- story,
1312
- {
1313
- name,
1314
- tags: [tag],
1315
- text: `data:${mediaTypeFromFilename(filename)};base64,${source}`
1316
- },
1317
- diagnostics
1318
- );
1332
+ return {
1333
+ passages: [{ name, tags: [tag], text: `data:${mediaTypeFromFilename(filename)};base64,${source}` }],
1334
+ diagnostics: []
1335
+ };
1319
1336
  }
1320
- function loadFont(story, filename, diagnostics) {
1337
+ function parseFontFile(filename) {
1321
1338
  const source = readBase64(filename);
1322
1339
  const name = basename(filename);
1323
1340
  const family = baseNameWithoutExt(filename);
1324
1341
  const ext = normalizedFileExt(filename);
1325
1342
  const mediaType = mediaTypeFromExt(ext);
1326
1343
  const hint = fontFormatHint(ext);
1327
- storyAdd(
1328
- story,
1329
- {
1330
- name,
1331
- tags: ["stylesheet"],
1332
- text: `@font-face {
1344
+ return {
1345
+ passages: [
1346
+ {
1347
+ name,
1348
+ tags: ["stylesheet"],
1349
+ text: `@font-face {
1333
1350
  font-family: "${family}";
1334
1351
  src: url("data:${mediaType};base64,${source}") format("${hint}");
1335
1352
  }`
1336
- },
1337
- diagnostics
1338
- );
1353
+ }
1354
+ ],
1355
+ diagnostics: []
1356
+ };
1357
+ }
1358
+ function parseFile(filename, opts) {
1359
+ const ext = normalizedFileExt(filename);
1360
+ switch (ext) {
1361
+ case "tw":
1362
+ case "twee":
1363
+ return parseTweeFile(filename, opts);
1364
+ case "tw2":
1365
+ case "twee2":
1366
+ return parseTweeFile(filename, { ...opts, twee2Compat: true });
1367
+ case "css":
1368
+ return parseTaggedFile("stylesheet", filename);
1369
+ case "js":
1370
+ return parseTaggedFile("script", filename);
1371
+ case "otf":
1372
+ case "ttf":
1373
+ case "woff":
1374
+ case "woff2":
1375
+ return parseFontFile(filename);
1376
+ case "gif":
1377
+ case "jpeg":
1378
+ case "jpg":
1379
+ case "png":
1380
+ case "svg":
1381
+ case "tif":
1382
+ case "tiff":
1383
+ case "webp":
1384
+ return parseMediaFile("Twine.image", filename);
1385
+ case "aac":
1386
+ case "flac":
1387
+ case "m4a":
1388
+ case "mp3":
1389
+ case "oga":
1390
+ case "ogg":
1391
+ case "opus":
1392
+ case "wav":
1393
+ case "wave":
1394
+ case "weba":
1395
+ return parseMediaFile("Twine.audio", filename);
1396
+ case "mp4":
1397
+ case "ogv":
1398
+ case "webm":
1399
+ return parseMediaFile("Twine.video", filename);
1400
+ case "vtt":
1401
+ return parseMediaFile("Twine.vtt", filename);
1402
+ default:
1403
+ return void 0;
1404
+ }
1405
+ }
1406
+ function loadTwee(story, filename, opts, diagnostics) {
1407
+ const result = parseTweeFile(filename, opts);
1408
+ diagnostics.push(...result.diagnostics);
1409
+ for (const p of result.passages) {
1410
+ storyAdd(story, p, diagnostics);
1411
+ }
1412
+ }
1413
+ function loadTagged(story, tag, filename, diagnostics) {
1414
+ const result = parseTaggedFile(tag, filename);
1415
+ for (const p of result.passages) {
1416
+ storyAdd(story, p, diagnostics);
1417
+ }
1418
+ }
1419
+ function loadMedia(story, tag, filename, diagnostics) {
1420
+ const result = parseMediaFile(tag, filename);
1421
+ for (const p of result.passages) {
1422
+ storyAdd(story, p, diagnostics);
1423
+ }
1424
+ }
1425
+ function loadFont(story, filename, diagnostics) {
1426
+ const result = parseFontFile(filename);
1427
+ for (const p of result.passages) {
1428
+ storyAdd(story, p, diagnostics);
1429
+ }
1430
+ }
1431
+ function loadSourcesCached(story, filenames, opts, diagnostics, processedFiles, cache, changedFiles) {
1432
+ const currentFiles = /* @__PURE__ */ new Set();
1433
+ for (const filename of filenames) {
1434
+ if (processedFiles.has(filename)) {
1435
+ diagnostics.push({ level: "warning", message: `load ${filename}: Skipping duplicate.` });
1436
+ continue;
1437
+ }
1438
+ currentFiles.add(filename);
1439
+ const cached = cache.get(filename);
1440
+ if (changedFiles && cached && !changedFiles.has(filename)) {
1441
+ diagnostics.push(...cached.diagnostics);
1442
+ for (const p of cached.passages) {
1443
+ storyAdd(story, p, diagnostics);
1444
+ }
1445
+ processedFiles.add(filename);
1446
+ continue;
1447
+ }
1448
+ let mtimeMs;
1449
+ try {
1450
+ mtimeMs = statSync3(filename).mtimeMs;
1451
+ } catch {
1452
+ continue;
1453
+ }
1454
+ if (cached && cached.mtimeMs === mtimeMs) {
1455
+ diagnostics.push(...cached.diagnostics);
1456
+ for (const p of cached.passages) {
1457
+ storyAdd(story, p, diagnostics);
1458
+ }
1459
+ processedFiles.add(filename);
1460
+ continue;
1461
+ }
1462
+ try {
1463
+ const result = parseFile(filename, opts);
1464
+ if (!result) continue;
1465
+ cache.set(filename, {
1466
+ mtimeMs,
1467
+ passages: result.passages,
1468
+ diagnostics: result.diagnostics
1469
+ });
1470
+ diagnostics.push(...result.diagnostics);
1471
+ for (const p of result.passages) {
1472
+ storyAdd(story, p, diagnostics);
1473
+ }
1474
+ } catch (e) {
1475
+ diagnostics.push({
1476
+ level: "error",
1477
+ message: `load ${filename}: ${e instanceof Error ? e.message : String(e)}`,
1478
+ file: filename
1479
+ });
1480
+ continue;
1481
+ }
1482
+ processedFiles.add(filename);
1483
+ }
1484
+ for (const key of cache.keys()) {
1485
+ if (!currentFiles.has(key)) {
1486
+ cache.delete(key);
1487
+ }
1488
+ }
1489
+ if (story.name !== "" && !story.passages.some((p) => p.name === "StoryTitle")) {
1490
+ storyPrepend(story, { name: "StoryTitle", tags: [], text: story.name }, diagnostics);
1491
+ }
1339
1492
  }
1340
1493
 
1341
1494
  // src/version.ts
@@ -1343,20 +1496,20 @@ var VERSION = "1.2.0";
1343
1496
 
1344
1497
  // src/output-twine2.ts
1345
1498
  var CREATOR_NAME = "Twee-ts";
1346
- function toTwine2Archive(story, startName, diagnostics) {
1347
- return getTwine2DataChunk(story, startName, diagnostics) + "\n";
1499
+ function toTwine2Archive(story, startName, diagnostics, options) {
1500
+ return getTwine2DataChunk(story, startName, diagnostics, options) + "\n";
1348
1501
  }
1349
- function toTwine2HTML(story, format, startName, diagnostics) {
1502
+ function toTwine2HTML(story, format, startName, diagnostics, options) {
1350
1503
  let template = readFormatSource(format);
1351
1504
  if (template.includes("{{STORY_NAME}}")) {
1352
1505
  template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
1353
1506
  }
1354
1507
  if (template.includes("{{STORY_DATA}}")) {
1355
- template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics));
1508
+ template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, diagnostics, options));
1356
1509
  }
1357
1510
  return template;
1358
1511
  }
1359
- function getTwine2DataChunk(story, startName, diagnostics) {
1512
+ function getTwine2DataChunk(story, startName, diagnostics, options) {
1360
1513
  ensureIFID(story, diagnostics);
1361
1514
  const parts = [];
1362
1515
  let startID = "";
@@ -1411,7 +1564,7 @@ function getTwine2DataChunk(story, startName, diagnostics) {
1411
1564
  if (p.name === "StorySettings" && story.twine1.settings.size === 0) {
1412
1565
  continue;
1413
1566
  }
1414
- parts.push(passageToPassagedata(p, pid));
1567
+ parts.push(passageToPassagedata(p, pid, options));
1415
1568
  if (startName === p.name) {
1416
1569
  startID = String(pid);
1417
1570
  }
@@ -1421,9 +1574,9 @@ function getTwine2DataChunk(story, startName, diagnostics) {
1421
1574
  for (const [opt, val] of story.twine2.options) {
1422
1575
  if (val) opts.push(opt);
1423
1576
  }
1424
- const options = opts.join(" ");
1577
+ const optionsStr = opts.join(" ");
1425
1578
  const zoom = String(story.twine2.zoom);
1426
- 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(options)}" tags="${attrEscape(story.twine2.tags)}" hidden>`;
1579
+ 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>`;
1427
1580
  return wrapper + parts.join("") + "</tw-storydata>";
1428
1581
  }
1429
1582
  function ensureIFID(story, diagnostics) {
@@ -1604,7 +1757,7 @@ function modifyHead(html, modulePaths, headFile, diagnostics) {
1604
1757
  }
1605
1758
 
1606
1759
  // src/remote-formats.ts
1607
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
1760
+ import { mkdirSync, readFileSync as readFileSync3, writeFileSync, existsSync, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
1608
1761
  import { join as join4 } from "path";
1609
1762
  import { homedir as homedir2 } from "os";
1610
1763
  var DEFAULT_SFA_INDICES = [
@@ -1814,13 +1967,14 @@ var TweeTsError = class extends Error {
1814
1967
  async function compile(options) {
1815
1968
  return buildOutput(options);
1816
1969
  }
1817
- async function buildOutput(options) {
1970
+ async function buildOutput(options, cache, changedFiles) {
1818
1971
  const diagnostics = [];
1819
1972
  const outputMode = options.outputMode ?? "html";
1820
1973
  const trim = options.trim ?? true;
1821
1974
  const twee2Compat = options.twee2Compat ?? false;
1822
1975
  const testMode = options.testMode ?? false;
1823
1976
  const noRemote = options.noRemote ?? false;
1977
+ const sourceInfo = options.sourceInfo ?? false;
1824
1978
  clearIndexCache();
1825
1979
  const filePaths = [];
1826
1980
  const inlineSources = [];
@@ -1834,7 +1988,11 @@ async function buildOutput(options) {
1834
1988
  const sourceFilenames = getFilenames(filePaths);
1835
1989
  const story = createStory();
1836
1990
  const processedFiles = /* @__PURE__ */ new Set();
1837
- loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
1991
+ if (cache) {
1992
+ loadSourcesCached(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles, cache, changedFiles);
1993
+ } else {
1994
+ loadSources(story, sourceFilenames, { trim, twee2Compat }, diagnostics, processedFiles);
1995
+ }
1838
1996
  loadInlineSources(story, inlineSources, { trim, twee2Compat }, diagnostics);
1839
1997
  if (options.tagAliases) {
1840
1998
  story.passages = applyTagAliases(story.passages, options.tagAliases);
@@ -1882,7 +2040,7 @@ async function buildOutput(options) {
1882
2040
  output = toTwee(story, outputMode);
1883
2041
  break;
1884
2042
  case "twine2-archive":
1885
- output = toTwine2Archive(story, startName, diagnostics);
2043
+ output = toTwine2Archive(story, startName, diagnostics, { sourceInfo });
1886
2044
  break;
1887
2045
  case "twine1-archive":
1888
2046
  output = toTwine1Archive(story, startName);
@@ -1901,7 +2059,7 @@ async function buildOutput(options) {
1901
2059
  throw new TweeTsError("No story format available for HTML output.", diagnostics);
1902
2060
  }
1903
2061
  if (format.isTwine2) {
1904
- output = toTwine2HTML(story, format, startName, diagnostics);
2062
+ output = toTwine2HTML(story, format, startName, diagnostics, { sourceInfo });
1905
2063
  } else {
1906
2064
  if (story.name === "" && !storyHas(story, "StoryTitle")) {
1907
2065
  diagnostics.push({
@@ -1975,4 +2133,4 @@ function storyToJSON(story) {
1975
2133
  export {
1976
2134
  compile
1977
2135
  };
1978
- //# sourceMappingURL=chunk-76URFKGD.js.map
2136
+ //# sourceMappingURL=chunk-HXL3JC4L.js.map