@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.
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/cjs/index.cjs +754 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/esm/bin.mjs +979 -0
- package/dist/esm/bin.mjs.map +1 -0
- package/dist/esm/index.d.mts +75 -0
- package/dist/esm/index.mjs +742 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +46 -0
package/dist/esm/bin.mjs
ADDED
|
@@ -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
|