document-cli 1.4.0 → 1.5.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/dist/cli.js +204 -23
- package/dist/index.cjs +225 -20
- package/dist/index.js +226 -21
- package/dist/{odb-structure-DkfNyHGR.js → odb-structure-Du1w_hOU.js} +25 -1
- package/dist/{tui-CQCLalYe.js → tui-B-o3iI9a.js} +91 -7
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as odbFormSummary, c as
|
|
2
|
+
import { a as odbFormSummary, c as formatDocxExtrasLines, d as writeOutput, f as loadProvidedFonts, h as isDocumentFormat, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, r as formatOdbFormLines, s as presentMetadataEntries, t as describeOdbForm, u as resolveDefaultOutputPath } from "./odb-structure-Du1w_hOU.js";
|
|
3
3
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
4
4
|
import { writeFile } from "node:fs/promises";
|
|
5
|
-
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxExtras, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
|
|
5
|
+
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodeMarkdownText, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readOdfFormulaContent, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, writePdf, xlsxToPdf } from "documents.js";
|
|
6
6
|
import { basename, dirname, extname, join } from "node:path";
|
|
7
7
|
import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
|
|
8
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -373,7 +373,7 @@ function registerFontsCommand(program) {
|
|
|
373
373
|
}
|
|
374
374
|
//#endregion
|
|
375
375
|
//#region src/commands/formats.ts
|
|
376
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras";
|
|
376
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
|
|
377
377
|
function registerFormatsCommand(program) {
|
|
378
378
|
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) => {
|
|
379
379
|
const { conversions } = createLocalDocumentConverter();
|
|
@@ -466,6 +466,54 @@ function registerFromPackageCommand(program) {
|
|
|
466
466
|
});
|
|
467
467
|
}
|
|
468
468
|
//#endregion
|
|
469
|
+
//#region src/commands/metadata.ts
|
|
470
|
+
function readMetadataForFormat(format, bytes, signal) {
|
|
471
|
+
switch (format) {
|
|
472
|
+
case "docx": return readDocxContent(decodePackage(bytes)).metadata;
|
|
473
|
+
case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
|
|
474
|
+
case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
|
|
475
|
+
case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
|
|
476
|
+
case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
|
|
477
|
+
case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
|
|
478
|
+
case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
|
|
479
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
|
|
480
|
+
case "pdf": return readPdf(bytes, { signal }).metadata;
|
|
481
|
+
case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
async function runMetadata(input, options) {
|
|
485
|
+
const command = "metadata";
|
|
486
|
+
const source = inferFormatFromExtension(input);
|
|
487
|
+
if (source === void 0) {
|
|
488
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
489
|
+
return 2;
|
|
490
|
+
}
|
|
491
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
492
|
+
try {
|
|
493
|
+
const inputBytes = await readInput(input, { signal });
|
|
494
|
+
const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
|
|
495
|
+
if (options.json) {
|
|
496
|
+
process.stdout.write(`${JSON.stringify(metadata)}\n`);
|
|
497
|
+
return 0;
|
|
498
|
+
}
|
|
499
|
+
const lines = formatMetadataLines(metadata);
|
|
500
|
+
if (lines.length === 0) {
|
|
501
|
+
process.stdout.write("This document carries no metadata.\n");
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
505
|
+
return 0;
|
|
506
|
+
} catch (error) {
|
|
507
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
508
|
+
return mapErrorToExit(error, getAbortReason());
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function registerMetadataCommand(program) {
|
|
512
|
+
program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
|
|
513
|
+
process.exitCode = await runMetadata(input, options);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
//#endregion
|
|
469
517
|
//#region src/sql-result-format.ts
|
|
470
518
|
const COLUMN_GAP = " ";
|
|
471
519
|
function columnWidths(columns, cells) {
|
|
@@ -937,12 +985,6 @@ function countImagesByFormat(images) {
|
|
|
937
985
|
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
938
986
|
return counts;
|
|
939
987
|
}
|
|
940
|
-
function isPresent(entry) {
|
|
941
|
-
return entry[1] !== void 0;
|
|
942
|
-
}
|
|
943
|
-
function formatMetadataValue(value) {
|
|
944
|
-
return typeof value === "string" ? value : value.join(", ");
|
|
945
|
-
}
|
|
946
988
|
async function runPdfInspect(input, options) {
|
|
947
989
|
const command = "pdf-inspect";
|
|
948
990
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
@@ -984,19 +1026,9 @@ async function runPdfInspect(input, options) {
|
|
|
984
1026
|
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
985
1027
|
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
986
1028
|
});
|
|
987
|
-
|
|
988
|
-
["title", layout.metadata.title],
|
|
989
|
-
["author", layout.metadata.author],
|
|
990
|
-
["subject", layout.metadata.subject],
|
|
991
|
-
["keywords", layout.metadata.keywords],
|
|
992
|
-
["creator", layout.metadata.creator],
|
|
993
|
-
["producer", layout.metadata.producer],
|
|
994
|
-
["createdIso", layout.metadata.createdIso],
|
|
995
|
-
["modifiedIso", layout.metadata.modifiedIso]
|
|
996
|
-
].filter(isPresent);
|
|
997
|
-
if (presentMetadata.length > 0) {
|
|
1029
|
+
if (presentMetadataEntries(layout.metadata).length > 0) {
|
|
998
1030
|
process.stdout.write("metadata:\n");
|
|
999
|
-
for (const
|
|
1031
|
+
for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
|
|
1000
1032
|
}
|
|
1001
1033
|
if (imagesByFormat.size > 0) {
|
|
1002
1034
|
process.stdout.write("images:\n");
|
|
@@ -1014,8 +1046,155 @@ function registerPdfInspectCommand(program) {
|
|
|
1014
1046
|
});
|
|
1015
1047
|
}
|
|
1016
1048
|
//#endregion
|
|
1049
|
+
//#region src/commands/set-metadata.ts
|
|
1050
|
+
const REBUILD_FORMATS = {
|
|
1051
|
+
docx: true,
|
|
1052
|
+
pptx: true,
|
|
1053
|
+
odt: true,
|
|
1054
|
+
odp: true,
|
|
1055
|
+
ods: true,
|
|
1056
|
+
odg: true,
|
|
1057
|
+
markdown: true
|
|
1058
|
+
};
|
|
1059
|
+
function isRebuildFormat(format) {
|
|
1060
|
+
return format in REBUILD_FORMATS;
|
|
1061
|
+
}
|
|
1062
|
+
function readContentForFormat(format, bytes) {
|
|
1063
|
+
switch (format) {
|
|
1064
|
+
case "docx": return readDocxContent(decodePackage(bytes));
|
|
1065
|
+
case "pptx": return readPptxContent(decodePackage(bytes));
|
|
1066
|
+
case "odt": return readOdtContent(decodePackage$1(bytes));
|
|
1067
|
+
case "odp": return readOdpContent(decodePackage$1(bytes));
|
|
1068
|
+
case "ods": return readOdsContent(decodePackage$1(bytes));
|
|
1069
|
+
case "odg": return readOdgContent(decodePackage$1(bytes));
|
|
1070
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function buildBytesForRebuildFormat(format, content) {
|
|
1074
|
+
switch (format) {
|
|
1075
|
+
case "docx": return encodePackage(buildDocxPackage(content));
|
|
1076
|
+
case "pptx": return encodePackage(buildPptxPackage(content));
|
|
1077
|
+
case "odt": return encodePackage$1(buildOdtPackage(content));
|
|
1078
|
+
case "odp": return encodePackage$1(buildOdpPackage(content));
|
|
1079
|
+
case "ods": return encodePackage$1(buildOdsPackage(content));
|
|
1080
|
+
case "odg": return encodePackage$1(buildOdgPackage(content));
|
|
1081
|
+
case "markdown": return encodeMarkdownText(buildMarkdownText(content));
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function mergeMetadata(current, overrides) {
|
|
1085
|
+
return {
|
|
1086
|
+
...current,
|
|
1087
|
+
...overrides.title !== void 0 ? { title: overrides.title } : {},
|
|
1088
|
+
...overrides.author !== void 0 ? { author: overrides.author } : {},
|
|
1089
|
+
...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
|
|
1090
|
+
...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function parseKeywords(csv) {
|
|
1094
|
+
return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1095
|
+
}
|
|
1096
|
+
function classifyWritePath(source, target) {
|
|
1097
|
+
if (source === "pdf" && target === "pdf") return { kind: "pdf" };
|
|
1098
|
+
if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
|
|
1099
|
+
if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
|
|
1100
|
+
if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1101
|
+
if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1102
|
+
return {
|
|
1103
|
+
kind: "rebuild",
|
|
1104
|
+
format: source
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
async function runSetMetadata(input, output, options) {
|
|
1108
|
+
const command = "set-metadata";
|
|
1109
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
1110
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
1111
|
+
return 2;
|
|
1112
|
+
}
|
|
1113
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
1114
|
+
if ("errorMessage" in target) {
|
|
1115
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
1116
|
+
return 2;
|
|
1117
|
+
}
|
|
1118
|
+
const source = inferFormatFromExtension(input);
|
|
1119
|
+
if (source === void 0) {
|
|
1120
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
1121
|
+
return 2;
|
|
1122
|
+
}
|
|
1123
|
+
const writePath = classifyWritePath(source, target.format);
|
|
1124
|
+
if ("errorMessage" in writePath) {
|
|
1125
|
+
process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
|
|
1126
|
+
return 2;
|
|
1127
|
+
}
|
|
1128
|
+
const overrides = {
|
|
1129
|
+
title: options.setTitle,
|
|
1130
|
+
author: options.setAuthor,
|
|
1131
|
+
subject: options.setSubject,
|
|
1132
|
+
keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
|
|
1133
|
+
};
|
|
1134
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
1135
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
1136
|
+
try {
|
|
1137
|
+
const inputBytes = await readInput(input, { signal });
|
|
1138
|
+
const bytes = writePath.kind === "pdf" ? (() => {
|
|
1139
|
+
const layout = readPdf(new Uint8Array(inputBytes), { signal });
|
|
1140
|
+
const patched = {
|
|
1141
|
+
...layout,
|
|
1142
|
+
metadata: mergeMetadata(layout.metadata, overrides)
|
|
1143
|
+
};
|
|
1144
|
+
return writePdf(patched, { signal });
|
|
1145
|
+
})() : (() => {
|
|
1146
|
+
const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
|
|
1147
|
+
const nextContent = {
|
|
1148
|
+
...content,
|
|
1149
|
+
metadata: mergeMetadata(content.metadata, overrides)
|
|
1150
|
+
};
|
|
1151
|
+
return buildBytesForRebuildFormat(writePath.format, nextContent);
|
|
1152
|
+
})();
|
|
1153
|
+
await writeOutput(resolvedOutput, bytes);
|
|
1154
|
+
createDiagnosticReporter({
|
|
1155
|
+
json: options.json,
|
|
1156
|
+
quiet: options.quiet,
|
|
1157
|
+
command
|
|
1158
|
+
}).summarize({
|
|
1159
|
+
output: resolvedOutput,
|
|
1160
|
+
bytes: bytes.byteLength,
|
|
1161
|
+
diagnosticCount: 0
|
|
1162
|
+
});
|
|
1163
|
+
return 0;
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
1166
|
+
return mapErrorToExit(error, getAbortReason());
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function registerSetMetadataCommand(program) {
|
|
1170
|
+
const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
|
|
1171
|
+
"",
|
|
1172
|
+
"Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
|
|
1173
|
+
"involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
|
|
1174
|
+
"odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
|
|
1175
|
+
"this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
|
|
1176
|
+
"since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
|
|
1177
|
+
"",
|
|
1178
|
+
"set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
|
|
1179
|
+
"set-metadata on the result, if you need a different target format."
|
|
1180
|
+
].join("\n"));
|
|
1181
|
+
addOutOption(command);
|
|
1182
|
+
addTimeoutOption(command);
|
|
1183
|
+
addJsonOption(command);
|
|
1184
|
+
addQuietOption(command);
|
|
1185
|
+
addVerboseOption(command);
|
|
1186
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
1187
|
+
command.option("--set-title <text>", "set the title field");
|
|
1188
|
+
command.option("--set-author <text>", "set the author field");
|
|
1189
|
+
command.option("--set-subject <text>", "set the subject field");
|
|
1190
|
+
command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
|
|
1191
|
+
command.action(async (input, output, options) => {
|
|
1192
|
+
process.exitCode = await runSetMetadata(input, output, options);
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
//#endregion
|
|
1017
1196
|
//#region package.json
|
|
1018
|
-
var version = "1.
|
|
1197
|
+
var version = "1.5.0";
|
|
1019
1198
|
//#endregion
|
|
1020
1199
|
//#region src/program.ts
|
|
1021
1200
|
function createProgram() {
|
|
@@ -1034,13 +1213,15 @@ function createProgram() {
|
|
|
1034
1213
|
registerPdfInspectCommand(program);
|
|
1035
1214
|
registerFontsCommand(program);
|
|
1036
1215
|
registerDocxExtrasCommand(program);
|
|
1216
|
+
registerMetadataCommand(program);
|
|
1217
|
+
registerSetMetadataCommand(program);
|
|
1037
1218
|
return program;
|
|
1038
1219
|
}
|
|
1039
1220
|
//#endregion
|
|
1040
1221
|
//#region src/cli.ts
|
|
1041
1222
|
async function launchTui(startPath, signal) {
|
|
1042
1223
|
try {
|
|
1043
|
-
const { runTui } = await import("./tui-
|
|
1224
|
+
const { runTui } = await import("./tui-B-o3iI9a.js");
|
|
1044
1225
|
await runTui({
|
|
1045
1226
|
startPath,
|
|
1046
1227
|
signal
|
package/dist/index.cjs
CHANGED
|
@@ -684,7 +684,7 @@ function registerFontsCommand(program) {
|
|
|
684
684
|
}
|
|
685
685
|
//#endregion
|
|
686
686
|
//#region src/commands/formats.ts
|
|
687
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras";
|
|
687
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
|
|
688
688
|
function registerFormatsCommand(program) {
|
|
689
689
|
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) => {
|
|
690
690
|
const { conversions } = (0, documents_js.createLocalDocumentConverter)();
|
|
@@ -777,6 +777,78 @@ function registerFromPackageCommand(program) {
|
|
|
777
777
|
});
|
|
778
778
|
}
|
|
779
779
|
//#endregion
|
|
780
|
+
//#region src/runtime/metadata-format.ts
|
|
781
|
+
const METADATA_KEYS = [
|
|
782
|
+
"title",
|
|
783
|
+
"author",
|
|
784
|
+
"subject",
|
|
785
|
+
"keywords",
|
|
786
|
+
"creator",
|
|
787
|
+
"producer",
|
|
788
|
+
"createdIso",
|
|
789
|
+
"modifiedIso"
|
|
790
|
+
];
|
|
791
|
+
function isPresent(entry) {
|
|
792
|
+
return entry[1] !== void 0;
|
|
793
|
+
}
|
|
794
|
+
function formatMetadataValue(value) {
|
|
795
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
796
|
+
}
|
|
797
|
+
function presentMetadataEntries(metadata) {
|
|
798
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
799
|
+
}
|
|
800
|
+
function formatMetadataLines(metadata) {
|
|
801
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/commands/metadata.ts
|
|
805
|
+
function readMetadataForFormat(format, bytes, signal) {
|
|
806
|
+
switch (format) {
|
|
807
|
+
case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes)).metadata;
|
|
808
|
+
case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes)).metadata;
|
|
809
|
+
case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
810
|
+
case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
811
|
+
case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
812
|
+
case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
813
|
+
case "odf": return (0, documents_js.readOdfFormulaContent)((0, odf_js.decodePackage)(bytes)).metadata;
|
|
814
|
+
case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes)).metadata;
|
|
815
|
+
case "pdf": return (0, documents_js.readPdf)(bytes, { signal }).metadata;
|
|
816
|
+
case "xlsx": return (0, documents_js.readPdf)((0, documents_js.xlsxToPdf)(bytes, { signal }), { signal }).metadata;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
async function runMetadata(input, options) {
|
|
820
|
+
const command = "metadata";
|
|
821
|
+
const source = inferFormatFromExtension(input);
|
|
822
|
+
if (source === void 0) {
|
|
823
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
824
|
+
return 2;
|
|
825
|
+
}
|
|
826
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
827
|
+
try {
|
|
828
|
+
const inputBytes = await readInput(input, { signal });
|
|
829
|
+
const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
|
|
830
|
+
if (options.json) {
|
|
831
|
+
process.stdout.write(`${JSON.stringify(metadata)}\n`);
|
|
832
|
+
return 0;
|
|
833
|
+
}
|
|
834
|
+
const lines = formatMetadataLines(metadata);
|
|
835
|
+
if (lines.length === 0) {
|
|
836
|
+
process.stdout.write("This document carries no metadata.\n");
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
839
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
840
|
+
return 0;
|
|
841
|
+
} catch (error) {
|
|
842
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
843
|
+
return mapErrorToExit(error, getAbortReason());
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
function registerMetadataCommand(program) {
|
|
847
|
+
program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
|
|
848
|
+
process.exitCode = await runMetadata(input, options);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
780
852
|
//#region src/odb-structure.ts
|
|
781
853
|
const INDENT = " ";
|
|
782
854
|
function indent(depth) {
|
|
@@ -1380,12 +1452,6 @@ function countImagesByFormat(images) {
|
|
|
1380
1452
|
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
1381
1453
|
return counts;
|
|
1382
1454
|
}
|
|
1383
|
-
function isPresent(entry) {
|
|
1384
|
-
return entry[1] !== void 0;
|
|
1385
|
-
}
|
|
1386
|
-
function formatMetadataValue(value) {
|
|
1387
|
-
return typeof value === "string" ? value : value.join(", ");
|
|
1388
|
-
}
|
|
1389
1455
|
async function runPdfInspect(input, options) {
|
|
1390
1456
|
const command = "pdf-inspect";
|
|
1391
1457
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
@@ -1427,19 +1493,9 @@ async function runPdfInspect(input, options) {
|
|
|
1427
1493
|
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
1428
1494
|
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
1429
1495
|
});
|
|
1430
|
-
|
|
1431
|
-
["title", layout.metadata.title],
|
|
1432
|
-
["author", layout.metadata.author],
|
|
1433
|
-
["subject", layout.metadata.subject],
|
|
1434
|
-
["keywords", layout.metadata.keywords],
|
|
1435
|
-
["creator", layout.metadata.creator],
|
|
1436
|
-
["producer", layout.metadata.producer],
|
|
1437
|
-
["createdIso", layout.metadata.createdIso],
|
|
1438
|
-
["modifiedIso", layout.metadata.modifiedIso]
|
|
1439
|
-
].filter(isPresent);
|
|
1440
|
-
if (presentMetadata.length > 0) {
|
|
1496
|
+
if (presentMetadataEntries(layout.metadata).length > 0) {
|
|
1441
1497
|
process.stdout.write("metadata:\n");
|
|
1442
|
-
for (const
|
|
1498
|
+
for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
|
|
1443
1499
|
}
|
|
1444
1500
|
if (imagesByFormat.size > 0) {
|
|
1445
1501
|
process.stdout.write("images:\n");
|
|
@@ -1457,8 +1513,155 @@ function registerPdfInspectCommand(program) {
|
|
|
1457
1513
|
});
|
|
1458
1514
|
}
|
|
1459
1515
|
//#endregion
|
|
1516
|
+
//#region src/commands/set-metadata.ts
|
|
1517
|
+
const REBUILD_FORMATS = {
|
|
1518
|
+
docx: true,
|
|
1519
|
+
pptx: true,
|
|
1520
|
+
odt: true,
|
|
1521
|
+
odp: true,
|
|
1522
|
+
ods: true,
|
|
1523
|
+
odg: true,
|
|
1524
|
+
markdown: true
|
|
1525
|
+
};
|
|
1526
|
+
function isRebuildFormat(format) {
|
|
1527
|
+
return format in REBUILD_FORMATS;
|
|
1528
|
+
}
|
|
1529
|
+
function readContentForFormat(format, bytes) {
|
|
1530
|
+
switch (format) {
|
|
1531
|
+
case "docx": return (0, documents_js.readDocxContent)((0, documents_js.decodePackage)(bytes));
|
|
1532
|
+
case "pptx": return (0, documents_js.readPptxContent)((0, documents_js.decodePackage)(bytes));
|
|
1533
|
+
case "odt": return (0, documents_js.readOdtContent)((0, odf_js.decodePackage)(bytes));
|
|
1534
|
+
case "odp": return (0, documents_js.readOdpContent)((0, odf_js.decodePackage)(bytes));
|
|
1535
|
+
case "ods": return (0, documents_js.readOdsContent)((0, odf_js.decodePackage)(bytes));
|
|
1536
|
+
case "odg": return (0, documents_js.readOdgContent)((0, odf_js.decodePackage)(bytes));
|
|
1537
|
+
case "markdown": return (0, documents_js.readMarkdownContent)((0, documents_js.decodeMarkdownText)(bytes));
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function buildBytesForRebuildFormat(format, content) {
|
|
1541
|
+
switch (format) {
|
|
1542
|
+
case "docx": return (0, documents_js.encodePackage)((0, documents_js.buildDocxPackage)(content));
|
|
1543
|
+
case "pptx": return (0, documents_js.encodePackage)((0, documents_js.buildPptxPackage)(content));
|
|
1544
|
+
case "odt": return (0, odf_js.encodePackage)((0, documents_js.buildOdtPackage)(content));
|
|
1545
|
+
case "odp": return (0, odf_js.encodePackage)((0, documents_js.buildOdpPackage)(content));
|
|
1546
|
+
case "ods": return (0, odf_js.encodePackage)((0, documents_js.buildOdsPackage)(content));
|
|
1547
|
+
case "odg": return (0, odf_js.encodePackage)((0, documents_js.buildOdgPackage)(content));
|
|
1548
|
+
case "markdown": return (0, documents_js.encodeMarkdownText)((0, documents_js.buildMarkdownText)(content));
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function mergeMetadata(current, overrides) {
|
|
1552
|
+
return {
|
|
1553
|
+
...current,
|
|
1554
|
+
...overrides.title !== void 0 ? { title: overrides.title } : {},
|
|
1555
|
+
...overrides.author !== void 0 ? { author: overrides.author } : {},
|
|
1556
|
+
...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
|
|
1557
|
+
...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
function parseKeywords(csv) {
|
|
1561
|
+
return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1562
|
+
}
|
|
1563
|
+
function classifyWritePath(source, target) {
|
|
1564
|
+
if (source === "pdf" && target === "pdf") return { kind: "pdf" };
|
|
1565
|
+
if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
|
|
1566
|
+
if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
|
|
1567
|
+
if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1568
|
+
if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1569
|
+
return {
|
|
1570
|
+
kind: "rebuild",
|
|
1571
|
+
format: source
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
async function runSetMetadata(input, output, options) {
|
|
1575
|
+
const command = "set-metadata";
|
|
1576
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
1577
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
1578
|
+
return 2;
|
|
1579
|
+
}
|
|
1580
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
1581
|
+
if ("errorMessage" in target) {
|
|
1582
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
1583
|
+
return 2;
|
|
1584
|
+
}
|
|
1585
|
+
const source = inferFormatFromExtension(input);
|
|
1586
|
+
if (source === void 0) {
|
|
1587
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
1588
|
+
return 2;
|
|
1589
|
+
}
|
|
1590
|
+
const writePath = classifyWritePath(source, target.format);
|
|
1591
|
+
if ("errorMessage" in writePath) {
|
|
1592
|
+
process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
|
|
1593
|
+
return 2;
|
|
1594
|
+
}
|
|
1595
|
+
const overrides = {
|
|
1596
|
+
title: options.setTitle,
|
|
1597
|
+
author: options.setAuthor,
|
|
1598
|
+
subject: options.setSubject,
|
|
1599
|
+
keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
|
|
1600
|
+
};
|
|
1601
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
1602
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
1603
|
+
try {
|
|
1604
|
+
const inputBytes = await readInput(input, { signal });
|
|
1605
|
+
const bytes = writePath.kind === "pdf" ? (() => {
|
|
1606
|
+
const layout = (0, documents_js.readPdf)(new Uint8Array(inputBytes), { signal });
|
|
1607
|
+
const patched = {
|
|
1608
|
+
...layout,
|
|
1609
|
+
metadata: mergeMetadata(layout.metadata, overrides)
|
|
1610
|
+
};
|
|
1611
|
+
return (0, documents_js.writePdf)(patched, { signal });
|
|
1612
|
+
})() : (() => {
|
|
1613
|
+
const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
|
|
1614
|
+
const nextContent = {
|
|
1615
|
+
...content,
|
|
1616
|
+
metadata: mergeMetadata(content.metadata, overrides)
|
|
1617
|
+
};
|
|
1618
|
+
return buildBytesForRebuildFormat(writePath.format, nextContent);
|
|
1619
|
+
})();
|
|
1620
|
+
await writeOutput(resolvedOutput, bytes);
|
|
1621
|
+
createDiagnosticReporter({
|
|
1622
|
+
json: options.json,
|
|
1623
|
+
quiet: options.quiet,
|
|
1624
|
+
command
|
|
1625
|
+
}).summarize({
|
|
1626
|
+
output: resolvedOutput,
|
|
1627
|
+
bytes: bytes.byteLength,
|
|
1628
|
+
diagnosticCount: 0
|
|
1629
|
+
});
|
|
1630
|
+
return 0;
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
1633
|
+
return mapErrorToExit(error, getAbortReason());
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
function registerSetMetadataCommand(program) {
|
|
1637
|
+
const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
|
|
1638
|
+
"",
|
|
1639
|
+
"Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
|
|
1640
|
+
"involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
|
|
1641
|
+
"odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
|
|
1642
|
+
"this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
|
|
1643
|
+
"since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
|
|
1644
|
+
"",
|
|
1645
|
+
"set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
|
|
1646
|
+
"set-metadata on the result, if you need a different target format."
|
|
1647
|
+
].join("\n"));
|
|
1648
|
+
addOutOption(command);
|
|
1649
|
+
addTimeoutOption(command);
|
|
1650
|
+
addJsonOption(command);
|
|
1651
|
+
addQuietOption(command);
|
|
1652
|
+
addVerboseOption(command);
|
|
1653
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
1654
|
+
command.option("--set-title <text>", "set the title field");
|
|
1655
|
+
command.option("--set-author <text>", "set the author field");
|
|
1656
|
+
command.option("--set-subject <text>", "set the subject field");
|
|
1657
|
+
command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
|
|
1658
|
+
command.action(async (input, output, options) => {
|
|
1659
|
+
process.exitCode = await runSetMetadata(input, output, options);
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
//#endregion
|
|
1460
1663
|
//#region package.json
|
|
1461
|
-
var version = "1.
|
|
1664
|
+
var version = "1.5.0";
|
|
1462
1665
|
//#endregion
|
|
1463
1666
|
//#region src/program.ts
|
|
1464
1667
|
function createProgram() {
|
|
@@ -1477,6 +1680,8 @@ function createProgram() {
|
|
|
1477
1680
|
registerPdfInspectCommand(program);
|
|
1478
1681
|
registerFontsCommand(program);
|
|
1479
1682
|
registerDocxExtrasCommand(program);
|
|
1683
|
+
registerMetadataCommand(program);
|
|
1684
|
+
registerSetMetadataCommand(program);
|
|
1480
1685
|
return program;
|
|
1481
1686
|
}
|
|
1482
1687
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxExtras, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
|
|
2
|
+
import { HsqldbSqlEvaluationError, HsqldbSqlParseError, HsqldbSqlUnsupportedError, OdbNoEmbeddedDataSourceError, OdbReportNotSpecifiedError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, convertWordprocessingToLayout, createFontMeasurer, createFontRegistry, createLocalDocumentConverter, decodeMarkdownText, decodePackage, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, extractSourceFonts, hsqldbCellDisplayText, layoutDocumentWithSchema, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readOdfFormulaContent, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, writePdf, xlsxToPdf } from "documents.js";
|
|
3
3
|
import { basename, dirname, extname, join } from "node:path";
|
|
4
4
|
import { Command, InvalidArgumentError } from "commander";
|
|
5
5
|
import { decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
|
|
@@ -683,7 +683,7 @@ function registerFontsCommand(program) {
|
|
|
683
683
|
}
|
|
684
684
|
//#endregion
|
|
685
685
|
//#region src/commands/formats.ts
|
|
686
|
-
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras";
|
|
686
|
+
const COMMANDS_NOT_LISTED = "odm-to-pdf, odb-to-csv, odb-to-xlsx, odb-tables, odb-forms, odb-reports, pdf-inspect, from-package, fonts, docx-extras, metadata, set-metadata";
|
|
687
687
|
function registerFormatsCommand(program) {
|
|
688
688
|
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) => {
|
|
689
689
|
const { conversions } = createLocalDocumentConverter();
|
|
@@ -776,6 +776,78 @@ function registerFromPackageCommand(program) {
|
|
|
776
776
|
});
|
|
777
777
|
}
|
|
778
778
|
//#endregion
|
|
779
|
+
//#region src/runtime/metadata-format.ts
|
|
780
|
+
const METADATA_KEYS = [
|
|
781
|
+
"title",
|
|
782
|
+
"author",
|
|
783
|
+
"subject",
|
|
784
|
+
"keywords",
|
|
785
|
+
"creator",
|
|
786
|
+
"producer",
|
|
787
|
+
"createdIso",
|
|
788
|
+
"modifiedIso"
|
|
789
|
+
];
|
|
790
|
+
function isPresent(entry) {
|
|
791
|
+
return entry[1] !== void 0;
|
|
792
|
+
}
|
|
793
|
+
function formatMetadataValue(value) {
|
|
794
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
795
|
+
}
|
|
796
|
+
function presentMetadataEntries(metadata) {
|
|
797
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
798
|
+
}
|
|
799
|
+
function formatMetadataLines(metadata) {
|
|
800
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
801
|
+
}
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/commands/metadata.ts
|
|
804
|
+
function readMetadataForFormat(format, bytes, signal) {
|
|
805
|
+
switch (format) {
|
|
806
|
+
case "docx": return readDocxContent(decodePackage(bytes)).metadata;
|
|
807
|
+
case "pptx": return readPptxContent(decodePackage(bytes)).metadata;
|
|
808
|
+
case "odt": return readOdtContent(decodePackage$1(bytes)).metadata;
|
|
809
|
+
case "odp": return readOdpContent(decodePackage$1(bytes)).metadata;
|
|
810
|
+
case "ods": return readOdsContent(decodePackage$1(bytes)).metadata;
|
|
811
|
+
case "odg": return readOdgContent(decodePackage$1(bytes)).metadata;
|
|
812
|
+
case "odf": return readOdfFormulaContent(decodePackage$1(bytes)).metadata;
|
|
813
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes)).metadata;
|
|
814
|
+
case "pdf": return readPdf(bytes, { signal }).metadata;
|
|
815
|
+
case "xlsx": return readPdf(xlsxToPdf(bytes, { signal }), { signal }).metadata;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function runMetadata(input, options) {
|
|
819
|
+
const command = "metadata";
|
|
820
|
+
const source = inferFormatFromExtension(input);
|
|
821
|
+
if (source === void 0) {
|
|
822
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
823
|
+
return 2;
|
|
824
|
+
}
|
|
825
|
+
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
826
|
+
try {
|
|
827
|
+
const inputBytes = await readInput(input, { signal });
|
|
828
|
+
const metadata = readMetadataForFormat(source, new Uint8Array(inputBytes), signal);
|
|
829
|
+
if (options.json) {
|
|
830
|
+
process.stdout.write(`${JSON.stringify(metadata)}\n`);
|
|
831
|
+
return 0;
|
|
832
|
+
}
|
|
833
|
+
const lines = formatMetadataLines(metadata);
|
|
834
|
+
if (lines.length === 0) {
|
|
835
|
+
process.stdout.write("This document carries no metadata.\n");
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
838
|
+
for (const line of lines) process.stdout.write(`${line}\n`);
|
|
839
|
+
return 0;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
process.stderr.write(`[${command}] ${formatError(error, false)}\n`);
|
|
842
|
+
return mapErrorToExit(error, getAbortReason());
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function registerMetadataCommand(program) {
|
|
846
|
+
program.command("metadata <input>").description(`print a document's own title/author/subject/keywords/creator/producer/created/modified metadata (${KNOWN_DOCUMENT_FORMATS})`).option("--json", "emit the metadata as a JSON object instead of a human-readable report", false).action(async (input, options) => {
|
|
847
|
+
process.exitCode = await runMetadata(input, options);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
//#endregion
|
|
779
851
|
//#region src/odb-structure.ts
|
|
780
852
|
const INDENT = " ";
|
|
781
853
|
function indent(depth) {
|
|
@@ -1379,12 +1451,6 @@ function countImagesByFormat(images) {
|
|
|
1379
1451
|
for (const asset of Object.values(images)) counts.set(asset.format, (counts.get(asset.format) ?? 0) + 1);
|
|
1380
1452
|
return counts;
|
|
1381
1453
|
}
|
|
1382
|
-
function isPresent(entry) {
|
|
1383
|
-
return entry[1] !== void 0;
|
|
1384
|
-
}
|
|
1385
|
-
function formatMetadataValue(value) {
|
|
1386
|
-
return typeof value === "string" ? value : value.join(", ");
|
|
1387
|
-
}
|
|
1388
1454
|
async function runPdfInspect(input, options) {
|
|
1389
1455
|
const command = "pdf-inspect";
|
|
1390
1456
|
const { signal, getAbortReason } = createRuntimeSignal({});
|
|
@@ -1426,19 +1492,9 @@ async function runPdfInspect(input, options) {
|
|
|
1426
1492
|
const histogramText = Array.from(histogram.entries()).map(([kind, count]) => `${kind}=${count}`).join(", ");
|
|
1427
1493
|
process.stdout.write(` page ${index + 1}: ${page.widthPt}pt x ${page.heightPt}pt${histogramText === "" ? "" : ` (${histogramText})`}\n`);
|
|
1428
1494
|
});
|
|
1429
|
-
|
|
1430
|
-
["title", layout.metadata.title],
|
|
1431
|
-
["author", layout.metadata.author],
|
|
1432
|
-
["subject", layout.metadata.subject],
|
|
1433
|
-
["keywords", layout.metadata.keywords],
|
|
1434
|
-
["creator", layout.metadata.creator],
|
|
1435
|
-
["producer", layout.metadata.producer],
|
|
1436
|
-
["createdIso", layout.metadata.createdIso],
|
|
1437
|
-
["modifiedIso", layout.metadata.modifiedIso]
|
|
1438
|
-
].filter(isPresent);
|
|
1439
|
-
if (presentMetadata.length > 0) {
|
|
1495
|
+
if (presentMetadataEntries(layout.metadata).length > 0) {
|
|
1440
1496
|
process.stdout.write("metadata:\n");
|
|
1441
|
-
for (const
|
|
1497
|
+
for (const line of formatMetadataLines(layout.metadata)) process.stdout.write(` ${line}\n`);
|
|
1442
1498
|
}
|
|
1443
1499
|
if (imagesByFormat.size > 0) {
|
|
1444
1500
|
process.stdout.write("images:\n");
|
|
@@ -1456,8 +1512,155 @@ function registerPdfInspectCommand(program) {
|
|
|
1456
1512
|
});
|
|
1457
1513
|
}
|
|
1458
1514
|
//#endregion
|
|
1515
|
+
//#region src/commands/set-metadata.ts
|
|
1516
|
+
const REBUILD_FORMATS = {
|
|
1517
|
+
docx: true,
|
|
1518
|
+
pptx: true,
|
|
1519
|
+
odt: true,
|
|
1520
|
+
odp: true,
|
|
1521
|
+
ods: true,
|
|
1522
|
+
odg: true,
|
|
1523
|
+
markdown: true
|
|
1524
|
+
};
|
|
1525
|
+
function isRebuildFormat(format) {
|
|
1526
|
+
return format in REBUILD_FORMATS;
|
|
1527
|
+
}
|
|
1528
|
+
function readContentForFormat(format, bytes) {
|
|
1529
|
+
switch (format) {
|
|
1530
|
+
case "docx": return readDocxContent(decodePackage(bytes));
|
|
1531
|
+
case "pptx": return readPptxContent(decodePackage(bytes));
|
|
1532
|
+
case "odt": return readOdtContent(decodePackage$1(bytes));
|
|
1533
|
+
case "odp": return readOdpContent(decodePackage$1(bytes));
|
|
1534
|
+
case "ods": return readOdsContent(decodePackage$1(bytes));
|
|
1535
|
+
case "odg": return readOdgContent(decodePackage$1(bytes));
|
|
1536
|
+
case "markdown": return readMarkdownContent(decodeMarkdownText(bytes));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function buildBytesForRebuildFormat(format, content) {
|
|
1540
|
+
switch (format) {
|
|
1541
|
+
case "docx": return encodePackage(buildDocxPackage(content));
|
|
1542
|
+
case "pptx": return encodePackage(buildPptxPackage(content));
|
|
1543
|
+
case "odt": return encodePackage$1(buildOdtPackage(content));
|
|
1544
|
+
case "odp": return encodePackage$1(buildOdpPackage(content));
|
|
1545
|
+
case "ods": return encodePackage$1(buildOdsPackage(content));
|
|
1546
|
+
case "odg": return encodePackage$1(buildOdgPackage(content));
|
|
1547
|
+
case "markdown": return encodeMarkdownText(buildMarkdownText(content));
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function mergeMetadata(current, overrides) {
|
|
1551
|
+
return {
|
|
1552
|
+
...current,
|
|
1553
|
+
...overrides.title !== void 0 ? { title: overrides.title } : {},
|
|
1554
|
+
...overrides.author !== void 0 ? { author: overrides.author } : {},
|
|
1555
|
+
...overrides.subject !== void 0 ? { subject: overrides.subject } : {},
|
|
1556
|
+
...overrides.keywords !== void 0 ? { keywords: overrides.keywords } : {}
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
function parseKeywords(csv) {
|
|
1560
|
+
return csv.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1561
|
+
}
|
|
1562
|
+
function classifyWritePath(source, target) {
|
|
1563
|
+
if (source === "pdf" && target === "pdf") return { kind: "pdf" };
|
|
1564
|
+
if (target === "xlsx" || source === "xlsx") return { errorMessage: "'xlsx' is not a supported set-metadata source or target -- documents.js does not re-export a ContentDocument-to-xlsx builder or a readXlsxContent from its own public surface (see that package's own README, Architecture section); convert with 'xlsx-to-ods'/'ods-to-xlsx' first, then set metadata on the ods" };
|
|
1565
|
+
if (target === "odf" || source === "odf") return { errorMessage: "'odf' (a standalone formula document) is not a supported set-metadata source or target -- it has no write path back out at all" };
|
|
1566
|
+
if (!isRebuildFormat(source) || !isRebuildFormat(target)) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format (or both 'pdf'). Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1567
|
+
if (source !== target) return { errorMessage: `set-metadata only patches metadata in place; it does not convert format -- source ('${source}') and target ('${target}') must be the same format. Run 'convert'/'from-package' first if you need a different target format.` };
|
|
1568
|
+
return {
|
|
1569
|
+
kind: "rebuild",
|
|
1570
|
+
format: source
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
async function runSetMetadata(input, output, options) {
|
|
1574
|
+
const command = "set-metadata";
|
|
1575
|
+
if (output !== void 0 && options.out !== void 0 && output !== options.out) {
|
|
1576
|
+
process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
|
|
1577
|
+
return 2;
|
|
1578
|
+
}
|
|
1579
|
+
const target = resolveTargetFormat(output, options.out, options.to);
|
|
1580
|
+
if ("errorMessage" in target) {
|
|
1581
|
+
process.stderr.write(`[${command}] ${target.errorMessage}\n`);
|
|
1582
|
+
return 2;
|
|
1583
|
+
}
|
|
1584
|
+
const source = inferFormatFromExtension(input);
|
|
1585
|
+
if (source === void 0) {
|
|
1586
|
+
process.stderr.write(`[${command}] cannot infer a source format from '${input}'; rename the file with a recognised extension (${KNOWN_DOCUMENT_FORMATS})\n`);
|
|
1587
|
+
return 2;
|
|
1588
|
+
}
|
|
1589
|
+
const writePath = classifyWritePath(source, target.format);
|
|
1590
|
+
if ("errorMessage" in writePath) {
|
|
1591
|
+
process.stderr.write(`[${command}] ${writePath.errorMessage}\n`);
|
|
1592
|
+
return 2;
|
|
1593
|
+
}
|
|
1594
|
+
const overrides = {
|
|
1595
|
+
title: options.setTitle,
|
|
1596
|
+
author: options.setAuthor,
|
|
1597
|
+
subject: options.setSubject,
|
|
1598
|
+
keywords: options.setKeywords === void 0 ? void 0 : parseKeywords(options.setKeywords)
|
|
1599
|
+
};
|
|
1600
|
+
const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, target.format));
|
|
1601
|
+
const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
|
|
1602
|
+
try {
|
|
1603
|
+
const inputBytes = await readInput(input, { signal });
|
|
1604
|
+
const bytes = writePath.kind === "pdf" ? (() => {
|
|
1605
|
+
const layout = readPdf(new Uint8Array(inputBytes), { signal });
|
|
1606
|
+
const patched = {
|
|
1607
|
+
...layout,
|
|
1608
|
+
metadata: mergeMetadata(layout.metadata, overrides)
|
|
1609
|
+
};
|
|
1610
|
+
return writePdf(patched, { signal });
|
|
1611
|
+
})() : (() => {
|
|
1612
|
+
const content = readContentForFormat(writePath.format, new Uint8Array(inputBytes));
|
|
1613
|
+
const nextContent = {
|
|
1614
|
+
...content,
|
|
1615
|
+
metadata: mergeMetadata(content.metadata, overrides)
|
|
1616
|
+
};
|
|
1617
|
+
return buildBytesForRebuildFormat(writePath.format, nextContent);
|
|
1618
|
+
})();
|
|
1619
|
+
await writeOutput(resolvedOutput, bytes);
|
|
1620
|
+
createDiagnosticReporter({
|
|
1621
|
+
json: options.json,
|
|
1622
|
+
quiet: options.quiet,
|
|
1623
|
+
command
|
|
1624
|
+
}).summarize({
|
|
1625
|
+
output: resolvedOutput,
|
|
1626
|
+
bytes: bytes.byteLength,
|
|
1627
|
+
diagnosticCount: 0
|
|
1628
|
+
});
|
|
1629
|
+
return 0;
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
process.stderr.write(`${formatError(error, options.verbose)}\n`);
|
|
1632
|
+
return mapErrorToExit(error, getAbortReason());
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
function registerSetMetadataCommand(program) {
|
|
1636
|
+
const command = program.command("set-metadata <input> [output]").description("patch a document's own title/author/subject/keywords, leaving every other field and every other flag as-is").addHelpText("after", [
|
|
1637
|
+
"",
|
|
1638
|
+
"Two write paths: a pdf source/target patches the metadata directly on the parsed PDF (writePdf), with no layout engine",
|
|
1639
|
+
"involved at all -- genuinely lossless for everything else on the page. Every other supported format (docx, pptx, odt,",
|
|
1640
|
+
"odp, ods, odg, markdown) rebuilds a fresh package from that format's own ContentDocument -- for docx specifically,",
|
|
1641
|
+
"this is LOSSY: it drops anything docx-extras covers (comments, footnotes, headers/footers, numbering definitions),",
|
|
1642
|
+
"since buildDocxPackage builds a fresh package from the ContentDocument alone, with no way to carry that data through.",
|
|
1643
|
+
"",
|
|
1644
|
+
"set-metadata does not convert format -- source and target must match. Run convert/from-package first, then",
|
|
1645
|
+
"set-metadata on the result, if you need a different target format."
|
|
1646
|
+
].join("\n"));
|
|
1647
|
+
addOutOption(command);
|
|
1648
|
+
addTimeoutOption(command);
|
|
1649
|
+
addJsonOption(command);
|
|
1650
|
+
addQuietOption(command);
|
|
1651
|
+
addVerboseOption(command);
|
|
1652
|
+
command.option("--to <format>", `target format when it cannot be inferred from the output path (${KNOWN_DOCUMENT_FORMATS})`);
|
|
1653
|
+
command.option("--set-title <text>", "set the title field");
|
|
1654
|
+
command.option("--set-author <text>", "set the author field");
|
|
1655
|
+
command.option("--set-subject <text>", "set the subject field");
|
|
1656
|
+
command.option("--set-keywords <csv>", "set the keywords field, comma-separated (trimmed, empty entries dropped)");
|
|
1657
|
+
command.action(async (input, output, options) => {
|
|
1658
|
+
process.exitCode = await runSetMetadata(input, output, options);
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
//#endregion
|
|
1459
1662
|
//#region package.json
|
|
1460
|
-
var version = "1.
|
|
1663
|
+
var version = "1.5.0";
|
|
1461
1664
|
//#endregion
|
|
1462
1665
|
//#region src/program.ts
|
|
1463
1666
|
function createProgram() {
|
|
@@ -1476,6 +1679,8 @@ function createProgram() {
|
|
|
1476
1679
|
registerPdfInspectCommand(program);
|
|
1477
1680
|
registerFontsCommand(program);
|
|
1478
1681
|
registerDocxExtrasCommand(program);
|
|
1682
|
+
registerMetadataCommand(program);
|
|
1683
|
+
registerSetMetadataCommand(program);
|
|
1479
1684
|
return program;
|
|
1480
1685
|
}
|
|
1481
1686
|
//#endregion
|
|
@@ -305,6 +305,30 @@ function formatDocxExtrasLines(extras) {
|
|
|
305
305
|
return nonEmptySections.flatMap((section, index) => index === 0 ? section : ["", ...section]);
|
|
306
306
|
}
|
|
307
307
|
//#endregion
|
|
308
|
+
//#region src/runtime/metadata-format.ts
|
|
309
|
+
const METADATA_KEYS = [
|
|
310
|
+
"title",
|
|
311
|
+
"author",
|
|
312
|
+
"subject",
|
|
313
|
+
"keywords",
|
|
314
|
+
"creator",
|
|
315
|
+
"producer",
|
|
316
|
+
"createdIso",
|
|
317
|
+
"modifiedIso"
|
|
318
|
+
];
|
|
319
|
+
function isPresent(entry) {
|
|
320
|
+
return entry[1] !== void 0;
|
|
321
|
+
}
|
|
322
|
+
function formatMetadataValue(value) {
|
|
323
|
+
return typeof value === "string" ? value : value.join(", ");
|
|
324
|
+
}
|
|
325
|
+
function presentMetadataEntries(metadata) {
|
|
326
|
+
return METADATA_KEYS.map((key) => [key, metadata[key]]).filter(isPresent);
|
|
327
|
+
}
|
|
328
|
+
function formatMetadataLines(metadata) {
|
|
329
|
+
return presentMetadataEntries(metadata).map(([key, value]) => `${key}: ${formatMetadataValue(value)}`);
|
|
330
|
+
}
|
|
331
|
+
//#endregion
|
|
308
332
|
//#region src/odb-structure.ts
|
|
309
333
|
const INDENT = " ";
|
|
310
334
|
function indent(depth) {
|
|
@@ -437,4 +461,4 @@ function formatOdbReportLines(report) {
|
|
|
437
461
|
return lines;
|
|
438
462
|
}
|
|
439
463
|
//#endregion
|
|
440
|
-
export { odbFormSummary as a,
|
|
464
|
+
export { odbFormSummary as a, formatDocxExtrasLines as c, writeOutput as d, loadProvidedFonts as f, isDocumentFormat as h, formatOdbReportLines as i, readInput as l, inferFormatFromExtension as m, describeOdbReport as n, formatMetadataLines as o, formatToExtension as p, formatOdbFormLines as r, presentMetadataEntries as s, describeOdbForm as t, resolveDefaultOutputPath as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as formatDocxExtrasLines, f as loadProvidedFonts, i as formatOdbReportLines, l as readInput, m as inferFormatFromExtension, n as describeOdbReport, o as formatMetadataLines, p as formatToExtension, r as formatOdbFormLines, t as describeOdbForm } from "./odb-structure-Du1w_hOU.js";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readDocxExtras, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
|
|
3
|
+
import { buildDocxPackage, buildOdtPackage, convertWordprocessingToLayout, createDocx, createFontMeasurer, createFontRegistry, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, encodePackage, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readDocxContent, readDocxExtras, readMarkdownContent, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdpContent, readOdsContent, readOdtContent, readPdf, readPptxContent, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
|
|
4
4
|
import { basename, dirname, extname, join } from "node:path";
|
|
5
5
|
import { cellReference, columnIndexToLetters, decodePackage as decodePackage$1, encodePackage as encodePackage$1 } from "odf.js";
|
|
6
6
|
import { readdirSync } from "node:fs";
|
|
@@ -219,7 +219,8 @@ function selectionKeyFor(screen) {
|
|
|
219
219
|
case "pdfPageList":
|
|
220
220
|
case "exportOptions":
|
|
221
221
|
case "saveAsPrompt":
|
|
222
|
-
case "markdownLineList":
|
|
222
|
+
case "markdownLineList":
|
|
223
|
+
case "metadata": return screen.kind;
|
|
223
224
|
case "filePicker": return `filePicker:${screen.purpose}:${screen.cwd}`;
|
|
224
225
|
case "paragraphDetail":
|
|
225
226
|
case "tableView":
|
|
@@ -1417,6 +1418,10 @@ const GLOBAL_KEYS = [
|
|
|
1417
1418
|
keys: "a",
|
|
1418
1419
|
description: "Append a new item to the current list"
|
|
1419
1420
|
},
|
|
1421
|
+
{
|
|
1422
|
+
keys: "m",
|
|
1423
|
+
description: "Show the open document's metadata (read-only)"
|
|
1424
|
+
},
|
|
1420
1425
|
{
|
|
1421
1426
|
keys: "Ctrl+S",
|
|
1422
1427
|
description: "Save the open document"
|
|
@@ -6240,6 +6245,67 @@ function SaveAsPromptScreen() {
|
|
|
6240
6245
|
});
|
|
6241
6246
|
}
|
|
6242
6247
|
//#endregion
|
|
6248
|
+
//#region src/tui/format/read-metadata.ts
|
|
6249
|
+
function metadataFor(doc) {
|
|
6250
|
+
switch (doc.format) {
|
|
6251
|
+
case "docx": return readDocxContent(doc.editor.toPackage()).metadata;
|
|
6252
|
+
case "pptx": return readPptxContent(doc.editor.toPackage()).metadata;
|
|
6253
|
+
case "odt": return readOdtContent(doc.editor.toPackage()).metadata;
|
|
6254
|
+
case "odp": return readOdpContent(doc.editor.toPackage()).metadata;
|
|
6255
|
+
case "ods": return readOdsContent(doc.editor.toPackage()).metadata;
|
|
6256
|
+
case "odg": return readOdgContent(doc.editor.toPackage()).metadata;
|
|
6257
|
+
case "markdown": return readMarkdownContent(doc.source).metadata;
|
|
6258
|
+
case "pdf":
|
|
6259
|
+
case "xlsx": return doc.layout.metadata;
|
|
6260
|
+
case "odb": throw new Error("A .odb database has no document-level metadata -- it is a table/form/report container, not a single document with its own title/author/etc.");
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
//#endregion
|
|
6264
|
+
//#region src/tui/screens/shared/metadata.tsx
|
|
6265
|
+
function MetadataScreen() {
|
|
6266
|
+
const state = useAppState();
|
|
6267
|
+
const dispatch = useAppDispatch();
|
|
6268
|
+
const doc = state.openDocument;
|
|
6269
|
+
useInput((input, key) => {
|
|
6270
|
+
if (key.escape || key.leftArrow || input === "h") dispatch({ type: "POP_SCREEN" });
|
|
6271
|
+
}, { isActive: doc !== void 0 && !anyOverlayOpen(state) });
|
|
6272
|
+
if (doc === void 0) return /* @__PURE__ */ jsx(Text, {
|
|
6273
|
+
color: "red",
|
|
6274
|
+
children: "MetadataScreen requires an open document."
|
|
6275
|
+
});
|
|
6276
|
+
let lines;
|
|
6277
|
+
let errorMessage;
|
|
6278
|
+
try {
|
|
6279
|
+
lines = formatMetadataLines(metadataFor(doc));
|
|
6280
|
+
} catch (error) {
|
|
6281
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
6282
|
+
}
|
|
6283
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
6284
|
+
flexDirection: "column",
|
|
6285
|
+
children: [
|
|
6286
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
6287
|
+
bold: true,
|
|
6288
|
+
children: [
|
|
6289
|
+
"Document metadata (",
|
|
6290
|
+
doc.format,
|
|
6291
|
+
")"
|
|
6292
|
+
]
|
|
6293
|
+
}),
|
|
6294
|
+
errorMessage !== void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
6295
|
+
color: "yellow",
|
|
6296
|
+
children: errorMessage
|
|
6297
|
+
}) : lines !== void 0 && lines.length > 0 ? lines.map((line) => /* @__PURE__ */ jsx(Text, { children: line }, line)) : /* @__PURE__ */ jsx(Text, {
|
|
6298
|
+
dimColor: true,
|
|
6299
|
+
children: "This document carries no metadata."
|
|
6300
|
+
}),
|
|
6301
|
+
/* @__PURE__ */ jsx(Text, {
|
|
6302
|
+
dimColor: true,
|
|
6303
|
+
children: "Esc / ← / h to go back"
|
|
6304
|
+
})
|
|
6305
|
+
]
|
|
6306
|
+
});
|
|
6307
|
+
}
|
|
6308
|
+
//#endregion
|
|
6243
6309
|
//#region src/tui/app.tsx
|
|
6244
6310
|
function App(props) {
|
|
6245
6311
|
return /* @__PURE__ */ jsx(AppStateProvider, {
|
|
@@ -6285,6 +6351,7 @@ function ScreenBody({ screen }) {
|
|
|
6285
6351
|
case "pdfItemDetail": return /* @__PURE__ */ jsx(PdfItemDetailScreen, {});
|
|
6286
6352
|
case "exportOptions": return /* @__PURE__ */ jsx(ExportOptionsScreen, {});
|
|
6287
6353
|
case "saveAsPrompt": return /* @__PURE__ */ jsx(SaveAsPromptScreen, {});
|
|
6354
|
+
case "metadata": return /* @__PURE__ */ jsx(MetadataScreen, {});
|
|
6288
6355
|
}
|
|
6289
6356
|
}
|
|
6290
6357
|
function Overlay({ state }) {
|
|
@@ -6395,10 +6462,27 @@ function AppShell({ startPath }) {
|
|
|
6395
6462
|
});
|
|
6396
6463
|
return;
|
|
6397
6464
|
}
|
|
6398
|
-
if (key.ctrl && input === "d")
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6465
|
+
if (key.ctrl && input === "d") {
|
|
6466
|
+
dispatch({
|
|
6467
|
+
type: "OPEN_OVERLAY",
|
|
6468
|
+
overlay: "diagnosticsPanel"
|
|
6469
|
+
});
|
|
6470
|
+
return;
|
|
6471
|
+
}
|
|
6472
|
+
if (input === "m") {
|
|
6473
|
+
if (state.openDocument === void 0) {
|
|
6474
|
+
dispatch({
|
|
6475
|
+
type: "SET_STATUS",
|
|
6476
|
+
severity: "warning",
|
|
6477
|
+
text: "There is no open document to show metadata for"
|
|
6478
|
+
});
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6481
|
+
dispatch({
|
|
6482
|
+
type: "PUSH_SCREEN",
|
|
6483
|
+
screen: { kind: "metadata" }
|
|
6484
|
+
});
|
|
6485
|
+
}
|
|
6402
6486
|
}, { isActive: !overlayOpen });
|
|
6403
6487
|
return /* @__PURE__ */ jsxs(Box, {
|
|
6404
6488
|
flexDirection: "column",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI and interactive Ink TUI for documents.js: every docx/pptx/odt/odp/ods/odg/odf/pdf/odm/odb/xlsx/markdown conversion, bridge, and editor as a scriptable command or a terminal app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|