@dev-pi2pie/word-counter 0.1.0-canary.5 → 0.1.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.
- package/README.md +8 -1
- package/dist/cjs/index.cjs +97 -15
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/bin.mjs +1945 -1744
- package/dist/esm/bin.mjs.map +1 -1
- package/dist/esm/index.d.mts +9 -1
- package/dist/esm/index.mjs +97 -15
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/esm/bin.mjs
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
|
|
5
|
-
import { parseDocument } from "yaml";
|
|
6
5
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
8
|
+
import { parseDocument } from "yaml";
|
|
8
9
|
|
|
9
10
|
//#region \0rolldown/runtime.js
|
|
10
11
|
var __create = Object.create;
|
|
@@ -34,1393 +35,1820 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
35
|
}) : target, mod));
|
|
35
36
|
|
|
36
37
|
//#endregion
|
|
37
|
-
//#region src/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const list = [];
|
|
42
|
-
result[key] = list;
|
|
43
|
-
return list;
|
|
38
|
+
//#region src/cli/debug/channel.ts
|
|
39
|
+
const NOOP_CLOSE = async () => {};
|
|
40
|
+
function shouldEmitAtVerbosity(channelVerbosity, eventVerbosity) {
|
|
41
|
+
return channelVerbosity === "verbose" || eventVerbosity === "compact";
|
|
44
42
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
function formatTimestampPart(value) {
|
|
44
|
+
return String(value).padStart(2, "0");
|
|
45
|
+
}
|
|
46
|
+
function formatDebugReportTimestamp(now) {
|
|
47
|
+
return [`${now.getFullYear()}${formatTimestampPart(now.getMonth() + 1)}${formatTimestampPart(now.getDate())}`, `${formatTimestampPart(now.getHours())}${formatTimestampPart(now.getMinutes())}${formatTimestampPart(now.getSeconds())}`].join("-");
|
|
48
|
+
}
|
|
49
|
+
function withCollisionSuffix(pathValue, sequence) {
|
|
50
|
+
if (sequence <= 0) return pathValue;
|
|
51
|
+
const extension = extname(pathValue);
|
|
52
|
+
const baseName = basename(pathValue, extension);
|
|
53
|
+
return join(dirname(pathValue), `${baseName}-${sequence}${extension}`);
|
|
54
|
+
}
|
|
55
|
+
function resolveReportPath(report, now, pid) {
|
|
56
|
+
const cwd = report.cwd ?? process.cwd();
|
|
57
|
+
const defaultName = `wc-debug-${formatDebugReportTimestamp(now)}-${pid}.jsonl`;
|
|
58
|
+
const explicitPath = typeof report.path === "string";
|
|
59
|
+
const basePath = explicitPath ? resolve(cwd, report.path) : resolve(cwd, defaultName);
|
|
60
|
+
mkdirSync(dirname(basePath), { recursive: true });
|
|
61
|
+
if (explicitPath) {
|
|
62
|
+
if (existsSync(basePath) && statSync(basePath).isDirectory()) throw new Error(`debug report path must be a file: ${basePath}`);
|
|
63
|
+
return basePath;
|
|
64
|
+
}
|
|
65
|
+
let candidate = basePath;
|
|
66
|
+
let sequence = 0;
|
|
67
|
+
while (existsSync(candidate)) {
|
|
68
|
+
sequence += 1;
|
|
69
|
+
candidate = withCollisionSuffix(basePath, sequence);
|
|
49
70
|
}
|
|
71
|
+
return candidate;
|
|
50
72
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
function createTerminalSink() {
|
|
74
|
+
return {
|
|
75
|
+
write(line) {
|
|
76
|
+
console.error(`[debug] ${line}`);
|
|
77
|
+
},
|
|
78
|
+
close: NOOP_CLOSE
|
|
79
|
+
};
|
|
58
80
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
81
|
+
function createFileSink(pathValue) {
|
|
82
|
+
try {
|
|
83
|
+
closeSync(openSync(pathValue, "a"));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
throw new Error(`debug report path is not writable: ${pathValue} (${message})`);
|
|
65
87
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
const stream = createWriteStream(pathValue, {
|
|
89
|
+
flags: "a",
|
|
90
|
+
encoding: "utf8"
|
|
91
|
+
});
|
|
92
|
+
let streamError;
|
|
93
|
+
stream.on("error", (error) => {
|
|
94
|
+
streamError = error;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
write(line) {
|
|
98
|
+
if (streamError || stream.destroyed) return;
|
|
99
|
+
stream.write(`${line}\n`);
|
|
100
|
+
},
|
|
101
|
+
close() {
|
|
102
|
+
if (streamError || stream.destroyed || stream.writableEnded) return Promise.resolve();
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
stream.end(() => {
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createDebugChannel(options) {
|
|
112
|
+
if (!options.enabled) return {
|
|
113
|
+
enabled: false,
|
|
114
|
+
verbosity: options.verbosity ?? "compact",
|
|
115
|
+
emit() {},
|
|
116
|
+
close: NOOP_CLOSE
|
|
117
|
+
};
|
|
118
|
+
const verbosity = options.verbosity ?? "compact";
|
|
119
|
+
const sinks = [];
|
|
120
|
+
let reportPath;
|
|
121
|
+
if (options.report) {
|
|
122
|
+
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
123
|
+
const pid = options.pid ?? process.pid;
|
|
124
|
+
reportPath = resolveReportPath(options.report, now, pid);
|
|
125
|
+
sinks.push(createFileSink(reportPath));
|
|
126
|
+
if (options.report.tee) sinks.push(createTerminalSink());
|
|
127
|
+
} else sinks.push(createTerminalSink());
|
|
128
|
+
return {
|
|
129
|
+
enabled: true,
|
|
130
|
+
verbosity,
|
|
131
|
+
reportPath,
|
|
132
|
+
emit(event, details = {}, eventOptions = {}) {
|
|
133
|
+
if (!shouldEmitAtVerbosity(verbosity, eventOptions.verbosity ?? "compact")) return;
|
|
134
|
+
const payload = JSON.stringify({
|
|
135
|
+
event,
|
|
136
|
+
...details
|
|
137
|
+
});
|
|
138
|
+
for (const sink of sinks) sink.write(payload);
|
|
139
|
+
},
|
|
140
|
+
async close() {
|
|
141
|
+
for (const sink of sinks) await sink.close();
|
|
142
|
+
}
|
|
143
|
+
};
|
|
69
144
|
}
|
|
70
145
|
|
|
71
146
|
//#endregion
|
|
72
|
-
//#region src/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (char === "\"") {
|
|
98
|
-
inString = "double";
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (char === "'") {
|
|
102
|
-
inString = "single";
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (char === "#") return line.slice(0, i).trimEnd();
|
|
147
|
+
//#region src/cli/path/filter.ts
|
|
148
|
+
const DEFAULT_INCLUDE_EXTENSIONS = new Set([
|
|
149
|
+
".md",
|
|
150
|
+
".markdown",
|
|
151
|
+
".mdx",
|
|
152
|
+
".mdc",
|
|
153
|
+
".txt"
|
|
154
|
+
]);
|
|
155
|
+
function collectExtensionOption(value, previous = []) {
|
|
156
|
+
return [...previous, value];
|
|
157
|
+
}
|
|
158
|
+
function normalizeExtensionToken(value) {
|
|
159
|
+
const trimmed = value.trim().toLowerCase();
|
|
160
|
+
if (!trimmed) return null;
|
|
161
|
+
const normalized = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
162
|
+
if (normalized === ".") return null;
|
|
163
|
+
return normalized;
|
|
164
|
+
}
|
|
165
|
+
function parseExtensionValues(values) {
|
|
166
|
+
const parsed = /* @__PURE__ */ new Set();
|
|
167
|
+
if (!values || values.length === 0) return parsed;
|
|
168
|
+
for (const value of values) for (const token of value.split(",")) {
|
|
169
|
+
const normalized = normalizeExtensionToken(token);
|
|
170
|
+
if (!normalized) continue;
|
|
171
|
+
parsed.add(normalized);
|
|
106
172
|
}
|
|
107
|
-
return
|
|
173
|
+
return parsed;
|
|
108
174
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
175
|
+
function buildDirectoryExtensionFilter(includeValues, excludeValues) {
|
|
176
|
+
const includeFromFlags = parseExtensionValues(includeValues);
|
|
177
|
+
const excludeExtensions = parseExtensionValues(excludeValues);
|
|
178
|
+
const includeExtensions = includeFromFlags.size > 0 ? includeFromFlags : new Set(DEFAULT_INCLUDE_EXTENSIONS);
|
|
179
|
+
const effectiveIncludeExtensions = /* @__PURE__ */ new Set();
|
|
180
|
+
for (const extension of includeExtensions) {
|
|
181
|
+
if (excludeExtensions.has(extension)) continue;
|
|
182
|
+
effectiveIncludeExtensions.add(extension);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
includeExtensions,
|
|
186
|
+
excludeExtensions,
|
|
187
|
+
effectiveIncludeExtensions
|
|
188
|
+
};
|
|
111
189
|
}
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (value.startsWith("\"") && value.endsWith("\"")) return unescapeBasic(value.slice(1, -1));
|
|
116
|
-
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
117
|
-
return null;
|
|
190
|
+
function shouldIncludeFromDirectory(filePath, filter) {
|
|
191
|
+
const extension = extname(filePath).toLowerCase();
|
|
192
|
+
return filter.effectiveIncludeExtensions.has(extension);
|
|
118
193
|
}
|
|
119
194
|
|
|
120
195
|
//#endregion
|
|
121
|
-
//#region src/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
196
|
+
//#region src/cli/total-of.ts
|
|
197
|
+
const TOTAL_OF_PARTS = [
|
|
198
|
+
"words",
|
|
199
|
+
"emoji",
|
|
200
|
+
"symbols",
|
|
201
|
+
"punctuation",
|
|
202
|
+
"whitespace"
|
|
203
|
+
];
|
|
204
|
+
const TOTAL_OF_PART_ALIASES = {
|
|
205
|
+
word: "words",
|
|
206
|
+
words: "words",
|
|
207
|
+
emoji: "emoji",
|
|
208
|
+
emojis: "emoji",
|
|
209
|
+
symbol: "symbols",
|
|
210
|
+
symbols: "symbols",
|
|
211
|
+
punction: "punctuation",
|
|
212
|
+
punctuation: "punctuation",
|
|
213
|
+
whitespace: "whitespace"
|
|
214
|
+
};
|
|
215
|
+
function createTotalOfCounts() {
|
|
216
|
+
return {
|
|
217
|
+
words: 0,
|
|
218
|
+
emoji: 0,
|
|
219
|
+
symbols: 0,
|
|
220
|
+
punctuation: 0,
|
|
221
|
+
whitespace: 0
|
|
222
|
+
};
|
|
132
223
|
}
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
224
|
+
function collectNonWordCounts(target, nonWords) {
|
|
225
|
+
if (!nonWords) return;
|
|
226
|
+
target.emoji += nonWords.counts.emoji;
|
|
227
|
+
target.symbols += nonWords.counts.symbols;
|
|
228
|
+
target.punctuation += nonWords.counts.punctuation;
|
|
229
|
+
target.whitespace += nonWords.counts.whitespace ?? 0;
|
|
230
|
+
}
|
|
231
|
+
function collectFromWordCounterResult(result) {
|
|
232
|
+
const counts = createTotalOfCounts();
|
|
233
|
+
counts.words += result.counts?.words ?? result.total;
|
|
234
|
+
if (result.breakdown.mode === "collector") {
|
|
235
|
+
collectNonWordCounts(counts, result.breakdown.nonWords);
|
|
236
|
+
return counts;
|
|
237
|
+
}
|
|
238
|
+
for (const item of result.breakdown.items) collectNonWordCounts(counts, item.nonWords);
|
|
239
|
+
return counts;
|
|
240
|
+
}
|
|
241
|
+
function collectTotalOfCounts(result) {
|
|
242
|
+
if (!("section" in result)) return collectFromWordCounterResult(result);
|
|
243
|
+
const counts = createTotalOfCounts();
|
|
244
|
+
for (const item of result.items) {
|
|
245
|
+
const itemCounts = collectFromWordCounterResult(item.result);
|
|
246
|
+
for (const part of TOTAL_OF_PARTS) counts[part] += itemCounts[part];
|
|
247
|
+
}
|
|
248
|
+
return counts;
|
|
249
|
+
}
|
|
250
|
+
function parseTotalOfToken(token) {
|
|
251
|
+
const canonical = TOTAL_OF_PART_ALIASES[token.trim().toLowerCase()];
|
|
252
|
+
if (canonical) return canonical;
|
|
253
|
+
throw new Error(`Invalid --total-of part: ${token}. Allowed: ${TOTAL_OF_PARTS.join(", ")}.`);
|
|
254
|
+
}
|
|
255
|
+
function parseTotalOfOption(value) {
|
|
256
|
+
const rawTokens = value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
257
|
+
if (rawTokens.length === 0) throw new Error(`Invalid --total-of value: "${value}". Use comma-separated parts from: ${TOTAL_OF_PARTS.join(", ")}.`);
|
|
258
|
+
const parts = [];
|
|
259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
260
|
+
for (const token of rawTokens) {
|
|
261
|
+
const parsed = parseTotalOfToken(token);
|
|
262
|
+
if (seen.has(parsed)) continue;
|
|
263
|
+
seen.add(parsed);
|
|
264
|
+
parts.push(parsed);
|
|
265
|
+
}
|
|
266
|
+
return parts;
|
|
267
|
+
}
|
|
268
|
+
function requiresNonWordCollection(parts) {
|
|
269
|
+
if (!parts || parts.length === 0) return false;
|
|
270
|
+
return parts.some((part) => part !== "words");
|
|
271
|
+
}
|
|
272
|
+
function requiresWhitespaceCollection(parts) {
|
|
273
|
+
if (!parts || parts.length === 0) return false;
|
|
274
|
+
return parts.includes("whitespace");
|
|
275
|
+
}
|
|
276
|
+
function resolveTotalOfOverride(result, parts) {
|
|
277
|
+
if (!parts || parts.length === 0) return;
|
|
278
|
+
const counts = collectTotalOfCounts(result);
|
|
279
|
+
let total = 0;
|
|
280
|
+
for (const part of parts) total += counts[part];
|
|
281
|
+
return {
|
|
282
|
+
parts: [...parts],
|
|
283
|
+
total
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function formatTotalOfParts(parts) {
|
|
287
|
+
return parts.join(", ");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/cli/program/options.ts
|
|
292
|
+
const MODE_CHOICES = [
|
|
293
|
+
"chunk",
|
|
294
|
+
"segments",
|
|
295
|
+
"collector",
|
|
296
|
+
"char",
|
|
297
|
+
"char-collector"
|
|
298
|
+
];
|
|
299
|
+
const FORMAT_CHOICES = [
|
|
300
|
+
"standard",
|
|
301
|
+
"raw",
|
|
302
|
+
"json"
|
|
303
|
+
];
|
|
304
|
+
const SECTION_CHOICES = [
|
|
305
|
+
"all",
|
|
306
|
+
"split",
|
|
307
|
+
"frontmatter",
|
|
308
|
+
"content",
|
|
309
|
+
"per-key",
|
|
310
|
+
"split-per-key"
|
|
311
|
+
];
|
|
312
|
+
const PATH_MODE_CHOICES = ["auto", "manual"];
|
|
313
|
+
function collectPathValue(value, previous = []) {
|
|
314
|
+
return [...previous, value];
|
|
315
|
+
}
|
|
316
|
+
function configureProgramOptions(program, parseMode) {
|
|
317
|
+
program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).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")).addOption(new Option("--path-mode <mode>", "path resolution mode").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies with --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-skips", "hide skip diagnostics (applies when --debug is enabled)").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("-p, --path <path>", "read input from file or directory", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region node_modules/picocolors/picocolors.js
|
|
322
|
+
var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
323
|
+
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
324
|
+
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);
|
|
325
|
+
let formatter = (open, close, replace = open) => (input) => {
|
|
326
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
327
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
328
|
+
};
|
|
329
|
+
let replaceClose = (string, close, replace, index) => {
|
|
330
|
+
let result = "", cursor = 0;
|
|
331
|
+
do {
|
|
332
|
+
result += string.substring(cursor, index) + replace;
|
|
333
|
+
cursor = index + close.length;
|
|
334
|
+
index = string.indexOf(close, cursor);
|
|
335
|
+
} while (~index);
|
|
336
|
+
return result + string.substring(cursor);
|
|
337
|
+
};
|
|
338
|
+
let createColors = (enabled = isColorSupported) => {
|
|
339
|
+
let f = enabled ? formatter : () => String;
|
|
340
|
+
return {
|
|
341
|
+
isColorSupported: enabled,
|
|
342
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
343
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
344
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
345
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
346
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
347
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
348
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
349
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
350
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
351
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
352
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
353
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
354
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
355
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
356
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
357
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
358
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
359
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
360
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
361
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
362
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
363
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
364
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
365
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
366
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
367
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
368
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
369
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
370
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
371
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
372
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
373
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
374
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
375
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
376
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
377
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
378
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
379
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
380
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
381
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
382
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
383
|
+
};
|
|
384
|
+
};
|
|
385
|
+
module.exports = createColors();
|
|
386
|
+
module.exports.createColors = createColors;
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region src/cli/program/version.ts
|
|
391
|
+
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
392
|
+
function* candidateSearchRoots() {
|
|
393
|
+
yield dirname(fileURLToPath(import.meta.url));
|
|
394
|
+
const argvPath = process.argv[1];
|
|
395
|
+
if (typeof argvPath === "string" && argvPath.length > 0) yield dirname(resolve(argvPath));
|
|
396
|
+
yield process.cwd();
|
|
397
|
+
}
|
|
398
|
+
function* walkUpDirectories(start, maxLevels) {
|
|
399
|
+
let current = start;
|
|
400
|
+
for (let depth = 0; depth < maxLevels; depth += 1) {
|
|
401
|
+
yield current;
|
|
402
|
+
const parent = dirname(current);
|
|
403
|
+
if (parent === current) break;
|
|
404
|
+
current = parent;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function resolveVersionFromPath(start, maxLevels) {
|
|
408
|
+
for (const directory of walkUpDirectories(start, maxLevels)) try {
|
|
409
|
+
const raw = readFileSync(join(directory, "package.json"), "utf8");
|
|
410
|
+
const data = JSON.parse(raw);
|
|
411
|
+
if (data.version) return data.version;
|
|
412
|
+
} catch {}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
function resolvePackageVersion() {
|
|
416
|
+
const maxLevels = 8;
|
|
417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
418
|
+
for (const root of candidateSearchRoots()) {
|
|
419
|
+
if (seen.has(root)) continue;
|
|
420
|
+
seen.add(root);
|
|
421
|
+
const version = resolveVersionFromPath(root, maxLevels);
|
|
422
|
+
if (version) return version;
|
|
423
|
+
}
|
|
424
|
+
return "0.0.0";
|
|
425
|
+
}
|
|
426
|
+
function getFormattedVersionLabel() {
|
|
427
|
+
const version = resolvePackageVersion();
|
|
428
|
+
return import_picocolors.default.bgBlack(import_picocolors.default.bold(import_picocolors.default.italic(` word-counter ${import_picocolors.default.cyanBright(`ver.${version}`)} `)));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/utils/append-all.ts
|
|
433
|
+
function appendAll(target, source) {
|
|
434
|
+
for (const item of source) target.push(item);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/cli/path/load.ts
|
|
439
|
+
function isProbablyBinary(buffer) {
|
|
440
|
+
if (buffer.length === 0) return false;
|
|
441
|
+
const sampleSize = Math.min(buffer.length, 1024);
|
|
442
|
+
let suspicious = 0;
|
|
443
|
+
for (let index = 0; index < sampleSize; index += 1) {
|
|
444
|
+
const byte = buffer[index] ?? 0;
|
|
445
|
+
if (byte === 0) return true;
|
|
446
|
+
if (byte === 9 || byte === 10 || byte === 13) continue;
|
|
447
|
+
if (byte >= 32 && byte <= 126) continue;
|
|
448
|
+
if (byte >= 128) continue;
|
|
449
|
+
suspicious += 1;
|
|
450
|
+
}
|
|
451
|
+
return suspicious / sampleSize > .3;
|
|
452
|
+
}
|
|
453
|
+
async function loadBatchInputs(filePaths) {
|
|
454
|
+
const files = [];
|
|
455
|
+
const skipped = [];
|
|
456
|
+
for (const filePath of filePaths) {
|
|
457
|
+
let buffer;
|
|
458
|
+
try {
|
|
459
|
+
buffer = await readFile(filePath);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
462
|
+
skipped.push({
|
|
463
|
+
path: filePath,
|
|
464
|
+
reason: `not readable: ${message}`
|
|
465
|
+
});
|
|
166
466
|
continue;
|
|
167
467
|
}
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
468
|
+
if (isProbablyBinary(buffer)) {
|
|
469
|
+
skipped.push({
|
|
470
|
+
path: filePath,
|
|
471
|
+
reason: "binary file"
|
|
472
|
+
});
|
|
173
473
|
continue;
|
|
174
474
|
}
|
|
175
|
-
|
|
475
|
+
files.push({
|
|
476
|
+
path: filePath,
|
|
477
|
+
content: buffer.toString("utf8")
|
|
478
|
+
});
|
|
176
479
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
480
|
+
return {
|
|
481
|
+
files,
|
|
482
|
+
skipped
|
|
483
|
+
};
|
|
181
484
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
|
|
485
|
+
|
|
486
|
+
//#endregion
|
|
487
|
+
//#region src/cli/path/resolve.ts
|
|
488
|
+
async function expandDirectory(directoryPath, recursive, filter, skipped, debug, stats) {
|
|
489
|
+
let entries;
|
|
490
|
+
try {
|
|
491
|
+
entries = await readdir(directoryPath, {
|
|
492
|
+
withFileTypes: true,
|
|
493
|
+
encoding: "utf8"
|
|
494
|
+
});
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
497
|
+
skipped.push({
|
|
498
|
+
path: directoryPath,
|
|
499
|
+
reason: `directory read failed: ${message}`
|
|
500
|
+
});
|
|
501
|
+
debug.emit("path.resolve.expand.read_failed", {
|
|
502
|
+
directory: directoryPath,
|
|
503
|
+
reason: `directory read failed: ${message}`
|
|
504
|
+
});
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
const sortedEntries = entries.slice().sort((left, right) => left.name.localeCompare(right.name));
|
|
508
|
+
const files = [];
|
|
509
|
+
debug.emit("path.resolve.expand.start", {
|
|
510
|
+
directory: directoryPath,
|
|
511
|
+
entries: sortedEntries.length,
|
|
512
|
+
recursive
|
|
513
|
+
});
|
|
514
|
+
for (const entry of sortedEntries) {
|
|
515
|
+
const entryPath = resolve(directoryPath, entry.name);
|
|
516
|
+
if (entry.isFile()) {
|
|
517
|
+
if (!shouldIncludeFromDirectory(entryPath, filter)) {
|
|
518
|
+
skipped.push({
|
|
519
|
+
path: entryPath,
|
|
520
|
+
reason: "extension excluded"
|
|
521
|
+
});
|
|
522
|
+
debug.emit("path.resolve.filter.excluded", {
|
|
523
|
+
path: entryPath,
|
|
524
|
+
reason: "extension excluded"
|
|
525
|
+
}, { verbosity: "verbose" });
|
|
526
|
+
stats.filterExcluded += 1;
|
|
203
527
|
continue;
|
|
204
528
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
current += char;
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (char === "'") {
|
|
215
|
-
inString = "single";
|
|
216
|
-
current += char;
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (char === "[") {
|
|
220
|
-
bracketDepth += 1;
|
|
221
|
-
current += char;
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (char === "]") {
|
|
225
|
-
if (bracketDepth > 0) bracketDepth -= 1;
|
|
226
|
-
current += char;
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (char === "{") {
|
|
230
|
-
braceDepth += 1;
|
|
231
|
-
current += char;
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
if (char === "}") {
|
|
235
|
-
if (braceDepth > 0) braceDepth -= 1;
|
|
236
|
-
current += char;
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
if (char === "," && bracketDepth === 0 && braceDepth === 0) {
|
|
240
|
-
pairs.push(current);
|
|
241
|
-
current = "";
|
|
529
|
+
files.push(entryPath);
|
|
530
|
+
stats.directoryIncluded += 1;
|
|
531
|
+
debug.emit("path.resolve.expand.include", {
|
|
532
|
+
path: entryPath,
|
|
533
|
+
source: "directory"
|
|
534
|
+
}, { verbosity: "verbose" });
|
|
242
535
|
continue;
|
|
243
536
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (current.trim()) pairs.push(current);
|
|
247
|
-
const output = {};
|
|
248
|
-
for (const pair of pairs) {
|
|
249
|
-
const separatorIndex = pair.indexOf("=");
|
|
250
|
-
if (separatorIndex === -1) return null;
|
|
251
|
-
const key = normalizeKeyPath(pair.slice(0, separatorIndex));
|
|
252
|
-
if (!key) return null;
|
|
253
|
-
const valueRaw = pair.slice(separatorIndex + 1).trim();
|
|
254
|
-
if (!valueRaw) return null;
|
|
255
|
-
if (valueRaw.startsWith("{")) return null;
|
|
256
|
-
const normalized = normalizeValue(valueRaw);
|
|
257
|
-
if (normalized === null) return null;
|
|
258
|
-
if (typeof normalized === "object" && !Array.isArray(normalized)) return null;
|
|
259
|
-
output[key] = normalized;
|
|
537
|
+
if (!entry.isDirectory() || !recursive) continue;
|
|
538
|
+
appendAll(files, await expandDirectory(entryPath, recursive, filter, skipped, debug, stats));
|
|
260
539
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (trimmed.startsWith("{") && trimmed.endsWith("}")) return parseInlineTable(trimmed);
|
|
267
|
-
const array = parseArray(trimmed);
|
|
268
|
-
if (array) return array;
|
|
269
|
-
if (trimmed.startsWith("[") && trimmed.endsWith("]")) return null;
|
|
270
|
-
return parsePrimitive(trimmed);
|
|
271
|
-
}
|
|
272
|
-
function toPlainText(value) {
|
|
273
|
-
if (value == null) return "";
|
|
274
|
-
if (Array.isArray(value)) return value.map((item) => String(item)).join(", ");
|
|
275
|
-
return String(value);
|
|
540
|
+
debug.emit("path.resolve.expand.complete", {
|
|
541
|
+
directory: directoryPath,
|
|
542
|
+
files: files.length
|
|
543
|
+
});
|
|
544
|
+
return files;
|
|
276
545
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
546
|
+
async function resolveBatchFilePaths(pathInputs, options) {
|
|
547
|
+
const skipped = [];
|
|
548
|
+
const resolvedFiles = /* @__PURE__ */ new Set();
|
|
549
|
+
const stats = {
|
|
550
|
+
dedupeAccepted: 0,
|
|
551
|
+
dedupeDuplicates: 0,
|
|
552
|
+
filterExcluded: 0,
|
|
553
|
+
directoryIncluded: 0
|
|
554
|
+
};
|
|
555
|
+
const extensionFilter = options.extensionFilter ?? buildDirectoryExtensionFilter(void 0, void 0);
|
|
556
|
+
const debug = options.debug ?? {
|
|
557
|
+
enabled: false,
|
|
558
|
+
verbosity: "compact",
|
|
559
|
+
emit() {},
|
|
560
|
+
close: async () => {}
|
|
561
|
+
};
|
|
562
|
+
debug.emit("path.resolve.inputs", {
|
|
563
|
+
inputs: pathInputs.length,
|
|
564
|
+
pathMode: options.pathMode,
|
|
565
|
+
recursive: options.recursive
|
|
566
|
+
});
|
|
567
|
+
const addResolvedFile = (filePath, details) => {
|
|
568
|
+
if (resolvedFiles.has(filePath)) {
|
|
569
|
+
stats.dedupeDuplicates += 1;
|
|
570
|
+
debug.emit("path.resolve.dedupe.duplicate", {
|
|
571
|
+
path: filePath,
|
|
572
|
+
source: details.source,
|
|
573
|
+
input: details.input
|
|
574
|
+
}, { verbosity: "verbose" });
|
|
575
|
+
return;
|
|
302
576
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
577
|
+
resolvedFiles.add(filePath);
|
|
578
|
+
stats.dedupeAccepted += 1;
|
|
579
|
+
debug.emit("path.resolve.dedupe.accept", {
|
|
580
|
+
path: filePath,
|
|
581
|
+
source: details.source,
|
|
582
|
+
input: details.input
|
|
583
|
+
}, { verbosity: "verbose" });
|
|
584
|
+
};
|
|
585
|
+
for (const rawPath of pathInputs) {
|
|
586
|
+
const targetPath = resolve(rawPath);
|
|
587
|
+
debug.emit("path.resolve.input", {
|
|
588
|
+
rawPath,
|
|
589
|
+
resolvedPath: targetPath
|
|
590
|
+
});
|
|
591
|
+
let metadata;
|
|
592
|
+
try {
|
|
593
|
+
metadata = await stat(targetPath);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
596
|
+
skipped.push({
|
|
597
|
+
path: targetPath,
|
|
598
|
+
reason: `not readable: ${message}`
|
|
599
|
+
});
|
|
600
|
+
debug.emit("path.resolve.skip", {
|
|
601
|
+
path: targetPath,
|
|
602
|
+
reason: `not readable: ${message}`
|
|
603
|
+
});
|
|
310
604
|
continue;
|
|
311
605
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const strippedAfter = stripInlineComment(valueRaw.slice(closingIndex + tripleDelimiter.length));
|
|
323
|
-
valueRaw = `${valueRaw.slice(0, closingIndex + tripleDelimiter.length)}${strippedAfter}`;
|
|
324
|
-
} else {
|
|
325
|
-
const delimiter = tripleDelimiter;
|
|
326
|
-
let combined = valueRaw;
|
|
327
|
-
let closed = false;
|
|
328
|
-
while (index + 1 < lines.length) {
|
|
329
|
-
index += 1;
|
|
330
|
-
const nextLine = lines[index] ?? "";
|
|
331
|
-
combined += `\n${nextLine}`;
|
|
332
|
-
if (new RegExp(`${delimiter}\\s*$`).test(nextLine)) {
|
|
333
|
-
closed = true;
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (!closed) return null;
|
|
338
|
-
valueRaw = combined;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
const normalized = normalizeValue(valueRaw);
|
|
342
|
-
if (normalized === null) return null;
|
|
343
|
-
const fullKey = tablePrefix ? `${tablePrefix}.${key}` : key;
|
|
344
|
-
if (typeof normalized === "object" && !Array.isArray(normalized)) {
|
|
345
|
-
for (const [inlineKey, inlineValue] of Object.entries(normalized)) {
|
|
346
|
-
const entryKey = tablePrefixInList ? `${key}.${inlineKey}` : `${fullKey}.${inlineKey}`;
|
|
347
|
-
if (tablePrefixInList && tableTarget) tableTarget[entryKey] = toPlainText(inlineValue);
|
|
348
|
-
else result[entryKey] = toPlainText(inlineValue);
|
|
349
|
-
}
|
|
606
|
+
if (metadata.isDirectory() && options.pathMode === "auto") {
|
|
607
|
+
debug.emit("path.resolve.root.expand", {
|
|
608
|
+
root: targetPath,
|
|
609
|
+
recursive: options.recursive
|
|
610
|
+
});
|
|
611
|
+
const files = await expandDirectory(targetPath, options.recursive, extensionFilter, skipped, debug, stats);
|
|
612
|
+
for (const file of files) addResolvedFile(file, {
|
|
613
|
+
source: "directory",
|
|
614
|
+
input: targetPath
|
|
615
|
+
});
|
|
350
616
|
continue;
|
|
351
617
|
}
|
|
352
|
-
if (
|
|
353
|
-
|
|
618
|
+
if (!metadata.isFile()) {
|
|
619
|
+
skipped.push({
|
|
620
|
+
path: targetPath,
|
|
621
|
+
reason: "not a regular file"
|
|
622
|
+
});
|
|
623
|
+
debug.emit("path.resolve.skip", {
|
|
624
|
+
path: targetPath,
|
|
625
|
+
reason: "not a regular file"
|
|
626
|
+
});
|
|
354
627
|
continue;
|
|
355
628
|
}
|
|
356
|
-
|
|
629
|
+
addResolvedFile(targetPath, {
|
|
630
|
+
source: "direct",
|
|
631
|
+
input: targetPath
|
|
632
|
+
});
|
|
357
633
|
}
|
|
358
|
-
|
|
359
|
-
|
|
634
|
+
const files = [...resolvedFiles].sort((left, right) => left.localeCompare(right));
|
|
635
|
+
debug.emit("path.resolve.filter.summary", {
|
|
636
|
+
excluded: stats.filterExcluded,
|
|
637
|
+
included: stats.directoryIncluded
|
|
638
|
+
});
|
|
639
|
+
debug.emit("path.resolve.dedupe.summary", {
|
|
640
|
+
accepted: stats.dedupeAccepted,
|
|
641
|
+
duplicates: stats.dedupeDuplicates
|
|
642
|
+
});
|
|
643
|
+
debug.emit("path.resolve.complete", {
|
|
644
|
+
files: files.length,
|
|
645
|
+
skipped: skipped.length,
|
|
646
|
+
ordering: "absolute-path-ascending"
|
|
647
|
+
});
|
|
648
|
+
return {
|
|
649
|
+
files,
|
|
650
|
+
skipped
|
|
651
|
+
};
|
|
360
652
|
}
|
|
361
653
|
|
|
362
654
|
//#endregion
|
|
363
|
-
//#region src/
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
function normalizeNewlines(input) {
|
|
370
|
-
return input.replace(/\r\n/g, "\n");
|
|
655
|
+
//#region src/cli/progress/reporter.ts
|
|
656
|
+
const PROGRESS_BAR_WIDTH = 20;
|
|
657
|
+
const FILLED_BAR_CHAR = "█";
|
|
658
|
+
const EMPTY_BAR_CHAR = "░";
|
|
659
|
+
function clamp(value, min, max) {
|
|
660
|
+
return Math.max(min, Math.min(max, value));
|
|
371
661
|
}
|
|
372
|
-
function
|
|
373
|
-
|
|
662
|
+
function buildProgressBar(completed, total) {
|
|
663
|
+
const safeTotal = Math.max(total, 1);
|
|
664
|
+
const ratio = clamp(completed / safeTotal, 0, 1);
|
|
665
|
+
const filled = completed >= safeTotal ? PROGRESS_BAR_WIDTH : Math.floor(ratio * PROGRESS_BAR_WIDTH);
|
|
666
|
+
const empty = PROGRESS_BAR_WIDTH - filled;
|
|
667
|
+
return `${FILLED_BAR_CHAR.repeat(filled)}${EMPTY_BAR_CHAR.repeat(empty)}`;
|
|
374
668
|
}
|
|
375
|
-
function
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
669
|
+
function formatElapsed(startedAtMs) {
|
|
670
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
671
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1e3));
|
|
672
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
673
|
+
const seconds = totalSeconds % 60;
|
|
674
|
+
const tenths = Math.floor(Math.max(0, elapsedMs) % 1e3 / 100);
|
|
675
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${tenths}`;
|
|
676
|
+
}
|
|
677
|
+
function buildProgressLine(completed, total, startedAtMs) {
|
|
678
|
+
const safeTotal = Math.max(total, 1);
|
|
679
|
+
const percent = completed >= safeTotal ? 100 : Math.floor(completed / safeTotal * 100);
|
|
680
|
+
return `Counting files [${buildProgressBar(completed, safeTotal)}] ${`${String(percent).padStart(3, " ")}%`} ${String(completed).padStart(String(safeTotal).length, " ")}/${safeTotal} elapsed ${formatElapsed(startedAtMs)}`;
|
|
681
|
+
}
|
|
682
|
+
function buildFinalizingLine(startedAtMs) {
|
|
683
|
+
return `Finalizing aggregate... elapsed ${formatElapsed(startedAtMs)}`;
|
|
684
|
+
}
|
|
685
|
+
function createBatchProgressReporter(options) {
|
|
686
|
+
const enabled = options.enabled;
|
|
687
|
+
const isTTY = Boolean(options.stream.isTTY);
|
|
688
|
+
const clearOnFinish = options.clearOnFinish ?? true;
|
|
689
|
+
let active = false;
|
|
690
|
+
let total = 0;
|
|
691
|
+
let lastLineLength = 0;
|
|
692
|
+
let startedAtMs = 0;
|
|
693
|
+
let lastRenderedPercent = -1;
|
|
694
|
+
let finalizingStarted = false;
|
|
695
|
+
const writeTTYLine = (line) => {
|
|
696
|
+
const trailingPadding = lastLineLength > line.length ? " ".repeat(lastLineLength - line.length) : "";
|
|
697
|
+
options.stream.write(`\r${line}${trailingPadding}`);
|
|
698
|
+
lastLineLength = line.length;
|
|
699
|
+
};
|
|
700
|
+
const render = (completed) => {
|
|
701
|
+
const line = buildProgressLine(completed, total, startedAtMs);
|
|
702
|
+
const safeTotal = Math.max(total, 1);
|
|
703
|
+
const percent = completed >= safeTotal ? 100 : Math.floor(completed / safeTotal * 100);
|
|
704
|
+
if (!isTTY && percent === lastRenderedPercent && completed < safeTotal) return;
|
|
705
|
+
lastRenderedPercent = percent;
|
|
706
|
+
if (isTTY) {
|
|
707
|
+
writeTTYLine(line);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
lastLineLength = line.length;
|
|
711
|
+
options.stream.write(`${line}\n`);
|
|
712
|
+
};
|
|
713
|
+
const clearLine = () => {
|
|
714
|
+
if (lastLineLength === 0) return;
|
|
715
|
+
options.stream.write(`\r${" ".repeat(lastLineLength)}\r`);
|
|
716
|
+
lastLineLength = 0;
|
|
717
|
+
};
|
|
718
|
+
return {
|
|
719
|
+
enabled,
|
|
720
|
+
start(nextTotal, nextStartedAtMs) {
|
|
721
|
+
if (!enabled || nextTotal <= 1) return;
|
|
722
|
+
total = nextTotal;
|
|
723
|
+
active = true;
|
|
724
|
+
startedAtMs = nextStartedAtMs ?? Date.now();
|
|
725
|
+
lastRenderedPercent = -1;
|
|
726
|
+
finalizingStarted = false;
|
|
727
|
+
render(0);
|
|
728
|
+
},
|
|
729
|
+
advance(snapshot) {
|
|
730
|
+
if (!active) return;
|
|
731
|
+
render(snapshot.completed);
|
|
732
|
+
},
|
|
733
|
+
startFinalizing() {
|
|
734
|
+
if (!active || finalizingStarted) return;
|
|
735
|
+
finalizingStarted = true;
|
|
736
|
+
const line = buildFinalizingLine(startedAtMs);
|
|
737
|
+
if (isTTY) {
|
|
738
|
+
if (!clearOnFinish) {
|
|
739
|
+
options.stream.write(`\n${line}`);
|
|
740
|
+
lastLineLength = line.length;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
writeTTYLine(line);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
lastLineLength = line.length;
|
|
747
|
+
options.stream.write(`${line}\n`);
|
|
748
|
+
},
|
|
749
|
+
finish() {
|
|
750
|
+
if (!active) return;
|
|
751
|
+
if (isTTY) if (clearOnFinish) clearLine();
|
|
752
|
+
else options.stream.write("\n");
|
|
753
|
+
active = false;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/markdown/toml/arrays.ts
|
|
760
|
+
function ensureArrayContainer(result, key) {
|
|
761
|
+
const existing = result[key];
|
|
762
|
+
if (Array.isArray(existing)) return existing;
|
|
763
|
+
const list = [];
|
|
764
|
+
result[key] = list;
|
|
765
|
+
return list;
|
|
379
766
|
}
|
|
380
|
-
function
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
} catch {
|
|
385
|
-
return null;
|
|
767
|
+
function flattenArrayTables(result) {
|
|
768
|
+
for (const [key, value] of Object.entries(result)) {
|
|
769
|
+
if (!Array.isArray(value)) continue;
|
|
770
|
+
result[key] = value.map((entry) => Object.entries(entry).map(([entryKey, entryValue]) => `${entryKey}=${entryValue}`).join(", ")).join(" | ");
|
|
386
771
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/markdown/toml/keys.ts
|
|
776
|
+
function stripKeyQuotes(key) {
|
|
777
|
+
const trimmed = key.trim();
|
|
778
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
779
|
+
return trimmed;
|
|
780
|
+
}
|
|
781
|
+
function normalizeKeyPath(key) {
|
|
782
|
+
const trimmed = key.trim();
|
|
783
|
+
if (!trimmed) return null;
|
|
784
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
785
|
+
const unquoted = stripKeyQuotes(trimmed);
|
|
786
|
+
return unquoted ? unquoted : null;
|
|
393
787
|
}
|
|
394
|
-
|
|
395
|
-
return null;
|
|
788
|
+
const segments = trimmed.split(".").map((segment) => segment.trim());
|
|
789
|
+
if (segments.some((segment) => !segment)) return null;
|
|
790
|
+
return segments.join(".");
|
|
396
791
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
792
|
+
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region src/markdown/toml/strings.ts
|
|
795
|
+
function stripInlineComment(line) {
|
|
796
|
+
let inString = null;
|
|
400
797
|
let escaped = false;
|
|
401
|
-
for (let i =
|
|
402
|
-
const char =
|
|
798
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
799
|
+
const char = line[i] ?? "";
|
|
403
800
|
if (inString) {
|
|
404
801
|
if (escaped) {
|
|
405
802
|
escaped = false;
|
|
406
803
|
continue;
|
|
407
804
|
}
|
|
408
|
-
if (char === "\\") {
|
|
805
|
+
if (char === "\\" && inString === "double") {
|
|
409
806
|
escaped = true;
|
|
410
807
|
continue;
|
|
411
808
|
}
|
|
412
|
-
if (char === "\"")
|
|
809
|
+
if (inString === "double" && char === "\"") {
|
|
810
|
+
inString = null;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (inString === "single" && char === "'") {
|
|
814
|
+
inString = null;
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
413
817
|
continue;
|
|
414
818
|
}
|
|
415
819
|
if (char === "\"") {
|
|
416
|
-
inString =
|
|
820
|
+
inString = "double";
|
|
417
821
|
continue;
|
|
418
822
|
}
|
|
419
|
-
if (char === "
|
|
420
|
-
|
|
823
|
+
if (char === "'") {
|
|
824
|
+
inString = "single";
|
|
421
825
|
continue;
|
|
422
826
|
}
|
|
423
|
-
if (char === "
|
|
424
|
-
depth -= 1;
|
|
425
|
-
if (depth === 0) return {
|
|
426
|
-
jsonText: text.slice(startIndex, i + 1),
|
|
427
|
-
endIndex: i
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
function parseMarkdown(input) {
|
|
434
|
-
const normalized = normalizeNewlines(input);
|
|
435
|
-
const lines = normalized.split("\n");
|
|
436
|
-
if (lines.length === 0) return {
|
|
437
|
-
frontmatter: null,
|
|
438
|
-
content: normalized,
|
|
439
|
-
data: null,
|
|
440
|
-
frontmatterType: null
|
|
441
|
-
};
|
|
442
|
-
lines[0] = stripBom(lines[0] ?? "");
|
|
443
|
-
const normalizedWithoutBom = lines.join("\n");
|
|
444
|
-
const openingType = getFenceType(lines[0] ?? "");
|
|
445
|
-
if (!openingType) {
|
|
446
|
-
const jsonStart = (normalizedWithoutBom.match(/^[\t \n]*/)?.[0] ?? "").length;
|
|
447
|
-
if (normalizedWithoutBom[jsonStart] !== "{") return {
|
|
448
|
-
frontmatter: null,
|
|
449
|
-
content: normalizedWithoutBom,
|
|
450
|
-
data: null,
|
|
451
|
-
frontmatterType: null
|
|
452
|
-
};
|
|
453
|
-
const jsonBlock = extractJsonBlock(normalizedWithoutBom, jsonStart);
|
|
454
|
-
if (!jsonBlock) return {
|
|
455
|
-
frontmatter: null,
|
|
456
|
-
content: normalizedWithoutBom,
|
|
457
|
-
data: null,
|
|
458
|
-
frontmatterType: null
|
|
459
|
-
};
|
|
460
|
-
const frontmatter = jsonBlock.jsonText;
|
|
461
|
-
let content = normalizedWithoutBom.slice(jsonBlock.endIndex + 1);
|
|
462
|
-
if (content.startsWith("\n")) content = content.slice(1);
|
|
463
|
-
const data = parseFrontmatter(frontmatter, "json");
|
|
464
|
-
if (!data) return {
|
|
465
|
-
frontmatter: null,
|
|
466
|
-
content: normalizedWithoutBom,
|
|
467
|
-
data: null,
|
|
468
|
-
frontmatterType: null
|
|
469
|
-
};
|
|
470
|
-
return {
|
|
471
|
-
frontmatter,
|
|
472
|
-
content,
|
|
473
|
-
data,
|
|
474
|
-
frontmatterType: "json"
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
let closingIndex = -1;
|
|
478
|
-
for (let i = 1; i < lines.length; i += 1) if (getFenceType(lines[i] ?? "") === openingType) {
|
|
479
|
-
closingIndex = i;
|
|
480
|
-
break;
|
|
827
|
+
if (char === "#") return line.slice(0, i).trimEnd();
|
|
481
828
|
}
|
|
482
|
-
|
|
483
|
-
frontmatter: null,
|
|
484
|
-
content: normalizedWithoutBom,
|
|
485
|
-
data: null,
|
|
486
|
-
frontmatterType: null
|
|
487
|
-
};
|
|
488
|
-
const frontmatter = lines.slice(1, closingIndex).join("\n");
|
|
489
|
-
return {
|
|
490
|
-
frontmatter,
|
|
491
|
-
content: lines.slice(closingIndex + 1).join("\n"),
|
|
492
|
-
data: parseFrontmatter(frontmatter, openingType),
|
|
493
|
-
frontmatterType: openingType
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
//#endregion
|
|
498
|
-
//#region src/wc/segmenter.ts
|
|
499
|
-
const segmenterCache = /* @__PURE__ */ new Map();
|
|
500
|
-
const graphemeSegmenterCache = /* @__PURE__ */ new Map();
|
|
501
|
-
function getSegmenter(locale) {
|
|
502
|
-
const cached = segmenterCache.get(locale);
|
|
503
|
-
if (cached) return cached;
|
|
504
|
-
const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
|
|
505
|
-
segmenterCache.set(locale, segmenter);
|
|
506
|
-
return segmenter;
|
|
507
|
-
}
|
|
508
|
-
function getGraphemeSegmenter(locale) {
|
|
509
|
-
const cached = graphemeSegmenterCache.get(locale);
|
|
510
|
-
if (cached) return cached;
|
|
511
|
-
const segmenter = new Intl.Segmenter(locale, { granularity: "grapheme" });
|
|
512
|
-
graphemeSegmenterCache.set(locale, segmenter);
|
|
513
|
-
return segmenter;
|
|
829
|
+
return line;
|
|
514
830
|
}
|
|
515
|
-
function
|
|
516
|
-
return
|
|
831
|
+
function unescapeBasic(input) {
|
|
832
|
+
return input.replace(/\\\\/g, "\\").replace(/\\"/g, "\"").replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r");
|
|
517
833
|
}
|
|
518
|
-
function
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
return
|
|
834
|
+
function parseStringLiteral(value) {
|
|
835
|
+
if (value.startsWith("\"\"\"") && value.endsWith("\"\"\"")) return unescapeBasic(value.slice(3, -3));
|
|
836
|
+
if (value.startsWith("'''") && value.endsWith("'''")) return value.slice(3, -3);
|
|
837
|
+
if (value.startsWith("\"") && value.endsWith("\"")) return unescapeBasic(value.slice(1, -1));
|
|
838
|
+
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
|
|
839
|
+
return null;
|
|
524
840
|
}
|
|
525
841
|
|
|
526
842
|
//#endregion
|
|
527
|
-
//#region src/
|
|
528
|
-
function
|
|
529
|
-
|
|
843
|
+
//#region src/markdown/toml/values.ts
|
|
844
|
+
function parsePrimitive(raw) {
|
|
845
|
+
const value = raw.trim();
|
|
846
|
+
if (!value) return null;
|
|
847
|
+
const stringLiteral = parseStringLiteral(value);
|
|
848
|
+
if (stringLiteral !== null) return stringLiteral;
|
|
849
|
+
if (value === "true") return true;
|
|
850
|
+
if (value === "false") return false;
|
|
851
|
+
if (/^[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(value)) return Number(value);
|
|
852
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(value)) return value;
|
|
853
|
+
return value;
|
|
530
854
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
855
|
+
function parseArray(raw) {
|
|
856
|
+
const value = raw.trim();
|
|
857
|
+
if (!value.startsWith("[") || !value.endsWith("]")) return null;
|
|
858
|
+
const inner = value.slice(1, -1).trim();
|
|
859
|
+
if (!inner) return [];
|
|
860
|
+
const items = [];
|
|
861
|
+
let current = "";
|
|
862
|
+
let inString = null;
|
|
863
|
+
let escaped = false;
|
|
864
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
865
|
+
const char = inner[i] ?? "";
|
|
866
|
+
if (inString) {
|
|
867
|
+
current += char;
|
|
868
|
+
if (escaped) {
|
|
869
|
+
escaped = false;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (char === "\\" && inString === "double") {
|
|
873
|
+
escaped = true;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (inString === "double" && char === "\"") inString = null;
|
|
877
|
+
else if (inString === "single" && char === "'") inString = null;
|
|
878
|
+
continue;
|
|
555
879
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
880
|
+
if (char === "\"") {
|
|
881
|
+
inString = "double";
|
|
882
|
+
current += char;
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (char === "'") {
|
|
886
|
+
inString = "single";
|
|
887
|
+
current += char;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
if (char === ",") {
|
|
891
|
+
const item = parsePrimitive(current);
|
|
892
|
+
if (item === null) return null;
|
|
893
|
+
items.push(item);
|
|
894
|
+
current = "";
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
current += char;
|
|
568
898
|
}
|
|
569
|
-
|
|
570
|
-
|
|
899
|
+
const finalItem = parsePrimitive(current);
|
|
900
|
+
if (finalItem === null) return null;
|
|
901
|
+
items.push(finalItem);
|
|
902
|
+
return items;
|
|
571
903
|
}
|
|
572
|
-
function
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
904
|
+
function parseInlineTable(raw) {
|
|
905
|
+
const trimmed = raw.trim();
|
|
906
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
|
|
907
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
908
|
+
if (!inner) return {};
|
|
909
|
+
const pairs = [];
|
|
910
|
+
let current = "";
|
|
911
|
+
let inString = null;
|
|
912
|
+
let escaped = false;
|
|
913
|
+
let bracketDepth = 0;
|
|
914
|
+
let braceDepth = 0;
|
|
915
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
916
|
+
const char = inner[i] ?? "";
|
|
917
|
+
if (inString) {
|
|
918
|
+
current += char;
|
|
919
|
+
if (escaped) {
|
|
920
|
+
escaped = false;
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (char === "\\" && inString === "double") {
|
|
924
|
+
escaped = true;
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (inString === "double" && char === "\"") inString = null;
|
|
928
|
+
else if (inString === "single" && char === "'") inString = null;
|
|
580
929
|
continue;
|
|
581
930
|
}
|
|
582
|
-
if (char === "
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
count += 1;
|
|
931
|
+
if (char === "\"") {
|
|
932
|
+
inString = "double";
|
|
933
|
+
current += char;
|
|
586
934
|
continue;
|
|
587
935
|
}
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
count += 1;
|
|
936
|
+
if (char === "'") {
|
|
937
|
+
inString = "single";
|
|
938
|
+
current += char;
|
|
592
939
|
continue;
|
|
593
940
|
}
|
|
594
|
-
if (
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
941
|
+
if (char === "[") {
|
|
942
|
+
bracketDepth += 1;
|
|
943
|
+
current += char;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (char === "]") {
|
|
947
|
+
if (bracketDepth > 0) bracketDepth -= 1;
|
|
948
|
+
current += char;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (char === "{") {
|
|
952
|
+
braceDepth += 1;
|
|
953
|
+
current += char;
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
if (char === "}") {
|
|
957
|
+
if (braceDepth > 0) braceDepth -= 1;
|
|
958
|
+
current += char;
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (char === "," && bracketDepth === 0 && braceDepth === 0) {
|
|
962
|
+
pairs.push(current);
|
|
963
|
+
current = "";
|
|
964
|
+
continue;
|
|
598
965
|
}
|
|
966
|
+
current += char;
|
|
599
967
|
}
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
|
|
968
|
+
if (current.trim()) pairs.push(current);
|
|
969
|
+
const output = {};
|
|
970
|
+
for (const pair of pairs) {
|
|
971
|
+
const separatorIndex = pair.indexOf("=");
|
|
972
|
+
if (separatorIndex === -1) return null;
|
|
973
|
+
const key = normalizeKeyPath(pair.slice(0, separatorIndex));
|
|
974
|
+
if (!key) return null;
|
|
975
|
+
const valueRaw = pair.slice(separatorIndex + 1).trim();
|
|
976
|
+
if (!valueRaw) return null;
|
|
977
|
+
if (valueRaw.startsWith("{")) return null;
|
|
978
|
+
const normalized = normalizeValue(valueRaw);
|
|
979
|
+
if (normalized === null) return null;
|
|
980
|
+
if (typeof normalized === "object" && !Array.isArray(normalized)) return null;
|
|
981
|
+
output[key] = normalized;
|
|
603
982
|
}
|
|
604
|
-
return
|
|
605
|
-
}
|
|
606
|
-
function classifyNonWordSegment(segment) {
|
|
607
|
-
const hasEmojiVariationSelector = segment.includes("️");
|
|
608
|
-
if (keycapEmojiRegex.test(segment) || emojiPresentationRegex.test(segment) || hasEmojiVariationSelector && emojiRegex.test(segment)) return "emoji";
|
|
609
|
-
if (symbolRegex.test(segment)) return "symbol";
|
|
610
|
-
if (punctuationRegex.test(segment)) return "punctuation";
|
|
611
|
-
return null;
|
|
983
|
+
return output;
|
|
612
984
|
}
|
|
613
|
-
function
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
if (source.counts.punctuation > 0) {
|
|
623
|
-
appendAll(target.punctuation, source.punctuation);
|
|
624
|
-
target.counts.punctuation += source.counts.punctuation;
|
|
625
|
-
}
|
|
626
|
-
if (source.counts.whitespace && source.counts.whitespace > 0 && source.whitespace) {
|
|
627
|
-
const whitespace = target.whitespace ?? createWhitespaceCounts();
|
|
628
|
-
whitespace.spaces += source.whitespace.spaces;
|
|
629
|
-
whitespace.tabs += source.whitespace.tabs;
|
|
630
|
-
whitespace.newlines += source.whitespace.newlines;
|
|
631
|
-
whitespace.other += source.whitespace.other;
|
|
632
|
-
target.whitespace = whitespace;
|
|
633
|
-
target.counts.whitespace = (target.counts.whitespace ?? 0) + source.counts.whitespace;
|
|
634
|
-
}
|
|
635
|
-
return target;
|
|
985
|
+
function normalizeValue(value) {
|
|
986
|
+
if (!value) return null;
|
|
987
|
+
const trimmed = value.trim();
|
|
988
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}")) return parseInlineTable(trimmed);
|
|
989
|
+
const array = parseArray(trimmed);
|
|
990
|
+
if (array) return array;
|
|
991
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) return null;
|
|
992
|
+
return parsePrimitive(trimmed);
|
|
636
993
|
}
|
|
637
|
-
function
|
|
638
|
-
return
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
newlines: 0,
|
|
642
|
-
other: 0
|
|
643
|
-
};
|
|
994
|
+
function toPlainText(value) {
|
|
995
|
+
if (value == null) return "";
|
|
996
|
+
if (Array.isArray(value)) return value.map((item) => String(item)).join(", ");
|
|
997
|
+
return String(value);
|
|
644
998
|
}
|
|
645
999
|
|
|
646
1000
|
//#endregion
|
|
647
|
-
//#region src/
|
|
648
|
-
function
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
let chars = 0;
|
|
670
|
-
let wordChars = 0;
|
|
671
|
-
let nonWordChars = 0;
|
|
672
|
-
for (const part of segmenter.segment(chunk.text)) {
|
|
673
|
-
if (part.isWordLike) {
|
|
674
|
-
const count = countCharsForLocale(part.segment, chunk.locale);
|
|
675
|
-
chars += count;
|
|
676
|
-
wordChars += count;
|
|
1001
|
+
//#region src/markdown/toml/parse-frontmatter.ts
|
|
1002
|
+
function parseTomlFrontmatter(frontmatter) {
|
|
1003
|
+
const result = {};
|
|
1004
|
+
const lines = frontmatter.split("\n");
|
|
1005
|
+
let tablePrefix = "";
|
|
1006
|
+
let tableTarget = null;
|
|
1007
|
+
let tablePrefixInList = false;
|
|
1008
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
1009
|
+
const rawLine = lines[index] ?? "";
|
|
1010
|
+
const trimmedLine = rawLine.trim();
|
|
1011
|
+
if (!trimmedLine || trimmedLine.startsWith("#")) continue;
|
|
1012
|
+
if (trimmedLine.startsWith("[[")) {
|
|
1013
|
+
const match = trimmedLine.match(/^\[\[([^\]]+)]]$/);
|
|
1014
|
+
if (!match) return null;
|
|
1015
|
+
const normalizedTable = normalizeKeyPath(match[1] ?? "");
|
|
1016
|
+
if (!normalizedTable) return null;
|
|
1017
|
+
const list = ensureArrayContainer(result, normalizedTable);
|
|
1018
|
+
const newEntry = {};
|
|
1019
|
+
list.push(newEntry);
|
|
1020
|
+
tableTarget = newEntry;
|
|
1021
|
+
tablePrefix = normalizedTable;
|
|
1022
|
+
tablePrefixInList = true;
|
|
677
1023
|
continue;
|
|
678
1024
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1025
|
+
const tableMatch = trimmedLine.match(/^\[([^\]]+)]$/);
|
|
1026
|
+
if (tableMatch) {
|
|
1027
|
+
const normalizedTable = normalizeKeyPath(tableMatch[1] ?? "");
|
|
1028
|
+
if (!normalizedTable) return null;
|
|
1029
|
+
tablePrefix = normalizedTable;
|
|
1030
|
+
tablePrefixInList = false;
|
|
1031
|
+
tableTarget = null;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
const lineForParsing = /("""|''')/.test(rawLine) ? rawLine : stripInlineComment(rawLine);
|
|
1035
|
+
const separatorIndex = lineForParsing.indexOf("=");
|
|
1036
|
+
if (separatorIndex === -1) return null;
|
|
1037
|
+
const key = normalizeKeyPath(lineForParsing.slice(0, separatorIndex));
|
|
1038
|
+
let valueRaw = lineForParsing.slice(separatorIndex + 1).trim();
|
|
1039
|
+
if (!key) return null;
|
|
1040
|
+
const tripleDelimiter = valueRaw.startsWith("\"\"\"") ? "\"\"\"" : valueRaw.startsWith("'''") ? "'''" : null;
|
|
1041
|
+
if (tripleDelimiter) {
|
|
1042
|
+
const closingIndex = valueRaw.indexOf(tripleDelimiter, tripleDelimiter.length);
|
|
1043
|
+
if (closingIndex !== -1) {
|
|
1044
|
+
const strippedAfter = stripInlineComment(valueRaw.slice(closingIndex + tripleDelimiter.length));
|
|
1045
|
+
valueRaw = `${valueRaw.slice(0, closingIndex + tripleDelimiter.length)}${strippedAfter}`;
|
|
1046
|
+
} else {
|
|
1047
|
+
const delimiter = tripleDelimiter;
|
|
1048
|
+
let combined = valueRaw;
|
|
1049
|
+
let closed = false;
|
|
1050
|
+
while (index + 1 < lines.length) {
|
|
1051
|
+
index += 1;
|
|
1052
|
+
const nextLine = lines[index] ?? "";
|
|
1053
|
+
combined += `\n${nextLine}`;
|
|
1054
|
+
if (new RegExp(`${delimiter}\\s*$`).test(nextLine)) {
|
|
1055
|
+
closed = true;
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (!closed) return null;
|
|
1060
|
+
valueRaw = combined;
|
|
688
1061
|
}
|
|
689
1062
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
function aggregateByLocale(chunks) {
|
|
701
|
-
const order = [];
|
|
702
|
-
const map = /* @__PURE__ */ new Map();
|
|
703
|
-
for (const chunk of chunks) {
|
|
704
|
-
const existing = map.get(chunk.locale);
|
|
705
|
-
if (existing) {
|
|
706
|
-
existing.words += chunk.words;
|
|
707
|
-
appendAll(existing.segments, chunk.segments);
|
|
1063
|
+
const normalized = normalizeValue(valueRaw);
|
|
1064
|
+
if (normalized === null) return null;
|
|
1065
|
+
const fullKey = tablePrefix ? `${tablePrefix}.${key}` : key;
|
|
1066
|
+
if (typeof normalized === "object" && !Array.isArray(normalized)) {
|
|
1067
|
+
for (const [inlineKey, inlineValue] of Object.entries(normalized)) {
|
|
1068
|
+
const entryKey = tablePrefixInList ? `${key}.${inlineKey}` : `${fullKey}.${inlineKey}`;
|
|
1069
|
+
if (tablePrefixInList && tableTarget) tableTarget[entryKey] = toPlainText(inlineValue);
|
|
1070
|
+
else result[entryKey] = toPlainText(inlineValue);
|
|
1071
|
+
}
|
|
708
1072
|
continue;
|
|
709
1073
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
});
|
|
1074
|
+
if (tablePrefixInList && tableTarget) {
|
|
1075
|
+
tableTarget[key] = toPlainText(normalized);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1078
|
+
result[fullKey] = toPlainText(normalized);
|
|
716
1079
|
}
|
|
717
|
-
|
|
1080
|
+
flattenArrayTables(result);
|
|
1081
|
+
return result;
|
|
718
1082
|
}
|
|
719
1083
|
|
|
720
1084
|
//#endregion
|
|
721
|
-
//#region src/
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
segment: "segments",
|
|
727
|
-
seg: "segments",
|
|
728
|
-
collector: "collector",
|
|
729
|
-
collect: "collector",
|
|
730
|
-
colle: "collector",
|
|
731
|
-
char: "char",
|
|
732
|
-
chars: "char",
|
|
733
|
-
character: "char",
|
|
734
|
-
characters: "char"
|
|
1085
|
+
//#region src/markdown/parse-markdown.ts
|
|
1086
|
+
const FENCE_TO_TYPE = {
|
|
1087
|
+
"---": "yaml",
|
|
1088
|
+
"+++": "toml",
|
|
1089
|
+
";;;": "json"
|
|
735
1090
|
};
|
|
736
|
-
function
|
|
737
|
-
|
|
738
|
-
return MODE_ALIASES[input.trim().toLowerCase()] ?? null;
|
|
1091
|
+
function normalizeNewlines(input) {
|
|
1092
|
+
return input.replace(/\r\n/g, "\n");
|
|
739
1093
|
}
|
|
740
|
-
function
|
|
741
|
-
return
|
|
1094
|
+
function stripBom(line) {
|
|
1095
|
+
return line.startsWith("") ? line.slice(1) : line;
|
|
1096
|
+
}
|
|
1097
|
+
function getFenceType(line) {
|
|
1098
|
+
const match = line.match(/^[\t ]*(---|\+\+\+|;;;)[\t ]*$/);
|
|
1099
|
+
if (!match) return null;
|
|
1100
|
+
return FENCE_TO_TYPE[match[1] ?? ""] ?? null;
|
|
1101
|
+
}
|
|
1102
|
+
function parseFrontmatter(frontmatter, type) {
|
|
1103
|
+
if (!type) return null;
|
|
1104
|
+
if (type === "json") try {
|
|
1105
|
+
return JSON.parse(frontmatter);
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
if (type === "yaml") {
|
|
1110
|
+
const doc = parseDocument(frontmatter, { prettyErrors: false });
|
|
1111
|
+
if (doc.errors.length > 0) return null;
|
|
1112
|
+
const data = doc.toJSON();
|
|
1113
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
1114
|
+
return data;
|
|
1115
|
+
}
|
|
1116
|
+
if (type === "toml") return parseTomlFrontmatter(frontmatter);
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
function extractJsonBlock(text, startIndex) {
|
|
1120
|
+
let depth = 0;
|
|
1121
|
+
let inString = false;
|
|
1122
|
+
let escaped = false;
|
|
1123
|
+
for (let i = startIndex; i < text.length; i += 1) {
|
|
1124
|
+
const char = text[i] ?? "";
|
|
1125
|
+
if (inString) {
|
|
1126
|
+
if (escaped) {
|
|
1127
|
+
escaped = false;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (char === "\\") {
|
|
1131
|
+
escaped = true;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (char === "\"") inString = false;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (char === "\"") {
|
|
1138
|
+
inString = true;
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
if (char === "{") {
|
|
1142
|
+
depth += 1;
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (char === "}") {
|
|
1146
|
+
depth -= 1;
|
|
1147
|
+
if (depth === 0) return {
|
|
1148
|
+
jsonText: text.slice(startIndex, i + 1),
|
|
1149
|
+
endIndex: i
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
function parseMarkdown(input) {
|
|
1156
|
+
const normalized = normalizeNewlines(input);
|
|
1157
|
+
const lines = normalized.split("\n");
|
|
1158
|
+
if (lines.length === 0) return {
|
|
1159
|
+
frontmatter: null,
|
|
1160
|
+
content: normalized,
|
|
1161
|
+
data: null,
|
|
1162
|
+
frontmatterType: null
|
|
1163
|
+
};
|
|
1164
|
+
lines[0] = stripBom(lines[0] ?? "");
|
|
1165
|
+
const normalizedWithoutBom = lines.join("\n");
|
|
1166
|
+
const openingType = getFenceType(lines[0] ?? "");
|
|
1167
|
+
if (!openingType) {
|
|
1168
|
+
const jsonStart = (normalizedWithoutBom.match(/^[\t \n]*/)?.[0] ?? "").length;
|
|
1169
|
+
if (normalizedWithoutBom[jsonStart] !== "{") return {
|
|
1170
|
+
frontmatter: null,
|
|
1171
|
+
content: normalizedWithoutBom,
|
|
1172
|
+
data: null,
|
|
1173
|
+
frontmatterType: null
|
|
1174
|
+
};
|
|
1175
|
+
const jsonBlock = extractJsonBlock(normalizedWithoutBom, jsonStart);
|
|
1176
|
+
if (!jsonBlock) return {
|
|
1177
|
+
frontmatter: null,
|
|
1178
|
+
content: normalizedWithoutBom,
|
|
1179
|
+
data: null,
|
|
1180
|
+
frontmatterType: null
|
|
1181
|
+
};
|
|
1182
|
+
const frontmatter = jsonBlock.jsonText;
|
|
1183
|
+
let content = normalizedWithoutBom.slice(jsonBlock.endIndex + 1);
|
|
1184
|
+
if (content.startsWith("\n")) content = content.slice(1);
|
|
1185
|
+
const data = parseFrontmatter(frontmatter, "json");
|
|
1186
|
+
if (!data) return {
|
|
1187
|
+
frontmatter: null,
|
|
1188
|
+
content: normalizedWithoutBom,
|
|
1189
|
+
data: null,
|
|
1190
|
+
frontmatterType: null
|
|
1191
|
+
};
|
|
1192
|
+
return {
|
|
1193
|
+
frontmatter,
|
|
1194
|
+
content,
|
|
1195
|
+
data,
|
|
1196
|
+
frontmatterType: "json"
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
let closingIndex = -1;
|
|
1200
|
+
for (let i = 1; i < lines.length; i += 1) if (getFenceType(lines[i] ?? "") === openingType) {
|
|
1201
|
+
closingIndex = i;
|
|
1202
|
+
break;
|
|
1203
|
+
}
|
|
1204
|
+
if (closingIndex === -1) return {
|
|
1205
|
+
frontmatter: null,
|
|
1206
|
+
content: normalizedWithoutBom,
|
|
1207
|
+
data: null,
|
|
1208
|
+
frontmatterType: null
|
|
1209
|
+
};
|
|
1210
|
+
const frontmatter = lines.slice(1, closingIndex).join("\n");
|
|
1211
|
+
return {
|
|
1212
|
+
frontmatter,
|
|
1213
|
+
content: lines.slice(closingIndex + 1).join("\n"),
|
|
1214
|
+
data: parseFrontmatter(frontmatter, openingType),
|
|
1215
|
+
frontmatterType: openingType
|
|
1216
|
+
};
|
|
742
1217
|
}
|
|
743
1218
|
|
|
744
1219
|
//#endregion
|
|
745
|
-
//#region src/wc/
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
arabic: /\p{Script=Arabic}/u,
|
|
755
|
-
cyrillic: /\p{Script=Cyrillic}/u,
|
|
756
|
-
devanagari: /\p{Script=Devanagari}/u,
|
|
757
|
-
thai: /\p{Script=Thai}/u
|
|
758
|
-
};
|
|
759
|
-
const latinLocaleHints = [
|
|
760
|
-
{
|
|
761
|
-
locale: "de",
|
|
762
|
-
regex: /[äöüÄÖÜß]/
|
|
763
|
-
},
|
|
764
|
-
{
|
|
765
|
-
locale: "es",
|
|
766
|
-
regex: /[ñÑ¿¡]/
|
|
767
|
-
},
|
|
768
|
-
{
|
|
769
|
-
locale: "pt",
|
|
770
|
-
regex: /[ãõÃÕ]/
|
|
771
|
-
},
|
|
772
|
-
{
|
|
773
|
-
locale: "fr",
|
|
774
|
-
regex: /[œŒæÆ]/
|
|
775
|
-
}
|
|
776
|
-
];
|
|
777
|
-
const latinLocales = new Set([DEFAULT_LOCALE, ...latinLocaleHints.map((hint) => hint.locale)]);
|
|
778
|
-
function isLatinLocale(locale) {
|
|
779
|
-
return latinLocales.has(locale);
|
|
1220
|
+
//#region src/wc/segmenter.ts
|
|
1221
|
+
const segmenterCache = /* @__PURE__ */ new Map();
|
|
1222
|
+
const graphemeSegmenterCache = /* @__PURE__ */ new Map();
|
|
1223
|
+
function getSegmenter(locale) {
|
|
1224
|
+
const cached = segmenterCache.get(locale);
|
|
1225
|
+
if (cached) return cached;
|
|
1226
|
+
const segmenter = new Intl.Segmenter(locale, { granularity: "word" });
|
|
1227
|
+
segmenterCache.set(locale, segmenter);
|
|
1228
|
+
return segmenter;
|
|
780
1229
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
return
|
|
1230
|
+
function getGraphemeSegmenter(locale) {
|
|
1231
|
+
const cached = graphemeSegmenterCache.get(locale);
|
|
1232
|
+
if (cached) return cached;
|
|
1233
|
+
const segmenter = new Intl.Segmenter(locale, { granularity: "grapheme" });
|
|
1234
|
+
graphemeSegmenterCache.set(locale, segmenter);
|
|
1235
|
+
return segmenter;
|
|
784
1236
|
}
|
|
785
|
-
function
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
if (
|
|
790
|
-
const
|
|
791
|
-
|
|
1237
|
+
function supportsSegmenter() {
|
|
1238
|
+
return typeof Intl !== "undefined" && typeof Intl.Segmenter === "function";
|
|
1239
|
+
}
|
|
1240
|
+
function countCharsForLocale(text, locale) {
|
|
1241
|
+
if (!supportsSegmenter()) return Array.from(text).length;
|
|
1242
|
+
const segmenter = getGraphemeSegmenter(locale);
|
|
1243
|
+
let count = 0;
|
|
1244
|
+
for (const _segment of segmenter.segment(text)) count++;
|
|
1245
|
+
return count;
|
|
792
1246
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1247
|
+
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/wc/non-words.ts
|
|
1250
|
+
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation})/u;
|
|
1251
|
+
const emojiPresentationRegex = /\p{Emoji_Presentation}/u;
|
|
1252
|
+
const keycapEmojiRegex = /[0-9#*]\uFE0F?\u20E3/u;
|
|
1253
|
+
const symbolRegex = /\p{S}/u;
|
|
1254
|
+
const punctuationRegex = /\p{P}/u;
|
|
1255
|
+
const whitespaceRegex = /\s/u;
|
|
1256
|
+
const newlineChars = new Set([
|
|
1257
|
+
"\n",
|
|
1258
|
+
"\r",
|
|
1259
|
+
"\u2028",
|
|
1260
|
+
"\u2029"
|
|
1261
|
+
]);
|
|
1262
|
+
function createNonWordCollection() {
|
|
1263
|
+
return {
|
|
1264
|
+
emoji: [],
|
|
1265
|
+
symbols: [],
|
|
1266
|
+
punctuation: [],
|
|
1267
|
+
counts: {
|
|
1268
|
+
emoji: 0,
|
|
1269
|
+
symbols: 0,
|
|
1270
|
+
punctuation: 0
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
798
1273
|
}
|
|
799
|
-
function
|
|
800
|
-
if (
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (regex.devanagari.test(char)) return "hi";
|
|
805
|
-
if (regex.thai.test(char)) return "th";
|
|
806
|
-
if (regex.han.test(char)) {
|
|
807
|
-
if (previousLocale && previousLocale.startsWith("ja")) return previousLocale;
|
|
808
|
-
return resolveHanHint(options) ?? DEFAULT_HAN_TAG;
|
|
1274
|
+
function addNonWord(collection, category, segment) {
|
|
1275
|
+
if (category === "emoji") {
|
|
1276
|
+
collection.emoji.push(segment);
|
|
1277
|
+
collection.counts.emoji += 1;
|
|
1278
|
+
return;
|
|
809
1279
|
}
|
|
810
|
-
if (
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const latinHint = resolveLatinHint(options);
|
|
815
|
-
if (latinHint) return latinHint;
|
|
816
|
-
return DEFAULT_LOCALE;
|
|
1280
|
+
if (category === "symbol") {
|
|
1281
|
+
collection.symbols.push(segment);
|
|
1282
|
+
collection.counts.symbols += 1;
|
|
1283
|
+
return;
|
|
817
1284
|
}
|
|
818
|
-
|
|
1285
|
+
collection.punctuation.push(segment);
|
|
1286
|
+
collection.counts.punctuation += 1;
|
|
819
1287
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
for (const char of text) {
|
|
829
|
-
const detected = detectLocaleForChar(char, currentLocale, options);
|
|
830
|
-
const targetLocale = detected ?? currentLocale;
|
|
831
|
-
if (buffer === "") {
|
|
832
|
-
currentLocale = targetLocale;
|
|
833
|
-
buffer = char;
|
|
834
|
-
bufferHasScript = detected !== null;
|
|
1288
|
+
function addWhitespace(collection, segment) {
|
|
1289
|
+
let whitespace = collection.whitespace;
|
|
1290
|
+
let count = 0;
|
|
1291
|
+
for (const char of segment) {
|
|
1292
|
+
if (char === " ") {
|
|
1293
|
+
whitespace = whitespace ?? createWhitespaceCounts();
|
|
1294
|
+
whitespace.spaces += 1;
|
|
1295
|
+
count += 1;
|
|
835
1296
|
continue;
|
|
836
1297
|
}
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1298
|
+
if (char === " ") {
|
|
1299
|
+
whitespace = whitespace ?? createWhitespaceCounts();
|
|
1300
|
+
whitespace.tabs += 1;
|
|
1301
|
+
count += 1;
|
|
841
1302
|
continue;
|
|
842
1303
|
}
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
bufferHasScript = true;
|
|
848
|
-
continue;
|
|
849
|
-
}
|
|
850
|
-
chunks.push({
|
|
851
|
-
locale: currentLocale,
|
|
852
|
-
text: buffer
|
|
853
|
-
});
|
|
854
|
-
currentLocale = targetLocale;
|
|
855
|
-
buffer = char;
|
|
856
|
-
bufferHasScript = true;
|
|
1304
|
+
if (newlineChars.has(char)) {
|
|
1305
|
+
whitespace = whitespace ?? createWhitespaceCounts();
|
|
1306
|
+
whitespace.newlines += 1;
|
|
1307
|
+
count += 1;
|
|
857
1308
|
continue;
|
|
858
1309
|
}
|
|
859
|
-
|
|
860
|
-
|
|
1310
|
+
if (whitespaceRegex.test(char)) {
|
|
1311
|
+
whitespace = whitespace ?? createWhitespaceCounts();
|
|
1312
|
+
whitespace.other += 1;
|
|
1313
|
+
count += 1;
|
|
1314
|
+
}
|
|
861
1315
|
}
|
|
862
|
-
if (
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
return
|
|
1316
|
+
if (count > 0) {
|
|
1317
|
+
collection.whitespace = whitespace ?? createWhitespaceCounts();
|
|
1318
|
+
collection.counts.whitespace = (collection.counts.whitespace ?? 0) + count;
|
|
1319
|
+
}
|
|
1320
|
+
return count;
|
|
867
1321
|
}
|
|
868
|
-
function
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
merged.push(last);
|
|
880
|
-
last = chunk;
|
|
881
|
-
}
|
|
1322
|
+
function classifyNonWordSegment(segment) {
|
|
1323
|
+
const hasEmojiVariationSelector = segment.includes("️");
|
|
1324
|
+
if (keycapEmojiRegex.test(segment) || emojiPresentationRegex.test(segment) || hasEmojiVariationSelector && emojiRegex.test(segment)) return "emoji";
|
|
1325
|
+
if (symbolRegex.test(segment)) return "symbol";
|
|
1326
|
+
if (punctuationRegex.test(segment)) return "punctuation";
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
function mergeNonWordCollections(target, source) {
|
|
1330
|
+
if (source.counts.emoji > 0) {
|
|
1331
|
+
appendAll(target.emoji, source.emoji);
|
|
1332
|
+
target.counts.emoji += source.counts.emoji;
|
|
882
1333
|
}
|
|
883
|
-
|
|
884
|
-
|
|
1334
|
+
if (source.counts.symbols > 0) {
|
|
1335
|
+
appendAll(target.symbols, source.symbols);
|
|
1336
|
+
target.counts.symbols += source.counts.symbols;
|
|
1337
|
+
}
|
|
1338
|
+
if (source.counts.punctuation > 0) {
|
|
1339
|
+
appendAll(target.punctuation, source.punctuation);
|
|
1340
|
+
target.counts.punctuation += source.counts.punctuation;
|
|
1341
|
+
}
|
|
1342
|
+
if (source.counts.whitespace && source.counts.whitespace > 0 && source.whitespace) {
|
|
1343
|
+
const whitespace = target.whitespace ?? createWhitespaceCounts();
|
|
1344
|
+
whitespace.spaces += source.whitespace.spaces;
|
|
1345
|
+
whitespace.tabs += source.whitespace.tabs;
|
|
1346
|
+
whitespace.newlines += source.whitespace.newlines;
|
|
1347
|
+
whitespace.other += source.whitespace.other;
|
|
1348
|
+
target.whitespace = whitespace;
|
|
1349
|
+
target.counts.whitespace = (target.counts.whitespace ?? 0) + source.counts.whitespace;
|
|
1350
|
+
}
|
|
1351
|
+
return target;
|
|
1352
|
+
}
|
|
1353
|
+
function createWhitespaceCounts() {
|
|
1354
|
+
return {
|
|
1355
|
+
spaces: 0,
|
|
1356
|
+
tabs: 0,
|
|
1357
|
+
newlines: 0,
|
|
1358
|
+
other: 0
|
|
1359
|
+
};
|
|
885
1360
|
}
|
|
886
1361
|
|
|
887
1362
|
//#endregion
|
|
888
|
-
//#region src/wc/
|
|
889
|
-
function
|
|
890
|
-
const
|
|
891
|
-
const
|
|
892
|
-
const
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
hanTagHint: options.hanTagHint
|
|
899
|
-
});
|
|
900
|
-
if (mode === "char") {
|
|
901
|
-
const analyzed = chunks.map((chunk) => analyzeCharChunk(chunk, collectNonWords, includeWhitespace));
|
|
902
|
-
const total = analyzed.reduce((sum, chunk) => sum + chunk.chars, 0);
|
|
903
|
-
const items = analyzed.map((chunk) => ({
|
|
904
|
-
locale: chunk.locale,
|
|
905
|
-
text: chunk.text,
|
|
906
|
-
chars: chunk.chars,
|
|
907
|
-
nonWords: chunk.nonWords
|
|
908
|
-
}));
|
|
909
|
-
return {
|
|
910
|
-
total,
|
|
911
|
-
counts: collectNonWords ? {
|
|
912
|
-
words: analyzed.reduce((sum, chunk) => sum + chunk.wordChars, 0),
|
|
913
|
-
nonWords: analyzed.reduce((sum, chunk) => sum + chunk.nonWordChars, 0),
|
|
914
|
-
total
|
|
915
|
-
} : void 0,
|
|
916
|
-
breakdown: {
|
|
917
|
-
mode,
|
|
918
|
-
items
|
|
919
|
-
}
|
|
920
|
-
};
|
|
1363
|
+
//#region src/wc/analyze.ts
|
|
1364
|
+
function analyzeChunk(chunk, collectNonWords, includeWhitespace) {
|
|
1365
|
+
const segmenter = getSegmenter(chunk.locale);
|
|
1366
|
+
const segments = [];
|
|
1367
|
+
const nonWords = collectNonWords ? createNonWordCollection() : null;
|
|
1368
|
+
for (const part of segmenter.segment(chunk.text)) if (part.isWordLike) segments.push(part.segment);
|
|
1369
|
+
else if (collectNonWords && nonWords) {
|
|
1370
|
+
if (includeWhitespace) addWhitespace(nonWords, part.segment);
|
|
1371
|
+
const category = classifyNonWordSegment(part.segment);
|
|
1372
|
+
if (category) addNonWord(nonWords, category, part.segment);
|
|
921
1373
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
mode,
|
|
943
|
-
items: analyzed.map((chunk) => ({
|
|
944
|
-
locale: chunk.locale,
|
|
945
|
-
text: chunk.text,
|
|
946
|
-
words: chunk.words,
|
|
947
|
-
segments: chunk.segments,
|
|
948
|
-
nonWords: chunk.nonWords
|
|
949
|
-
}))
|
|
1374
|
+
return {
|
|
1375
|
+
locale: chunk.locale,
|
|
1376
|
+
text: chunk.text,
|
|
1377
|
+
segments,
|
|
1378
|
+
words: segments.length,
|
|
1379
|
+
nonWords: nonWords ?? void 0
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function analyzeCharChunk(chunk, collectNonWords, includeWhitespace) {
|
|
1383
|
+
const segmenter = getSegmenter(chunk.locale);
|
|
1384
|
+
const nonWords = collectNonWords ? createNonWordCollection() : null;
|
|
1385
|
+
let chars = 0;
|
|
1386
|
+
let wordChars = 0;
|
|
1387
|
+
let nonWordChars = 0;
|
|
1388
|
+
for (const part of segmenter.segment(chunk.text)) {
|
|
1389
|
+
if (part.isWordLike) {
|
|
1390
|
+
const count = countCharsForLocale(part.segment, chunk.locale);
|
|
1391
|
+
chars += count;
|
|
1392
|
+
wordChars += count;
|
|
1393
|
+
continue;
|
|
950
1394
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1395
|
+
if (collectNonWords && nonWords) {
|
|
1396
|
+
let whitespaceCount = 0;
|
|
1397
|
+
if (includeWhitespace) whitespaceCount = addWhitespace(nonWords, part.segment);
|
|
1398
|
+
const category = classifyNonWordSegment(part.segment);
|
|
1399
|
+
if (category) addNonWord(nonWords, category, part.segment);
|
|
1400
|
+
if (category || whitespaceCount > 0) {
|
|
1401
|
+
const count = countCharsForLocale(part.segment, chunk.locale);
|
|
1402
|
+
chars += count;
|
|
1403
|
+
nonWordChars += count;
|
|
1404
|
+
}
|
|
959
1405
|
}
|
|
960
|
-
}
|
|
1406
|
+
}
|
|
961
1407
|
return {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
text: chunk.text,
|
|
969
|
-
words: chunk.words,
|
|
970
|
-
nonWords: chunk.nonWords
|
|
971
|
-
}))
|
|
972
|
-
}
|
|
1408
|
+
locale: chunk.locale,
|
|
1409
|
+
text: chunk.text,
|
|
1410
|
+
chars,
|
|
1411
|
+
wordChars,
|
|
1412
|
+
nonWordChars,
|
|
1413
|
+
nonWords: nonWords ?? void 0
|
|
973
1414
|
};
|
|
974
1415
|
}
|
|
975
|
-
function
|
|
976
|
-
|
|
1416
|
+
function aggregateCharsByLocale(chunks) {
|
|
1417
|
+
const order = [];
|
|
1418
|
+
const map = /* @__PURE__ */ new Map();
|
|
1419
|
+
for (const chunk of chunks) {
|
|
1420
|
+
const existing = map.get(chunk.locale);
|
|
1421
|
+
if (existing) {
|
|
1422
|
+
existing.chars += chunk.chars;
|
|
1423
|
+
existing.wordChars += chunk.wordChars;
|
|
1424
|
+
existing.nonWordChars += chunk.nonWordChars;
|
|
1425
|
+
if (chunk.nonWords) {
|
|
1426
|
+
if (!existing.nonWords) existing.nonWords = createNonWordCollection();
|
|
1427
|
+
mergeNonWordCollections(existing.nonWords, chunk.nonWords);
|
|
1428
|
+
}
|
|
1429
|
+
continue;
|
|
1430
|
+
}
|
|
1431
|
+
order.push(chunk.locale);
|
|
1432
|
+
map.set(chunk.locale, {
|
|
1433
|
+
locale: chunk.locale,
|
|
1434
|
+
chars: chunk.chars,
|
|
1435
|
+
wordChars: chunk.wordChars,
|
|
1436
|
+
nonWordChars: chunk.nonWordChars,
|
|
1437
|
+
nonWords: chunk.nonWords ? mergeNonWordCollections(createNonWordCollection(), chunk.nonWords) : void 0
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
return order.map((locale) => map.get(locale));
|
|
977
1441
|
}
|
|
978
|
-
function
|
|
979
|
-
|
|
980
|
-
const
|
|
981
|
-
for (const chunk of
|
|
982
|
-
|
|
983
|
-
|
|
1442
|
+
function aggregateByLocale(chunks) {
|
|
1443
|
+
const order = [];
|
|
1444
|
+
const map = /* @__PURE__ */ new Map();
|
|
1445
|
+
for (const chunk of chunks) {
|
|
1446
|
+
const existing = map.get(chunk.locale);
|
|
1447
|
+
if (existing) {
|
|
1448
|
+
existing.words += chunk.words;
|
|
1449
|
+
appendAll(existing.segments, chunk.segments);
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
order.push(chunk.locale);
|
|
1453
|
+
map.set(chunk.locale, {
|
|
1454
|
+
locale: chunk.locale,
|
|
1455
|
+
words: chunk.words,
|
|
1456
|
+
segments: [...chunk.segments]
|
|
1457
|
+
});
|
|
984
1458
|
}
|
|
985
|
-
return
|
|
1459
|
+
return order.map((locale) => map.get(locale));
|
|
986
1460
|
}
|
|
987
1461
|
|
|
988
1462
|
//#endregion
|
|
989
|
-
//#region src/wc/
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1463
|
+
//#region src/wc/mode.ts
|
|
1464
|
+
const MODE_ALIASES = {
|
|
1465
|
+
chunk: "chunk",
|
|
1466
|
+
chunks: "chunk",
|
|
1467
|
+
segments: "segments",
|
|
1468
|
+
segment: "segments",
|
|
1469
|
+
seg: "segments",
|
|
1470
|
+
collector: "collector",
|
|
1471
|
+
collect: "collector",
|
|
1472
|
+
colle: "collector",
|
|
1473
|
+
char: "char",
|
|
1474
|
+
chars: "char",
|
|
1475
|
+
character: "char",
|
|
1476
|
+
characters: "char",
|
|
1477
|
+
"char-collector": "char-collector"
|
|
1478
|
+
};
|
|
1479
|
+
const CHAR_MODE_ALIASES = new Set([
|
|
1480
|
+
"char",
|
|
1481
|
+
"chars",
|
|
1482
|
+
"character",
|
|
1483
|
+
"characters"
|
|
1484
|
+
]);
|
|
1485
|
+
const COLLECTOR_MODE_ALIASES = new Set([
|
|
1486
|
+
"collector",
|
|
1487
|
+
"collect",
|
|
1488
|
+
"colle",
|
|
1489
|
+
"col"
|
|
1490
|
+
]);
|
|
1491
|
+
function collapseSeparators(value) {
|
|
1492
|
+
return value.replace(/[-_\s]+/g, "");
|
|
1493
|
+
}
|
|
1494
|
+
function isComposedCharCollectorFromTokens(value) {
|
|
1495
|
+
const tokens = value.split(/[-_\s]+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
1496
|
+
if (tokens.length < 2) return false;
|
|
1497
|
+
let hasCharAlias = false;
|
|
1498
|
+
let hasCollectorAlias = false;
|
|
1499
|
+
for (const token of tokens) {
|
|
1500
|
+
if (CHAR_MODE_ALIASES.has(token)) {
|
|
1501
|
+
hasCharAlias = true;
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
if (COLLECTOR_MODE_ALIASES.has(token)) {
|
|
1505
|
+
hasCollectorAlias = true;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
return false;
|
|
1002
1509
|
}
|
|
1510
|
+
return hasCharAlias && hasCollectorAlias;
|
|
1003
1511
|
}
|
|
1004
|
-
function
|
|
1005
|
-
|
|
1006
|
-
return
|
|
1007
|
-
const valueText = normalizeText(value);
|
|
1008
|
-
return {
|
|
1009
|
-
name: key,
|
|
1010
|
-
source: "frontmatter",
|
|
1011
|
-
result: wc_default(valueText ? `${key}: ${valueText}` : key, options)
|
|
1012
|
-
};
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
function buildSingleItem(name, text, mode, options, source) {
|
|
1016
|
-
return [{
|
|
1017
|
-
name,
|
|
1018
|
-
source,
|
|
1019
|
-
result: wc_default(text, options)
|
|
1020
|
-
}];
|
|
1512
|
+
function isComposedCharCollectorCompact(value) {
|
|
1513
|
+
for (const charAlias of CHAR_MODE_ALIASES) for (const collectorAlias of COLLECTOR_MODE_ALIASES) if (value === `${charAlias}${collectorAlias}` || value === `${collectorAlias}${charAlias}`) return true;
|
|
1514
|
+
return false;
|
|
1021
1515
|
}
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1516
|
+
function normalizeMode(input) {
|
|
1517
|
+
if (!input) return null;
|
|
1518
|
+
const normalized = input.trim().toLowerCase();
|
|
1519
|
+
const direct = MODE_ALIASES[normalized];
|
|
1520
|
+
if (direct) return direct;
|
|
1521
|
+
if (isComposedCharCollectorFromTokens(normalized)) return "char-collector";
|
|
1522
|
+
const compact = collapseSeparators(normalized);
|
|
1523
|
+
if (isComposedCharCollectorCompact(compact)) return "char-collector";
|
|
1524
|
+
return MODE_ALIASES[compact] ?? null;
|
|
1024
1525
|
}
|
|
1025
|
-
function
|
|
1026
|
-
|
|
1027
|
-
if (section === "all") {
|
|
1028
|
-
const result = wc_default(input, options);
|
|
1029
|
-
return {
|
|
1030
|
-
section,
|
|
1031
|
-
total: result.total,
|
|
1032
|
-
frontmatterType: null,
|
|
1033
|
-
items: [{
|
|
1034
|
-
name: "all",
|
|
1035
|
-
source: "content",
|
|
1036
|
-
result
|
|
1037
|
-
}]
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
const parsed = parseMarkdown(input);
|
|
1041
|
-
const frontmatterText = parsed.frontmatter ?? "";
|
|
1042
|
-
const contentText = parsed.content ?? "";
|
|
1043
|
-
let items = [];
|
|
1044
|
-
if (section === "frontmatter") items = buildSingleItem("frontmatter", frontmatterText, mode, options, "frontmatter");
|
|
1045
|
-
else if (section === "content") items = buildSingleItem("content", contentText, mode, options, "content");
|
|
1046
|
-
else if (section === "split") items = [...buildSingleItem("frontmatter", frontmatterText, mode, options, "frontmatter"), ...buildSingleItem("content", contentText, mode, options, "content")];
|
|
1047
|
-
else if (section === "per-key") items = buildPerKeyItems(parsed.data, mode, options);
|
|
1048
|
-
else if (section === "split-per-key") items = [...buildPerKeyItems(parsed.data, mode, options), ...buildSingleItem("content", contentText, mode, options, "content")];
|
|
1049
|
-
return {
|
|
1050
|
-
section,
|
|
1051
|
-
total: sumTotals(items),
|
|
1052
|
-
frontmatterType: parsed.frontmatterType,
|
|
1053
|
-
items
|
|
1054
|
-
};
|
|
1526
|
+
function resolveMode(input, fallback = "chunk") {
|
|
1527
|
+
return normalizeMode(input) ?? fallback;
|
|
1055
1528
|
}
|
|
1056
1529
|
|
|
1057
1530
|
//#endregion
|
|
1058
|
-
//#region src/
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1531
|
+
//#region src/wc/locale-detect.ts
|
|
1532
|
+
const DEFAULT_LOCALE = "und-Latn";
|
|
1533
|
+
const DEFAULT_HAN_TAG = "zh-Hani";
|
|
1534
|
+
const regex = {
|
|
1535
|
+
hiragana: /\p{Script=Hiragana}/u,
|
|
1536
|
+
katakana: /\p{Script=Katakana}/u,
|
|
1537
|
+
hangul: /\p{Script=Hangul}/u,
|
|
1538
|
+
han: /\p{Script=Han}/u,
|
|
1539
|
+
latin: /\p{Script=Latin}/u,
|
|
1540
|
+
arabic: /\p{Script=Arabic}/u,
|
|
1541
|
+
cyrillic: /\p{Script=Cyrillic}/u,
|
|
1542
|
+
devanagari: /\p{Script=Devanagari}/u,
|
|
1543
|
+
thai: /\p{Script=Thai}/u
|
|
1544
|
+
};
|
|
1545
|
+
const latinLocaleHints = [
|
|
1546
|
+
{
|
|
1547
|
+
locale: "de",
|
|
1548
|
+
regex: /[äöüÄÖÜß]/
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
locale: "es",
|
|
1552
|
+
regex: /[ñÑ¿¡]/
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
locale: "pt",
|
|
1556
|
+
regex: /[ãõÃÕ]/
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
locale: "fr",
|
|
1560
|
+
regex: /[œŒæÆ]/
|
|
1561
|
+
}
|
|
1562
|
+
];
|
|
1563
|
+
const latinLocales = new Set([DEFAULT_LOCALE, ...latinLocaleHints.map((hint) => hint.locale)]);
|
|
1564
|
+
function isLatinLocale(locale) {
|
|
1565
|
+
return latinLocales.has(locale);
|
|
1068
1566
|
}
|
|
1069
|
-
function
|
|
1070
|
-
const
|
|
1071
|
-
|
|
1072
|
-
const normalized = trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
1073
|
-
if (normalized === ".") return null;
|
|
1074
|
-
return normalized;
|
|
1567
|
+
function detectLatinLocale(char) {
|
|
1568
|
+
for (const hint of latinLocaleHints) if (hint.regex.test(char)) return hint.locale;
|
|
1569
|
+
return DEFAULT_LOCALE;
|
|
1075
1570
|
}
|
|
1076
|
-
function
|
|
1077
|
-
const
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1571
|
+
function resolveLatinHint(options) {
|
|
1572
|
+
const latinTagHint = options.latinTagHint?.trim();
|
|
1573
|
+
if (latinTagHint) return latinTagHint;
|
|
1574
|
+
const latinLanguageHint = options.latinLanguageHint?.trim();
|
|
1575
|
+
if (latinLanguageHint) return latinLanguageHint;
|
|
1576
|
+
const latinLocaleHint = options.latinLocaleHint?.trim();
|
|
1577
|
+
if (latinLocaleHint) return latinLocaleHint;
|
|
1578
|
+
}
|
|
1579
|
+
function resolveHanHint(options) {
|
|
1580
|
+
const hanTagHint = options.hanTagHint?.trim();
|
|
1581
|
+
if (hanTagHint) return hanTagHint;
|
|
1582
|
+
const hanLanguageHint = options.hanLanguageHint?.trim();
|
|
1583
|
+
if (hanLanguageHint) return hanLanguageHint;
|
|
1584
|
+
}
|
|
1585
|
+
function detectLocaleForChar(char, previousLocale, options = {}) {
|
|
1586
|
+
if (regex.hiragana.test(char) || regex.katakana.test(char)) return "ja";
|
|
1587
|
+
if (regex.hangul.test(char)) return "ko";
|
|
1588
|
+
if (regex.arabic.test(char)) return "ar";
|
|
1589
|
+
if (regex.cyrillic.test(char)) return "ru";
|
|
1590
|
+
if (regex.devanagari.test(char)) return "hi";
|
|
1591
|
+
if (regex.thai.test(char)) return "th";
|
|
1592
|
+
if (regex.han.test(char)) {
|
|
1593
|
+
if (previousLocale && previousLocale.startsWith("ja")) return previousLocale;
|
|
1594
|
+
return resolveHanHint(options) ?? DEFAULT_HAN_TAG;
|
|
1083
1595
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
for (const extension of includeExtensions) {
|
|
1092
|
-
if (excludeExtensions.has(extension)) continue;
|
|
1093
|
-
effectiveIncludeExtensions.add(extension);
|
|
1596
|
+
if (regex.latin.test(char)) {
|
|
1597
|
+
const hintedLocale = detectLatinLocale(char);
|
|
1598
|
+
if (hintedLocale !== DEFAULT_LOCALE) return hintedLocale;
|
|
1599
|
+
if (previousLocale && isLatinLocale(previousLocale) && previousLocale !== DEFAULT_LOCALE) return previousLocale;
|
|
1600
|
+
const latinHint = resolveLatinHint(options);
|
|
1601
|
+
if (latinHint) return latinHint;
|
|
1602
|
+
return DEFAULT_LOCALE;
|
|
1094
1603
|
}
|
|
1095
|
-
return
|
|
1096
|
-
includeExtensions,
|
|
1097
|
-
excludeExtensions,
|
|
1098
|
-
effectiveIncludeExtensions
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
function shouldIncludeFromDirectory(filePath, filter) {
|
|
1102
|
-
const extension = extname(filePath).toLowerCase();
|
|
1103
|
-
return filter.effectiveIncludeExtensions.has(extension);
|
|
1604
|
+
return null;
|
|
1104
1605
|
}
|
|
1105
1606
|
|
|
1106
1607
|
//#endregion
|
|
1107
|
-
//#region src/
|
|
1108
|
-
function
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
let
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
if (
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
return suspicious / sampleSize > .3;
|
|
1121
|
-
}
|
|
1122
|
-
async function loadBatchInputs(filePaths) {
|
|
1123
|
-
const files = [];
|
|
1124
|
-
const skipped = [];
|
|
1125
|
-
for (const filePath of filePaths) {
|
|
1126
|
-
let buffer;
|
|
1127
|
-
try {
|
|
1128
|
-
buffer = await readFile(filePath);
|
|
1129
|
-
} catch (error) {
|
|
1130
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1131
|
-
skipped.push({
|
|
1132
|
-
path: filePath,
|
|
1133
|
-
reason: `not readable: ${message}`
|
|
1134
|
-
});
|
|
1608
|
+
//#region src/wc/segment.ts
|
|
1609
|
+
function segmentTextByLocale(text, options = {}) {
|
|
1610
|
+
const chunks = [];
|
|
1611
|
+
let currentLocale = DEFAULT_LOCALE;
|
|
1612
|
+
let buffer = "";
|
|
1613
|
+
let bufferHasScript = false;
|
|
1614
|
+
for (const char of text) {
|
|
1615
|
+
const detected = detectLocaleForChar(char, currentLocale, options);
|
|
1616
|
+
const targetLocale = detected ?? currentLocale;
|
|
1617
|
+
if (buffer === "") {
|
|
1618
|
+
currentLocale = targetLocale;
|
|
1619
|
+
buffer = char;
|
|
1620
|
+
bufferHasScript = detected !== null;
|
|
1135
1621
|
continue;
|
|
1136
1622
|
}
|
|
1137
|
-
if (
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
});
|
|
1623
|
+
if (detected !== null && !bufferHasScript) {
|
|
1624
|
+
currentLocale = targetLocale;
|
|
1625
|
+
buffer += char;
|
|
1626
|
+
bufferHasScript = true;
|
|
1142
1627
|
continue;
|
|
1143
1628
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
return {
|
|
1150
|
-
files,
|
|
1151
|
-
skipped
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
//#endregion
|
|
1156
|
-
//#region src/cli/path/resolve.ts
|
|
1157
|
-
async function expandDirectory(directoryPath, recursive, filter, skipped, debug, stats) {
|
|
1158
|
-
let entries;
|
|
1159
|
-
try {
|
|
1160
|
-
entries = await readdir(directoryPath, {
|
|
1161
|
-
withFileTypes: true,
|
|
1162
|
-
encoding: "utf8"
|
|
1163
|
-
});
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1166
|
-
skipped.push({
|
|
1167
|
-
path: directoryPath,
|
|
1168
|
-
reason: `directory read failed: ${message}`
|
|
1169
|
-
});
|
|
1170
|
-
debug.emit("path.resolve.expand.read_failed", {
|
|
1171
|
-
directory: directoryPath,
|
|
1172
|
-
reason: `directory read failed: ${message}`
|
|
1173
|
-
});
|
|
1174
|
-
return [];
|
|
1175
|
-
}
|
|
1176
|
-
const sortedEntries = entries.slice().sort((left, right) => left.name.localeCompare(right.name));
|
|
1177
|
-
const files = [];
|
|
1178
|
-
debug.emit("path.resolve.expand.start", {
|
|
1179
|
-
directory: directoryPath,
|
|
1180
|
-
entries: sortedEntries.length,
|
|
1181
|
-
recursive
|
|
1182
|
-
});
|
|
1183
|
-
for (const entry of sortedEntries) {
|
|
1184
|
-
const entryPath = resolve(directoryPath, entry.name);
|
|
1185
|
-
if (entry.isFile()) {
|
|
1186
|
-
if (!shouldIncludeFromDirectory(entryPath, filter)) {
|
|
1187
|
-
skipped.push({
|
|
1188
|
-
path: entryPath,
|
|
1189
|
-
reason: "extension excluded"
|
|
1190
|
-
});
|
|
1191
|
-
debug.emit("path.resolve.filter.excluded", {
|
|
1192
|
-
path: entryPath,
|
|
1193
|
-
reason: "extension excluded"
|
|
1194
|
-
}, { verbosity: "verbose" });
|
|
1195
|
-
stats.filterExcluded += 1;
|
|
1629
|
+
if (targetLocale !== currentLocale && detected !== null) {
|
|
1630
|
+
if (currentLocale === DEFAULT_LOCALE && isLatinLocale(targetLocale)) {
|
|
1631
|
+
currentLocale = targetLocale;
|
|
1632
|
+
buffer += char;
|
|
1633
|
+
bufferHasScript = true;
|
|
1196
1634
|
continue;
|
|
1197
1635
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1636
|
+
chunks.push({
|
|
1637
|
+
locale: currentLocale,
|
|
1638
|
+
text: buffer
|
|
1639
|
+
});
|
|
1640
|
+
currentLocale = targetLocale;
|
|
1641
|
+
buffer = char;
|
|
1642
|
+
bufferHasScript = true;
|
|
1204
1643
|
continue;
|
|
1205
1644
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1645
|
+
buffer += char;
|
|
1646
|
+
if (detected !== null) bufferHasScript = true;
|
|
1208
1647
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1648
|
+
if (buffer.length > 0) chunks.push({
|
|
1649
|
+
locale: currentLocale,
|
|
1650
|
+
text: buffer
|
|
1212
1651
|
});
|
|
1213
|
-
return
|
|
1652
|
+
return mergeAdjacentChunks(chunks);
|
|
1214
1653
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1654
|
+
function mergeAdjacentChunks(chunks) {
|
|
1655
|
+
if (chunks.length === 0) return chunks;
|
|
1656
|
+
const merged = [];
|
|
1657
|
+
let last = chunks[0];
|
|
1658
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
1659
|
+
const chunk = chunks[i];
|
|
1660
|
+
if (chunk.locale === last.locale) last = {
|
|
1661
|
+
locale: last.locale,
|
|
1662
|
+
text: last.text + chunk.text
|
|
1663
|
+
};
|
|
1664
|
+
else {
|
|
1665
|
+
merged.push(last);
|
|
1666
|
+
last = chunk;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
merged.push(last);
|
|
1670
|
+
return merged;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
//#endregion
|
|
1674
|
+
//#region src/wc/wc.ts
|
|
1675
|
+
function wordCounter(text, options = {}) {
|
|
1676
|
+
const mode = resolveMode(options.mode, "chunk");
|
|
1677
|
+
const collectNonWords = Boolean(options.nonWords);
|
|
1678
|
+
const includeWhitespace = Boolean(options.includeWhitespace);
|
|
1679
|
+
const chunks = segmentTextByLocale(text, {
|
|
1680
|
+
latinLanguageHint: options.latinLanguageHint,
|
|
1681
|
+
latinTagHint: options.latinTagHint,
|
|
1682
|
+
latinLocaleHint: options.latinLocaleHint,
|
|
1683
|
+
hanLanguageHint: options.hanLanguageHint,
|
|
1684
|
+
hanTagHint: options.hanTagHint
|
|
1235
1685
|
});
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1686
|
+
if (mode === "char" || mode === "char-collector") {
|
|
1687
|
+
const analyzed = chunks.map((chunk) => analyzeCharChunk(chunk, collectNonWords, includeWhitespace));
|
|
1688
|
+
const total = analyzed.reduce((sum, chunk) => sum + chunk.chars, 0);
|
|
1689
|
+
const counts = collectNonWords ? {
|
|
1690
|
+
words: analyzed.reduce((sum, chunk) => sum + chunk.wordChars, 0),
|
|
1691
|
+
nonWords: analyzed.reduce((sum, chunk) => sum + chunk.nonWordChars, 0),
|
|
1692
|
+
total
|
|
1693
|
+
} : void 0;
|
|
1694
|
+
if (mode === "char") return {
|
|
1695
|
+
total,
|
|
1696
|
+
counts,
|
|
1697
|
+
breakdown: {
|
|
1698
|
+
mode,
|
|
1699
|
+
items: analyzed.map((chunk) => ({
|
|
1700
|
+
locale: chunk.locale,
|
|
1701
|
+
text: chunk.text,
|
|
1702
|
+
chars: chunk.chars,
|
|
1703
|
+
nonWords: chunk.nonWords
|
|
1704
|
+
}))
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
return {
|
|
1708
|
+
total,
|
|
1709
|
+
counts,
|
|
1710
|
+
breakdown: {
|
|
1711
|
+
mode,
|
|
1712
|
+
items: aggregateCharsByLocale(analyzed).map((chunk) => ({
|
|
1713
|
+
locale: chunk.locale,
|
|
1714
|
+
chars: chunk.chars,
|
|
1715
|
+
nonWords: chunk.nonWords
|
|
1716
|
+
}))
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
const analyzed = chunks.map((chunk) => analyzeChunk(chunk, collectNonWords, includeWhitespace));
|
|
1721
|
+
const wordsTotal = analyzed.reduce((sum, chunk) => sum + chunk.words, 0);
|
|
1722
|
+
const nonWordsTotal = collectNonWords ? analyzed.reduce((sum, chunk) => {
|
|
1723
|
+
if (!chunk.nonWords) return sum;
|
|
1724
|
+
return sum + getNonWordTotal(chunk.nonWords);
|
|
1725
|
+
}, 0) : 0;
|
|
1726
|
+
const total = analyzed.reduce((sum, chunk) => {
|
|
1727
|
+
let chunkTotal = chunk.words;
|
|
1728
|
+
if (collectNonWords && chunk.nonWords) chunkTotal += getNonWordTotal(chunk.nonWords);
|
|
1729
|
+
return sum + chunkTotal;
|
|
1730
|
+
}, 0);
|
|
1731
|
+
const counts = collectNonWords ? {
|
|
1732
|
+
words: wordsTotal,
|
|
1733
|
+
nonWords: nonWordsTotal,
|
|
1734
|
+
total
|
|
1735
|
+
} : void 0;
|
|
1736
|
+
if (mode === "segments") return {
|
|
1737
|
+
total,
|
|
1738
|
+
counts,
|
|
1739
|
+
breakdown: {
|
|
1740
|
+
mode,
|
|
1741
|
+
items: analyzed.map((chunk) => ({
|
|
1742
|
+
locale: chunk.locale,
|
|
1743
|
+
text: chunk.text,
|
|
1744
|
+
words: chunk.words,
|
|
1745
|
+
segments: chunk.segments,
|
|
1746
|
+
nonWords: chunk.nonWords
|
|
1747
|
+
}))
|
|
1245
1748
|
}
|
|
1246
|
-
resolvedFiles.add(filePath);
|
|
1247
|
-
stats.dedupeAccepted += 1;
|
|
1248
|
-
debug.emit("path.resolve.dedupe.accept", {
|
|
1249
|
-
path: filePath,
|
|
1250
|
-
source: details.source,
|
|
1251
|
-
input: details.input
|
|
1252
|
-
}, { verbosity: "verbose" });
|
|
1253
1749
|
};
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
try {
|
|
1262
|
-
metadata = await stat(targetPath);
|
|
1263
|
-
} catch (error) {
|
|
1264
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1265
|
-
skipped.push({
|
|
1266
|
-
path: targetPath,
|
|
1267
|
-
reason: `not readable: ${message}`
|
|
1268
|
-
});
|
|
1269
|
-
debug.emit("path.resolve.skip", {
|
|
1270
|
-
path: targetPath,
|
|
1271
|
-
reason: `not readable: ${message}`
|
|
1272
|
-
});
|
|
1273
|
-
continue;
|
|
1274
|
-
}
|
|
1275
|
-
if (metadata.isDirectory() && options.pathMode === "auto") {
|
|
1276
|
-
debug.emit("path.resolve.root.expand", {
|
|
1277
|
-
root: targetPath,
|
|
1278
|
-
recursive: options.recursive
|
|
1279
|
-
});
|
|
1280
|
-
const files = await expandDirectory(targetPath, options.recursive, extensionFilter, skipped, debug, stats);
|
|
1281
|
-
for (const file of files) addResolvedFile(file, {
|
|
1282
|
-
source: "directory",
|
|
1283
|
-
input: targetPath
|
|
1284
|
-
});
|
|
1285
|
-
continue;
|
|
1286
|
-
}
|
|
1287
|
-
if (!metadata.isFile()) {
|
|
1288
|
-
skipped.push({
|
|
1289
|
-
path: targetPath,
|
|
1290
|
-
reason: "not a regular file"
|
|
1291
|
-
});
|
|
1292
|
-
debug.emit("path.resolve.skip", {
|
|
1293
|
-
path: targetPath,
|
|
1294
|
-
reason: "not a regular file"
|
|
1295
|
-
});
|
|
1296
|
-
continue;
|
|
1750
|
+
if (mode === "collector") return {
|
|
1751
|
+
total,
|
|
1752
|
+
counts,
|
|
1753
|
+
breakdown: {
|
|
1754
|
+
mode,
|
|
1755
|
+
items: aggregateByLocale(analyzed),
|
|
1756
|
+
nonWords: collectNonWordsAggregate(analyzed, collectNonWords)
|
|
1297
1757
|
}
|
|
1298
|
-
|
|
1299
|
-
source: "direct",
|
|
1300
|
-
input: targetPath
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
const files = [...resolvedFiles].sort((left, right) => left.localeCompare(right));
|
|
1304
|
-
debug.emit("path.resolve.filter.summary", {
|
|
1305
|
-
excluded: stats.filterExcluded,
|
|
1306
|
-
included: stats.directoryIncluded
|
|
1307
|
-
});
|
|
1308
|
-
debug.emit("path.resolve.dedupe.summary", {
|
|
1309
|
-
accepted: stats.dedupeAccepted,
|
|
1310
|
-
duplicates: stats.dedupeDuplicates
|
|
1311
|
-
});
|
|
1312
|
-
debug.emit("path.resolve.complete", {
|
|
1313
|
-
files: files.length,
|
|
1314
|
-
skipped: skipped.length,
|
|
1315
|
-
ordering: "absolute-path-ascending"
|
|
1316
|
-
});
|
|
1758
|
+
};
|
|
1317
1759
|
return {
|
|
1318
|
-
|
|
1319
|
-
|
|
1760
|
+
total,
|
|
1761
|
+
counts,
|
|
1762
|
+
breakdown: {
|
|
1763
|
+
mode,
|
|
1764
|
+
items: analyzed.map((chunk) => ({
|
|
1765
|
+
locale: chunk.locale,
|
|
1766
|
+
text: chunk.text,
|
|
1767
|
+
words: chunk.words,
|
|
1768
|
+
nonWords: chunk.nonWords
|
|
1769
|
+
}))
|
|
1770
|
+
}
|
|
1320
1771
|
};
|
|
1321
1772
|
}
|
|
1773
|
+
function getNonWordTotal(nonWords) {
|
|
1774
|
+
return nonWords.counts.emoji + nonWords.counts.symbols + nonWords.counts.punctuation + (nonWords.counts.whitespace ?? 0);
|
|
1775
|
+
}
|
|
1776
|
+
function collectNonWordsAggregate(analyzed, enabled) {
|
|
1777
|
+
if (!enabled) return;
|
|
1778
|
+
const collection = createNonWordCollection();
|
|
1779
|
+
for (const chunk of analyzed) {
|
|
1780
|
+
if (!chunk.nonWords) continue;
|
|
1781
|
+
mergeNonWordCollections(collection, chunk.nonWords);
|
|
1782
|
+
}
|
|
1783
|
+
return collection;
|
|
1784
|
+
}
|
|
1322
1785
|
|
|
1323
1786
|
//#endregion
|
|
1324
|
-
//#region src/
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1787
|
+
//#region src/wc/index.ts
|
|
1788
|
+
var wc_default = wordCounter;
|
|
1789
|
+
|
|
1790
|
+
//#endregion
|
|
1791
|
+
//#region src/markdown/section-count.ts
|
|
1792
|
+
function normalizeText(value) {
|
|
1793
|
+
if (value == null) return "";
|
|
1794
|
+
if (typeof value === "string") return value;
|
|
1795
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
1796
|
+
try {
|
|
1797
|
+
return JSON.stringify(value);
|
|
1798
|
+
} catch {
|
|
1799
|
+
return String(value);
|
|
1800
|
+
}
|
|
1337
1801
|
}
|
|
1338
|
-
function
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1802
|
+
function buildPerKeyItems(data, mode, options) {
|
|
1803
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return [];
|
|
1804
|
+
return Object.entries(data).map(([key, value]) => {
|
|
1805
|
+
const valueText = normalizeText(value);
|
|
1806
|
+
return {
|
|
1807
|
+
name: key,
|
|
1808
|
+
source: "frontmatter",
|
|
1809
|
+
result: wc_default(valueText ? `${key}: ${valueText}` : key, options)
|
|
1810
|
+
};
|
|
1811
|
+
});
|
|
1345
1812
|
}
|
|
1346
|
-
function
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1813
|
+
function buildSingleItem(name, text, mode, options, source) {
|
|
1814
|
+
return [{
|
|
1815
|
+
name,
|
|
1816
|
+
source,
|
|
1817
|
+
result: wc_default(text, options)
|
|
1818
|
+
}];
|
|
1350
1819
|
}
|
|
1351
|
-
function
|
|
1352
|
-
return
|
|
1820
|
+
function sumTotals(items) {
|
|
1821
|
+
return items.reduce((sum, item) => sum + item.result.total, 0);
|
|
1353
1822
|
}
|
|
1354
|
-
function
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
if (lastLineLength === 0) return;
|
|
1384
|
-
options.stream.write(`\r${" ".repeat(lastLineLength)}\r`);
|
|
1385
|
-
lastLineLength = 0;
|
|
1386
|
-
};
|
|
1387
|
-
return {
|
|
1388
|
-
enabled,
|
|
1389
|
-
start(nextTotal, nextStartedAtMs) {
|
|
1390
|
-
if (!enabled || nextTotal <= 1) return;
|
|
1391
|
-
total = nextTotal;
|
|
1392
|
-
active = true;
|
|
1393
|
-
startedAtMs = nextStartedAtMs ?? Date.now();
|
|
1394
|
-
lastRenderedPercent = -1;
|
|
1395
|
-
finalizingStarted = false;
|
|
1396
|
-
render(0);
|
|
1397
|
-
},
|
|
1398
|
-
advance(snapshot) {
|
|
1399
|
-
if (!active) return;
|
|
1400
|
-
render(snapshot.completed);
|
|
1401
|
-
},
|
|
1402
|
-
startFinalizing() {
|
|
1403
|
-
if (!active || finalizingStarted) return;
|
|
1404
|
-
finalizingStarted = true;
|
|
1405
|
-
const line = buildFinalizingLine(startedAtMs);
|
|
1406
|
-
if (isTTY) {
|
|
1407
|
-
if (!clearOnFinish) {
|
|
1408
|
-
options.stream.write(`\n${line}`);
|
|
1409
|
-
lastLineLength = line.length;
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
writeTTYLine(line);
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
lastLineLength = line.length;
|
|
1416
|
-
options.stream.write(`${line}\n`);
|
|
1417
|
-
},
|
|
1418
|
-
finish() {
|
|
1419
|
-
if (!active) return;
|
|
1420
|
-
if (isTTY) if (clearOnFinish) clearLine();
|
|
1421
|
-
else options.stream.write("\n");
|
|
1422
|
-
active = false;
|
|
1423
|
-
}
|
|
1823
|
+
function countSections(input, section, options = {}) {
|
|
1824
|
+
const mode = options.mode ?? "chunk";
|
|
1825
|
+
if (section === "all") {
|
|
1826
|
+
const result = wc_default(input, options);
|
|
1827
|
+
return {
|
|
1828
|
+
section,
|
|
1829
|
+
total: result.total,
|
|
1830
|
+
frontmatterType: null,
|
|
1831
|
+
items: [{
|
|
1832
|
+
name: "all",
|
|
1833
|
+
source: "content",
|
|
1834
|
+
result
|
|
1835
|
+
}]
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
const parsed = parseMarkdown(input);
|
|
1839
|
+
const frontmatterText = parsed.frontmatter ?? "";
|
|
1840
|
+
const contentText = parsed.content ?? "";
|
|
1841
|
+
let items = [];
|
|
1842
|
+
if (section === "frontmatter") items = buildSingleItem("frontmatter", frontmatterText, mode, options, "frontmatter");
|
|
1843
|
+
else if (section === "content") items = buildSingleItem("content", contentText, mode, options, "content");
|
|
1844
|
+
else if (section === "split") items = [...buildSingleItem("frontmatter", frontmatterText, mode, options, "frontmatter"), ...buildSingleItem("content", contentText, mode, options, "content")];
|
|
1845
|
+
else if (section === "per-key") items = buildPerKeyItems(parsed.data, mode, options);
|
|
1846
|
+
else if (section === "split-per-key") items = [...buildPerKeyItems(parsed.data, mode, options), ...buildSingleItem("content", contentText, mode, options, "content")];
|
|
1847
|
+
return {
|
|
1848
|
+
section,
|
|
1849
|
+
total: sumTotals(items),
|
|
1850
|
+
frontmatterType: parsed.frontmatterType,
|
|
1851
|
+
items
|
|
1424
1852
|
};
|
|
1425
1853
|
}
|
|
1426
1854
|
|
|
@@ -1458,6 +1886,43 @@ function mergeWordCounterResult(left, right, preserveCollectorSegments) {
|
|
|
1458
1886
|
items: [...left.breakdown.items, ...right.breakdown.items]
|
|
1459
1887
|
}
|
|
1460
1888
|
};
|
|
1889
|
+
if (left.breakdown.mode === "char-collector" && right.breakdown.mode === "char-collector") {
|
|
1890
|
+
const localeOrder = [];
|
|
1891
|
+
const mergedByLocale = /* @__PURE__ */ new Map();
|
|
1892
|
+
const addItems = (items) => {
|
|
1893
|
+
for (const item of items) {
|
|
1894
|
+
const existing = mergedByLocale.get(item.locale);
|
|
1895
|
+
if (existing) {
|
|
1896
|
+
existing.chars += item.chars;
|
|
1897
|
+
if (item.nonWords) {
|
|
1898
|
+
if (!existing.nonWords) existing.nonWords = createNonWordCollection();
|
|
1899
|
+
mergeNonWordCollections(existing.nonWords, item.nonWords);
|
|
1900
|
+
}
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
localeOrder.push(item.locale);
|
|
1904
|
+
mergedByLocale.set(item.locale, {
|
|
1905
|
+
locale: item.locale,
|
|
1906
|
+
chars: item.chars,
|
|
1907
|
+
nonWords: item.nonWords ? mergeNonWordCollections(createNonWordCollection(), item.nonWords) : void 0
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
addItems(left.breakdown.items);
|
|
1912
|
+
addItems(right.breakdown.items);
|
|
1913
|
+
return {
|
|
1914
|
+
total,
|
|
1915
|
+
counts,
|
|
1916
|
+
breakdown: {
|
|
1917
|
+
mode: "char-collector",
|
|
1918
|
+
items: localeOrder.map((locale) => {
|
|
1919
|
+
const value = mergedByLocale.get(locale);
|
|
1920
|
+
if (!value) throw new Error(`Missing char-collector entry for locale: ${locale}`);
|
|
1921
|
+
return value;
|
|
1922
|
+
})
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1461
1926
|
if (left.breakdown.mode === "collector" && right.breakdown.mode === "collector") {
|
|
1462
1927
|
const localeOrder = [];
|
|
1463
1928
|
const mergedByLocale = /* @__PURE__ */ new Map();
|
|
@@ -1693,302 +2158,28 @@ async function runBatchCount(options) {
|
|
|
1693
2158
|
elapsedMs: finalizeElapsedMs
|
|
1694
2159
|
});
|
|
1695
2160
|
appendAll(summary.skipped, resolved.skipped);
|
|
1696
|
-
appendAll(summary.skipped, loaded.skipped);
|
|
1697
|
-
options.debug.emit("batch.aggregate.complete", {
|
|
1698
|
-
files: summary.files.length,
|
|
1699
|
-
skipped: summary.skipped.length,
|
|
1700
|
-
total: summary.aggregate.total
|
|
1701
|
-
});
|
|
1702
|
-
return summary;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
//#endregion
|
|
1706
|
-
//#region src/cli/debug/channel.ts
|
|
1707
|
-
const NOOP_CLOSE = async () => {};
|
|
1708
|
-
function shouldEmitAtVerbosity(channelVerbosity, eventVerbosity) {
|
|
1709
|
-
return channelVerbosity === "verbose" || eventVerbosity === "compact";
|
|
1710
|
-
}
|
|
1711
|
-
function formatTimestampPart(value) {
|
|
1712
|
-
return String(value).padStart(2, "0");
|
|
1713
|
-
}
|
|
1714
|
-
function formatDebugReportTimestamp(now) {
|
|
1715
|
-
return [`${now.getFullYear()}${formatTimestampPart(now.getMonth() + 1)}${formatTimestampPart(now.getDate())}`, `${formatTimestampPart(now.getHours())}${formatTimestampPart(now.getMinutes())}${formatTimestampPart(now.getSeconds())}`].join("-");
|
|
1716
|
-
}
|
|
1717
|
-
function withCollisionSuffix(pathValue, sequence) {
|
|
1718
|
-
if (sequence <= 0) return pathValue;
|
|
1719
|
-
const extension = extname(pathValue);
|
|
1720
|
-
const baseName = basename(pathValue, extension);
|
|
1721
|
-
return join(dirname(pathValue), `${baseName}-${sequence}${extension}`);
|
|
1722
|
-
}
|
|
1723
|
-
function resolveReportPath(report, now, pid) {
|
|
1724
|
-
const cwd = report.cwd ?? process.cwd();
|
|
1725
|
-
const defaultName = `wc-debug-${formatDebugReportTimestamp(now)}-${pid}.jsonl`;
|
|
1726
|
-
const explicitPath = typeof report.path === "string";
|
|
1727
|
-
const basePath = explicitPath ? resolve(cwd, report.path) : resolve(cwd, defaultName);
|
|
1728
|
-
mkdirSync(dirname(basePath), { recursive: true });
|
|
1729
|
-
if (explicitPath) {
|
|
1730
|
-
if (existsSync(basePath) && statSync(basePath).isDirectory()) throw new Error(`debug report path must be a file: ${basePath}`);
|
|
1731
|
-
return basePath;
|
|
1732
|
-
}
|
|
1733
|
-
let candidate = basePath;
|
|
1734
|
-
let sequence = 0;
|
|
1735
|
-
while (existsSync(candidate)) {
|
|
1736
|
-
sequence += 1;
|
|
1737
|
-
candidate = withCollisionSuffix(basePath, sequence);
|
|
1738
|
-
}
|
|
1739
|
-
return candidate;
|
|
1740
|
-
}
|
|
1741
|
-
function createTerminalSink() {
|
|
1742
|
-
return {
|
|
1743
|
-
write(line) {
|
|
1744
|
-
console.error(`[debug] ${line}`);
|
|
1745
|
-
},
|
|
1746
|
-
close: NOOP_CLOSE
|
|
1747
|
-
};
|
|
1748
|
-
}
|
|
1749
|
-
function createFileSink(pathValue) {
|
|
1750
|
-
try {
|
|
1751
|
-
closeSync(openSync(pathValue, "a"));
|
|
1752
|
-
} catch (error) {
|
|
1753
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1754
|
-
throw new Error(`debug report path is not writable: ${pathValue} (${message})`);
|
|
1755
|
-
}
|
|
1756
|
-
const stream = createWriteStream(pathValue, {
|
|
1757
|
-
flags: "a",
|
|
1758
|
-
encoding: "utf8"
|
|
1759
|
-
});
|
|
1760
|
-
let streamError;
|
|
1761
|
-
stream.on("error", (error) => {
|
|
1762
|
-
streamError = error;
|
|
1763
|
-
});
|
|
1764
|
-
return {
|
|
1765
|
-
write(line) {
|
|
1766
|
-
if (streamError || stream.destroyed) return;
|
|
1767
|
-
stream.write(`${line}\n`);
|
|
1768
|
-
},
|
|
1769
|
-
close() {
|
|
1770
|
-
if (streamError || stream.destroyed || stream.writableEnded) return Promise.resolve();
|
|
1771
|
-
return new Promise((resolve) => {
|
|
1772
|
-
stream.end(() => {
|
|
1773
|
-
resolve();
|
|
1774
|
-
});
|
|
1775
|
-
});
|
|
1776
|
-
}
|
|
1777
|
-
};
|
|
1778
|
-
}
|
|
1779
|
-
function createDebugChannel(options) {
|
|
1780
|
-
if (!options.enabled) return {
|
|
1781
|
-
enabled: false,
|
|
1782
|
-
verbosity: options.verbosity ?? "compact",
|
|
1783
|
-
emit() {},
|
|
1784
|
-
close: NOOP_CLOSE
|
|
1785
|
-
};
|
|
1786
|
-
const verbosity = options.verbosity ?? "compact";
|
|
1787
|
-
const sinks = [];
|
|
1788
|
-
let reportPath;
|
|
1789
|
-
if (options.report) {
|
|
1790
|
-
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
1791
|
-
const pid = options.pid ?? process.pid;
|
|
1792
|
-
reportPath = resolveReportPath(options.report, now, pid);
|
|
1793
|
-
sinks.push(createFileSink(reportPath));
|
|
1794
|
-
if (options.report.tee) sinks.push(createTerminalSink());
|
|
1795
|
-
} else sinks.push(createTerminalSink());
|
|
1796
|
-
return {
|
|
1797
|
-
enabled: true,
|
|
1798
|
-
verbosity,
|
|
1799
|
-
reportPath,
|
|
1800
|
-
emit(event, details = {}, eventOptions = {}) {
|
|
1801
|
-
if (!shouldEmitAtVerbosity(verbosity, eventOptions.verbosity ?? "compact")) return;
|
|
1802
|
-
const payload = JSON.stringify({
|
|
1803
|
-
event,
|
|
1804
|
-
...details
|
|
1805
|
-
});
|
|
1806
|
-
for (const sink of sinks) sink.write(payload);
|
|
1807
|
-
},
|
|
1808
|
-
async close() {
|
|
1809
|
-
for (const sink of sinks) await sink.close();
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
//#endregion
|
|
1815
|
-
//#region src/utils/show-singular-or-plural-word.ts
|
|
1816
|
-
function showSingularOrPluralWord(count, word) {
|
|
1817
|
-
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
//#endregion
|
|
1821
|
-
//#region src/cli/total-of.ts
|
|
1822
|
-
const TOTAL_OF_PARTS = [
|
|
1823
|
-
"words",
|
|
1824
|
-
"emoji",
|
|
1825
|
-
"symbols",
|
|
1826
|
-
"punctuation",
|
|
1827
|
-
"whitespace"
|
|
1828
|
-
];
|
|
1829
|
-
const TOTAL_OF_PART_ALIASES = {
|
|
1830
|
-
word: "words",
|
|
1831
|
-
words: "words",
|
|
1832
|
-
emoji: "emoji",
|
|
1833
|
-
emojis: "emoji",
|
|
1834
|
-
symbol: "symbols",
|
|
1835
|
-
symbols: "symbols",
|
|
1836
|
-
punction: "punctuation",
|
|
1837
|
-
punctuation: "punctuation",
|
|
1838
|
-
whitespace: "whitespace"
|
|
1839
|
-
};
|
|
1840
|
-
function createTotalOfCounts() {
|
|
1841
|
-
return {
|
|
1842
|
-
words: 0,
|
|
1843
|
-
emoji: 0,
|
|
1844
|
-
symbols: 0,
|
|
1845
|
-
punctuation: 0,
|
|
1846
|
-
whitespace: 0
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
function collectNonWordCounts(target, nonWords) {
|
|
1850
|
-
if (!nonWords) return;
|
|
1851
|
-
target.emoji += nonWords.counts.emoji;
|
|
1852
|
-
target.symbols += nonWords.counts.symbols;
|
|
1853
|
-
target.punctuation += nonWords.counts.punctuation;
|
|
1854
|
-
target.whitespace += nonWords.counts.whitespace ?? 0;
|
|
1855
|
-
}
|
|
1856
|
-
function collectFromWordCounterResult(result) {
|
|
1857
|
-
const counts = createTotalOfCounts();
|
|
1858
|
-
counts.words += result.counts?.words ?? result.total;
|
|
1859
|
-
if (result.breakdown.mode === "collector") {
|
|
1860
|
-
collectNonWordCounts(counts, result.breakdown.nonWords);
|
|
1861
|
-
return counts;
|
|
1862
|
-
}
|
|
1863
|
-
for (const item of result.breakdown.items) collectNonWordCounts(counts, item.nonWords);
|
|
1864
|
-
return counts;
|
|
1865
|
-
}
|
|
1866
|
-
function collectTotalOfCounts(result) {
|
|
1867
|
-
if (!("section" in result)) return collectFromWordCounterResult(result);
|
|
1868
|
-
const counts = createTotalOfCounts();
|
|
1869
|
-
for (const item of result.items) {
|
|
1870
|
-
const itemCounts = collectFromWordCounterResult(item.result);
|
|
1871
|
-
for (const part of TOTAL_OF_PARTS) counts[part] += itemCounts[part];
|
|
1872
|
-
}
|
|
1873
|
-
return counts;
|
|
1874
|
-
}
|
|
1875
|
-
function parseTotalOfToken(token) {
|
|
1876
|
-
const canonical = TOTAL_OF_PART_ALIASES[token.trim().toLowerCase()];
|
|
1877
|
-
if (canonical) return canonical;
|
|
1878
|
-
throw new Error(`Invalid --total-of part: ${token}. Allowed: ${TOTAL_OF_PARTS.join(", ")}.`);
|
|
1879
|
-
}
|
|
1880
|
-
function parseTotalOfOption(value) {
|
|
1881
|
-
const rawTokens = value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
1882
|
-
if (rawTokens.length === 0) throw new Error(`Invalid --total-of value: "${value}". Use comma-separated parts from: ${TOTAL_OF_PARTS.join(", ")}.`);
|
|
1883
|
-
const parts = [];
|
|
1884
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1885
|
-
for (const token of rawTokens) {
|
|
1886
|
-
const parsed = parseTotalOfToken(token);
|
|
1887
|
-
if (seen.has(parsed)) continue;
|
|
1888
|
-
seen.add(parsed);
|
|
1889
|
-
parts.push(parsed);
|
|
1890
|
-
}
|
|
1891
|
-
return parts;
|
|
1892
|
-
}
|
|
1893
|
-
function requiresNonWordCollection(parts) {
|
|
1894
|
-
if (!parts || parts.length === 0) return false;
|
|
1895
|
-
return parts.some((part) => part !== "words");
|
|
1896
|
-
}
|
|
1897
|
-
function requiresWhitespaceCollection(parts) {
|
|
1898
|
-
if (!parts || parts.length === 0) return false;
|
|
1899
|
-
return parts.includes("whitespace");
|
|
1900
|
-
}
|
|
1901
|
-
function resolveTotalOfOverride(result, parts) {
|
|
1902
|
-
if (!parts || parts.length === 0) return;
|
|
1903
|
-
const counts = collectTotalOfCounts(result);
|
|
1904
|
-
let total = 0;
|
|
1905
|
-
for (const part of parts) total += counts[part];
|
|
1906
|
-
return {
|
|
1907
|
-
parts: [...parts],
|
|
1908
|
-
total
|
|
1909
|
-
};
|
|
1910
|
-
}
|
|
1911
|
-
function formatTotalOfParts(parts) {
|
|
1912
|
-
return parts.join(", ");
|
|
2161
|
+
appendAll(summary.skipped, loaded.skipped);
|
|
2162
|
+
options.debug.emit("batch.aggregate.complete", {
|
|
2163
|
+
files: summary.files.length,
|
|
2164
|
+
skipped: summary.skipped.length,
|
|
2165
|
+
total: summary.aggregate.total
|
|
2166
|
+
});
|
|
2167
|
+
return summary;
|
|
1913
2168
|
}
|
|
1914
2169
|
|
|
1915
2170
|
//#endregion
|
|
1916
|
-
//#region
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
let formatter = (open, close, replace = open) => (input) => {
|
|
1921
|
-
let string = "" + input, index = string.indexOf(close, open.length);
|
|
1922
|
-
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
1923
|
-
};
|
|
1924
|
-
let replaceClose = (string, close, replace, index) => {
|
|
1925
|
-
let result = "", cursor = 0;
|
|
1926
|
-
do {
|
|
1927
|
-
result += string.substring(cursor, index) + replace;
|
|
1928
|
-
cursor = index + close.length;
|
|
1929
|
-
index = string.indexOf(close, cursor);
|
|
1930
|
-
} while (~index);
|
|
1931
|
-
return result + string.substring(cursor);
|
|
1932
|
-
};
|
|
1933
|
-
let createColors = (enabled = isColorSupported) => {
|
|
1934
|
-
let f = enabled ? formatter : () => String;
|
|
1935
|
-
return {
|
|
1936
|
-
isColorSupported: enabled,
|
|
1937
|
-
reset: f("\x1B[0m", "\x1B[0m"),
|
|
1938
|
-
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
1939
|
-
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
1940
|
-
italic: f("\x1B[3m", "\x1B[23m"),
|
|
1941
|
-
underline: f("\x1B[4m", "\x1B[24m"),
|
|
1942
|
-
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
1943
|
-
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
1944
|
-
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
1945
|
-
black: f("\x1B[30m", "\x1B[39m"),
|
|
1946
|
-
red: f("\x1B[31m", "\x1B[39m"),
|
|
1947
|
-
green: f("\x1B[32m", "\x1B[39m"),
|
|
1948
|
-
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
1949
|
-
blue: f("\x1B[34m", "\x1B[39m"),
|
|
1950
|
-
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
1951
|
-
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
1952
|
-
white: f("\x1B[37m", "\x1B[39m"),
|
|
1953
|
-
gray: f("\x1B[90m", "\x1B[39m"),
|
|
1954
|
-
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
1955
|
-
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
1956
|
-
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
1957
|
-
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
1958
|
-
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
1959
|
-
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
1960
|
-
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
1961
|
-
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
1962
|
-
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
1963
|
-
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
1964
|
-
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
1965
|
-
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
1966
|
-
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
1967
|
-
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
1968
|
-
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
1969
|
-
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
1970
|
-
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
1971
|
-
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
1972
|
-
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
1973
|
-
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
1974
|
-
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
1975
|
-
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
1976
|
-
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
1977
|
-
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
1978
|
-
};
|
|
1979
|
-
};
|
|
1980
|
-
module.exports = createColors();
|
|
1981
|
-
module.exports.createColors = createColors;
|
|
1982
|
-
}));
|
|
2171
|
+
//#region src/utils/show-singular-or-plural-word.ts
|
|
2172
|
+
function showSingularOrPluralWord(count, word) {
|
|
2173
|
+
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
2174
|
+
}
|
|
1983
2175
|
|
|
1984
2176
|
//#endregion
|
|
1985
2177
|
//#region src/cli/output/render.ts
|
|
1986
|
-
var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
|
|
1987
2178
|
function getCountUnit(mode) {
|
|
1988
|
-
return mode === "char" ? "character" : "word";
|
|
2179
|
+
return mode === "char" || mode === "char-collector" ? "character" : "word";
|
|
1989
2180
|
}
|
|
1990
2181
|
function getTotalLabels(mode, includeNonWords) {
|
|
1991
|
-
const unit = mode === "char" ? "characters" : "words";
|
|
2182
|
+
const unit = mode === "char" || mode === "char-collector" ? "characters" : "words";
|
|
1992
2183
|
if (includeNonWords) return {
|
|
1993
2184
|
overall: "Total count",
|
|
1994
2185
|
section: "total count"
|
|
@@ -2044,7 +2235,7 @@ function renderStandardResult(result, totalLabel, totalOfOverride) {
|
|
|
2044
2235
|
renderNonWords(result.breakdown.nonWords, false);
|
|
2045
2236
|
return;
|
|
2046
2237
|
}
|
|
2047
|
-
if (result.breakdown.mode === "char") {
|
|
2238
|
+
if (result.breakdown.mode === "char" || result.breakdown.mode === "char-collector") {
|
|
2048
2239
|
renderCountBreakdown(result.breakdown.items.map((item) => ({
|
|
2049
2240
|
locale: item.locale,
|
|
2050
2241
|
count: item.chars,
|
|
@@ -2082,7 +2273,7 @@ function renderStandardSectionedResult(result, labels, totalOfOverride) {
|
|
|
2082
2273
|
renderNonWords(item.result.breakdown.nonWords, false);
|
|
2083
2274
|
continue;
|
|
2084
2275
|
}
|
|
2085
|
-
if (item.result.breakdown.mode === "char") {
|
|
2276
|
+
if (item.result.breakdown.mode === "char" || item.result.breakdown.mode === "char-collector") {
|
|
2086
2277
|
renderCountBreakdown(item.result.breakdown.items.map((chunk) => ({
|
|
2087
2278
|
locale: chunk.locale,
|
|
2088
2279
|
count: chunk.chars,
|
|
@@ -2128,78 +2319,7 @@ function renderPerFileStandard(summary, labels, resolveTotalOfOverride) {
|
|
|
2128
2319
|
}
|
|
2129
2320
|
|
|
2130
2321
|
//#endregion
|
|
2131
|
-
//#region src/
|
|
2132
|
-
const MODE_CHOICES = [
|
|
2133
|
-
"chunk",
|
|
2134
|
-
"segments",
|
|
2135
|
-
"collector",
|
|
2136
|
-
"char"
|
|
2137
|
-
];
|
|
2138
|
-
const FORMAT_CHOICES = [
|
|
2139
|
-
"standard",
|
|
2140
|
-
"raw",
|
|
2141
|
-
"json"
|
|
2142
|
-
];
|
|
2143
|
-
const SECTION_CHOICES = [
|
|
2144
|
-
"all",
|
|
2145
|
-
"split",
|
|
2146
|
-
"frontmatter",
|
|
2147
|
-
"content",
|
|
2148
|
-
"per-key",
|
|
2149
|
-
"split-per-key"
|
|
2150
|
-
];
|
|
2151
|
-
const PATH_MODE_CHOICES = ["auto", "manual"];
|
|
2152
|
-
function getPackageVersion() {
|
|
2153
|
-
const packageCandidates = [new URL("../package.json", import.meta.url), new URL("../../package.json", import.meta.url)];
|
|
2154
|
-
let version = "0.0.0";
|
|
2155
|
-
for (const packageUrl of packageCandidates) try {
|
|
2156
|
-
const raw = readFileSync(packageUrl, "utf8");
|
|
2157
|
-
const data = JSON.parse(raw);
|
|
2158
|
-
if (data.version) {
|
|
2159
|
-
version = data.version;
|
|
2160
|
-
break;
|
|
2161
|
-
}
|
|
2162
|
-
} catch {
|
|
2163
|
-
continue;
|
|
2164
|
-
}
|
|
2165
|
-
return import_picocolors.default.bgBlack(import_picocolors.default.bold(import_picocolors.default.italic(` word-counter ${import_picocolors.default.cyanBright(`ver.${version}`)} `)));
|
|
2166
|
-
}
|
|
2167
|
-
async function readStdin() {
|
|
2168
|
-
if (process.stdin.isTTY) return "";
|
|
2169
|
-
return new Promise((resolve, reject) => {
|
|
2170
|
-
const chunks = [];
|
|
2171
|
-
process.stdin.setEncoding("utf8");
|
|
2172
|
-
process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
|
|
2173
|
-
process.stdin.on("end", () => resolve(chunks.join("")));
|
|
2174
|
-
process.stdin.on("error", (error) => reject(error));
|
|
2175
|
-
process.stdin.resume();
|
|
2176
|
-
});
|
|
2177
|
-
}
|
|
2178
|
-
async function resolveInput(textTokens) {
|
|
2179
|
-
if (textTokens.length > 0) return textTokens.join(" ");
|
|
2180
|
-
return readStdin();
|
|
2181
|
-
}
|
|
2182
|
-
function collectPathValue(value, previous = []) {
|
|
2183
|
-
return [...previous, value];
|
|
2184
|
-
}
|
|
2185
|
-
function resolveBatchScope(argv) {
|
|
2186
|
-
let scope = "merged";
|
|
2187
|
-
for (const token of argv) {
|
|
2188
|
-
if (token === "--merged") {
|
|
2189
|
-
scope = "merged";
|
|
2190
|
-
continue;
|
|
2191
|
-
}
|
|
2192
|
-
if (token === "--per-file") scope = "per-file";
|
|
2193
|
-
}
|
|
2194
|
-
return scope;
|
|
2195
|
-
}
|
|
2196
|
-
function hasPathInput(pathValues) {
|
|
2197
|
-
return Array.isArray(pathValues) && pathValues.length > 0;
|
|
2198
|
-
}
|
|
2199
|
-
function resolveDebugReportPathOption(rawValue) {
|
|
2200
|
-
if (rawValue === void 0 || rawValue === false) return;
|
|
2201
|
-
if (typeof rawValue === "string") return rawValue;
|
|
2202
|
-
}
|
|
2322
|
+
//#region src/cli/output/normalize-base.ts
|
|
2203
2323
|
function normalizeWordCounterResultBase(result) {
|
|
2204
2324
|
result.total = result.counts?.words ?? result.total;
|
|
2205
2325
|
delete result.counts;
|
|
@@ -2207,7 +2327,7 @@ function normalizeWordCounterResultBase(result) {
|
|
|
2207
2327
|
delete result.breakdown.nonWords;
|
|
2208
2328
|
return result;
|
|
2209
2329
|
}
|
|
2210
|
-
if (result.breakdown.mode === "char") {
|
|
2330
|
+
if (result.breakdown.mode === "char" || result.breakdown.mode === "char-collector") {
|
|
2211
2331
|
for (const item of result.breakdown.items) {
|
|
2212
2332
|
const nonWordCount = (item.nonWords?.counts.emoji ?? 0) + (item.nonWords?.counts.symbols ?? 0) + (item.nonWords?.counts.punctuation ?? 0) + (item.nonWords?.counts.whitespace ?? 0);
|
|
2213
2333
|
item.chars = Math.max(0, item.chars - nonWordCount);
|
|
@@ -2236,6 +2356,225 @@ function normalizeBatchSummaryBase(summary) {
|
|
|
2236
2356
|
normalizeResultBase(summary.aggregate);
|
|
2237
2357
|
return summary;
|
|
2238
2358
|
}
|
|
2359
|
+
|
|
2360
|
+
//#endregion
|
|
2361
|
+
//#region src/cli/runtime/options.ts
|
|
2362
|
+
function hasPathInput(pathValues) {
|
|
2363
|
+
return Array.isArray(pathValues) && pathValues.length > 0;
|
|
2364
|
+
}
|
|
2365
|
+
function resolveBatchScope(argv) {
|
|
2366
|
+
let scope = "merged";
|
|
2367
|
+
for (const token of argv) {
|
|
2368
|
+
if (token === "--merged") {
|
|
2369
|
+
scope = "merged";
|
|
2370
|
+
continue;
|
|
2371
|
+
}
|
|
2372
|
+
if (token === "--per-file") scope = "per-file";
|
|
2373
|
+
}
|
|
2374
|
+
return scope;
|
|
2375
|
+
}
|
|
2376
|
+
function resolveDebugReportPathOption(rawValue) {
|
|
2377
|
+
if (rawValue === void 0 || rawValue === false) return;
|
|
2378
|
+
if (typeof rawValue === "string") return rawValue;
|
|
2379
|
+
}
|
|
2380
|
+
function resolveCountRunOptions(options) {
|
|
2381
|
+
const useSection = options.section !== "all";
|
|
2382
|
+
const totalOfParts = options.totalOf;
|
|
2383
|
+
const requestedNonWords = Boolean(options.nonWords || options.includeWhitespace || options.misc);
|
|
2384
|
+
const collectNonWordsForOverride = requiresNonWordCollection(totalOfParts);
|
|
2385
|
+
const collectWhitespaceForOverride = requiresWhitespaceCollection(totalOfParts);
|
|
2386
|
+
const enableNonWords = Boolean(options.nonWords || options.includeWhitespace || options.misc || collectNonWordsForOverride);
|
|
2387
|
+
const enableWhitespace = Boolean(options.includeWhitespace || options.misc || collectWhitespaceForOverride);
|
|
2388
|
+
return {
|
|
2389
|
+
useSection,
|
|
2390
|
+
totalOfParts,
|
|
2391
|
+
requestedNonWords,
|
|
2392
|
+
shouldNormalizeBaseOutput: !requestedNonWords && enableNonWords,
|
|
2393
|
+
wcOptions: {
|
|
2394
|
+
mode: options.mode,
|
|
2395
|
+
latinLanguageHint: options.latinLanguage,
|
|
2396
|
+
latinTagHint: options.latinTag,
|
|
2397
|
+
latinLocaleHint: options.latinLocale,
|
|
2398
|
+
hanLanguageHint: options.hanLanguage,
|
|
2399
|
+
hanTagHint: options.hanTag,
|
|
2400
|
+
nonWords: enableNonWords,
|
|
2401
|
+
includeWhitespace: enableWhitespace
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
function formatInputReadError(error) {
|
|
2406
|
+
return `Failed to read input: ${error instanceof Error ? error.message : String(error)}`;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
//#endregion
|
|
2410
|
+
//#region src/cli/runtime/batch.ts
|
|
2411
|
+
async function executeBatchCount({ argv, options, runtime, resolved, debug, teeEnabled }) {
|
|
2412
|
+
const batchOptions = {
|
|
2413
|
+
scope: resolveBatchScope(argv),
|
|
2414
|
+
pathMode: options.pathMode,
|
|
2415
|
+
recursive: options.recursive,
|
|
2416
|
+
quietSkips: Boolean(options.quietSkips)
|
|
2417
|
+
};
|
|
2418
|
+
const extensionFilter = buildDirectoryExtensionFilter(options.includeExt, options.excludeExt);
|
|
2419
|
+
const debugEnabled = Boolean(options.debug);
|
|
2420
|
+
const mirrorDebugToTerminal = debugEnabled && (!debug.reportPath || teeEnabled);
|
|
2421
|
+
const summary = await runBatchCount({
|
|
2422
|
+
pathInputs: options.path ?? [],
|
|
2423
|
+
batchOptions,
|
|
2424
|
+
extensionFilter,
|
|
2425
|
+
section: options.section,
|
|
2426
|
+
wcOptions: resolved.wcOptions,
|
|
2427
|
+
preserveCollectorSegments: options.format === "json",
|
|
2428
|
+
debug,
|
|
2429
|
+
progressReporter: createBatchProgressReporter({
|
|
2430
|
+
enabled: options.format === "standard" && options.progress,
|
|
2431
|
+
stream: runtime.stderr ?? process.stderr,
|
|
2432
|
+
clearOnFinish: !(mirrorDebugToTerminal || options.keepProgress)
|
|
2433
|
+
})
|
|
2434
|
+
});
|
|
2435
|
+
const showSkipDiagnostics = debugEnabled && !batchOptions.quietSkips;
|
|
2436
|
+
debug.emit("batch.skips.policy", {
|
|
2437
|
+
enabled: showSkipDiagnostics,
|
|
2438
|
+
quietSkips: batchOptions.quietSkips
|
|
2439
|
+
});
|
|
2440
|
+
if (showSkipDiagnostics) {
|
|
2441
|
+
debug.emit("batch.skips.report", { count: summary.skipped.length });
|
|
2442
|
+
if (options.verbose) for (const skip of summary.skipped) debug.emit("batch.skips.item", {
|
|
2443
|
+
path: skip.path,
|
|
2444
|
+
reason: skip.reason
|
|
2445
|
+
}, { verbosity: "verbose" });
|
|
2446
|
+
if (mirrorDebugToTerminal) reportSkipped(summary.skipped);
|
|
2447
|
+
}
|
|
2448
|
+
if (summary.files.length === 0) throw new Error("No readable text-like inputs were found from --path.");
|
|
2449
|
+
let aggregateTotalOfOverride;
|
|
2450
|
+
let totalOfOverridesByResult;
|
|
2451
|
+
if (resolved.totalOfParts && resolved.totalOfParts.length > 0) {
|
|
2452
|
+
totalOfOverridesByResult = /* @__PURE__ */ new WeakMap();
|
|
2453
|
+
const aggregateOverride = resolveTotalOfOverride(summary.aggregate, resolved.totalOfParts);
|
|
2454
|
+
if (aggregateOverride) {
|
|
2455
|
+
totalOfOverridesByResult.set(summary.aggregate, aggregateOverride);
|
|
2456
|
+
aggregateTotalOfOverride = aggregateOverride;
|
|
2457
|
+
}
|
|
2458
|
+
for (const file of summary.files) {
|
|
2459
|
+
const fileOverride = resolveTotalOfOverride(file.result, resolved.totalOfParts);
|
|
2460
|
+
if (!fileOverride) continue;
|
|
2461
|
+
totalOfOverridesByResult.set(file.result, fileOverride);
|
|
2462
|
+
}
|
|
2463
|
+
} else aggregateTotalOfOverride = resolveTotalOfOverride(summary.aggregate, resolved.totalOfParts);
|
|
2464
|
+
if (resolved.shouldNormalizeBaseOutput) normalizeBatchSummaryBase(summary);
|
|
2465
|
+
if (!aggregateTotalOfOverride && totalOfOverridesByResult) aggregateTotalOfOverride = totalOfOverridesByResult.get(summary.aggregate);
|
|
2466
|
+
if (options.format === "raw") {
|
|
2467
|
+
console.log(aggregateTotalOfOverride?.total ?? summary.aggregate.total);
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
if (options.format === "json") {
|
|
2471
|
+
const spacing = options.pretty ? 2 : 0;
|
|
2472
|
+
if (batchOptions.scope === "per-file") {
|
|
2473
|
+
const skipped = showSkipDiagnostics ? summary.skipped : void 0;
|
|
2474
|
+
const meta = resolved.totalOfParts && resolved.totalOfParts.length > 0 ? {
|
|
2475
|
+
totalOf: resolved.totalOfParts,
|
|
2476
|
+
aggregateTotalOfOverride: aggregateTotalOfOverride?.total ?? summary.aggregate.total
|
|
2477
|
+
} : void 0;
|
|
2478
|
+
const payload = {
|
|
2479
|
+
scope: "per-file",
|
|
2480
|
+
files: summary.files.map((file) => ({
|
|
2481
|
+
path: file.path,
|
|
2482
|
+
result: file.result
|
|
2483
|
+
})),
|
|
2484
|
+
...skipped ? { skipped } : {},
|
|
2485
|
+
aggregate: summary.aggregate,
|
|
2486
|
+
...meta ? { meta } : {}
|
|
2487
|
+
};
|
|
2488
|
+
console.log(JSON.stringify(payload, null, spacing));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
if (!aggregateTotalOfOverride) {
|
|
2492
|
+
console.log(JSON.stringify(summary.aggregate, null, spacing));
|
|
2493
|
+
return;
|
|
2494
|
+
}
|
|
2495
|
+
console.log(JSON.stringify({
|
|
2496
|
+
...summary.aggregate,
|
|
2497
|
+
meta: {
|
|
2498
|
+
totalOf: aggregateTotalOfOverride.parts,
|
|
2499
|
+
totalOfOverride: aggregateTotalOfOverride.total
|
|
2500
|
+
}
|
|
2501
|
+
}, null, spacing));
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
const labels = getTotalLabels(options.mode, resolved.requestedNonWords);
|
|
2505
|
+
const totalOfResolver = resolved.totalOfParts && resolved.totalOfParts.length > 0 ? (result) => totalOfOverridesByResult?.get(result) ?? resolveTotalOfOverride(result, resolved.totalOfParts) : void 0;
|
|
2506
|
+
if (batchOptions.scope === "per-file") {
|
|
2507
|
+
renderPerFileStandard(summary, labels, totalOfResolver);
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
if (isSectionedResult(summary.aggregate)) {
|
|
2511
|
+
renderStandardSectionedResult(summary.aggregate, labels, aggregateTotalOfOverride);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
renderStandardResult(summary.aggregate, labels.overall, aggregateTotalOfOverride);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
//#endregion
|
|
2518
|
+
//#region src/cli/runtime/input.ts
|
|
2519
|
+
async function readStdin() {
|
|
2520
|
+
if (process.stdin.isTTY) return "";
|
|
2521
|
+
return new Promise((resolve, reject) => {
|
|
2522
|
+
const chunks = [];
|
|
2523
|
+
process.stdin.setEncoding("utf8");
|
|
2524
|
+
process.stdin.on("data", (chunk) => chunks.push(String(chunk)));
|
|
2525
|
+
process.stdin.on("end", () => resolve(chunks.join("")));
|
|
2526
|
+
process.stdin.on("error", (error) => reject(error));
|
|
2527
|
+
process.stdin.resume();
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
async function resolveInput(textTokens) {
|
|
2531
|
+
if (textTokens.length > 0) return textTokens.join(" ");
|
|
2532
|
+
return readStdin();
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
//#endregion
|
|
2536
|
+
//#region src/cli/runtime/single.ts
|
|
2537
|
+
async function executeSingleCount({ textTokens, options, resolved }) {
|
|
2538
|
+
let input;
|
|
2539
|
+
try {
|
|
2540
|
+
input = await resolveInput(textTokens);
|
|
2541
|
+
} catch (error) {
|
|
2542
|
+
throw new Error(formatInputReadError(error));
|
|
2543
|
+
}
|
|
2544
|
+
const trimmed = input.trim();
|
|
2545
|
+
if (!trimmed) throw new Error("No input provided. Pass text, pipe stdin, or use --path.");
|
|
2546
|
+
const result = resolved.useSection ? countSections(trimmed, options.section, resolved.wcOptions) : wc_default(trimmed, resolved.wcOptions);
|
|
2547
|
+
const totalOfOverride = resolveTotalOfOverride(result, resolved.totalOfParts);
|
|
2548
|
+
const displayResult = resolved.shouldNormalizeBaseOutput ? normalizeResultBase(result) : result;
|
|
2549
|
+
if (options.format === "raw") {
|
|
2550
|
+
console.log(totalOfOverride?.total ?? displayResult.total);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (options.format === "json") {
|
|
2554
|
+
const spacing = options.pretty ? 2 : 0;
|
|
2555
|
+
if (!totalOfOverride) {
|
|
2556
|
+
console.log(JSON.stringify(displayResult, null, spacing));
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
console.log(JSON.stringify({
|
|
2560
|
+
...displayResult,
|
|
2561
|
+
meta: {
|
|
2562
|
+
totalOf: totalOfOverride.parts,
|
|
2563
|
+
totalOfOverride: totalOfOverride.total
|
|
2564
|
+
}
|
|
2565
|
+
}, null, spacing));
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
const labels = getTotalLabels(options.mode, resolved.requestedNonWords);
|
|
2569
|
+
if (isSectionedResult(displayResult)) {
|
|
2570
|
+
renderStandardSectionedResult(displayResult, labels, totalOfOverride);
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
renderStandardResult(displayResult, labels.overall, totalOfOverride);
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
//#endregion
|
|
2577
|
+
//#region src/command.ts
|
|
2239
2578
|
async function runCli(argv = process.argv, runtime = {}) {
|
|
2240
2579
|
const program = new Command();
|
|
2241
2580
|
const parseMode = (value) => {
|
|
@@ -2243,7 +2582,8 @@ async function runCli(argv = process.argv, runtime = {}) {
|
|
|
2243
2582
|
if (!normalized) throw new Error(`Invalid mode: ${value}`);
|
|
2244
2583
|
return normalized;
|
|
2245
2584
|
};
|
|
2246
|
-
program.name("word-counter").description("Locale-aware word counting powered by Intl.Segmenter.").version(
|
|
2585
|
+
program.name("word-counter").description("Locale-aware word counting powered by Intl.Segmenter.").version(getFormattedVersionLabel(), "-v, --version", "output the version number");
|
|
2586
|
+
configureProgramOptions(program, parseMode);
|
|
2247
2587
|
program.action(async (textTokens, options) => {
|
|
2248
2588
|
const debugEnabled = Boolean(options.debug);
|
|
2249
2589
|
const debugReportPath = resolveDebugReportPathOption(options.debugReport);
|
|
@@ -2277,173 +2617,34 @@ async function runCli(argv = process.argv, runtime = {}) {
|
|
|
2277
2617
|
return;
|
|
2278
2618
|
}
|
|
2279
2619
|
try {
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
latinLanguageHint: options.latinLanguage,
|
|
2291
|
-
latinTagHint: options.latinTag,
|
|
2292
|
-
latinLocaleHint: options.latinLocale,
|
|
2293
|
-
hanLanguageHint: options.hanLanguage,
|
|
2294
|
-
hanTagHint: options.hanTag,
|
|
2295
|
-
nonWords: enableNonWords,
|
|
2296
|
-
includeWhitespace: enableWhitespace
|
|
2297
|
-
};
|
|
2298
|
-
if (!hasPathInput(options.path)) {
|
|
2299
|
-
let input;
|
|
2300
|
-
try {
|
|
2301
|
-
input = await resolveInput(textTokens);
|
|
2302
|
-
} catch (error) {
|
|
2303
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2304
|
-
program.error(`Failed to read input: ${message}`);
|
|
2305
|
-
return;
|
|
2306
|
-
}
|
|
2307
|
-
const trimmed = input.trim();
|
|
2308
|
-
if (!trimmed) {
|
|
2309
|
-
program.error(import_picocolors.default.red("No input provided. Pass text, pipe stdin, or use --path."));
|
|
2310
|
-
return;
|
|
2311
|
-
}
|
|
2312
|
-
const result = useSection ? countSections(trimmed, options.section, wcOptions) : wc_default(trimmed, wcOptions);
|
|
2313
|
-
const totalOfOverride = resolveTotalOfOverride(result, totalOfParts);
|
|
2314
|
-
const displayResult = shouldNormalizeBaseOutput ? normalizeResultBase(result) : result;
|
|
2315
|
-
if (options.format === "raw") {
|
|
2316
|
-
console.log(totalOfOverride?.total ?? displayResult.total);
|
|
2317
|
-
return;
|
|
2318
|
-
}
|
|
2319
|
-
if (options.format === "json") {
|
|
2320
|
-
const spacing = options.pretty ? 2 : 0;
|
|
2321
|
-
if (!totalOfOverride) {
|
|
2322
|
-
console.log(JSON.stringify(displayResult, null, spacing));
|
|
2323
|
-
return;
|
|
2324
|
-
}
|
|
2325
|
-
console.log(JSON.stringify({
|
|
2326
|
-
...displayResult,
|
|
2327
|
-
meta: {
|
|
2328
|
-
totalOf: totalOfOverride.parts,
|
|
2329
|
-
totalOfOverride: totalOfOverride.total
|
|
2330
|
-
}
|
|
2331
|
-
}, null, spacing));
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
const labels = getTotalLabels(options.mode, requestedNonWords);
|
|
2335
|
-
if (isSectionedResult(displayResult)) {
|
|
2336
|
-
renderStandardSectionedResult(displayResult, labels, totalOfOverride);
|
|
2337
|
-
return;
|
|
2338
|
-
}
|
|
2339
|
-
renderStandardResult(displayResult, labels.overall, totalOfOverride);
|
|
2620
|
+
const resolved = resolveCountRunOptions(options);
|
|
2621
|
+
if (hasPathInput(options.path)) {
|
|
2622
|
+
await executeBatchCount({
|
|
2623
|
+
argv,
|
|
2624
|
+
options,
|
|
2625
|
+
runtime,
|
|
2626
|
+
resolved,
|
|
2627
|
+
debug,
|
|
2628
|
+
teeEnabled
|
|
2629
|
+
});
|
|
2340
2630
|
return;
|
|
2341
2631
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
quietSkips: Boolean(options.quietSkips)
|
|
2347
|
-
};
|
|
2348
|
-
const extensionFilter = buildDirectoryExtensionFilter(options.includeExt, options.excludeExt);
|
|
2349
|
-
const mirrorDebugToTerminal = debugEnabled && (!debug.reportPath || teeEnabled);
|
|
2350
|
-
const summary = await runBatchCount({
|
|
2351
|
-
pathInputs: options.path,
|
|
2352
|
-
batchOptions,
|
|
2353
|
-
extensionFilter,
|
|
2354
|
-
section: options.section,
|
|
2355
|
-
wcOptions,
|
|
2356
|
-
preserveCollectorSegments: options.format === "json",
|
|
2357
|
-
debug,
|
|
2358
|
-
progressReporter: createBatchProgressReporter({
|
|
2359
|
-
enabled: options.format === "standard" && options.progress,
|
|
2360
|
-
stream: runtime.stderr ?? process.stderr,
|
|
2361
|
-
clearOnFinish: !(mirrorDebugToTerminal || options.keepProgress)
|
|
2362
|
-
})
|
|
2632
|
+
await executeSingleCount({
|
|
2633
|
+
textTokens,
|
|
2634
|
+
options,
|
|
2635
|
+
resolved
|
|
2363
2636
|
});
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
});
|
|
2369
|
-
if (showSkipDiagnostics) {
|
|
2370
|
-
debug.emit("batch.skips.report", { count: summary.skipped.length });
|
|
2371
|
-
if (options.verbose) for (const skip of summary.skipped) debug.emit("batch.skips.item", {
|
|
2372
|
-
path: skip.path,
|
|
2373
|
-
reason: skip.reason
|
|
2374
|
-
}, { verbosity: "verbose" });
|
|
2375
|
-
if (mirrorDebugToTerminal) reportSkipped(summary.skipped);
|
|
2376
|
-
}
|
|
2377
|
-
if (summary.files.length === 0) {
|
|
2378
|
-
program.error(import_picocolors.default.red("No readable text-like inputs were found from --path."));
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
let aggregateTotalOfOverride;
|
|
2382
|
-
let totalOfOverridesByResult;
|
|
2383
|
-
if (totalOfParts && totalOfParts.length > 0) {
|
|
2384
|
-
totalOfOverridesByResult = /* @__PURE__ */ new WeakMap();
|
|
2385
|
-
const aggregateOverride = resolveTotalOfOverride(summary.aggregate, totalOfParts);
|
|
2386
|
-
if (aggregateOverride) {
|
|
2387
|
-
totalOfOverridesByResult.set(summary.aggregate, aggregateOverride);
|
|
2388
|
-
aggregateTotalOfOverride = aggregateOverride;
|
|
2389
|
-
}
|
|
2390
|
-
for (const file of summary.files) {
|
|
2391
|
-
const fileOverride = resolveTotalOfOverride(file.result, totalOfParts);
|
|
2392
|
-
if (!fileOverride) continue;
|
|
2393
|
-
totalOfOverridesByResult.set(file.result, fileOverride);
|
|
2394
|
-
}
|
|
2395
|
-
} else aggregateTotalOfOverride = resolveTotalOfOverride(summary.aggregate, totalOfParts);
|
|
2396
|
-
if (shouldNormalizeBaseOutput) normalizeBatchSummaryBase(summary);
|
|
2397
|
-
if (!aggregateTotalOfOverride && totalOfOverridesByResult) aggregateTotalOfOverride = totalOfOverridesByResult.get(summary.aggregate);
|
|
2398
|
-
if (options.format === "raw") {
|
|
2399
|
-
console.log(aggregateTotalOfOverride?.total ?? summary.aggregate.total);
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
if (options.format === "json") {
|
|
2403
|
-
const spacing = options.pretty ? 2 : 0;
|
|
2404
|
-
if (batchOptions.scope === "per-file") {
|
|
2405
|
-
const skipped = showSkipDiagnostics ? summary.skipped : void 0;
|
|
2406
|
-
const meta = totalOfParts && totalOfParts.length > 0 ? {
|
|
2407
|
-
totalOf: totalOfParts,
|
|
2408
|
-
aggregateTotalOfOverride: aggregateTotalOfOverride?.total ?? summary.aggregate.total
|
|
2409
|
-
} : void 0;
|
|
2410
|
-
const payload = {
|
|
2411
|
-
scope: "per-file",
|
|
2412
|
-
files: summary.files.map((file) => ({
|
|
2413
|
-
path: file.path,
|
|
2414
|
-
result: file.result
|
|
2415
|
-
})),
|
|
2416
|
-
...skipped ? { skipped } : {},
|
|
2417
|
-
aggregate: summary.aggregate,
|
|
2418
|
-
...meta ? { meta } : {}
|
|
2419
|
-
};
|
|
2420
|
-
console.log(JSON.stringify(payload, null, spacing));
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
if (!aggregateTotalOfOverride) {
|
|
2424
|
-
console.log(JSON.stringify(summary.aggregate, null, spacing));
|
|
2425
|
-
return;
|
|
2426
|
-
}
|
|
2427
|
-
console.log(JSON.stringify({
|
|
2428
|
-
...summary.aggregate,
|
|
2429
|
-
meta: {
|
|
2430
|
-
totalOf: aggregateTotalOfOverride.parts,
|
|
2431
|
-
totalOfOverride: aggregateTotalOfOverride.total
|
|
2432
|
-
}
|
|
2433
|
-
}, null, spacing));
|
|
2434
|
-
return;
|
|
2435
|
-
}
|
|
2436
|
-
const labels = getTotalLabels(options.mode, requestedNonWords);
|
|
2437
|
-
const totalOfResolver = totalOfParts && totalOfParts.length > 0 ? (result) => totalOfOverridesByResult?.get(result) ?? resolveTotalOfOverride(result, totalOfParts) : void 0;
|
|
2438
|
-
if (batchOptions.scope === "per-file") {
|
|
2439
|
-
renderPerFileStandard(summary, labels, totalOfResolver);
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2639
|
+
if (message === "No input provided. Pass text, pipe stdin, or use --path.") {
|
|
2640
|
+
program.error(import_picocolors.default.red(message));
|
|
2440
2641
|
return;
|
|
2441
2642
|
}
|
|
2442
|
-
if (
|
|
2443
|
-
|
|
2643
|
+
if (message === "No readable text-like inputs were found from --path.") {
|
|
2644
|
+
program.error(import_picocolors.default.red(message));
|
|
2444
2645
|
return;
|
|
2445
2646
|
}
|
|
2446
|
-
|
|
2647
|
+
program.error(message);
|
|
2447
2648
|
} finally {
|
|
2448
2649
|
await debug.close();
|
|
2449
2650
|
}
|