document-cli 0.0.0 → 1.0.1
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 +186 -0
- package/dist/cli.js +615 -0
- package/dist/index.cjs +664 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +651 -0
- package/dist/io-Cl3MaB0L.js +81 -0
- package/dist/tui-CucYgqmL.js +5647 -0
- package/package.json +101 -2
package/dist/cli.js
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as inferFormatFromExtension, n as resolveDefaultOutputPath, o as isDocumentFormat, r as writeOutput, t as readInput } from "./io-Cl3MaB0L.js";
|
|
3
|
+
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
4
|
+
import { writeFile } from "node:fs/promises";
|
|
5
|
+
import { OdbNoEmbeddedDataSourceError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, createLocalDocumentConverter, odbToCsv, odbToXlsx, odmToPdf, readOdbTables, readPdf } from "documents.js";
|
|
6
|
+
import { basename, dirname, extname, join } from "node:path";
|
|
7
|
+
import { decodePackage } from "odf.js";
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
//#region src/runtime/abort.ts
|
|
10
|
+
function combineSignals(a, b) {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const forward = (signal) => {
|
|
13
|
+
controller.abort(signal.reason);
|
|
14
|
+
};
|
|
15
|
+
if (a.aborted) forward(a);
|
|
16
|
+
else a.addEventListener("abort", () => forward(a), { once: true });
|
|
17
|
+
if (b.aborted) forward(b);
|
|
18
|
+
else b.addEventListener("abort", () => forward(b), { once: true });
|
|
19
|
+
return controller.signal;
|
|
20
|
+
}
|
|
21
|
+
function createRuntimeSignal(options) {
|
|
22
|
+
let abortReason;
|
|
23
|
+
const interruptController = new AbortController();
|
|
24
|
+
process.on("SIGINT", () => {
|
|
25
|
+
abortReason ??= "interrupt";
|
|
26
|
+
interruptController.abort(/* @__PURE__ */ new Error("Interrupted by SIGINT"));
|
|
27
|
+
});
|
|
28
|
+
const getAbortReason = () => abortReason;
|
|
29
|
+
if (options.timeoutMs === void 0) return {
|
|
30
|
+
signal: interruptController.signal,
|
|
31
|
+
getAbortReason
|
|
32
|
+
};
|
|
33
|
+
const timeoutController = new AbortController();
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
abortReason ??= "timeout";
|
|
36
|
+
timeoutController.abort(/* @__PURE__ */ new Error(`Timed out after ${options.timeoutMs}ms`));
|
|
37
|
+
}, options.timeoutMs).unref();
|
|
38
|
+
return {
|
|
39
|
+
signal: combineSignals(interruptController.signal, timeoutController.signal),
|
|
40
|
+
getAbortReason
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/runtime/diagnostics.ts
|
|
45
|
+
function createDiagnosticReporter(options) {
|
|
46
|
+
const { json, quiet, command } = options;
|
|
47
|
+
return {
|
|
48
|
+
report(diagnostic) {
|
|
49
|
+
if (quiet) return;
|
|
50
|
+
if (json) {
|
|
51
|
+
process.stderr.write(`${JSON.stringify({
|
|
52
|
+
type: "diagnostic",
|
|
53
|
+
command,
|
|
54
|
+
...diagnostic
|
|
55
|
+
})}\n`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const pageClause = diagnostic.pageIndex === void 0 ? "" : ` (page ${diagnostic.pageIndex})`;
|
|
59
|
+
process.stderr.write(`[${command}] ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${pageClause}\n`);
|
|
60
|
+
},
|
|
61
|
+
summarize(result) {
|
|
62
|
+
if (json) {
|
|
63
|
+
process.stderr.write(`${JSON.stringify({
|
|
64
|
+
type: "result",
|
|
65
|
+
...result
|
|
66
|
+
})}\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (quiet) return;
|
|
70
|
+
const diagnosticClause = `${result.diagnosticCount} diagnostic${result.diagnosticCount === 1 ? "" : "s"}`;
|
|
71
|
+
process.stderr.write(`[${command}] wrote ${result.bytes} bytes to ${result.output} (${diagnosticClause})\n`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function substitutionToDiagnostic(substitution, pageIndex) {
|
|
76
|
+
return {
|
|
77
|
+
severity: "warning",
|
|
78
|
+
code: "win-ansi-substitution",
|
|
79
|
+
message: `Character '${substitution.from}' has no glyph in the standard font; substituted with '${substitution.to}'`,
|
|
80
|
+
pageIndex
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function pdfDiagnosticToDiagnostic(diagnostic) {
|
|
84
|
+
return {
|
|
85
|
+
severity: diagnostic.severity,
|
|
86
|
+
code: diagnostic.code,
|
|
87
|
+
message: diagnostic.message,
|
|
88
|
+
pageIndex: diagnostic.pageIndex
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function mapErrorToExit(error, abortReason) {
|
|
92
|
+
if (abortReason === "interrupt") return 130;
|
|
93
|
+
if (abortReason === "timeout") return 124;
|
|
94
|
+
if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) return 3;
|
|
95
|
+
if (error instanceof PdfEncryptedError || error instanceof PdfParseError) return 1;
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/commands/shared.ts
|
|
100
|
+
function formatError(error, verbose) {
|
|
101
|
+
if (!(error instanceof Error)) return `error: ${String(error)}`;
|
|
102
|
+
const stackClause = verbose && error.stack !== void 0 ? `\n${error.stack}` : "";
|
|
103
|
+
return `error: ${error.message}${stackClause}`;
|
|
104
|
+
}
|
|
105
|
+
function buildConversionAction(source, target) {
|
|
106
|
+
const command = `${source}-to-${target}`;
|
|
107
|
+
return async (input, output, options) => {
|
|
108
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
109
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
110
|
+
return 2;
|
|
111
|
+
}
|
|
112
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target));
|
|
113
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeoutMs });
|
|
114
|
+
try {
|
|
115
|
+
const inputBytes = await readInput(input, { signal });
|
|
116
|
+
const result = await createLocalDocumentConverter().convert({
|
|
117
|
+
source: {
|
|
118
|
+
format: source,
|
|
119
|
+
bytes: new Uint8Array(inputBytes)
|
|
120
|
+
},
|
|
121
|
+
targetFormat: target
|
|
122
|
+
}, { signal });
|
|
123
|
+
await writeOutput(resolvedOutput, result.document.bytes);
|
|
124
|
+
const reporter = createDiagnosticReporter({
|
|
125
|
+
json: options.json,
|
|
126
|
+
quiet: options.quiet,
|
|
127
|
+
command
|
|
128
|
+
});
|
|
129
|
+
for (const diagnostic of result.diagnostics) reporter.report(diagnostic);
|
|
130
|
+
if (options.dumpPackage !== void 0) if (result.package === void 0) process.stderr.write(`[${command}] this conversion does not produce an intermediate DocumentPackage\n`);
|
|
131
|
+
else await writeFile(options.dumpPackage, JSON.stringify(result.package, void 0, 2));
|
|
132
|
+
reporter.summarize({
|
|
133
|
+
output: resolvedOutput,
|
|
134
|
+
bytes: result.document.bytes.byteLength,
|
|
135
|
+
diagnosticCount: result.diagnostics.length
|
|
136
|
+
});
|
|
137
|
+
return 0;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
140
|
+
return mapErrorToExit(error, getAbortReason());
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/commands/options.ts
|
|
146
|
+
function addOutOption(command) {
|
|
147
|
+
return command.option("-o, --out <file>", "output file path (defaults to the input path with the target format's extension); use - for stdout");
|
|
148
|
+
}
|
|
149
|
+
function addTimeoutOption(command) {
|
|
150
|
+
return command.option("--timeout <ms>", "abort the run after this many milliseconds", (value) => Number.parseInt(value, 10));
|
|
151
|
+
}
|
|
152
|
+
function addJsonOption(command) {
|
|
153
|
+
return command.option("--json", "emit diagnostics and the result summary as newline-delimited JSON on stderr", false);
|
|
154
|
+
}
|
|
155
|
+
function addQuietOption(command) {
|
|
156
|
+
return command.option("-q, --quiet", "suppress diagnostic and summary output", false);
|
|
157
|
+
}
|
|
158
|
+
function addVerboseOption(command) {
|
|
159
|
+
return command.option("--verbose", "include a full stack trace when the run fails", false);
|
|
160
|
+
}
|
|
161
|
+
function addDumpPackageOption(command) {
|
|
162
|
+
return command.option("--dump-package <file>", "write the intermediate DocumentPackage (content + layout) this conversion built to a JSON file");
|
|
163
|
+
}
|
|
164
|
+
function addConversionFlags(command) {
|
|
165
|
+
addOutOption(command);
|
|
166
|
+
addTimeoutOption(command);
|
|
167
|
+
addJsonOption(command);
|
|
168
|
+
addQuietOption(command);
|
|
169
|
+
addVerboseOption(command);
|
|
170
|
+
return command;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/commands/convert.ts
|
|
174
|
+
function toConversionCommandOptions(options) {
|
|
175
|
+
return {
|
|
176
|
+
out: options.out,
|
|
177
|
+
timeoutMs: options.timeout,
|
|
178
|
+
json: options.json,
|
|
179
|
+
quiet: options.quiet,
|
|
180
|
+
verbose: options.verbose,
|
|
181
|
+
dumpPackage: options.dumpPackage
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const KNOWN_FORMATS = "docx, pptx, xlsx, odt, odp, ods, odg, odf, pdf";
|
|
185
|
+
function resolveGenericTarget(output, options) {
|
|
186
|
+
if (options.to !== void 0) {
|
|
187
|
+
if (!isDocumentFormat(options.to)) return { errorMessage: `unknown --to format '${options.to}'; expected one of ${KNOWN_FORMATS}` };
|
|
188
|
+
return { format: options.to };
|
|
189
|
+
}
|
|
190
|
+
const destination = output ?? options.out;
|
|
191
|
+
if (destination === void 0) return { errorMessage: "cannot infer a target format -- pass an output path with a recognised extension, --out with one, or --to <format>" };
|
|
192
|
+
const inferred = inferFormatFromExtension(destination);
|
|
193
|
+
if (inferred === void 0) return { errorMessage: `cannot infer a target format from '${destination}'; pass --to <format> instead` };
|
|
194
|
+
return { format: inferred };
|
|
195
|
+
}
|
|
196
|
+
async function runGenericConvert(input, output, options) {
|
|
197
|
+
const extension = extname(input).toLowerCase();
|
|
198
|
+
if (extension === ".odm") {
|
|
199
|
+
process.stderr.write("convert: '.odm' master documents are not supported by the generic convert command -- use 'odm-to-pdf' instead\n");
|
|
200
|
+
return 2;
|
|
201
|
+
}
|
|
202
|
+
if (extension === ".odb") {
|
|
203
|
+
process.stderr.write("convert: '.odb' embedded databases are not supported by the generic convert command -- use 'odb-to-csv', 'odb-to-xlsx', or 'odb-tables' instead\n");
|
|
204
|
+
return 2;
|
|
205
|
+
}
|
|
206
|
+
const source = inferFormatFromExtension(input);
|
|
207
|
+
if (source === void 0) {
|
|
208
|
+
process.stderr.write(`convert: cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_FORMATS}) or use one of the explicit '<source>-to-<target>' commands\n`);
|
|
209
|
+
return 2;
|
|
210
|
+
}
|
|
211
|
+
const target = resolveGenericTarget(output, options);
|
|
212
|
+
if ("errorMessage" in target) {
|
|
213
|
+
process.stderr.write(`convert: ${target.errorMessage}\n`);
|
|
214
|
+
return 2;
|
|
215
|
+
}
|
|
216
|
+
return buildConversionAction(source, target.format)(input, output, toConversionCommandOptions(options));
|
|
217
|
+
}
|
|
218
|
+
function registerConversionCommands(program) {
|
|
219
|
+
const { conversions } = createLocalDocumentConverter();
|
|
220
|
+
for (const { source, target } of conversions) {
|
|
221
|
+
const commandName = `${source}-to-${target}`;
|
|
222
|
+
const command = program.command(`${commandName} <input> [output]`).description(`convert a ${source} document to ${target}`);
|
|
223
|
+
addConversionFlags(command);
|
|
224
|
+
addDumpPackageOption(command);
|
|
225
|
+
command.action(async (input, output, options) => {
|
|
226
|
+
process.exitCode = await buildConversionAction(source, target)(input, output, toConversionCommandOptions(options));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const generic = program.command("convert <input> [output]").description("convert between any two supported document formats, inferring source/target from file extensions where possible");
|
|
230
|
+
addConversionFlags(generic);
|
|
231
|
+
addDumpPackageOption(generic);
|
|
232
|
+
generic.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_FORMATS})`);
|
|
233
|
+
generic.action(async (input, output, options) => {
|
|
234
|
+
process.exitCode = await runGenericConvert(input, output, options);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/commands/formats.ts
|
|
239
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, pdf-inspect";
|
|
240
|
+
function registerFormatsCommand(program) {
|
|
241
|
+
program.command("formats").description("list every source -> target conversion this CLI supports via a <source>-to-<target> command").option("--json", "emit the conversion list as a JSON array instead of a human-readable table", false).action((options) => {
|
|
242
|
+
const { conversions } = createLocalDocumentConverter();
|
|
243
|
+
if (options.json) {
|
|
244
|
+
process.stdout.write(`${JSON.stringify(conversions)}\n`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for (const { source, target } of conversions) process.stdout.write(`${source} -> ${target}\n`);
|
|
248
|
+
process.stdout.write(`\nnot covered by this list, each its own command: ${COMMANDS_NOT_LISTED}\n`);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/commands/odb.ts
|
|
253
|
+
function reportOdbError(command, error, verbose, abortReason) {
|
|
254
|
+
if (error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) {
|
|
255
|
+
process.stderr.write(`[${command}] ${error.message}\n`);
|
|
256
|
+
return mapErrorToExit(error, abortReason);
|
|
257
|
+
}
|
|
258
|
+
process.stderr.write(`[${command}] ${formatError(error, verbose)}\n`);
|
|
259
|
+
return mapErrorToExit(error, abortReason);
|
|
260
|
+
}
|
|
261
|
+
function reportOdbCsvError(command, error, verbose, abortReason) {
|
|
262
|
+
if (error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError) {
|
|
263
|
+
process.stderr.write(`[${command}] ${error.message}\nrun 'odb-tables' first to see the available tables\n`);
|
|
264
|
+
return mapErrorToExit(error, abortReason);
|
|
265
|
+
}
|
|
266
|
+
return reportOdbError(command, error, verbose, abortReason);
|
|
267
|
+
}
|
|
268
|
+
function resolveDefaultCsvOutputPath(inputPath) {
|
|
269
|
+
const directory = dirname(inputPath);
|
|
270
|
+
const stem = basename(inputPath, extname(inputPath));
|
|
271
|
+
return join(directory, `${stem}.csv`);
|
|
272
|
+
}
|
|
273
|
+
async function runOdbToXlsx(input, output, options) {
|
|
274
|
+
const command = "odb-to-xlsx";
|
|
275
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
276
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
277
|
+
return 2;
|
|
278
|
+
}
|
|
279
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, "xlsx"));
|
|
280
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
281
|
+
try {
|
|
282
|
+
const inputBytes = await readInput(input, { signal });
|
|
283
|
+
const bytes = odbToXlsx(new Uint8Array(inputBytes), { signal });
|
|
284
|
+
await writeOutput(resolvedOutput, bytes);
|
|
285
|
+
createDiagnosticReporter({
|
|
286
|
+
json: options.json,
|
|
287
|
+
quiet: options.quiet,
|
|
288
|
+
command
|
|
289
|
+
}).summarize({
|
|
290
|
+
output: resolvedOutput,
|
|
291
|
+
bytes: bytes.byteLength,
|
|
292
|
+
diagnosticCount: 0
|
|
293
|
+
});
|
|
294
|
+
return 0;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return reportOdbError(command, error, options.verbose, getAbortReason());
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function runOdbToCsv(input, output, options) {
|
|
300
|
+
const command = "odb-to-csv";
|
|
301
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
302
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
303
|
+
return 2;
|
|
304
|
+
}
|
|
305
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultCsvOutputPath(input));
|
|
306
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
307
|
+
try {
|
|
308
|
+
const inputBytes = await readInput(input, { signal });
|
|
309
|
+
const bytes = odbToCsv(new Uint8Array(inputBytes), {
|
|
310
|
+
signal,
|
|
311
|
+
table: options.table
|
|
312
|
+
});
|
|
313
|
+
await writeOutput(resolvedOutput, bytes);
|
|
314
|
+
createDiagnosticReporter({
|
|
315
|
+
json: options.json,
|
|
316
|
+
quiet: options.quiet,
|
|
317
|
+
command
|
|
318
|
+
}).summarize({
|
|
319
|
+
output: resolvedOutput,
|
|
320
|
+
bytes: bytes.byteLength,
|
|
321
|
+
diagnosticCount: 0
|
|
322
|
+
});
|
|
323
|
+
return 0;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return reportOdbCsvError(command, error, options.verbose, getAbortReason());
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function runOdbTables(input, options) {
|
|
329
|
+
const command = "odb-tables";
|
|
330
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
331
|
+
try {
|
|
332
|
+
const inputBytes = await readInput(input, { signal });
|
|
333
|
+
const pkg = decodePackage(new Uint8Array(inputBytes));
|
|
334
|
+
const tables = readOdbTables(pkg);
|
|
335
|
+
if (options.json) {
|
|
336
|
+
const summary = tables.map((table) => ({
|
|
337
|
+
tableName: table.tableName,
|
|
338
|
+
columns: table.columns,
|
|
339
|
+
rowCount: table.rows.length
|
|
340
|
+
}));
|
|
341
|
+
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
for (const table of tables) {
|
|
345
|
+
process.stdout.write(`${table.tableName} (${table.rows.length} row${table.rows.length === 1 ? "" : "s"})\n`);
|
|
346
|
+
for (const column of table.columns) process.stdout.write(` ${column.name}: ${column.type}\n`);
|
|
347
|
+
}
|
|
348
|
+
return 0;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
return reportOdbError(command, error, false, getAbortReason());
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function registerOdbToXlsxCommand(program) {
|
|
354
|
+
const command = program.command("odb-to-xlsx <input> [output]").description("extract every table an embedded .odb database declares into one xlsx workbook, one sheet per table");
|
|
355
|
+
addOutOption(command);
|
|
356
|
+
addTimeoutOption(command);
|
|
357
|
+
addJsonOption(command);
|
|
358
|
+
addQuietOption(command);
|
|
359
|
+
addVerboseOption(command);
|
|
360
|
+
command.action(async (input, output, options) => {
|
|
361
|
+
process.exitCode = await runOdbToXlsx(input, output, options);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
function registerOdbToCsvCommand(program) {
|
|
365
|
+
const command = program.command("odb-to-csv <input> [output]").description("extract exactly one named table from an embedded .odb database as CSV");
|
|
366
|
+
addOutOption(command);
|
|
367
|
+
addTimeoutOption(command);
|
|
368
|
+
addJsonOption(command);
|
|
369
|
+
addQuietOption(command);
|
|
370
|
+
addVerboseOption(command);
|
|
371
|
+
command.option("--table <name>", "the table to export -- required when the .odb declares more than one table");
|
|
372
|
+
command.action(async (input, output, options) => {
|
|
373
|
+
process.exitCode = await runOdbToCsv(input, output, options);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function registerOdbTablesCommand(program) {
|
|
377
|
+
program.command("odb-tables <input>").description("list every table an embedded .odb database declares, with column names/types and row counts").option("--json", "emit the table list as a JSON array instead of a human-readable report", false).action(async (input, options) => {
|
|
378
|
+
process.exitCode = await runOdbTables(input, options);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function registerOdbCommands(program) {
|
|
382
|
+
registerOdbToXlsxCommand(program);
|
|
383
|
+
registerOdbToCsvCommand(program);
|
|
384
|
+
registerOdbTablesCommand(program);
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/commands/odm.ts
|
|
388
|
+
function collectChapterOverride(value, previous) {
|
|
389
|
+
const separatorIndex = value.indexOf("=");
|
|
390
|
+
if (separatorIndex === -1) throw new InvalidArgumentError(`--chapter must be formatted as <href>=<file>, got '${value}'`);
|
|
391
|
+
const next = new Map(previous);
|
|
392
|
+
next.set(value.slice(0, separatorIndex), value.slice(separatorIndex + 1));
|
|
393
|
+
return next;
|
|
394
|
+
}
|
|
395
|
+
function createResolveSubDocument(overrides, chaptersDir) {
|
|
396
|
+
return (href) => {
|
|
397
|
+
const overridePath = overrides.get(href);
|
|
398
|
+
if (overridePath !== void 0) return new Uint8Array(readFileSync(overridePath));
|
|
399
|
+
if (chaptersDir === void 0) return;
|
|
400
|
+
const candidate = join(chaptersDir, basename(href));
|
|
401
|
+
if (!existsSync(candidate)) return;
|
|
402
|
+
return new Uint8Array(readFileSync(candidate));
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
async function runOdmToPdf(input, output, options) {
|
|
406
|
+
const command = "odm-to-pdf";
|
|
407
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
408
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, "pdf"));
|
|
412
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
413
|
+
const resolveSubDocument = createResolveSubDocument(options.chapter, options.chaptersDir);
|
|
414
|
+
const reporter = createDiagnosticReporter({
|
|
415
|
+
json: options.json,
|
|
416
|
+
quiet: options.quiet,
|
|
417
|
+
command
|
|
418
|
+
});
|
|
419
|
+
let diagnosticCount = 0;
|
|
420
|
+
try {
|
|
421
|
+
const inputBytes = await readInput(input, { signal });
|
|
422
|
+
const bytes = odmToPdf(new Uint8Array(inputBytes), {
|
|
423
|
+
signal,
|
|
424
|
+
resolveSubDocument,
|
|
425
|
+
onSubstitution: (substitution, context) => {
|
|
426
|
+
diagnosticCount += 1;
|
|
427
|
+
reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
await writeOutput(resolvedOutput, bytes);
|
|
431
|
+
reporter.summarize({
|
|
432
|
+
output: resolvedOutput,
|
|
433
|
+
bytes: bytes.byteLength,
|
|
434
|
+
diagnosticCount
|
|
435
|
+
});
|
|
436
|
+
return 0;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (error instanceof OdmUnresolvedSectionError) {
|
|
439
|
+
process.stderr.write(`${error.message}\npass --chapters-dir <dir> containing these files, or --chapter <href>=<file>\n`);
|
|
440
|
+
return mapErrorToExit(error, getAbortReason());
|
|
441
|
+
}
|
|
442
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
443
|
+
return mapErrorToExit(error, getAbortReason());
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function registerOdmCommand(program) {
|
|
447
|
+
const command = program.command("odm-to-pdf <input> [output]").description("convert a .odm master document to pdf, resolving each chapter's external .odt reference via --chapters-dir and/or --chapter");
|
|
448
|
+
addOutOption(command);
|
|
449
|
+
addTimeoutOption(command);
|
|
450
|
+
addJsonOption(command);
|
|
451
|
+
addQuietOption(command);
|
|
452
|
+
addVerboseOption(command);
|
|
453
|
+
command.option("--chapters-dir <dir>", "directory to search for each unresolved chapter href, matched by the href's own basename");
|
|
454
|
+
command.option("--chapter <href>=<file>", "resolve one chapter href to a local file explicitly; repeatable", collectChapterOverride, /* @__PURE__ */ new Map());
|
|
455
|
+
command.action(async (input, output, options) => {
|
|
456
|
+
process.exitCode = await runOdmToPdf(input, output, options);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region src/commands/pdf-inspect.ts
|
|
461
|
+
function buildItemKindHistogram(items) {
|
|
462
|
+
const histogram = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const item of items) histogram.set(item.kind, (histogram.get(item.kind) ?? 0) + 1);
|
|
464
|
+
return histogram;
|
|
465
|
+
}
|
|
466
|
+
function countImagesByFormat(images) {
|
|
467
|
+
const counts = /* @__PURE__ */ new Map();
|
|
468
|
+
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
469
|
+
return counts;
|
|
470
|
+
}
|
|
471
|
+
function isPresent(entry) {
|
|
472
|
+
return entry[1] !== void 0;
|
|
473
|
+
}
|
|
474
|
+
function formatMetadataValue(value) {
|
|
475
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
476
|
+
}
|
|
477
|
+
async function runPdfInspect(input, options) {
|
|
478
|
+
const command = "pdf-inspect";
|
|
479
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
480
|
+
const reporter = createDiagnosticReporter({
|
|
481
|
+
json: options.json,
|
|
482
|
+
quiet: false,
|
|
483
|
+
command
|
|
484
|
+
});
|
|
485
|
+
try {
|
|
486
|
+
const inputBytes = await readInput(input, { signal });
|
|
487
|
+
const layout = readPdf(new Uint8Array(inputBytes), {
|
|
488
|
+
signal,
|
|
489
|
+
sink: (diagnostic) => {
|
|
490
|
+
reporter.report(pdfDiagnosticToDiagnostic(diagnostic));
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
if (options.full) {
|
|
494
|
+
process.stdout.write(`${JSON.stringify(layout, void 0, 2)}\n`);
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
const imagesByFormat = countImagesByFormat(layout.images);
|
|
498
|
+
if (options.json) {
|
|
499
|
+
const summary = {
|
|
500
|
+
pageCount: layout.pages.length,
|
|
501
|
+
pages: layout.pages.map((page) => ({
|
|
502
|
+
widthPt: page.widthPt,
|
|
503
|
+
heightPt: page.heightPt,
|
|
504
|
+
itemKinds: Object.fromEntries(buildItemKindHistogram(page.items))
|
|
505
|
+
})),
|
|
506
|
+
metadata: layout.metadata,
|
|
507
|
+
imagesByFormat: Object.fromEntries(imagesByFormat)
|
|
508
|
+
};
|
|
509
|
+
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
510
|
+
return 0;
|
|
511
|
+
}
|
|
512
|
+
process.stdout.write(`${layout.pages.length} page${layout.pages.length === 1 ? "" : "s"}\n`);
|
|
513
|
+
layout.pages.forEach((page, index) => {
|
|
514
|
+
const histogram = buildItemKindHistogram(page.items);
|
|
515
|
+
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
516
|
+
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
517
|
+
});
|
|
518
|
+
const presentMetadata = [
|
|
519
|
+
["title", layout.metadata.title],
|
|
520
|
+
["author", layout.metadata.author],
|
|
521
|
+
["subject", layout.metadata.subject],
|
|
522
|
+
["keywords", layout.metadata.keywords],
|
|
523
|
+
["creator", layout.metadata.creator],
|
|
524
|
+
["producer", layout.metadata.producer],
|
|
525
|
+
["createdIso", layout.metadata.createdIso],
|
|
526
|
+
["modifiedIso", layout.metadata.modifiedIso]
|
|
527
|
+
].filter(isPresent);
|
|
528
|
+
if (presentMetadata.length > 0) {
|
|
529
|
+
process.stdout.write("metadata:\n");
|
|
530
|
+
for (const [key, value] of presentMetadata) process.stdout.write(` ${key}: ${formatMetadataValue(value)}\n`);
|
|
531
|
+
}
|
|
532
|
+
if (imagesByFormat.size > 0) {
|
|
533
|
+
process.stdout.write("images:\n");
|
|
534
|
+
for (const [format, count] of imagesByFormat) process.stdout.write(` ${format}: ${count}\n`);
|
|
535
|
+
}
|
|
536
|
+
return 0;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
process.stderr.write(`${formatError(error, false)}\n`);
|
|
539
|
+
return mapErrorToExit(error, getAbortReason());
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function registerPdfInspectCommand(program) {
|
|
543
|
+
program.command("pdf-inspect <input>").description("inspect a PDF: page count, per-page size and item-kind histogram, document metadata, and embedded image formats").option("--json", "emit the summary as JSON instead of a human-readable report", false).option("--full", "dump the entire parsed LayoutDocument as JSON instead of a summary", false).action(async (input, options) => {
|
|
544
|
+
process.exitCode = await runPdfInspect(input, options);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region package.json
|
|
549
|
+
var version = "1.0.1";
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/program.ts
|
|
552
|
+
function createProgram() {
|
|
553
|
+
const program = new Command("document-cli");
|
|
554
|
+
program.description("every documents.js docx/pptx/odt/odp/ods/odg/odf/odm/odb conversion, bridge, and inspector as a scriptable command");
|
|
555
|
+
program.version(version);
|
|
556
|
+
program.exitOverride((error) => {
|
|
557
|
+
process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
558
|
+
throw error;
|
|
559
|
+
});
|
|
560
|
+
registerConversionCommands(program);
|
|
561
|
+
registerFormatsCommand(program);
|
|
562
|
+
registerOdmCommand(program);
|
|
563
|
+
registerOdbCommands(program);
|
|
564
|
+
registerPdfInspectCommand(program);
|
|
565
|
+
return program;
|
|
566
|
+
}
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/cli.ts
|
|
569
|
+
async function launchTui(startPath, signal) {
|
|
570
|
+
try {
|
|
571
|
+
const { runTui } = await import("./tui-CucYgqmL.js");
|
|
572
|
+
await runTui({
|
|
573
|
+
startPath,
|
|
574
|
+
signal
|
|
575
|
+
});
|
|
576
|
+
process.exitCode = 0;
|
|
577
|
+
} catch (error) {
|
|
578
|
+
process.stderr.write(`${formatError(error, false)}\n`);
|
|
579
|
+
process.exitCode = 1;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function findStartPath(args) {
|
|
583
|
+
return args.find((arg) => !arg.startsWith("-"));
|
|
584
|
+
}
|
|
585
|
+
async function main() {
|
|
586
|
+
const { signal } = createRuntimeSignal({});
|
|
587
|
+
const args = process.argv.slice(2);
|
|
588
|
+
const [dispatchToken] = args;
|
|
589
|
+
if (dispatchToken === void 0 || dispatchToken === "tui") {
|
|
590
|
+
if (process.stdout.isTTY !== true) {
|
|
591
|
+
if (dispatchToken === "tui") {
|
|
592
|
+
process.stderr.write("the TUI requires an interactive terminal (TTY); stdout is currently redirected\n");
|
|
593
|
+
process.exitCode = 2;
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
createProgram().outputHelp();
|
|
597
|
+
process.exitCode = 0;
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
await launchTui(findStartPath(dispatchToken === "tui" ? args.slice(1) : args), signal);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const program = createProgram();
|
|
604
|
+
program.command("tui [file]").description("launch the interactive terminal UI, optionally opening a file immediately").action(async (file) => {
|
|
605
|
+
await launchTui(file, signal);
|
|
606
|
+
});
|
|
607
|
+
try {
|
|
608
|
+
await program.parseAsync(process.argv);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
if (!(error instanceof CommanderError)) throw error;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
await main();
|
|
614
|
+
//#endregion
|
|
615
|
+
export {};
|