@rohal12/twee-ts 1.10.0 → 1.11.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.
@@ -236,15 +236,32 @@ function passageToTiddler(p, pid, obfuscateRot13 = false) {
236
236
  const nameComment = obfuscateRot13 ? `<!-- ${commentSanitize(p.name)} -->` : "";
237
237
  return `<div tiddler=${quote(attrEscape(p.name))} tags=${quote(attrEscape(p.tags.join(" ")))} created=${quote(created)} modifier=${quote("twee")} twine-position=${quote(attrEscape(position))}>${nameComment}${content}</div>`;
238
238
  }
239
- function countWords(p) {
240
- let text = p.text;
241
- text = text.replace(/\n/g, "");
242
- text = text.replace(/(?:\/%.+?%\/|\/\*.+?\*\/|<!--.+?-->)/gs, "");
243
- const normalized = text.normalize("NFKD");
244
- const count = [...normalized].length;
245
- if (count === 0) return 0;
246
- const words = Math.floor(count / 5);
247
- return count % 5 > 0 ? words + 1 : words;
239
+ function countWords(p, method = "tweego") {
240
+ switch (method) {
241
+ case "tweego": {
242
+ let text = p.text;
243
+ text = text.replace(/\n/g, "");
244
+ text = text.replace(/(?:\/%.+?%\/|\/\*.+?\*\/|<!--.+?-->)/gs, "");
245
+ const normalized = text.normalize("NFKD");
246
+ const count = [...normalized].length;
247
+ if (count === 0) return 0;
248
+ const words = Math.floor(count / 5);
249
+ return count % 5 > 0 ? words + 1 : words;
250
+ }
251
+ case "whitespace": {
252
+ let text = p.text;
253
+ text = text.replace(/(?:\/%.+?%\/|\/\*.+?\*\/|<!--.+?-->)/gs, "");
254
+ text = text.replace(/<<[^>]*>>/g, "");
255
+ text = text.replace(/\[\[([^\]|]*?)(?:\|[^\]]*?)?\]\]/g, "$1");
256
+ text = text.replace(/<[^>]+>/g, "");
257
+ const tokens = text.split(/\s+/).filter((t) => t.length > 0);
258
+ return tokens.length;
259
+ }
260
+ default: {
261
+ const _exhaustive = method;
262
+ throw new Error(`Unhandled word count method: ${_exhaustive}`);
263
+ }
264
+ }
248
265
  }
249
266
  function applyTagAliases(passages, aliases) {
250
267
  const entries = Object.entries(aliases);
@@ -462,13 +479,13 @@ function storyAdd(story, p, diagnostics) {
462
479
  }
463
480
  storyAppend(story, processed, diagnostics);
464
481
  }
465
- function getStoryStats(story) {
482
+ function getStoryStats(story, wordCountMethod = "tweego") {
466
483
  let storyPassages = 0;
467
484
  let words = 0;
468
485
  for (const p of story.passages) {
469
486
  if (isStoryPassage(p)) {
470
487
  storyPassages++;
471
- words += countWords(p);
488
+ words += countWords(p, wordCountMethod);
472
489
  }
473
490
  }
474
491
  return { passages: story.passages.length, storyPassages, words };
@@ -2293,7 +2310,7 @@ async function buildOutput(options, cache, changedFiles) {
2293
2310
  }
2294
2311
  }
2295
2312
  const stats = {
2296
- ...getStoryStats(story),
2313
+ ...getStoryStats(story, options.wordCountMethod),
2297
2314
  files: [...processedFiles]
2298
2315
  };
2299
2316
  return { output, story, format, diagnostics, stats };
@@ -2514,6 +2531,7 @@ import { readFileSync as readFileSync4, existsSync as existsSync2 } from "fs";
2514
2531
  import { join as join5 } from "path";
2515
2532
  var CONFIG_FILENAME = "twee-ts.config.json";
2516
2533
  var VALID_OUTPUT_MODES = ["html", "twee3", "twee1", "twine2-archive", "twine1-archive", "json"];
2534
+ var VALID_WORD_COUNT_METHODS = ["tweego", "whitespace"];
2517
2535
  function loadConfig(dir) {
2518
2536
  const base = dir ?? process.cwd();
2519
2537
  const configPath = join5(base, CONFIG_FILENAME);
@@ -2600,6 +2618,11 @@ function validateConfig(data) {
2600
2618
  errors.push(`"outputMode" must be one of: ${VALID_OUTPUT_MODES.join(", ")}.`);
2601
2619
  }
2602
2620
  }
2621
+ if ("wordCountMethod" in obj) {
2622
+ if (typeof obj["wordCountMethod"] !== "string" || !VALID_WORD_COUNT_METHODS.includes(obj["wordCountMethod"])) {
2623
+ errors.push(`"wordCountMethod" must be one of: ${VALID_WORD_COUNT_METHODS.join(", ")}.`);
2624
+ }
2625
+ }
2603
2626
  return errors;
2604
2627
  }
