@dev-pi2pie/word-counter 0.0.6

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.
@@ -0,0 +1,979 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command, Option } from "commander";
4
+ import { readFileSync } from "node:fs";
5
+ import { readFile } from "node:fs/promises";
6
+ import { resolve } from "node:path";
7
+ import { parseDocument } from "yaml";
8
+
9
+ //#region rolldown:runtime
10
+ var __create = Object.create;
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) {
22
+ __defProp(to, key, {
23
+ get: ((k) => from[k]).bind(null, key),
24
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
25
+ });
26
+ }
27
+ }
28
+ }
29
+ return to;
30
+ };
31
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
32
+ value: mod,
33
+ enumerable: true
34
+ }) : target, mod));
35
+
36
+ //#endregion
37
+ //#region src/utils/show-singular-or-plural-word.ts
38
+ function showSingularOrPluralWord(count, word) {
39
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
40
+ }
41
+
42
+ //#endregion
43
+ //#region src/wc/segmenter.ts
44
+ const segmenterCache = /* @__PURE__ */ new Map();
45
+ function getSegmenter(locale) {
46
+ const cached = segmenterCache.get(locale);
47
+ if (cached) return cached;
48
+ const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
49
+ segmenterCache.set(locale, segmenter);
50
+ return segmenter;
51
+ }
52
+
53
+ //#endregion
54
+ //#region src/wc/analyze.ts
55
+ function analyzeChunk(chunk) {
56
+ const segmenter = getSegmenter(chunk.locale);
57
+ const segments = [];
58
+ for (const part of segmenter.segment(chunk.text)) if (part.isWordLike) segments.push(part.segment);
59
+ return {
60
+ locale: chunk.locale,
61
+ text: chunk.text,
62
+ segments,
63
+ words: segments.length
64
+ };
65
+ }
66
+ function aggregateByLocale(chunks) {
67
+ const order = [];
68
+ const map = /* @__PURE__ */ new Map();
69
+ for (const chunk of chunks) {
70
+ const existing = map.get(chunk.locale);
71
+ if (existing) {
72
+ existing.words += chunk.words;
73
+ existing.segments.push(...chunk.segments);
74
+ continue;
75
+ }
76
+ order.push(chunk.locale);
77
+ map.set(chunk.locale, {
78
+ locale: chunk.locale,
79
+ words: chunk.words,
80
+ segments: [...chunk.segments]
81
+ });
82
+ }
83
+ return order.map((locale) => map.get(locale));
84
+ }
85
+
86
+ //#endregion
87
+ //#region src/wc/locale-detect.ts
88
+ const DEFAULT_LOCALE = "und-Latn";
89
+ const regex = {
90
+ hiragana: /\p{Script=Hiragana}/u,
91
+ katakana: /\p{Script=Katakana}/u,
92
+ hangul: /\p{Script=Hangul}/u,
93
+ han: /\p{Script=Han}/u,
94
+ latin: /\p{Script=Latin}/u,
95
+ arabic: /\p{Script=Arabic}/u,
96
+ cyrillic: /\p{Script=Cyrillic}/u,
97
+ devanagari: /\p{Script=Devanagari}/u,
98
+ thai: /\p{Script=Thai}/u
99
+ };
100
+ const latinLocaleHints = [
101
+ {
102
+ locale: "de",
103
+ regex: /[äöüÄÖÜß]/
104
+ },
105
+ {
106
+ locale: "es",
107
+ regex: /[ñÑ¿¡]/
108
+ },
109
+ {
110
+ locale: "pt",
111
+ regex: /[ãõÃÕ]/
112
+ },
113
+ {
114
+ locale: "fr",
115
+ regex: /[œŒæÆ]/
116
+ }
117
+ ];
118
+ const latinLocales = new Set([DEFAULT_LOCALE, ...latinLocaleHints.map((hint) => hint.locale)]);
119
+ function isLatinLocale(locale) {
120
+ return latinLocales.has(locale);
121
+ }
122
+ function detectLatinLocale(char) {
123
+ for (const hint of latinLocaleHints) if (hint.regex.test(char)) return hint.locale;
124
+ return DEFAULT_LOCALE;
125
+ }
126
+ function detectLocaleForChar(char, previousLocale) {
127
+ if (regex.hiragana.test(char) || regex.katakana.test(char)) return "ja";
128
+ if (regex.hangul.test(char)) return "ko";
129
+ if (regex.arabic.test(char)) return "ar";
130
+ if (regex.cyrillic.test(char)) return "ru";
131
+ if (regex.devanagari.test(char)) return "hi";
132
+ if (regex.thai.test(char)) return "th";
133
+ if (regex.han.test(char)) {
134
+ if (previousLocale && previousLocale.startsWith("ja")) return previousLocale;
135
+ return "zh-Hans";
136
+ }
137
+ if (regex.latin.test(char)) {
138
+ const hintedLocale = detectLatinLocale(char);
139
+ if (hintedLocale !== DEFAULT_LOCALE) return hintedLocale;
140
+ if (previousLocale && isLatinLocale(previousLocale)) return previousLocale;
141
+ return DEFAULT_LOCALE;
142
+ }
143
+ return null;
144
+ }
145
+
146
+ //#endregion
147
+ //#region src/wc/segment.ts
148
+ function segmentTextByLocale(text) {
149
+ const chunks = [];
150
+ let currentLocale = DEFAULT_LOCALE;
151
+ let buffer = "";
152
+ for (const char of text) {
153
+ const detected = detectLocaleForChar(char, currentLocale);
154
+ const targetLocale = detected ?? currentLocale;
155
+ if (buffer === "") {
156
+ currentLocale = targetLocale;
157
+ buffer = char;
158
+ continue;
159
+ }
160
+ if (targetLocale !== currentLocale && detected !== null) {
161
+ if (currentLocale === DEFAULT_LOCALE && isLatinLocale(targetLocale)) {
162
+ currentLocale = targetLocale;
163
+ buffer += char;
164
+ continue;
165
+ }
166
+ chunks.push({
167
+ locale: currentLocale,
168
+ text: buffer
169
+ });
170
+ currentLocale = targetLocale;
171
+ buffer = char;
172
+ continue;
173
+ }
174
+ buffer += char;
175
+ }
176
+ if (buffer.length > 0) chunks.push({
177
+ locale: currentLocale,
178
+ text: buffer
179
+ });
180
+ return mergeAdjacentChunks(chunks);
181
+ }
182
+ function mergeAdjacentChunks(chunks) {
183
+ if (chunks.length === 0) return chunks;
184
+ const merged = [];
185
+ let last = chunks[0];
186
+ for (let i = 1; i < chunks.length; i++) {
187
+ const chunk = chunks[i];
188
+ if (chunk.locale === last.locale) last = {
189
+ locale: last.locale,
190
+ text: last.text + chunk.text
191
+ };
192
+ else {
193
+ merged.push(last);
194
+ last = chunk;
195
+ }
196
+ }
197
+ merged.push(last);
198
+ return merged;
199
+ }
200
+
201
+ //#endregion
202
+ //#region src/wc/wc.ts
203
+ function wordCounter(text, options = {}) {
204
+ const mode = options.mode ?? "chunk";
205
+ const analyzed = segmentTextByLocale(text).map(analyzeChunk);
206
+ const total = analyzed.reduce((sum, chunk) => sum + chunk.words, 0);
207
+ if (mode === "segments") return {
208
+ total,
209
+ breakdown: {
210
+ mode,
211
+ items: analyzed.map((chunk) => ({
212
+ locale: chunk.locale,
213
+ text: chunk.text,
214
+ words: chunk.words,
215
+ segments: chunk.segments
216
+ }))
217
+ }
218
+ };
219
+ if (mode === "collector") return {
220
+ total,
221
+ breakdown: {
222
+ mode,
223
+ items: aggregateByLocale(analyzed)
224
+ }
225
+ };
226
+ return {
227
+ total,
228
+ breakdown: {
229
+ mode,
230
+ items: analyzed.map((chunk) => ({
231
+ locale: chunk.locale,
232
+ text: chunk.text,
233
+ words: chunk.words
234
+ }))
235
+ }
236
+ };
237
+ }
238
+
239
+ //#endregion
240
+ //#region src/wc/index.ts
241
+ var wc_default = wordCounter;
242
+
243
+ //#endregion
244
+ //#region src/markdown/toml/arrays.ts
245
+ function ensureArrayContainer(result, key) {
246
+ const existing = result[key];
247
+ if (Array.isArray(existing)) return existing;
248
+ const list = [];
249
+ result[key] = list;
250
+ return list;
251
+ }
252
+ function flattenArrayTables(result) {
253
+ for (const [key, value] of Object.entries(result)) {
254
+ if (!Array.isArray(value)) continue;
255
+ result[key] = value.map((entry) => Object.entries(entry).map(([entryKey, entryValue]) => `${entryKey}=${entryValue}`).join(", ")).join(" | ");
256
+ }
257
+ }
258
+
259
+ //#endregion
260
+ //#region src/markdown/toml/keys.ts
261
+ function stripKeyQuotes(key) {
262
+ const trimmed = key.trim();
263
+ if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
264
+ return trimmed;
265
+ }
266
+ function normalizeKeyPath(key) {
267
+ const trimmed = key.trim();
268
+ if (!trimmed) return null;
269
+ if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) {
270
+ const unquoted = stripKeyQuotes(trimmed);
271
+ return unquoted ? unquoted : null;
272
+ }
273
+ const segments = trimmed.split(".").map((segment) => segment.trim());
274
+ if (segments.some((segment) => !segment)) return null;
275
+ return segments.join(".");
276
+ }
277
+
278
+ //#endregion
279
+ //#region src/markdown/toml/strings.ts
280
+ function stripInlineComment(line) {
281
+ let inString = null;
282
+ let escaped = false;
283
+ for (let i = 0; i < line.length; i += 1) {
284
+ const char = line[i] ?? "";
285
+ if (inString) {
286
+ if (escaped) {
287
+ escaped = false;
288
+ continue;
289
+ }
290
+ if (char === "\\" && inString === "double") {
291
+ escaped = true;
292
+ continue;
293
+ }
294
+ if (inString === "double" && char === "\"") {
295
+ inString = null;
296
+ continue;
297
+ }
298
+ if (inString === "single" && char === "'") {
299
+ inString = null;
300
+ continue;
301
+ }
302
+ continue;
303
+ }
304
+ if (char === "\"") {
305
+ inString = "double";
306
+ continue;
307
+ }
308
+ if (char === "'") {
309
+ inString = "single";
310
+ continue;
311
+ }
312
+ if (char === "#") return line.slice(0, i).trimEnd();
313
+ }
314
+ return line;
315
+ }
316
+ function unescapeBasic(input) {
317
+ return input.replace(/\\\\/g, "\\").replace(/\\"/g, "\"").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r");
318
+ }
319
+ function parseStringLiteral(value) {
320
+ if (value.startsWith("\"\"\"") && value.endsWith("\"\"\"")) return unescapeBasic(value.slice(3, -3));
321
+ if (value.startsWith("'''") && value.endsWith("'''")) return value.slice(3, -3);
322
+ if (value.startsWith("\"") && value.endsWith("\"")) return unescapeBasic(value.slice(1, -1));
323
+ if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
324
+ return null;
325
+ }
326
+
327
+ //#endregion
328
+ //#region src/markdown/toml/values.ts
329
+ function parsePrimitive(raw) {
330
+ const value = raw.trim();
331
+ if (!value) return null;
332
+ const stringLiteral = parseStringLiteral(value);
333
+ if (stringLiteral !== null) return stringLiteral;
334
+ if (value === "true") return true;
335
+ if (value === "false") return false;
336
+ if (/^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value)) return Number(value);
337
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) return value;
338
+ return value;
339
+ }
340
+ function parseArray(raw) {
341
+ const value = raw.trim();
342
+ if (!value.startsWith("[") || !value.endsWith("]")) return null;
343
+ const inner = value.slice(1, -1).trim();
344
+ if (!inner) return [];
345
+ const items = [];
346
+ let current = "";
347
+ let inString = null;
348
+ let escaped = false;
349
+ for (let i = 0; i < inner.length; i += 1) {
350
+ const char = inner[i] ?? "";
351
+ if (inString) {
352
+ current += char;
353
+ if (escaped) {
354
+ escaped = false;
355
+ continue;
356
+ }
357
+ if (char === "\\" && inString === "double") {
358
+ escaped = true;
359
+ continue;
360
+ }
361
+ if (inString === "double" && char === "\"") inString = null;
362
+ else if (inString === "single" && char === "'") inString = null;
363
+ continue;
364
+ }
365
+ if (char === "\"") {
366
+ inString = "double";
367
+ current += char;
368
+ continue;
369
+ }
370
+ if (char === "'") {
371
+ inString = "single";
372
+ current += char;
373
+ continue;
374
+ }
375
+ if (char === ",") {
376
+ const item = parsePrimitive(current);
377
+ if (item === null) return null;
378
+ items.push(item);
379
+ current = "";
380
+ continue;
381
+ }
382
+ current += char;
383
+ }
384
+ const finalItem = parsePrimitive(current);
385
+ if (finalItem === null) return null;
386
+ items.push(finalItem);
387
+ return items;
388
+ }
389
+ function parseInlineTable(raw) {
390
+ const trimmed = raw.trim();
391
+ if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
392
+ const inner = trimmed.slice(1, -1).trim();
393
+ if (!inner) return {};
394
+ const pairs = [];
395
+ let current = "";
396
+ let inString = null;
397
+ let escaped = false;
398
+ let bracketDepth = 0;
399
+ let braceDepth = 0;
400
+ for (let i = 0; i < inner.length; i += 1) {
401
+ const char = inner[i] ?? "";
402
+ if (inString) {
403
+ current += char;
404
+ if (escaped) {
405
+ escaped = false;
406
+ continue;
407
+ }
408
+ if (char === "\\" && inString === "double") {
409
+ escaped = true;
410
+ continue;
411
+ }
412
+ if (inString === "double" && char === "\"") inString = null;
413
+ else if (inString === "single" && char === "'") inString = null;
414
+ continue;
415
+ }
416
+ if (char === "\"") {
417
+ inString = "double";
418
+ current += char;
419
+ continue;
420
+ }
421
+ if (char === "'") {
422
+ inString = "single";
423
+ current += char;
424
+ continue;
425
+ }
426
+ if (char === "[") {
427
+ bracketDepth += 1;
428
+ current += char;
429
+ continue;
430
+ }
431
+ if (char === "]") {
432
+ if (bracketDepth > 0) bracketDepth -= 1;
433
+ current += char;
434
+ continue;
435
+ }
436
+ if (char === "{") {
437
+ braceDepth += 1;
438
+ current += char;
439
+ continue;
440
+ }
441
+ if (char === "}") {
442
+ if (braceDepth > 0) braceDepth -= 1;
443
+ current += char;
444
+ continue;
445
+ }
446
+ if (char === "," && bracketDepth === 0 && braceDepth === 0) {
447
+ pairs.push(current);
448
+ current = "";
449
+ continue;
450
+ }
451
+ current += char;
452
+ }
453
+ if (current.trim()) pairs.push(current);
454
+ const output = {};
455
+ for (const pair of pairs) {
456
+ const separatorIndex = pair.indexOf("=");
457
+ if (separatorIndex === -1) return null;
458
+ const key = normalizeKeyPath(pair.slice(0, separatorIndex));
459
+ if (!key) return null;
460
+ const valueRaw = pair.slice(separatorIndex + 1).trim();
461
+ if (!valueRaw) return null;
462
+ if (valueRaw.startsWith("{")) return null;
463
+ const normalized = normalizeValue(valueRaw);
464
+ if (normalized === null) return null;
465
+ if (typeof normalized === "object" && !Array.isArray(normalized)) return null;
466
+ output[key] = normalized;
467
+ }
468
+ return output;
469
+ }
470
+ function normalizeValue(value) {
471
+ if (!value) return null;
472
+ const trimmed = value.trim();
473
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) return parseInlineTable(trimmed);
474
+ const array = parseArray(trimmed);
475
+ if (array) return array;
476
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) return null;
477
+ return parsePrimitive(trimmed);
478
+ }
479
+ function toPlainText(value) {
480
+ if (value == null) return "";
481
+ if (Array.isArray(value)) return value.map((item) => String(item)).join(", ");
482
+ return String(value);
483
+ }
484
+
485
+ //#endregion
486
+ //#region src/markdown/toml/parse-frontmatter.ts
487
+ function parseTomlFrontmatter(frontmatter) {
488
+ const result = {};
489
+ const lines = frontmatter.split("\n");
490
+ let tablePrefix = "";
491
+ let tableTarget = null;
492
+ let tablePrefixInList = false;
493
+ for (let index = 0; index < lines.length; index += 1) {
494
+ const rawLine = lines[index] ?? "";
495
+ const trimmedLine = rawLine.trim();
496
+ if (!trimmedLine || trimmedLine.startsWith("#")) continue;
497
+ if (trimmedLine.startsWith("[[")) {
498
+ const match = trimmedLine.match(/^\[\[([^\]]+)]]$/);
499
+ if (!match) return null;
500
+ const normalizedTable = normalizeKeyPath(match[1] ?? "");
501
+ if (!normalizedTable) return null;
502
+ const list = ensureArrayContainer(result, normalizedTable);
503
+ const newEntry = {};
504
+ list.push(newEntry);
505
+ tableTarget = newEntry;
506
+ tablePrefix = normalizedTable;
507
+ tablePrefixInList = true;
508
+ continue;
509
+ }
510
+ const tableMatch = trimmedLine.match(/^\[([^\]]+)]$/);
511
+ if (tableMatch) {
512
+ const normalizedTable = normalizeKeyPath(tableMatch[1] ?? "");
513
+ if (!normalizedTable) return null;
514
+ tablePrefix = normalizedTable;
515
+ tablePrefixInList = false;
516
+ tableTarget = null;
517
+ continue;
518
+ }
519
+ const lineForParsing = /("""|''')/.test(rawLine) ? rawLine : stripInlineComment(rawLine);
520
+ const separatorIndex = lineForParsing.indexOf("=");
521
+ if (separatorIndex === -1) return null;
522
+ const key = normalizeKeyPath(lineForParsing.slice(0, separatorIndex));
523
+ let valueRaw = lineForParsing.slice(separatorIndex + 1).trim();
524
+ if (!key) return null;
525
+ const tripleDelimiter = valueRaw.startsWith("\"\"\"") ? "\"\"\"" : valueRaw.startsWith("'''") ? "'''" : null;
526
+ if (tripleDelimiter) {
527
+ const closingIndex = valueRaw.indexOf(tripleDelimiter, tripleDelimiter.length);
528
+ if (closingIndex !== -1) {
529
+ const strippedAfter = stripInlineComment(valueRaw.slice(closingIndex + tripleDelimiter.length));
530
+ valueRaw = `${valueRaw.slice(0, closingIndex + tripleDelimiter.length)}${strippedAfter}`;
531
+ } else {
532
+ const delimiter = tripleDelimiter;
533
+ let combined = valueRaw;
534
+ let closed = false;
535
+ while (index + 1 < lines.length) {
536
+ index += 1;
537
+ const nextLine = lines[index] ?? "";
538
+ combined += `\n${nextLine}`;
539
+ if ((/* @__PURE__ */ new RegExp(`${delimiter}\\s*$`)).test(nextLine)) {
540
+ closed = true;
541
+ break;
542
+ }
543
+ }
544
+ if (!closed) return null;
545
+ valueRaw = combined;
546
+ }
547
+ }
548
+ const normalized = normalizeValue(valueRaw);
549
+ if (normalized === null) return null;
550
+ const fullKey = tablePrefix ? `${tablePrefix}.${key}` : key;
551
+ if (typeof normalized === "object" && !Array.isArray(normalized)) {
552
+ for (const [inlineKey, inlineValue] of Object.entries(normalized)) {
553
+ const entryKey = tablePrefixInList ? `${key}.${inlineKey}` : `${fullKey}.${inlineKey}`;
554
+ if (tablePrefixInList && tableTarget) tableTarget[entryKey] = toPlainText(inlineValue);
555
+ else result[entryKey] = toPlainText(inlineValue);
556
+ }
557
+ continue;
558
+ }
559
+ if (tablePrefixInList && tableTarget) {
560
+ tableTarget[key] = toPlainText(normalized);
561
+ continue;
562
+ }
563
+ result[fullKey] = toPlainText(normalized);
564
+ }
565
+ flattenArrayTables(result);
566
+ return result;
567
+ }
568
+
569
+ //#endregion
570
+ //#region src/markdown/parse-markdown.ts
571
+ const FENCE_TO_TYPE = {
572
+ "---": "yaml",
573
+ "+++": "toml",
574
+ ";;;": "json"
575
+ };
576
+ function normalizeNewlines(input) {
577
+ return input.replace(/\r\n/g, "\n");
578
+ }
579
+ function stripBom(line) {
580
+ return line.startsWith("") ? line.slice(1) : line;
581
+ }
582
+ function getFenceType(line) {
583
+ const match = line.match(/^[\t ]*(---|\+\+\+|;;;)[\t ]*$/);
584
+ if (!match) return null;
585
+ return FENCE_TO_TYPE[match[1] ?? ""] ?? null;
586
+ }
587
+ function parseFrontmatter(frontmatter, type) {
588
+ if (!type) return null;
589
+ if (type === "json") try {
590
+ return JSON.parse(frontmatter);
591
+ } catch {
592
+ return null;
593
+ }
594
+ if (type === "yaml") {
595
+ const doc = parseDocument(frontmatter, { prettyErrors: false });
596
+ if (doc.errors.length > 0) return null;
597
+ const data = doc.toJSON();
598
+ if (!data || typeof data !== "object" || Array.isArray(data)) return null;
599
+ return data;
600
+ }
601
+ if (type === "toml") return parseTomlFrontmatter(frontmatter);
602
+ return null;
603
+ }
604
+ function extractJsonBlock(text, startIndex) {
605
+ let depth = 0;
606
+ let inString = false;
607
+ let escaped = false;
608
+ for (let i = startIndex; i < text.length; i += 1) {
609
+ const char = text[i] ?? "";
610
+ if (inString) {
611
+ if (escaped) {
612
+ escaped = false;
613
+ continue;
614
+ }
615
+ if (char === "\\") {
616
+ escaped = true;
617
+ continue;
618
+ }
619
+ if (char === "\"") inString = false;
620
+ continue;
621
+ }
622
+ if (char === "\"") {
623
+ inString = true;
624
+ continue;
625
+ }
626
+ if (char === "{") {
627
+ depth += 1;
628
+ continue;
629
+ }
630
+ if (char === "}") {
631
+ depth -= 1;
632
+ if (depth === 0) return {
633
+ jsonText: text.slice(startIndex, i + 1),
634
+ endIndex: i
635
+ };
636
+ }
637
+ }
638
+ return null;
639
+ }
640
+ function parseMarkdown(input) {
641
+ const normalized = normalizeNewlines(input);
642
+ const lines = normalized.split("\n");
643
+ if (lines.length === 0) return {
644
+ frontmatter: null,
645
+ content: normalized,
646
+ data: null,
647
+ frontmatterType: null
648
+ };
649
+ lines[0] = stripBom(lines[0] ?? "");
650
+ const normalizedWithoutBom = lines.join("\n");
651
+ const openingType = getFenceType(lines[0] ?? "");
652
+ if (!openingType) {
653
+ const jsonStart = (normalizedWithoutBom.match(/^[\t \n]*/)?.[0] ?? "").length;
654
+ if (normalizedWithoutBom[jsonStart] !== "{") return {
655
+ frontmatter: null,
656
+ content: normalizedWithoutBom,
657
+ data: null,
658
+ frontmatterType: null
659
+ };
660
+ const jsonBlock = extractJsonBlock(normalizedWithoutBom, jsonStart);
661
+ if (!jsonBlock) return {
662
+ frontmatter: null,
663
+ content: normalizedWithoutBom,
664
+ data: null,
665
+ frontmatterType: null
666
+ };
667
+ const frontmatter$1 = jsonBlock.jsonText;
668
+ let content = normalizedWithoutBom.slice(jsonBlock.endIndex + 1);
669
+ if (content.startsWith("\n")) content = content.slice(1);
670
+ const data = parseFrontmatter(frontmatter$1, "json");
671
+ if (!data) return {
672
+ frontmatter: null,
673
+ content: normalizedWithoutBom,
674
+ data: null,
675
+ frontmatterType: null
676
+ };
677
+ return {
678
+ frontmatter: frontmatter$1,
679
+ content,
680
+ data,
681
+ frontmatterType: "json"
682
+ };
683
+ }
684
+ let closingIndex = -1;
685
+ for (let i = 1; i < lines.length; i += 1) if (getFenceType(lines[i] ?? "") === openingType) {
686
+ closingIndex = i;
687
+ break;
688
+ }
689
+ if (closingIndex === -1) return {
690
+ frontmatter: null,
691
+ content: normalizedWithoutBom,
692
+ data: null,
693
+ frontmatterType: null
694
+ };
695
+ const frontmatter = lines.slice(1, closingIndex).join("\n");
696
+ return {
697
+ frontmatter,
698
+ content: lines.slice(closingIndex + 1).join("\n"),
699
+ data: parseFrontmatter(frontmatter, openingType),
700
+ frontmatterType: openingType
701
+ };
702
+ }
703
+
704
+ //#endregion
705
+ //#region src/markdown/section-count.ts
706
+ function normalizeText(value) {
707
+ if (value == null) return "";
708
+ if (typeof value === "string") return value;
709
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
710
+ try {
711
+ return JSON.stringify(value);
712
+ } catch {
713
+ return String(value);
714
+ }
715
+ }
716
+ function buildPerKeyItems(data, mode) {
717
+ if (!data || typeof data !== "object" || Array.isArray(data)) return [];
718
+ return Object.entries(data).map(([key, value]) => {
719
+ const valueText = normalizeText(value);
720
+ return {
721
+ name: key,
722
+ source: "frontmatter",
723
+ result: wc_default(valueText ? `${key}: ${valueText}` : key, { mode })
724
+ };
725
+ });
726
+ }
727
+ function buildSingleItem(name, text, mode, source) {
728
+ return [{
729
+ name,
730
+ source,
731
+ result: wc_default(text, { mode })
732
+ }];
733
+ }
734
+ function sumTotals(items) {
735
+ return items.reduce((sum, item) => sum + item.result.total, 0);
736
+ }
737
+ function countSections(input, section, mode) {
738
+ if (section === "all") {
739
+ const result = wc_default(input, { mode });
740
+ return {
741
+ section,
742
+ total: result.total,
743
+ frontmatterType: null,
744
+ items: [{
745
+ name: "all",
746
+ source: "content",
747
+ result
748
+ }]
749
+ };
750
+ }
751
+ const parsed = parseMarkdown(input);
752
+ const frontmatterText = parsed.frontmatter ?? "";
753
+ const contentText = parsed.content ?? "";
754
+ let items = [];
755
+ if (section === "frontmatter") items = buildSingleItem("frontmatter", frontmatterText, mode, "frontmatter");
756
+ else if (section === "content") items = buildSingleItem("content", contentText, mode, "content");
757
+ else if (section === "split") items = [...buildSingleItem("frontmatter", frontmatterText, mode, "frontmatter"), ...buildSingleItem("content", contentText, mode, "content")];
758
+ else if (section === "per-key") items = buildPerKeyItems(parsed.data, mode);
759
+ else if (section === "split-per-key") items = [...buildPerKeyItems(parsed.data, mode), ...buildSingleItem("content", contentText, mode, "content")];
760
+ return {
761
+ section,
762
+ total: sumTotals(items),
763
+ frontmatterType: parsed.frontmatterType,
764
+ items
765
+ };
766
+ }
767
+
768
+ //#endregion
769
+ //#region node_modules/picocolors/picocolors.js
770
+ var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
771
+ let p = process || {}, argv = p.argv || [], env = p.env || {};
772
+ let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
773
+ let formatter = (open, close, replace = open) => (input) => {
774
+ let string = "" + input, index = string.indexOf(close, open.length);
775
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
776
+ };
777
+ let replaceClose = (string, close, replace, index) => {
778
+ let result = "", cursor = 0;
779
+ do {
780
+ result += string.substring(cursor, index) + replace;
781
+ cursor = index + close.length;
782
+ index = string.indexOf(close, cursor);
783
+ } while (~index);
784
+ return result + string.substring(cursor);
785
+ };
786
+ let createColors = (enabled = isColorSupported) => {
787
+ let f = enabled ? formatter : () => String;
788
+ return {
789
+ isColorSupported: enabled,
790
+ reset: f("\x1B[0m", "\x1B[0m"),
791
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
792
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
793
+ italic: f("\x1B[3m", "\x1B[23m"),
794
+ underline: f("\x1B[4m", "\x1B[24m"),
795
+ inverse: f("\x1B[7m", "\x1B[27m"),
796
+ hidden: f("\x1B[8m", "\x1B[28m"),
797
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
798
+ black: f("\x1B[30m", "\x1B[39m"),
799
+ red: f("\x1B[31m", "\x1B[39m"),
800
+ green: f("\x1B[32m", "\x1B[39m"),
801
+ yellow: f("\x1B[33m", "\x1B[39m"),
802
+ blue: f("\x1B[34m", "\x1B[39m"),
803
+ magenta: f("\x1B[35m", "\x1B[39m"),
804
+ cyan: f("\x1B[36m", "\x1B[39m"),
805
+ white: f("\x1B[37m", "\x1B[39m"),
806
+ gray: f("\x1B[90m", "\x1B[39m"),
807
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
808
+ bgRed: f("\x1B[41m", "\x1B[49m"),
809
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
810
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
811
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
812
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
813
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
814
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
815
+ blackBright: f("\x1B[90m", "\x1B[39m"),
816
+ redBright: f("\x1B[91m", "\x1B[39m"),
817
+ greenBright: f("\x1B[92m", "\x1B[39m"),
818
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
819
+ blueBright: f("\x1B[94m", "\x1B[39m"),
820
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
821
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
822
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
823
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
824
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
825
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
826
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
827
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
828
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
829
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
830
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
831
+ };
832
+ };
833
+ module.exports = createColors();
834
+ module.exports.createColors = createColors;
835
+ }));
836
+
837
+ //#endregion
838
+ //#region src/command.ts
839
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
840
+ const MODE_CHOICES = [
841
+ "chunk",
842
+ "segments",
843
+ "collector"
844
+ ];
845
+ const FORMAT_CHOICES = [
846
+ "standard",
847
+ "raw",
848
+ "json"
849
+ ];
850
+ const SECTION_CHOICES = [
851
+ "all",
852
+ "split",
853
+ "frontmatter",
854
+ "content",
855
+ "per-key",
856
+ "split-per-key"
857
+ ];
858
+ function getPackageVersion() {
859
+ const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
860
+ const version = JSON.parse(raw).version ?? "0.0.0";
861
+ return import_picocolors.default.bgBlack(import_picocolors.default.bold(import_picocolors.default.italic(` word-counter ${import_picocolors.default.cyanBright(`ver.${version}`)} `)));
862
+ }
863
+ function renderChunkBreakdown(items) {
864
+ for (const item of items) console.log(`Locale ${item.locale}: ${showSingularOrPluralWord(item.words, "word")}`);
865
+ }
866
+ function renderSegmentBreakdown(items) {
867
+ for (const item of items) console.log(`Locale ${item.locale}: ${JSON.stringify(item.segments)} (${showSingularOrPluralWord(item.words, "word")})`);
868
+ }
869
+ function renderCollectorBreakdown(items) {
870
+ for (const item of items) console.log(`Locale ${item.locale}: ${showSingularOrPluralWord(item.words, "word")}`);
871
+ }
872
+ function renderStandardResult(result) {
873
+ console.log(`Total words: ${result.total}`);
874
+ if (result.breakdown.mode === "segments") {
875
+ renderSegmentBreakdown(result.breakdown.items);
876
+ return;
877
+ }
878
+ if (result.breakdown.mode === "collector") {
879
+ renderCollectorBreakdown(result.breakdown.items);
880
+ return;
881
+ }
882
+ renderChunkBreakdown(result.breakdown.items);
883
+ }
884
+ function buildSectionLabel(sectionName, sectionMode, source) {
885
+ if (sectionMode === "frontmatter") return `[Frontmatter] (total)`;
886
+ if (sectionMode === "content") return `[Content] (total)`;
887
+ if (sectionMode === "split") {
888
+ if (source === "frontmatter") return `[Frontmatter] (total)`;
889
+ return `[Content] (total)`;
890
+ }
891
+ if (sectionMode === "per-key") return `[Frontmatter] ${sectionName} (total)`;
892
+ if (sectionMode === "split-per-key") {
893
+ if (source === "content") return `[Content] (total)`;
894
+ return `[Frontmatter] ${sectionName} (total)`;
895
+ }
896
+ return `[Section] ${sectionName} (total)`;
897
+ }
898
+ function renderStandardSectionedResult(result) {
899
+ console.log(`Total words: ${result.total}`);
900
+ for (const item of result.items) {
901
+ const label = buildSectionLabel(item.name, result.section, item.source);
902
+ console.log(import_picocolors.default.cyan(import_picocolors.default.bold(`${label}: ${showSingularOrPluralWord(item.result.total, "word")}`)));
903
+ if (item.result.breakdown.mode === "segments") {
904
+ renderSegmentBreakdown(item.result.breakdown.items);
905
+ continue;
906
+ }
907
+ if (item.result.breakdown.mode === "collector") {
908
+ renderCollectorBreakdown(item.result.breakdown.items);
909
+ continue;
910
+ }
911
+ renderChunkBreakdown(item.result.breakdown.items);
912
+ }
913
+ }
914
+ function isSectionedResult(result) {
915
+ return "section" in result;
916
+ }
917
+ async function readStdin() {
918
+ if (process.stdin.isTTY) return "";
919
+ return new Promise((resolve$1, reject) => {
920
+ const chunks = [];
921
+ process.stdin.setEncoding("utf8");
922
+ process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
923
+ process.stdin.on("end", () => resolve$1(chunks.join("")));
924
+ process.stdin.on("error", (error) => reject(error));
925
+ process.stdin.resume();
926
+ });
927
+ }
928
+ async function resolveInput(textTokens, pathInput) {
929
+ if (pathInput) return readFile(resolve(pathInput), "utf8");
930
+ if (textTokens.length > 0) return textTokens.join(" ");
931
+ return readStdin();
932
+ }
933
+ async function runCli(argv = process.argv) {
934
+ const program = new Command();
935
+ program.name("word-counter").description("Locale-aware word counting powered by Intl.Segmenter.").version(getPackageVersion(), "-v, --version", "output the version number").addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).option("--pretty", "pretty print JSON output", false).option("-p, --path <file>", "read input from a text file").argument("[text...]", "text to count").showHelpAfterError();
936
+ program.action(async (textTokens, options) => {
937
+ let input;
938
+ try {
939
+ input = await resolveInput(textTokens, options.path);
940
+ } catch (error) {
941
+ const message = error instanceof Error ? error.message : String(error);
942
+ program.error(`Failed to read input: ${message}`);
943
+ return;
944
+ }
945
+ const trimmed = input.trim();
946
+ if (!trimmed) {
947
+ program.error(import_picocolors.default.red("No input provided. Pass text, pipe stdin, or use --path."));
948
+ return;
949
+ }
950
+ const result = options.section !== "all" ? countSections(trimmed, options.section, options.mode) : wc_default(trimmed, { mode: options.mode });
951
+ if (options.format === "raw") {
952
+ console.log(result.total);
953
+ return;
954
+ }
955
+ if (options.format === "json") {
956
+ const spacing = options.pretty ? 2 : 0;
957
+ console.log(JSON.stringify(result, null, spacing));
958
+ return;
959
+ }
960
+ if (isSectionedResult(result)) {
961
+ renderStandardSectionedResult(result);
962
+ return;
963
+ }
964
+ renderStandardResult(result);
965
+ });
966
+ await program.parseAsync(argv);
967
+ }
968
+
969
+ //#endregion
970
+ //#region src/bin.ts
971
+ runCli().catch((error) => {
972
+ const message = error instanceof Error ? error.message : String(error);
973
+ console.error("Failed to run CLI:", message);
974
+ process.exitCode = 1;
975
+ });
976
+
977
+ //#endregion
978
+ export { };
979
+ //# sourceMappingURL=bin.mjs.map