@rohal12/twee-ts 1.9.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,10 +2618,16 @@ 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() {
2606
2629
  const config = {
2630
+ $schema: "https://unpkg.com/@rohal12/twee-ts/schemas/twee-ts.config.schema.json",
2607
2631
  sources: ["src/"],
2608
2632
  output: "story.html"
2609
2633
  };
@@ -2641,6 +2665,7 @@ var { values, positionals } = parseArgs({
2641
2665
  "no-remote": { type: "boolean" },
2642
2666
  "tag-alias": { type: "string", multiple: true },
2643
2667
  "source-info": { type: "boolean" },
2668
+ "word-count-method": { type: "string" },
2644
2669
  config: { type: "string", short: "c" },
2645
2670
  "no-config": { type: "boolean" }
2646
2671
  }
@@ -2698,6 +2723,16 @@ async function main() {
2698
2723
  tagAliases[pair.slice(0, eq)] = pair.slice(eq + 1);
2699
2724
  }
2700
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
+ })();
2701
2736
  if (values.lint) {
2702
2737
  const lintResult = await lint({
2703
2738
  sources,
@@ -2713,7 +2748,8 @@ async function main() {
2713
2748
  formatUrls: values["format-url"] ?? config?.formatUrls,
2714
2749
  noRemote: values["no-remote"] ?? config?.noRemote ?? false,
2715
2750
  tagAliases,
2716
- sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
2751
+ sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false,
2752
+ wordCountMethod
2717
2753
  });
2718
2754
  console.log(formatLintReport(lintResult));
2719
2755
  const hasErrors = lintResult.brokenLinks.length > 0 || lintResult.diagnostics.some((d) => d.level === "error");
@@ -2735,7 +2771,8 @@ async function main() {
2735
2771
  formatUrls: values["format-url"] ?? config?.formatUrls,
2736
2772
  noRemote: values["no-remote"] ?? config?.noRemote ?? false,
2737
2773
  tagAliases,
2738
- sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false
2774
+ sourceInfo: values["source-info"] ?? config?.sourceInfo ?? false,
2775
+ wordCountMethod
2739
2776
  };
2740
2777
  const outFile = values.output ?? config?.output ?? "-";
2741
2778
  if (values.watch) {
@@ -2920,6 +2957,7 @@ Options:
2920
2957
  --format-url <url> Direct format.js URL (repeatable)
2921
2958
  --tag-alias <alias=target> Map a tag to a special tag (repeatable)
2922
2959
  --source-info Emit source file/line as data- attributes on passages
2960
+ --word-count-method <m> Word counting method: tweego (default), whitespace
2923
2961
  --no-remote Disable remote format fetching
2924
2962
  -c, --config <file> Config file path (default: ${CONFIG_FILENAME})
2925
2963
  --no-config Skip config file loading