2605
2628
  function scaffoldConfig() {
@@ -2642,6 +2665,7 @@ var { values, positionals } = parseArgs({
2642
2665
  "no-remote": { type: "boolean" },
2643
2666
  "tag-alias": { type: "string", multiple: true },
2644
2667
  "source-info": { type: "boolean" },
2668
+ "word-count-method": { type: "string" },
2645
2669
  config: { type: "string", short: "c" },
2646
2670
  "no-config": { type: "boolean" }
2647
2671
  }
@@ -2699,6 +2723,16 @@ async function main() {
2699
2723
  tagAliases[pair.slice(0, eq)] = pair.slice(eq + 1);
2700
2724
  }
2701
2725
  }
2726
+ const VALID_WORD_COUNT_METHODS2 = ["tweego", "whitespace"];
2727
+ const wordCountMethod = (() => {
2728
+ const raw = values["word-count-method"] ?? config?.wordCountMethod;
2729
+ if (raw === void 0) return void 0;
2730
+ if (!VALID_WORD_COUNT_METHODS2.includes(raw)) {
2731
+ console.error(`Error: Invalid --word-count-method "${raw}". Expected: ${VALID_WORD_COUNT_METHODS2.join(", ")}`);
2732
+ process.exit(1);
2733
+ }
2734
+ return raw;
2735
+ })();
2702
2736
  if (values.lint) {
2703
2737
  const lintResult = await lint({
2704
2738
  sources,
@@ -2714,7 +2748,8 @@ async function main() {
2714
2748
  formatUrls: values["format-url"] ?? config?.formatUrls,
2715
2749
  noRemote: values["no-remote"] ?? config?.noRemote ?? false,
2716
2750
  tagAliases,
2717
- sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
2751
+ sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false,
2752
+ wordCountMethod
2718
2753
  });
2719
2754
  console.log(formatLintReport(lintResult));
2720
2755
  const hasErrors = lintResult.brokenLinks.length > 0 || lintResult.diagnostics.some((d) => d.level === "error");
@@ -2736,7 +2771,8 @@ async function main() {
2736
2771
  formatUrls: values["format-url"] ?? config?.formatUrls,
2737
2772
  noRemote: values["no-remote"] ?? config?.noRemote ?? false,
2738
2773
  tagAliases,
2739
- sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
2774
+ sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false,
2775
+ wordCountMethod
2740
2776
  };
2741
2777
  const outFile = values.output ?? config?.output ?? "-";
2742
2778
  if (values.watch) {
@@ -2921,6 +2957,7 @@ Options:
2921
2957
  --format-url <url> Direct format.js URL (repeatable)
2922
2958
  --tag-alias <alias=target> Map a tag to a special tag (repeatable)
2923
2959
  --source-info Emit source file/line as data- attributes on passages
2960
+ --word-count-method <m> Word counting method: tweego (default), whitespace
2924
2961
  --no-remote Disable remote format fetching
2925
2962
  -c, --config <file> Config file path (default: ${CONFIG_FILENAME})
2926
2963
  --no-config Skip config file loading