token-goat 2.9.12 → 2.9.14
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 +20 -1
- package/dist/token-goat-chunk-2ESBO4IN.mjs +209 -0
- package/dist/token-goat-chunk-3BTK54F3.mjs +1733 -0
- package/dist/{token-goat-chunk-6NIGPPN6.mjs → token-goat-chunk-3XQPEJMV.mjs} +1 -1
- package/dist/token-goat-chunk-4SDX3QP3.mjs +122 -0
- package/dist/token-goat-chunk-7OGKZ7AP.mjs +142 -0
- package/dist/token-goat-chunk-AH6QILZM.mjs +26 -0
- package/dist/token-goat-chunk-AMYCQJX4.mjs +1548 -0
- package/dist/{token-goat-chunk-OTW7LC4Y.mjs → token-goat-chunk-ASVVF4JV.mjs} +13 -10
- package/dist/{token-goat-chunk-GCXX67HM.mjs → token-goat-chunk-DAMXYVIW.mjs} +12477 -12156
- package/dist/token-goat-chunk-DBNY4RLN.mjs +308 -0
- package/dist/token-goat-chunk-EG3663UT.mjs +226 -0
- package/dist/token-goat-chunk-EZNVAIR3.mjs +21 -0
- package/dist/token-goat-chunk-GMOUBOX4.mjs +386 -0
- package/dist/{token-goat-chunk-6F5TLJC7.mjs → token-goat-chunk-HF6H7RNK.mjs} +1 -1
- package/dist/{token-goat-chunk-GM6QZWCF.mjs → token-goat-chunk-IT6O3PNN.mjs} +2660 -6877
- package/dist/token-goat-chunk-NEI4NC54.mjs +424 -0
- package/dist/token-goat-chunk-O5WAMISC.mjs +167 -0
- package/dist/{token-goat-chunk-LHLQFGWQ.mjs → token-goat-chunk-OMNQUUIT.mjs} +9 -27
- package/dist/{token-goat-chunk-SF2CKCFQ.mjs → token-goat-chunk-PGGDW7DZ.mjs} +30 -13
- package/dist/token-goat-chunk-POBYR64E.mjs +1632 -0
- package/dist/token-goat-chunk-PRJVGIC5.mjs +58 -0
- package/dist/{token-goat-chunk-4CK445AW.mjs → token-goat-chunk-RUDOKYPJ.mjs} +10074 -9765
- package/dist/token-goat-chunk-SAQ5PG4L.mjs +2634 -0
- package/dist/token-goat-chunk-T2IWWTHB.mjs +3155 -0
- package/dist/{token-goat-chunk-TOGYS5A7.mjs → token-goat-chunk-VZYD4OZB.mjs} +1469 -5752
- package/dist/token-goat-chunk-XEH6KBWW.mjs +23 -0
- package/dist/token-goat-chunk-XQF5J25J.mjs +33 -0
- package/dist/token-goat-chunk-XTQAOTSO.mjs +89 -0
- package/dist/{token-goat-chunk-THLHC6QJ.mjs → token-goat-chunk-XVZ4MNQC.mjs} +614 -603
- package/dist/token-goat-chunk-Y4AFKTHK.mjs +22 -0
- package/dist/token-goat-chunk-YHGTGG6K.mjs +396 -0
- package/dist/{token-goat-chunk-SBYRP3X4.mjs → token-goat-chunk-YTQHZJXW.mjs} +61 -27
- package/dist/token-goat-chunk-Z6UXPYJA.mjs +62 -0
- package/dist/token-goat-chunk-ZD4EM4LR.mjs +30 -0
- package/dist/token-goat-chunk-ZFM4PWXL.mjs +585 -0
- package/dist/{token-goat-chunk-A6QTLAWO.mjs → token-goat-chunk-ZYNNQ36L.mjs} +128 -328
- package/dist/token-goat-hook.mjs +12 -6
- package/dist/token-goat.core.mjs +22 -7
- package/docs/cli.md +12 -8
- package/docs/security.md +1 -1
- package/package.json +4 -2
- package/dist/token-goat-chunk-HKFOH6JH.mjs +0 -28
- package/dist/token-goat-chunk-LOCOX2ML.mjs +0 -3564
- package/dist/token-goat-chunk-VRWX6QYW.mjs +0 -24
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_OCR_LANG,
|
|
5
|
+
isSupportedOcrLang,
|
|
6
|
+
loadConfig,
|
|
7
|
+
resolveOcrLangs
|
|
8
|
+
} from "./token-goat-chunk-SAQ5PG4L.mjs";
|
|
9
|
+
import {
|
|
10
|
+
ensureDirSync,
|
|
11
|
+
fenceUntrustedOcrText,
|
|
12
|
+
tokenGoatHome
|
|
13
|
+
} from "./token-goat-chunk-AMYCQJX4.mjs";
|
|
14
|
+
import {
|
|
15
|
+
init_define_import_meta_env
|
|
16
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
17
|
+
|
|
18
|
+
// src/image_ocr.ts
|
|
19
|
+
init_define_import_meta_env();
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
|
|
26
|
+
// src/ocr_hashes.ts
|
|
27
|
+
init_define_import_meta_env();
|
|
28
|
+
function ocrLangPath(lang) {
|
|
29
|
+
return `https://cdn.jsdelivr.net/npm/@tesseract.js-data/${lang}@1.0.0/4.0.0_best_int`;
|
|
30
|
+
}
|
|
31
|
+
var OCR_LANG_HASHES = {
|
|
32
|
+
eng: "5dc5d8d640a212c9d6184921ba103b186f50e0fed9ee716c53e6b312b400d747",
|
|
33
|
+
fra: "bf83833fa957ff0076f6aa93f69e3bdf7b014dea829ea0c0d6be6b48a3ceef6d",
|
|
34
|
+
spa: "0062377729b81cc268b1822f09eb1c08c09f3f7f1c6b422540b51555a7eeea70",
|
|
35
|
+
deu: "a1b72cc25753eac167edfef5af4448a8dc34973503a2265c34c84520f896eb03",
|
|
36
|
+
ita: "36ec897f5f1f489b257801881286167a79b99a16040c6fbc7e3e30f03821b10b",
|
|
37
|
+
por: "42fab1f017aedab69b92bdecc01bbb11166cd3b177575612ee860f8e2825ece0",
|
|
38
|
+
nld: "363c360db9838838ff7ed3d8b885b33acc0d61d37165708303cf8a81e50164f3",
|
|
39
|
+
pol: "02b89cad819f1374631b4a3c92bdac79c214150f97a3686df78de6c8c30782db",
|
|
40
|
+
rus: "eb9be824435f6bb0f993925acb85fd842c8418d6db7613c818e749e619a1ad6d",
|
|
41
|
+
tur: "f0127d0f3745f9c65e2ae7ec6b23198fbe5aa186a61b35661426f5e1ef9dedd7",
|
|
42
|
+
swe: "a4c33cbd23d988c84b5f9d5d5b2417fc16da061e3dc7b16acb904a4566088b14",
|
|
43
|
+
ara: "e7d6494e2ef249ee97ad151eb01e0e6ae3aaf429256442ad6af534862a2a8c0f",
|
|
44
|
+
chi_sim: "9784f7c917c546424b690fcde708ce1f604a4393d08bb51ddab146d7d7c794e6",
|
|
45
|
+
chi_tra: "6abfb87cce5db0d09624f16eedd8a0b24173718856121f721b6d1214193d4dab",
|
|
46
|
+
jpn: "1a0175291ea145d4a66be681d1084496f10af938aacab247c5d40b31359a604e",
|
|
47
|
+
kor: "ec1749377d49ac38fb3d3cd05dd5e2a53359d329f359762bc638a81132109992"
|
|
48
|
+
};
|
|
49
|
+
var SUPPORTED_OCR_LANGS = Object.fromEntries(
|
|
50
|
+
Object.entries(OCR_LANG_HASHES).map(([lang, sha256]) => [lang, { langPath: ocrLangPath(lang), sha256 }])
|
|
51
|
+
);
|
|
52
|
+
function getOcrLangSpec(lang) {
|
|
53
|
+
const tokens = lang.split("+");
|
|
54
|
+
const primary = tokens.find((t) => t !== DEFAULT_OCR_LANG && isSupportedOcrLang(t)) ?? tokens[0] ?? DEFAULT_OCR_LANG;
|
|
55
|
+
const active = isSupportedOcrLang(primary) ? primary : DEFAULT_OCR_LANG;
|
|
56
|
+
const sha256 = OCR_LANG_HASHES[active];
|
|
57
|
+
return {
|
|
58
|
+
langPath: ocrLangPath(active),
|
|
59
|
+
sha256
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/image_ocr.ts
|
|
64
|
+
var _ocrTimeoutMs = 12e3;
|
|
65
|
+
function setOcrTimeoutForTesting(ms) {
|
|
66
|
+
_ocrTimeoutMs = ms ?? 12e3;
|
|
67
|
+
}
|
|
68
|
+
function getActiveOcrLangs(lang) {
|
|
69
|
+
if (typeof lang === "string" && lang.trim()) {
|
|
70
|
+
return resolveOcrLangs(lang);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const configured = loadConfig().image_shrink?.ocr_lang;
|
|
74
|
+
if (typeof configured === "string" && configured.trim()) {
|
|
75
|
+
return resolveOcrLangs(configured);
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return [DEFAULT_OCR_LANG];
|
|
80
|
+
}
|
|
81
|
+
function resolveOcrLang(lang) {
|
|
82
|
+
return getActiveOcrLangs(lang).join("+");
|
|
83
|
+
}
|
|
84
|
+
var OCR_LANG_FILE = `${DEFAULT_OCR_LANG}.traineddata`;
|
|
85
|
+
var OCR_LANG_PATH = getOcrLangSpec("eng").langPath;
|
|
86
|
+
var OCR_LANG_SHA256 = getOcrLangSpec("eng").sha256;
|
|
87
|
+
function verifyOcrLangCache(lang) {
|
|
88
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
89
|
+
let anyAbsent = false;
|
|
90
|
+
for (const l of activeLangs) {
|
|
91
|
+
const spec = getOcrLangSpec(l);
|
|
92
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(file)) {
|
|
95
|
+
anyAbsent = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const bytes = fs.readFileSync(file);
|
|
99
|
+
if (createHash("sha256").update(bytes).digest("hex") !== spec.sha256) {
|
|
100
|
+
return "mismatch";
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return "unreadable";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return anyAbsent ? "absent" : "ok";
|
|
107
|
+
}
|
|
108
|
+
function quarantineOcrLangCache(lang) {
|
|
109
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
110
|
+
for (const l of activeLangs) {
|
|
111
|
+
const spec = getOcrLangSpec(l);
|
|
112
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(file)) {
|
|
115
|
+
const bytes = fs.readFileSync(file);
|
|
116
|
+
if (createHash("sha256").update(bytes).digest("hex") !== spec.sha256) {
|
|
117
|
+
fs.rmSync(file, { force: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
var _ocrIntegrityFailed = false;
|
|
125
|
+
function ocrIntegrityFailed() {
|
|
126
|
+
return _ocrIntegrityFailed;
|
|
127
|
+
}
|
|
128
|
+
function ocrBlockedOffline(lang) {
|
|
129
|
+
if (!loadConfig().network.offline) return false;
|
|
130
|
+
return verifyOcrLangCache(lang) === "absent";
|
|
131
|
+
}
|
|
132
|
+
function ocrCacheDir() {
|
|
133
|
+
return path.join(tokenGoatHome(), "ocr-cache");
|
|
134
|
+
}
|
|
135
|
+
function ensureOcrCacheDir() {
|
|
136
|
+
try {
|
|
137
|
+
ensureDirSync(ocrCacheDir());
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
var _require = createRequire(import.meta.url);
|
|
142
|
+
var _tesseractEntryPath;
|
|
143
|
+
function resolveTesseractEntry() {
|
|
144
|
+
if (_tesseractEntryPath !== void 0) return _tesseractEntryPath;
|
|
145
|
+
try {
|
|
146
|
+
_tesseractEntryPath = _require.resolve("tesseract.js");
|
|
147
|
+
} catch {
|
|
148
|
+
_tesseractEntryPath = null;
|
|
149
|
+
}
|
|
150
|
+
return _tesseractEntryPath;
|
|
151
|
+
}
|
|
152
|
+
function isOcrEngineAvailable() {
|
|
153
|
+
return !_ocrUnavailableThisProcess && resolveTesseractEntry() !== null;
|
|
154
|
+
}
|
|
155
|
+
function setTesseractEntryForTesting(entryPath) {
|
|
156
|
+
_tesseractEntryPath = entryPath;
|
|
157
|
+
}
|
|
158
|
+
var _ocrUnavailableThisProcess = false;
|
|
159
|
+
function resetOcrStateForTesting() {
|
|
160
|
+
_ocrUnavailableThisProcess = false;
|
|
161
|
+
_ocrIntegrityFailed = false;
|
|
162
|
+
_tesseractEntryPath = void 0;
|
|
163
|
+
_ocrTimeoutMs = 12e3;
|
|
164
|
+
}
|
|
165
|
+
function buildChildScript(entryPath, cacheDir, lang) {
|
|
166
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
167
|
+
const activeLangString = activeLangs.join("+");
|
|
168
|
+
const spec = getOcrLangSpec(lang ?? activeLangString);
|
|
169
|
+
return [
|
|
170
|
+
`const { createWorker } = require(${JSON.stringify(entryPath)});`,
|
|
171
|
+
"const chunks = [];",
|
|
172
|
+
"process.stdin.on('data', (c) => chunks.push(c));",
|
|
173
|
+
"process.stdin.on('end', async () => {",
|
|
174
|
+
" try {",
|
|
175
|
+
" const buf = Buffer.concat(chunks);",
|
|
176
|
+
` const worker = await createWorker(${JSON.stringify(activeLangString)}, 1, { cachePath: ${JSON.stringify(cacheDir)}, langPath: ${JSON.stringify(spec.langPath)}, errorHandler: () => {} });`,
|
|
177
|
+
" const { data } = await worker.recognize(buf);",
|
|
178
|
+
" process.stdout.write(JSON.stringify({ text: data.text || '', confidence: data.confidence || 0 }));",
|
|
179
|
+
" await worker.terminate();",
|
|
180
|
+
" process.exit(0);",
|
|
181
|
+
" } catch (e) {",
|
|
182
|
+
" process.exit(1);",
|
|
183
|
+
" }",
|
|
184
|
+
"});"
|
|
185
|
+
].join("\n");
|
|
186
|
+
}
|
|
187
|
+
async function ocrImage(input, lang) {
|
|
188
|
+
if (_ocrUnavailableThisProcess) return null;
|
|
189
|
+
const activeLangs = getActiveOcrLangs(lang);
|
|
190
|
+
const entryPath = resolveTesseractEntry();
|
|
191
|
+
if (entryPath === null) return null;
|
|
192
|
+
if (ocrBlockedOffline(lang)) return null;
|
|
193
|
+
if (_ocrIntegrityFailed) return null;
|
|
194
|
+
if (verifyOcrLangCache(lang) === "mismatch") {
|
|
195
|
+
_ocrIntegrityFailed = true;
|
|
196
|
+
quarantineOcrLangCache(lang);
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
for (const l of activeLangs) {
|
|
200
|
+
if (l !== DEFAULT_OCR_LANG) {
|
|
201
|
+
const file = path.join(ocrCacheDir(), `${l}.traineddata`);
|
|
202
|
+
if (!fs.existsSync(file)) {
|
|
203
|
+
if (loadConfig().network.offline) return null;
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetch(getOcrLangSpec(l).langPath + `/${l}.traineddata.gz`);
|
|
206
|
+
if (!res.ok) return null;
|
|
207
|
+
const gz = Buffer.from(await res.arrayBuffer());
|
|
208
|
+
const unzipped = (await import("node:zlib")).gunzipSync(gz);
|
|
209
|
+
if (createHash("sha256").update(unzipped).digest("hex") !== getOcrLangSpec(l).sha256) {
|
|
210
|
+
_ocrIntegrityFailed = true;
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
ensureOcrCacheDir();
|
|
214
|
+
fs.writeFileSync(file, unzipped);
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
let settled = false;
|
|
223
|
+
let child;
|
|
224
|
+
try {
|
|
225
|
+
ensureOcrCacheDir();
|
|
226
|
+
child = spawn(process.execPath, ["-e", buildChildScript(entryPath, ocrCacheDir(), lang)], {
|
|
227
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
228
|
+
});
|
|
229
|
+
} catch {
|
|
230
|
+
_ocrUnavailableThisProcess = true;
|
|
231
|
+
resolve(null);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const chunks = [];
|
|
235
|
+
const finish = (result, subprocessBroken) => {
|
|
236
|
+
if (settled) return;
|
|
237
|
+
settled = true;
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
if (subprocessBroken) _ocrUnavailableThisProcess = true;
|
|
240
|
+
try {
|
|
241
|
+
child.kill();
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
resolve(result);
|
|
245
|
+
};
|
|
246
|
+
const timer = setTimeout(() => finish(null, true), _ocrTimeoutMs);
|
|
247
|
+
child.stdout?.on("data", (c) => chunks.push(c));
|
|
248
|
+
child.on("error", () => finish(null, true));
|
|
249
|
+
child.on("close", (code) => {
|
|
250
|
+
if (code !== 0) {
|
|
251
|
+
finish(null, false);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (verifyOcrLangCache(lang) === "mismatch") {
|
|
255
|
+
_ocrIntegrityFailed = true;
|
|
256
|
+
quarantineOcrLangCache(lang);
|
|
257
|
+
finish(null, false);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const raw = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
262
|
+
const parsed = raw;
|
|
263
|
+
const text = typeof parsed.text === "string" ? parsed.text.trim() : "";
|
|
264
|
+
const confidence = typeof parsed.confidence === "number" ? parsed.confidence : 0;
|
|
265
|
+
finish({ text, confidence }, false);
|
|
266
|
+
} catch {
|
|
267
|
+
finish(null, false);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
try {
|
|
271
|
+
child.stdin?.end(input);
|
|
272
|
+
} catch {
|
|
273
|
+
finish(null, false);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
function isTextHeavy(result, minConfidence, minChars = 40) {
|
|
278
|
+
return result.confidence >= minConfidence && result.text.length >= minChars;
|
|
279
|
+
}
|
|
280
|
+
function formatOcrSummary(result, subject, originalBytes) {
|
|
281
|
+
const kb = Math.round(originalBytes / 1024);
|
|
282
|
+
const summary = `token-goat OCR'd ${subject} instead of shrinking it: text-heavy image detected (${Math.round(result.confidence)}% confidence), extracted ${result.text.length} chars of text from ${kb}kb of pixels.`;
|
|
283
|
+
return `${summary}
|
|
284
|
+
|
|
285
|
+
${fenceUntrustedOcrText(result.text)}`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export {
|
|
289
|
+
SUPPORTED_OCR_LANGS,
|
|
290
|
+
getOcrLangSpec,
|
|
291
|
+
setOcrTimeoutForTesting,
|
|
292
|
+
getActiveOcrLangs,
|
|
293
|
+
resolveOcrLang,
|
|
294
|
+
OCR_LANG_FILE,
|
|
295
|
+
OCR_LANG_PATH,
|
|
296
|
+
OCR_LANG_SHA256,
|
|
297
|
+
verifyOcrLangCache,
|
|
298
|
+
quarantineOcrLangCache,
|
|
299
|
+
ocrIntegrityFailed,
|
|
300
|
+
ocrBlockedOffline,
|
|
301
|
+
isOcrEngineAvailable,
|
|
302
|
+
setTesseractEntryForTesting,
|
|
303
|
+
resetOcrStateForTesting,
|
|
304
|
+
buildChildScript,
|
|
305
|
+
ocrImage,
|
|
306
|
+
isTextHeavy,
|
|
307
|
+
formatOcrSummary
|
|
308
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
parse
|
|
5
|
+
} from "./token-goat-chunk-3BTK54F3.mjs";
|
|
6
|
+
import {
|
|
7
|
+
displaySafeText,
|
|
8
|
+
filtersFilteredToEmptyNotice
|
|
9
|
+
} from "./token-goat-chunk-AMYCQJX4.mjs";
|
|
10
|
+
import {
|
|
11
|
+
compileGuardedRegexCached
|
|
12
|
+
} from "./token-goat-chunk-GMOUBOX4.mjs";
|
|
13
|
+
import {
|
|
14
|
+
init_define_import_meta_env
|
|
15
|
+
} from "./token-goat-chunk-A37V4PBF.mjs";
|
|
16
|
+
|
|
17
|
+
// src/csv_query.ts
|
|
18
|
+
init_define_import_meta_env();
|
|
19
|
+
var DELIMITER_CANDIDATES = [",", " ", ";", "|"];
|
|
20
|
+
var DELIMITER_SAMPLE_ROWS = 5;
|
|
21
|
+
function detectDelimiter(content) {
|
|
22
|
+
const slice = content.slice(0, 1e4);
|
|
23
|
+
if (slice.trim() === "") return ",";
|
|
24
|
+
for (const cand of DELIMITER_CANDIDATES) {
|
|
25
|
+
let rows;
|
|
26
|
+
try {
|
|
27
|
+
rows = parse(slice, { columns: false, skip_empty_lines: true, trim: true, delimiter: cand, bom: true, relax_quotes: true, to: DELIMITER_SAMPLE_ROWS });
|
|
28
|
+
} catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if ((rows[0]?.length ?? 0) < 2) continue;
|
|
32
|
+
return cand;
|
|
33
|
+
}
|
|
34
|
+
return ",";
|
|
35
|
+
}
|
|
36
|
+
function parseRecords(content, opts) {
|
|
37
|
+
const delimiter = opts.delimiter ?? detectDelimiter(content);
|
|
38
|
+
if (opts.noHeader === true) {
|
|
39
|
+
const rows = parse(content, { columns: false, skip_empty_lines: true, trim: true, delimiter, bom: true, relax_column_count: true, relax_quotes: true });
|
|
40
|
+
return rows.map((row) => Object.fromEntries(row.map((cell, i) => [`col${i + 1}`, cell])));
|
|
41
|
+
}
|
|
42
|
+
const header = csvHeader(content, { ...opts, delimiter });
|
|
43
|
+
const dupes = header.filter((name, i) => name !== "" && header.indexOf(name) !== i);
|
|
44
|
+
if (dupes.length > 0) {
|
|
45
|
+
const unique = [...new Set(dupes)];
|
|
46
|
+
throw new Error(
|
|
47
|
+
`duplicate column ${unique.length === 1 ? "name" : "names"} in header: ${unique.join(", ")} \u2014 rename the duplicates or pass --no-header to address columns positionally as col1, col2, \u2026`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return parse(content, { columns: true, skip_empty_lines: true, trim: true, delimiter, bom: true, relax_column_count: true, relax_quotes: true });
|
|
51
|
+
}
|
|
52
|
+
function csvHeader(content, opts) {
|
|
53
|
+
if (opts.noHeader === true) return [];
|
|
54
|
+
const delimiter = opts.delimiter ?? detectDelimiter(content);
|
|
55
|
+
try {
|
|
56
|
+
const rows = parse(content, { columns: false, skip_empty_lines: true, trim: true, delimiter, bom: true, to: 1, relax_quotes: true });
|
|
57
|
+
return rows[0] ?? [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var WHERE_SPEC_RE = /^((?:[^=<>~!]|!(?!=)|~(?!=))+)(!=|~=|>=|<=|=|>|<)(.*)$/;
|
|
63
|
+
function parseWhereSpecs(specs) {
|
|
64
|
+
if (specs === void 0 || specs.length === 0) return void 0;
|
|
65
|
+
return specs.map((spec) => {
|
|
66
|
+
const m = WHERE_SPEC_RE.exec(spec);
|
|
67
|
+
if (!m) throw new Error(`invalid --where spec: ${spec} (expected col=value, col!=value, col>value, col<value, col>=value, or col<=value, or col~=regex)`);
|
|
68
|
+
const op = m[2];
|
|
69
|
+
const value = m[3];
|
|
70
|
+
if ((op === ">" || op === "<" || op === ">=" || op === "<=") && value.trim() === "") {
|
|
71
|
+
throw new Error(`invalid --where spec: ${spec} (missing comparison value after '${op}')`);
|
|
72
|
+
}
|
|
73
|
+
if (op === "~=") {
|
|
74
|
+
const guarded = compileGuardedRegexCached(value);
|
|
75
|
+
if (!guarded.ok) throw new Error(`invalid --where spec: ${spec} (the pattern ${guarded.reason})`);
|
|
76
|
+
}
|
|
77
|
+
return { column: m[1].trim(), op, value };
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function resolveWhereColumn(where, allColumns) {
|
|
81
|
+
const rawSpec = where.column + where.op + where.value;
|
|
82
|
+
let best = where;
|
|
83
|
+
for (const col of allColumns) {
|
|
84
|
+
if (col.length <= best.column.length) continue;
|
|
85
|
+
if (!rawSpec.startsWith(col)) continue;
|
|
86
|
+
const rest = rawSpec.slice(col.length);
|
|
87
|
+
const m = /^(!=|~=|>=|<=|=|>|<)(.*)$/.exec(rest);
|
|
88
|
+
if (m) {
|
|
89
|
+
best = { column: col, op: m[1], value: m[2] };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return best;
|
|
93
|
+
}
|
|
94
|
+
function matchesWhere(row, where) {
|
|
95
|
+
const cell = row[where.column] ?? "";
|
|
96
|
+
switch (where.op) {
|
|
97
|
+
case "=":
|
|
98
|
+
return cell === where.value;
|
|
99
|
+
case "!=":
|
|
100
|
+
return cell !== where.value;
|
|
101
|
+
case "~=": {
|
|
102
|
+
const guarded = compileGuardedRegexCached(where.value);
|
|
103
|
+
return guarded.ok && guarded.re.test(cell);
|
|
104
|
+
}
|
|
105
|
+
case ">":
|
|
106
|
+
case "<":
|
|
107
|
+
case ">=":
|
|
108
|
+
case "<=": {
|
|
109
|
+
if (cell.trim() === "") return false;
|
|
110
|
+
const cellNum = Number(cell);
|
|
111
|
+
const valNum = Number(where.value);
|
|
112
|
+
const useNum = !Number.isNaN(cellNum) && !Number.isNaN(valNum);
|
|
113
|
+
const lhs = useNum ? cellNum : cell;
|
|
114
|
+
const rhs = useNum ? valNum : where.value;
|
|
115
|
+
switch (where.op) {
|
|
116
|
+
case ">":
|
|
117
|
+
return lhs > rhs;
|
|
118
|
+
case "<":
|
|
119
|
+
return lhs < rhs;
|
|
120
|
+
case ">=":
|
|
121
|
+
return lhs >= rhs;
|
|
122
|
+
case "<=":
|
|
123
|
+
return lhs <= rhs;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function queryCsv(content, opts) {
|
|
129
|
+
const records = parseRecords(content, opts);
|
|
130
|
+
const allColumns = records.length > 0 ? Object.keys(records[0]) : [];
|
|
131
|
+
const realHeader = records.length > 0 ? allColumns : csvHeader(content, opts);
|
|
132
|
+
const columns = opts.columns && opts.columns.length > 0 ? opts.columns : allColumns;
|
|
133
|
+
for (const c of columns) {
|
|
134
|
+
if (!realHeader.includes(c)) {
|
|
135
|
+
throw new Error(`unknown column: ${c} (available: ${realHeader.join(", ")})`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const wheres = (opts.wheres ?? []).map((w) => resolveWhereColumn(w, realHeader));
|
|
139
|
+
for (const w of wheres) {
|
|
140
|
+
if (!realHeader.includes(w.column)) {
|
|
141
|
+
throw new Error(`unknown column: ${w.column} (available: ${realHeader.join(", ")})`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let filtered = records;
|
|
145
|
+
if (wheres.length > 0) {
|
|
146
|
+
filtered = records.filter((r) => wheres.every((w) => matchesWhere(r, w)));
|
|
147
|
+
}
|
|
148
|
+
const totalRows = filtered.length;
|
|
149
|
+
const limited = opts.head !== void 0 ? filtered.slice(0, opts.head) : filtered;
|
|
150
|
+
const rows = limited.map((r) => columns.map((c) => r[c] ?? ""));
|
|
151
|
+
return { header: columns, rows, totalRows, preFilterRows: records.length };
|
|
152
|
+
}
|
|
153
|
+
function quoteCsvCell(cell) {
|
|
154
|
+
if (cell.includes(",") || cell.includes('"') || cell.includes("\n") || cell.includes("\r")) {
|
|
155
|
+
return `"${cell.replace(/"/g, '""')}"`;
|
|
156
|
+
}
|
|
157
|
+
return cell;
|
|
158
|
+
}
|
|
159
|
+
function formatCsvTable(result, activeFilters = []) {
|
|
160
|
+
const lines = [
|
|
161
|
+
result.header.map(quoteCsvCell).join(","),
|
|
162
|
+
...result.rows.map((r) => r.map(quoteCsvCell).join(","))
|
|
163
|
+
];
|
|
164
|
+
if (result.totalRows > result.rows.length) {
|
|
165
|
+
lines.push(`...(${result.totalRows - result.rows.length} more rows elided; use --head to see more)`);
|
|
166
|
+
}
|
|
167
|
+
if (result.totalRows === 0 && result.preFilterRows > 0) {
|
|
168
|
+
lines.push(filtersFilteredToEmptyNotice(result.preFilterRows, activeFilters, "data row", "data rows"));
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
function profileCsv(content, opts = {}) {
|
|
173
|
+
const records = parseRecords(content, opts);
|
|
174
|
+
const columns = records.length > 0 ? Object.keys(records[0]) : [];
|
|
175
|
+
return columns.map((col) => {
|
|
176
|
+
const values = records.map((r) => r[col] ?? "");
|
|
177
|
+
const nullCount = values.filter((v) => v.trim() === "").length;
|
|
178
|
+
const nonEmpty = values.filter((v) => v.trim() !== "");
|
|
179
|
+
const distinct = new Set(nonEmpty);
|
|
180
|
+
const isNumber = nonEmpty.length > 0 && nonEmpty.every((v) => v.trim() !== "" && !Number.isNaN(Number(v)));
|
|
181
|
+
const isDate = !isNumber && nonEmpty.length > 0 && nonEmpty.every((v) => !Number.isNaN(Date.parse(v)));
|
|
182
|
+
const inferredType = isNumber ? "number" : isDate ? "date" : "string";
|
|
183
|
+
const profile = { name: col, inferredType, nullCount, distinctCount: distinct.size };
|
|
184
|
+
if (nonEmpty.length > 0) {
|
|
185
|
+
if (isNumber) {
|
|
186
|
+
const nums = nonEmpty.map(Number);
|
|
187
|
+
profile.min = String(nums.reduce((a, b) => Math.min(a, b)));
|
|
188
|
+
profile.max = String(nums.reduce((a, b) => Math.max(a, b)));
|
|
189
|
+
} else if (isDate) {
|
|
190
|
+
const sorted = [...nonEmpty].sort((a, b) => Date.parse(a) - Date.parse(b));
|
|
191
|
+
profile.min = sorted[0];
|
|
192
|
+
profile.max = sorted[sorted.length - 1];
|
|
193
|
+
} else {
|
|
194
|
+
const sorted = [...nonEmpty].sort();
|
|
195
|
+
profile.min = sorted[0];
|
|
196
|
+
profile.max = sorted[sorted.length - 1];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (distinct.size > 0 && distinct.size <= 10) {
|
|
200
|
+
const counts = /* @__PURE__ */ new Map();
|
|
201
|
+
for (const v of nonEmpty) counts.set(v, (counts.get(v) ?? 0) + 1);
|
|
202
|
+
profile.topValues = [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([value, count]) => ({ value, count }));
|
|
203
|
+
}
|
|
204
|
+
return profile;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function formatCsvProfile(profiles) {
|
|
208
|
+
return profiles.map((p) => {
|
|
209
|
+
const lines = [
|
|
210
|
+
`${displaySafeText(p.name)} (${p.inferredType})`,
|
|
211
|
+
` nulls: ${p.nullCount} distinct: ${p.distinctCount}`
|
|
212
|
+
];
|
|
213
|
+
if (p.min !== void 0) lines.push(` range: ${displaySafeText(p.min)} .. ${displaySafeText(p.max ?? "")}`);
|
|
214
|
+
if (p.topValues !== void 0) lines.push(` values: ${p.topValues.map((t) => `${displaySafeText(t.value)} (${t.count})`).join(", ")}`);
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}).join("\n\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export {
|
|
220
|
+
parseWhereSpecs,
|
|
221
|
+
queryCsv,
|
|
222
|
+
quoteCsvCell,
|
|
223
|
+
formatCsvTable,
|
|
224
|
+
profileCsv,
|
|
225
|
+
formatCsvProfile
|
|
226
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createRequire as __cjsRequire } from 'node:module';
|
|
2
|
+
const require = __cjsRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
docxOutline,
|
|
5
|
+
docxTables,
|
|
6
|
+
docxText,
|
|
7
|
+
formatDocxTables
|
|
8
|
+
} from "./token-goat-chunk-4SDX3QP3.mjs";
|
|
9
|
+
import "./token-goat-chunk-YHGTGG6K.mjs";
|
|
10
|
+
import "./token-goat-chunk-XTQAOTSO.mjs";
|
|
11
|
+
import "./token-goat-chunk-AH6QILZM.mjs";
|
|
12
|
+
import "./token-goat-chunk-Y4AFKTHK.mjs";
|
|
13
|
+
import "./token-goat-chunk-AMYCQJX4.mjs";
|
|
14
|
+
import "./token-goat-chunk-GMOUBOX4.mjs";
|
|
15
|
+
import "./token-goat-chunk-A37V4PBF.mjs";
|
|
16
|
+
export {
|
|
17
|
+
docxOutline,
|
|
18
|
+
docxTables,
|
|
19
|
+
docxText,
|
|
20
|
+
formatDocxTables
|
|
21
|
+
};
|