document-cli 1.2.11 → 1.3.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 CHANGED
@@ -2,7 +2,7 @@
2
2
  import { a as odbFormSummary, c as writeOutput, d as inferFormatFromExtension, f as isDocumentFormat, i as formatOdbReportLines, l as loadProvidedFonts, n as describeOdbReport, o as readInput, r as formatOdbFormLines, s as resolveDefaultOutputPath, t as describeOdbForm } from "./odb-structure-CX_0zL8_.js";
3
3
  import { Command, CommanderError, InvalidArgumentError } from "commander";
4
4
  import { writeFile } from "node:fs/promises";
5
- import { OdbNoEmbeddedDataSourceError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, createLocalDocumentConverter, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, odbToCsv, odbToXlsx, odmToPdf, readOdbForms, 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, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, hsqldbCellDisplayText, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
6
6
  import { basename, dirname, extname, join } from "node:path";
7
7
  import { decodePackage, encodePackage as encodePackage$1 } from "odf.js";
8
8
  import { existsSync, readFileSync } from "node:fs";
@@ -114,7 +114,8 @@ function pdfDiagnosticToDiagnostic(diagnostic) {
114
114
  function mapErrorToExit(error, abortReason) {
115
115
  if (abortReason === "interrupt") return 130;
116
116
  if (abortReason === "timeout") return 124;
117
- if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) return 3;
117
+ if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError || error instanceof OdbReportNotSpecifiedError) return 3;
118
+ if (error instanceof HsqldbSqlUnsupportedError || error instanceof HsqldbSqlParseError || error instanceof HsqldbSqlEvaluationError) return 1;
118
119
  if (error instanceof PdfEncryptedError || error instanceof PdfParseError) return 1;
119
120
  return 1;
120
121
  }
@@ -373,6 +374,27 @@ function registerFromPackageCommand(program) {
373
374
  });
374
375
  }
375
376
  //#endregion
377
+ //#region src/sql-result-format.ts
378
+ const COLUMN_GAP = " ";
379
+ function columnWidths(columns, cells) {
380
+ return columns.map((column, index) => {
381
+ const cellWidths = cells.map((row) => row[index]?.length ?? 0);
382
+ return Math.max(column.length, ...cellWidths);
383
+ });
384
+ }
385
+ function formatRow(values, widths) {
386
+ return values.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(COLUMN_GAP).trimEnd();
387
+ }
388
+ function formatSqlResultSetTable(result) {
389
+ const { columns, rows } = result;
390
+ const cells = rows.map((row) => row.map((value) => hsqldbCellDisplayText(value)));
391
+ const widths = columnWidths(columns, cells);
392
+ const lines = [formatRow(columns, widths), formatRow(widths.map((width) => "-".repeat(width)), widths)];
393
+ for (const row of cells) lines.push(formatRow(row, widths));
394
+ lines.push(`${rows.length} row${rows.length === 1 ? "" : "s"}`);
395
+ return lines;
396
+ }
397
+ //#endregion
376
398
  //#region src/commands/odb.ts
377
399
  function reportOdbError(command, error, verbose, abortReason) {
378
400
  if (error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) {
@@ -389,6 +411,13 @@ function reportOdbCsvError(command, error, verbose, abortReason) {
389
411
  }
390
412
  return reportOdbError(command, error, verbose, abortReason);
391
413
  }
414
+ function reportOdbReportError(command, error, verbose, abortReason) {
415
+ if (error instanceof OdbReportNotSpecifiedError) {
416
+ process.stderr.write(`[${command}] ${error.message}\nrun 'odb-reports' first to see the available reports\n`);
417
+ return mapErrorToExit(error, abortReason);
418
+ }
419
+ return reportOdbError(command, error, verbose, abortReason);
420
+ }
392
421
  function resolveDefaultCsvOutputPath(inputPath) {
393
422
  const directory = dirname(inputPath);
394
423
  const stem = basename(inputPath, extname(inputPath));
@@ -474,6 +503,48 @@ async function runOdbTables(input, options) {
474
503
  return reportOdbError(command, error, false, getAbortReason());
475
504
  }
476
505
  }
506
+ function resolveQuerySql(pkg, options) {
507
+ if (options.sql !== void 0) return { sql: options.sql };
508
+ const queryName = options.query;
509
+ if (queryName === void 0) return { errorMessage: "pass --sql <text> or --query <savedName>" };
510
+ const inventory = readOdbInventory(pkg);
511
+ const saved = inventory.queries.find((candidate) => candidate.name === queryName);
512
+ if (saved === void 0) {
513
+ const available = inventory.queries.map((candidate) => candidate.name);
514
+ return { errorMessage: `this .odb declares no saved query named '${queryName}'${available.length === 0 ? "" : ` -- available: ${available.join(", ")}`}` };
515
+ }
516
+ return { sql: saved.command };
517
+ }
518
+ async function runOdbQuery(input, options) {
519
+ const command = "odb-query";
520
+ if (options.sql !== void 0 && options.query !== void 0) {
521
+ process.stderr.write(`[${command}] pass --sql or --query, not both\n`);
522
+ return 2;
523
+ }
524
+ if (options.sql === void 0 && options.query === void 0) {
525
+ process.stderr.write(`[${command}] pass --sql <text> or --query <savedName>\n`);
526
+ return 2;
527
+ }
528
+ const { signal, getAbortReason } = createRuntimeSignal({});
529
+ try {
530
+ const inputBytes = await readInput(input, { signal });
531
+ const pkg = decodePackage(new Uint8Array(inputBytes));
532
+ const resolved = resolveQuerySql(pkg, options);
533
+ if ("errorMessage" in resolved) {
534
+ process.stderr.write(`[${command}] ${resolved.errorMessage}\n`);
535
+ return 2;
536
+ }
537
+ const result = evaluateSelect(parseSelect(resolved.sql), readOdbTables(pkg));
538
+ if (options.json) {
539
+ process.stdout.write(`${JSON.stringify(result)}\n`);
540
+ return 0;
541
+ }
542
+ for (const line of formatSqlResultSetTable(result)) process.stdout.write(`${line}\n`);
543
+ return 0;
544
+ } catch (error) {
545
+ return reportOdbError(command, error, false, getAbortReason());
546
+ }
547
+ }
477
548
  async function runOdbForms(input, options) {
478
549
  const command = "odb-forms";
479
550
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -520,6 +591,93 @@ async function runOdbReports(input, options) {
520
591
  return reportOdbError(command, error, false, getAbortReason());
521
592
  }
522
593
  }
594
+ const ODB_REPORT_TARGET_FORMATS = {
595
+ docx: true,
596
+ odt: true,
597
+ pdf: true
598
+ };
599
+ function isOdbReportTargetFormat(format) {
600
+ return format in ODB_REPORT_TARGET_FORMATS;
601
+ }
602
+ function renderOdbReportBytes(content, target, options) {
603
+ if (target === "docx") return encodePackage(buildDocxPackage(content));
604
+ if (target === "odt") return encodePackage$1(buildOdtPackage(content));
605
+ if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
606
+ const fonts = createFontRegistry({
607
+ fonts: options.fonts,
608
+ onSubstitution: (substitution) => {
609
+ if (options.reportFontSubstitution !== void 0) {
610
+ options.reportFontSubstitution(substitution);
611
+ return;
612
+ }
613
+ options.onDiagnosticCounted();
614
+ options.reporter.report(fontSubstitutionToDiagnostic(substitution));
615
+ }
616
+ });
617
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
618
+ return writePdf(layout, {
619
+ signal: options.signal,
620
+ onSubstitution: (substitution, context) => {
621
+ options.onDiagnosticCounted();
622
+ options.reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
623
+ },
624
+ formulas,
625
+ fonts
626
+ });
627
+ }
628
+ async function runOdbRenderReport(input, output, options) {
629
+ const command = "odb-render-report";
630
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
631
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
632
+ return 2;
633
+ }
634
+ const target = resolveTargetFormat(output, options.out, options.to);
635
+ if ("errorMessage" in target) {
636
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
637
+ return 2;
638
+ }
639
+ if (!isOdbReportTargetFormat(target.format)) {
640
+ process.stderr.write(`[${command}] '${target.format}' is not a supported report render target; expected one of docx, odt, pdf\n`);
641
+ return 2;
642
+ }
643
+ const targetFormat = target.format;
644
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, targetFormat));
645
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
646
+ const reporter = createDiagnosticReporter({
647
+ json: options.json,
648
+ quiet: options.quiet,
649
+ command
650
+ });
651
+ let diagnosticCount = 0;
652
+ const reportFontSubstitution = options.reportFontSubstitutions === true ? createFontSubstitutionReporter({
653
+ json: options.json,
654
+ quiet: options.quiet,
655
+ command
656
+ }) : void 0;
657
+ try {
658
+ const inputBytes = await readInput(input, { signal });
659
+ const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
660
+ const pkg = decodePackage(new Uint8Array(inputBytes));
661
+ const bytes = renderOdbReportBytes(readOdbReportContent(pkg, { report: options.report }), targetFormat, {
662
+ fonts,
663
+ signal,
664
+ reporter,
665
+ reportFontSubstitution,
666
+ onDiagnosticCounted: () => {
667
+ diagnosticCount += 1;
668
+ }
669
+ });
670
+ await writeOutput(resolvedOutput, bytes);
671
+ reporter.summarize({
672
+ output: resolvedOutput,
673
+ bytes: bytes.byteLength,
674
+ diagnosticCount
675
+ });
676
+ return 0;
677
+ } catch (error) {
678
+ return reportOdbReportError(command, error, options.verbose, getAbortReason());
679
+ }
680
+ }
523
681
  function registerOdbToXlsxCommand(program) {
524
682
  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");
525
683
  addOutOption(command);
@@ -558,12 +716,33 @@ function registerOdbReportsCommand(program) {
558
716
  process.exitCode = await runOdbReports(input, options);
559
717
  });
560
718
  }
719
+ function registerOdbQueryCommand(program) {
720
+ program.command("odb-query <input>").description("run a bounded SELECT over an embedded .odb database's own extracted tables, given directly or by naming one of its saved queries").option("--sql <text>", "the SELECT statement to run -- mutually exclusive with --query").option("--query <savedName>", "the name of one of the .odb's own saved queries to run -- mutually exclusive with --sql").option("--json", "emit the result set as JSON ({ columns, rows }) instead of a plain-text table", false).action(async (input, options) => {
721
+ process.exitCode = await runOdbQuery(input, options);
722
+ });
723
+ }
724
+ function registerOdbRenderReportCommand(program) {
725
+ const command = program.command("odb-render-report <input> [output]").description("render one of an .odb's own reports -- its query resolved, its rpt: formulas evaluated, its bands laid out -- to docx, odt, or pdf");
726
+ addOutOption(command);
727
+ addTimeoutOption(command);
728
+ addJsonOption(command);
729
+ addQuietOption(command);
730
+ addVerboseOption(command);
731
+ addFontOptions(command);
732
+ command.option("--report <name>", "the report to render -- required only when the .odb declares more than one report");
733
+ command.option("--to <format>", "target format when it cannot be inferred from the output path (docx, odt, pdf)");
734
+ command.action(async (input, output, options) => {
735
+ process.exitCode = await runOdbRenderReport(input, output, options);
736
+ });
737
+ }
561
738
  function registerOdbCommands(program) {
562
739
  registerOdbToXlsxCommand(program);
563
740
  registerOdbToCsvCommand(program);
564
741
  registerOdbTablesCommand(program);
565
742
  registerOdbFormsCommand(program);
566
743
  registerOdbReportsCommand(program);
744
+ registerOdbQueryCommand(program);
745
+ registerOdbRenderReportCommand(program);
567
746
  }
568
747
  //#endregion
569
748
  //#region src/commands/odm.ts
@@ -744,7 +923,7 @@ function registerPdfInspectCommand(program) {
744
923
  }
745
924
  //#endregion
746
925
  //#region package.json
747
- var version = "1.2.11";
926
+ var version = "1.3.0";
748
927
  //#endregion
749
928
  //#region src/program.ts
750
929
  function createProgram() {
@@ -767,7 +946,7 @@ function createProgram() {
767
946
  //#region src/cli.ts
768
947
  async function launchTui(startPath, signal) {
769
948
  try {
770
- const { runTui } = await import("./tui-DD4bQp39.js");
949
+ const { runTui } = await import("./tui-C8ITn1zQ.js");
771
950
  await runTui({
772
951
  startPath,
773
952
  signal
package/dist/index.cjs CHANGED
@@ -162,7 +162,8 @@ const EXIT_INTERRUPTED = 130;
162
162
  function mapErrorToExit(error, abortReason) {
163
163
  if (abortReason === "interrupt") return 130;
164
164
  if (abortReason === "timeout") return 124;
165
- if (error instanceof documents_js.OdmUnresolvedSectionError || error instanceof documents_js.OdbTableNotSpecifiedError || error instanceof documents_js.OdbTableNotFoundError || error instanceof documents_js.OdbNoEmbeddedDataSourceError || error instanceof documents_js.OdbUnsupportedFormatError) return 3;
165
+ if (error instanceof documents_js.OdmUnresolvedSectionError || error instanceof documents_js.OdbTableNotSpecifiedError || error instanceof documents_js.OdbTableNotFoundError || error instanceof documents_js.OdbNoEmbeddedDataSourceError || error instanceof documents_js.OdbUnsupportedFormatError || error instanceof documents_js.OdbReportNotSpecifiedError) return 3;
166
+ if (error instanceof documents_js.HsqldbSqlUnsupportedError || error instanceof documents_js.HsqldbSqlParseError || error instanceof documents_js.HsqldbSqlEvaluationError) return 1;
166
167
  if (error instanceof documents_js.PdfEncryptedError || error instanceof documents_js.PdfParseError) return 1;
167
168
  return 1;
168
169
  }
@@ -758,6 +759,27 @@ function formatOdbReportLines(report) {
758
759
  return lines;
759
760
  }
760
761
  //#endregion
762
+ //#region src/sql-result-format.ts
763
+ const COLUMN_GAP = " ";
764
+ function columnWidths(columns, cells) {
765
+ return columns.map((column, index) => {
766
+ const cellWidths = cells.map((row) => row[index]?.length ?? 0);
767
+ return Math.max(column.length, ...cellWidths);
768
+ });
769
+ }
770
+ function formatRow(values, widths) {
771
+ return values.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(COLUMN_GAP).trimEnd();
772
+ }
773
+ function formatSqlResultSetTable(result) {
774
+ const { columns, rows } = result;
775
+ const cells = rows.map((row) => row.map((value) => (0, documents_js.hsqldbCellDisplayText)(value)));
776
+ const widths = columnWidths(columns, cells);
777
+ const lines = [formatRow(columns, widths), formatRow(widths.map((width) => "-".repeat(width)), widths)];
778
+ for (const row of cells) lines.push(formatRow(row, widths));
779
+ lines.push(`${rows.length} row${rows.length === 1 ? "" : "s"}`);
780
+ return lines;
781
+ }
782
+ //#endregion
761
783
  //#region src/commands/odb.ts
762
784
  function reportOdbError(command, error, verbose, abortReason) {
763
785
  if (error instanceof documents_js.OdbNoEmbeddedDataSourceError || error instanceof documents_js.OdbUnsupportedFormatError) {
@@ -774,6 +796,13 @@ function reportOdbCsvError(command, error, verbose, abortReason) {
774
796
  }
775
797
  return reportOdbError(command, error, verbose, abortReason);
776
798
  }
799
+ function reportOdbReportError(command, error, verbose, abortReason) {
800
+ if (error instanceof documents_js.OdbReportNotSpecifiedError) {
801
+ process.stderr.write(`[${command}] ${error.message}\nrun 'odb-reports' first to see the available reports\n`);
802
+ return mapErrorToExit(error, abortReason);
803
+ }
804
+ return reportOdbError(command, error, verbose, abortReason);
805
+ }
777
806
  function resolveDefaultCsvOutputPath(inputPath) {
778
807
  const directory = (0, node_path.dirname)(inputPath);
779
808
  const stem = (0, node_path.basename)(inputPath, (0, node_path.extname)(inputPath));
@@ -859,6 +888,48 @@ async function runOdbTables(input, options) {
859
888
  return reportOdbError(command, error, false, getAbortReason());
860
889
  }
861
890
  }
891
+ function resolveQuerySql(pkg, options) {
892
+ if (options.sql !== void 0) return { sql: options.sql };
893
+ const queryName = options.query;
894
+ if (queryName === void 0) return { errorMessage: "pass --sql <text> or --query <savedName>" };
895
+ const inventory = (0, documents_js.readOdbInventory)(pkg);
896
+ const saved = inventory.queries.find((candidate) => candidate.name === queryName);
897
+ if (saved === void 0) {
898
+ const available = inventory.queries.map((candidate) => candidate.name);
899
+ return { errorMessage: `this .odb declares no saved query named '${queryName}'${available.length === 0 ? "" : ` -- available: ${available.join(", ")}`}` };
900
+ }
901
+ return { sql: saved.command };
902
+ }
903
+ async function runOdbQuery(input, options) {
904
+ const command = "odb-query";
905
+ if (options.sql !== void 0 && options.query !== void 0) {
906
+ process.stderr.write(`[${command}] pass --sql or --query, not both\n`);
907
+ return 2;
908
+ }
909
+ if (options.sql === void 0 && options.query === void 0) {
910
+ process.stderr.write(`[${command}] pass --sql <text> or --query <savedName>\n`);
911
+ return 2;
912
+ }
913
+ const { signal, getAbortReason } = createRuntimeSignal({});
914
+ try {
915
+ const inputBytes = await readInput(input, { signal });
916
+ const pkg = (0, odf_js.decodePackage)(new Uint8Array(inputBytes));
917
+ const resolved = resolveQuerySql(pkg, options);
918
+ if ("errorMessage" in resolved) {
919
+ process.stderr.write(`[${command}] ${resolved.errorMessage}\n`);
920
+ return 2;
921
+ }
922
+ const result = (0, documents_js.evaluateSelect)((0, documents_js.parseSelect)(resolved.sql), (0, documents_js.readOdbTables)(pkg));
923
+ if (options.json) {
924
+ process.stdout.write(`${JSON.stringify(result)}\n`);
925
+ return 0;
926
+ }
927
+ for (const line of formatSqlResultSetTable(result)) process.stdout.write(`${line}\n`);
928
+ return 0;
929
+ } catch (error) {
930
+ return reportOdbError(command, error, false, getAbortReason());
931
+ }
932
+ }
862
933
  async function runOdbForms(input, options) {
863
934
  const command = "odb-forms";
864
935
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -905,6 +976,93 @@ async function runOdbReports(input, options) {
905
976
  return reportOdbError(command, error, false, getAbortReason());
906
977
  }
907
978
  }
979
+ const ODB_REPORT_TARGET_FORMATS = {
980
+ docx: true,
981
+ odt: true,
982
+ pdf: true
983
+ };
984
+ function isOdbReportTargetFormat(format) {
985
+ return format in ODB_REPORT_TARGET_FORMATS;
986
+ }
987
+ function renderOdbReportBytes(content, target, options) {
988
+ if (target === "docx") return (0, documents_js.encodePackage)((0, documents_js.buildDocxPackage)(content));
989
+ if (target === "odt") return (0, odf_js.encodePackage)((0, documents_js.buildOdtPackage)(content));
990
+ if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
991
+ const fonts = (0, documents_js.createFontRegistry)({
992
+ fonts: options.fonts,
993
+ onSubstitution: (substitution) => {
994
+ if (options.reportFontSubstitution !== void 0) {
995
+ options.reportFontSubstitution(substitution);
996
+ return;
997
+ }
998
+ options.onDiagnosticCounted();
999
+ options.reporter.report(fontSubstitutionToDiagnostic(substitution));
1000
+ }
1001
+ });
1002
+ const { document: layout, formulas } = (0, documents_js.convertWordprocessingToLayout)(content, { measurer: (0, documents_js.createFontMeasurer)(fonts) });
1003
+ return (0, documents_js.writePdf)(layout, {
1004
+ signal: options.signal,
1005
+ onSubstitution: (substitution, context) => {
1006
+ options.onDiagnosticCounted();
1007
+ options.reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
1008
+ },
1009
+ formulas,
1010
+ fonts
1011
+ });
1012
+ }
1013
+ async function runOdbRenderReport(input, output, options) {
1014
+ const command = "odb-render-report";
1015
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1016
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1017
+ return 2;
1018
+ }
1019
+ const target = resolveTargetFormat(output, options.out, options.to);
1020
+ if ("errorMessage" in target) {
1021
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1022
+ return 2;
1023
+ }
1024
+ if (!isOdbReportTargetFormat(target.format)) {
1025
+ process.stderr.write(`[${command}] '${target.format}' is not a supported report render target; expected one of docx, odt, pdf\n`);
1026
+ return 2;
1027
+ }
1028
+ const targetFormat = target.format;
1029
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, targetFormat));
1030
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1031
+ const reporter = createDiagnosticReporter({
1032
+ json: options.json,
1033
+ quiet: options.quiet,
1034
+ command
1035
+ });
1036
+ let diagnosticCount = 0;
1037
+ const reportFontSubstitution = options.reportFontSubstitutions === true ? createFontSubstitutionReporter({
1038
+ json: options.json,
1039
+ quiet: options.quiet,
1040
+ command
1041
+ }) : void 0;
1042
+ try {
1043
+ const inputBytes = await readInput(input, { signal });
1044
+ const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
1045
+ const pkg = (0, odf_js.decodePackage)(new Uint8Array(inputBytes));
1046
+ const bytes = renderOdbReportBytes((0, documents_js.readOdbReportContent)(pkg, { report: options.report }), targetFormat, {
1047
+ fonts,
1048
+ signal,
1049
+ reporter,
1050
+ reportFontSubstitution,
1051
+ onDiagnosticCounted: () => {
1052
+ diagnosticCount += 1;
1053
+ }
1054
+ });
1055
+ await writeOutput(resolvedOutput, bytes);
1056
+ reporter.summarize({
1057
+ output: resolvedOutput,
1058
+ bytes: bytes.byteLength,
1059
+ diagnosticCount
1060
+ });
1061
+ return 0;
1062
+ } catch (error) {
1063
+ return reportOdbReportError(command, error, options.verbose, getAbortReason());
1064
+ }
1065
+ }
908
1066
  function registerOdbToXlsxCommand(program) {
909
1067
  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");
910
1068
  addOutOption(command);
@@ -943,12 +1101,33 @@ function registerOdbReportsCommand(program) {
943
1101
  process.exitCode = await runOdbReports(input, options);
944
1102
  });
945
1103
  }
1104
+ function registerOdbQueryCommand(program) {
1105
+ program.command("odb-query <input>").description("run a bounded SELECT over an embedded .odb database's own extracted tables, given directly or by naming one of its saved queries").option("--sql <text>", "the SELECT statement to run -- mutually exclusive with --query").option("--query <savedName>", "the name of one of the .odb's own saved queries to run -- mutually exclusive with --sql").option("--json", "emit the result set as JSON ({ columns, rows }) instead of a plain-text table", false).action(async (input, options) => {
1106
+ process.exitCode = await runOdbQuery(input, options);
1107
+ });
1108
+ }
1109
+ function registerOdbRenderReportCommand(program) {
1110
+ const command = program.command("odb-render-report <input> [output]").description("render one of an .odb's own reports -- its query resolved, its rpt: formulas evaluated, its bands laid out -- to docx, odt, or pdf");
1111
+ addOutOption(command);
1112
+ addTimeoutOption(command);
1113
+ addJsonOption(command);
1114
+ addQuietOption(command);
1115
+ addVerboseOption(command);
1116
+ addFontOptions(command);
1117
+ command.option("--report <name>", "the report to render -- required only when the .odb declares more than one report");
1118
+ command.option("--to <format>", "target format when it cannot be inferred from the output path (docx, odt, pdf)");
1119
+ command.action(async (input, output, options) => {
1120
+ process.exitCode = await runOdbRenderReport(input, output, options);
1121
+ });
1122
+ }
946
1123
  function registerOdbCommands(program) {
947
1124
  registerOdbToXlsxCommand(program);
948
1125
  registerOdbToCsvCommand(program);
949
1126
  registerOdbTablesCommand(program);
950
1127
  registerOdbFormsCommand(program);
951
1128
  registerOdbReportsCommand(program);
1129
+ registerOdbQueryCommand(program);
1130
+ registerOdbRenderReportCommand(program);
952
1131
  }
953
1132
  //#endregion
954
1133
  //#region src/commands/odm.ts
@@ -1129,7 +1308,7 @@ function registerPdfInspectCommand(program) {
1129
1308
  }
1130
1309
  //#endregion
1131
1310
  //#region package.json
1132
- var version = "1.2.11";
1311
+ var version = "1.3.0";
1133
1312
  //#endregion
1134
1313
  //#region src/program.ts
1135
1314
  function createProgram() {
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { OdbNoEmbeddedDataSourceError, OdbTableNotFoundError, OdbTableNotSpecifiedError, OdbUnsupportedFormatError, OdmUnresolvedSectionError, PdfEncryptedError, PdfParseError, UnrecognizedDocumentSchemaError, buildDocxPackage, buildMarkdownText, buildOdgPackage, buildOdpPackage, buildOdsPackage, buildOdtPackage, buildPptxPackage, createLocalDocumentConverter, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, odbToCsv, odbToXlsx, odmToPdf, readOdbForms, 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, documentFromJson, documentPackageWithSchema, encodeMarkdownText, encodePackage, evaluateSelect, hsqldbCellDisplayText, odbToCsv, odbToXlsx, odmToPdf, parseSelect, readOdbForms, readOdbInventory, readOdbReportContent, readOdbReports, readOdbTables, readPdf, writePdf } from "documents.js";
3
3
  import { basename, dirname, extname, join } from "node:path";
4
4
  import { Command, InvalidArgumentError } from "commander";
5
5
  import { decodePackage, encodePackage as encodePackage$1 } from "odf.js";
@@ -161,7 +161,8 @@ const EXIT_INTERRUPTED = 130;
161
161
  function mapErrorToExit(error, abortReason) {
162
162
  if (abortReason === "interrupt") return 130;
163
163
  if (abortReason === "timeout") return 124;
164
- if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) return 3;
164
+ if (error instanceof OdmUnresolvedSectionError || error instanceof OdbTableNotSpecifiedError || error instanceof OdbTableNotFoundError || error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError || error instanceof OdbReportNotSpecifiedError) return 3;
165
+ if (error instanceof HsqldbSqlUnsupportedError || error instanceof HsqldbSqlParseError || error instanceof HsqldbSqlEvaluationError) return 1;
165
166
  if (error instanceof PdfEncryptedError || error instanceof PdfParseError) return 1;
166
167
  return 1;
167
168
  }
@@ -757,6 +758,27 @@ function formatOdbReportLines(report) {
757
758
  return lines;
758
759
  }
759
760
  //#endregion
761
+ //#region src/sql-result-format.ts
762
+ const COLUMN_GAP = " ";
763
+ function columnWidths(columns, cells) {
764
+ return columns.map((column, index) => {
765
+ const cellWidths = cells.map((row) => row[index]?.length ?? 0);
766
+ return Math.max(column.length, ...cellWidths);
767
+ });
768
+ }
769
+ function formatRow(values, widths) {
770
+ return values.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(COLUMN_GAP).trimEnd();
771
+ }
772
+ function formatSqlResultSetTable(result) {
773
+ const { columns, rows } = result;
774
+ const cells = rows.map((row) => row.map((value) => hsqldbCellDisplayText(value)));
775
+ const widths = columnWidths(columns, cells);
776
+ const lines = [formatRow(columns, widths), formatRow(widths.map((width) => "-".repeat(width)), widths)];
777
+ for (const row of cells) lines.push(formatRow(row, widths));
778
+ lines.push(`${rows.length} row${rows.length === 1 ? "" : "s"}`);
779
+ return lines;
780
+ }
781
+ //#endregion
760
782
  //#region src/commands/odb.ts
761
783
  function reportOdbError(command, error, verbose, abortReason) {
762
784
  if (error instanceof OdbNoEmbeddedDataSourceError || error instanceof OdbUnsupportedFormatError) {
@@ -773,6 +795,13 @@ function reportOdbCsvError(command, error, verbose, abortReason) {
773
795
  }
774
796
  return reportOdbError(command, error, verbose, abortReason);
775
797
  }
798
+ function reportOdbReportError(command, error, verbose, abortReason) {
799
+ if (error instanceof OdbReportNotSpecifiedError) {
800
+ process.stderr.write(`[${command}] ${error.message}\nrun 'odb-reports' first to see the available reports\n`);
801
+ return mapErrorToExit(error, abortReason);
802
+ }
803
+ return reportOdbError(command, error, verbose, abortReason);
804
+ }
776
805
  function resolveDefaultCsvOutputPath(inputPath) {
777
806
  const directory = dirname(inputPath);
778
807
  const stem = basename(inputPath, extname(inputPath));
@@ -858,6 +887,48 @@ async function runOdbTables(input, options) {
858
887
  return reportOdbError(command, error, false, getAbortReason());
859
888
  }
860
889
  }
890
+ function resolveQuerySql(pkg, options) {
891
+ if (options.sql !== void 0) return { sql: options.sql };
892
+ const queryName = options.query;
893
+ if (queryName === void 0) return { errorMessage: "pass --sql <text> or --query <savedName>" };
894
+ const inventory = readOdbInventory(pkg);
895
+ const saved = inventory.queries.find((candidate) => candidate.name === queryName);
896
+ if (saved === void 0) {
897
+ const available = inventory.queries.map((candidate) => candidate.name);
898
+ return { errorMessage: `this .odb declares no saved query named '${queryName}'${available.length === 0 ? "" : ` -- available: ${available.join(", ")}`}` };
899
+ }
900
+ return { sql: saved.command };
901
+ }
902
+ async function runOdbQuery(input, options) {
903
+ const command = "odb-query";
904
+ if (options.sql !== void 0 && options.query !== void 0) {
905
+ process.stderr.write(`[${command}] pass --sql or --query, not both\n`);
906
+ return 2;
907
+ }
908
+ if (options.sql === void 0 && options.query === void 0) {
909
+ process.stderr.write(`[${command}] pass --sql <text> or --query <savedName>\n`);
910
+ return 2;
911
+ }
912
+ const { signal, getAbortReason } = createRuntimeSignal({});
913
+ try {
914
+ const inputBytes = await readInput(input, { signal });
915
+ const pkg = decodePackage(new Uint8Array(inputBytes));
916
+ const resolved = resolveQuerySql(pkg, options);
917
+ if ("errorMessage" in resolved) {
918
+ process.stderr.write(`[${command}] ${resolved.errorMessage}\n`);
919
+ return 2;
920
+ }
921
+ const result = evaluateSelect(parseSelect(resolved.sql), readOdbTables(pkg));
922
+ if (options.json) {
923
+ process.stdout.write(`${JSON.stringify(result)}\n`);
924
+ return 0;
925
+ }
926
+ for (const line of formatSqlResultSetTable(result)) process.stdout.write(`${line}\n`);
927
+ return 0;
928
+ } catch (error) {
929
+ return reportOdbError(command, error, false, getAbortReason());
930
+ }
931
+ }
861
932
  async function runOdbForms(input, options) {
862
933
  const command = "odb-forms";
863
934
  const { signal, getAbortReason } = createRuntimeSignal({});
@@ -904,6 +975,93 @@ async function runOdbReports(input, options) {
904
975
  return reportOdbError(command, error, false, getAbortReason());
905
976
  }
906
977
  }
978
+ const ODB_REPORT_TARGET_FORMATS = {
979
+ docx: true,
980
+ odt: true,
981
+ pdf: true
982
+ };
983
+ function isOdbReportTargetFormat(format) {
984
+ return format in ODB_REPORT_TARGET_FORMATS;
985
+ }
986
+ function renderOdbReportBytes(content, target, options) {
987
+ if (target === "docx") return encodePackage(buildDocxPackage(content));
988
+ if (target === "odt") return encodePackage$1(buildOdtPackage(content));
989
+ if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
990
+ const fonts = createFontRegistry({
991
+ fonts: options.fonts,
992
+ onSubstitution: (substitution) => {
993
+ if (options.reportFontSubstitution !== void 0) {
994
+ options.reportFontSubstitution(substitution);
995
+ return;
996
+ }
997
+ options.onDiagnosticCounted();
998
+ options.reporter.report(fontSubstitutionToDiagnostic(substitution));
999
+ }
1000
+ });
1001
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(fonts) });
1002
+ return writePdf(layout, {
1003
+ signal: options.signal,
1004
+ onSubstitution: (substitution, context) => {
1005
+ options.onDiagnosticCounted();
1006
+ options.reporter.report(substitutionToDiagnostic(substitution, context.pageIndex));
1007
+ },
1008
+ formulas,
1009
+ fonts
1010
+ });
1011
+ }
1012
+ async function runOdbRenderReport(input, output, options) {
1013
+ const command = "odb-render-report";
1014
+ if (output !== void 0 && options.out !== void 0 && output !== options.out) {
1015
+ process.stderr.write(`[${command}] conflicting output destinations: positional '${output}' and --out '${options.out}'\n`);
1016
+ return 2;
1017
+ }
1018
+ const target = resolveTargetFormat(output, options.out, options.to);
1019
+ if ("errorMessage" in target) {
1020
+ process.stderr.write(`[${command}] ${target.errorMessage}\n`);
1021
+ return 2;
1022
+ }
1023
+ if (!isOdbReportTargetFormat(target.format)) {
1024
+ process.stderr.write(`[${command}] '${target.format}' is not a supported report render target; expected one of docx, odt, pdf\n`);
1025
+ return 2;
1026
+ }
1027
+ const targetFormat = target.format;
1028
+ const resolvedOutput = output ?? options.out ?? (input === "-" ? "-" : resolveDefaultOutputPath(input, targetFormat));
1029
+ const { signal, getAbortReason } = createRuntimeSignal({ timeoutMs: options.timeout });
1030
+ const reporter = createDiagnosticReporter({
1031
+ json: options.json,
1032
+ quiet: options.quiet,
1033
+ command
1034
+ });
1035
+ let diagnosticCount = 0;
1036
+ const reportFontSubstitution = options.reportFontSubstitutions === true ? createFontSubstitutionReporter({
1037
+ json: options.json,
1038
+ quiet: options.quiet,
1039
+ command
1040
+ }) : void 0;
1041
+ try {
1042
+ const inputBytes = await readInput(input, { signal });
1043
+ const fonts = await loadProvidedFonts(options.fontFile ?? [], { signal });
1044
+ const pkg = decodePackage(new Uint8Array(inputBytes));
1045
+ const bytes = renderOdbReportBytes(readOdbReportContent(pkg, { report: options.report }), targetFormat, {
1046
+ fonts,
1047
+ signal,
1048
+ reporter,
1049
+ reportFontSubstitution,
1050
+ onDiagnosticCounted: () => {
1051
+ diagnosticCount += 1;
1052
+ }
1053
+ });
1054
+ await writeOutput(resolvedOutput, bytes);
1055
+ reporter.summarize({
1056
+ output: resolvedOutput,
1057
+ bytes: bytes.byteLength,
1058
+ diagnosticCount
1059
+ });
1060
+ return 0;
1061
+ } catch (error) {
1062
+ return reportOdbReportError(command, error, options.verbose, getAbortReason());
1063
+ }
1064
+ }
907
1065
  function registerOdbToXlsxCommand(program) {
908
1066
  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");
909
1067
  addOutOption(command);
@@ -942,12 +1100,33 @@ function registerOdbReportsCommand(program) {
942
1100
  process.exitCode = await runOdbReports(input, options);
943
1101
  });
944
1102
  }
1103
+ function registerOdbQueryCommand(program) {
1104
+ program.command("odb-query <input>").description("run a bounded SELECT over an embedded .odb database's own extracted tables, given directly or by naming one of its saved queries").option("--sql <text>", "the SELECT statement to run -- mutually exclusive with --query").option("--query <savedName>", "the name of one of the .odb's own saved queries to run -- mutually exclusive with --sql").option("--json", "emit the result set as JSON ({ columns, rows }) instead of a plain-text table", false).action(async (input, options) => {
1105
+ process.exitCode = await runOdbQuery(input, options);
1106
+ });
1107
+ }
1108
+ function registerOdbRenderReportCommand(program) {
1109
+ const command = program.command("odb-render-report <input> [output]").description("render one of an .odb's own reports -- its query resolved, its rpt: formulas evaluated, its bands laid out -- to docx, odt, or pdf");
1110
+ addOutOption(command);
1111
+ addTimeoutOption(command);
1112
+ addJsonOption(command);
1113
+ addQuietOption(command);
1114
+ addVerboseOption(command);
1115
+ addFontOptions(command);
1116
+ command.option("--report <name>", "the report to render -- required only when the .odb declares more than one report");
1117
+ command.option("--to <format>", "target format when it cannot be inferred from the output path (docx, odt, pdf)");
1118
+ command.action(async (input, output, options) => {
1119
+ process.exitCode = await runOdbRenderReport(input, output, options);
1120
+ });
1121
+ }
945
1122
  function registerOdbCommands(program) {
946
1123
  registerOdbToXlsxCommand(program);
947
1124
  registerOdbToCsvCommand(program);
948
1125
  registerOdbTablesCommand(program);
949
1126
  registerOdbFormsCommand(program);
950
1127
  registerOdbReportsCommand(program);
1128
+ registerOdbQueryCommand(program);
1129
+ registerOdbRenderReportCommand(program);
951
1130
  }
952
1131
  //#endregion
953
1132
  //#region src/commands/odm.ts
@@ -1128,7 +1307,7 @@ function registerPdfInspectCommand(program) {
1128
1307
  }
1129
1308
  //#endregion
1130
1309
  //#region package.json
1131
- var version = "1.2.11";
1310
+ var version = "1.3.0";
1132
1311
  //#endregion
1133
1312
  //#region src/program.ts
1134
1313
  function createProgram() {
@@ -1,8 +1,8 @@
1
1
  import { d as inferFormatFromExtension, i as formatOdbReportLines, l as loadProvidedFonts, n as describeOdbReport, o as readInput, r as formatOdbFormLines, t as describeOdbForm, u as formatToExtension } from "./odb-structure-CX_0zL8_.js";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { createDocx, createOdg, createOdp, createOds, createOdt, createPptx, decodeMarkdownText, docxToPdf, encodeMarkdownText, hsqldbCellDisplayText, markdownToPdf, odgToPdf, odpToPdf, odsToPdf, odtToPdf, openDocx, openOdg, openOdp, openOds, openOdt, openPptx, pptxToPdf, readOdbForms, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, 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, readOdbForms, readOdbReportContent, readOdbReports, readOdbTables, readOdgContent, readOdsContent, readPdf, rgbHexToColor, writePdf, xlsxToPdf } from "documents.js";
4
4
  import { basename, dirname, extname, join } from "node:path";
5
- import { cellReference, columnIndexToLetters, decodePackage } from "odf.js";
5
+ import { cellReference, columnIndexToLetters, decodePackage, encodePackage as encodePackage$1 } from "odf.js";
6
6
  import { readdirSync } from "node:fs";
7
7
  import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
8
8
  import { createContext, useContext, useEffect, useReducer, useState } from "react";
@@ -238,6 +238,7 @@ function selectionKeyFor(screen) {
238
238
  case "odbTableRows": return `odbTableRows:${screen.tableName}`;
239
239
  case "odbFormDetail": return `odbFormDetail:${screen.formName}`;
240
240
  case "odbReportDetail": return `odbReportDetail:${screen.reportName}`;
241
+ case "odbReportRender": return `odbReportRender:${screen.reportName}`;
241
242
  case "markdownLineEditor": return `markdownLineEditor:${screen.lineIndex}`;
242
243
  }
243
244
  }
@@ -2537,7 +2538,15 @@ function OdbReportDetailScreen() {
2537
2538
  const lines = query === "" ? allLines : allLines.filter((line) => line.toLowerCase().includes(query));
2538
2539
  const { selectedIndex } = useNavigationInput({
2539
2540
  itemCount: lines.length,
2540
- onSelect: () => {},
2541
+ onSelect: () => {
2542
+ dispatch({
2543
+ type: "PUSH_SCREEN",
2544
+ screen: {
2545
+ kind: "odbReportRender",
2546
+ reportName: report.name
2547
+ }
2548
+ });
2549
+ },
2541
2550
  onBack: () => {
2542
2551
  dispatch({ type: "POP_SCREEN" });
2543
2552
  },
@@ -2574,7 +2583,7 @@ function OdbReportDetailScreen() {
2574
2583
  }),
2575
2584
  /* @__PURE__ */ jsx(Text, {
2576
2585
  dimColor: true,
2577
- children: "Esc to go back to the report list"
2586
+ children: "Enter to render this report, Esc to go back to the report list"
2578
2587
  })
2579
2588
  ]
2580
2589
  });
@@ -2636,6 +2645,146 @@ function OdbReportListScreen() {
2636
2645
  });
2637
2646
  }
2638
2647
  //#endregion
2648
+ //#region src/tui/format/render-odb-report.ts
2649
+ const REPORT_RENDER_TARGET_FORMATS = {
2650
+ docx: true,
2651
+ odt: true,
2652
+ pdf: true
2653
+ };
2654
+ function isReportRenderTargetFormat(format) {
2655
+ return format !== void 0 && format in REPORT_RENDER_TARGET_FORMATS;
2656
+ }
2657
+ function renderReportBytes(content, format, fonts, options) {
2658
+ if (format === "docx") return encodePackage(buildDocxPackage(content));
2659
+ if (format === "odt") return encodePackage$1(buildOdtPackage(content));
2660
+ if (content.kind !== "wordprocessing") throw new Error("readOdbReportContent returned a non-wordprocessing ContentDocument");
2661
+ const registry = createFontRegistry({
2662
+ fonts,
2663
+ onSubstitution: (substitution) => {
2664
+ const requested = `${substitution.requestedFamily}${substitution.requestedBold ? " bold" : ""}${substitution.requestedItalic ? " italic" : ""}`;
2665
+ options.onDiagnostic({
2666
+ severity: "info",
2667
+ message: `No "${requested}" face available; drew it in "${substitution.resolvedFamily}"`
2668
+ });
2669
+ }
2670
+ });
2671
+ const { document: layout, formulas } = convertWordprocessingToLayout(content, { measurer: createFontMeasurer(registry) });
2672
+ return writePdf(layout, {
2673
+ signal: options.signal,
2674
+ onSubstitution: (substitution, context) => {
2675
+ options.onDiagnostic({
2676
+ severity: "info",
2677
+ message: `Substituted "${substitution.to}" for "${substitution.from}"`,
2678
+ pageIndex: context.pageIndex
2679
+ });
2680
+ },
2681
+ formulas,
2682
+ fonts: registry
2683
+ });
2684
+ }
2685
+ async function renderOdbReportTo(doc, destinationPath, options) {
2686
+ const format = detectFormat(destinationPath);
2687
+ if (!isReportRenderTargetFormat(format)) throw new Error(`Cannot tell whether to render "${options.reportName}" as docx, odt, or pdf from '${destinationPath}' -- give the destination one of those three extensions`);
2688
+ const fonts = await loadProvidedFonts(options.fontFiles ?? [], { signal: options.signal });
2689
+ const pkg = decodePackage(new Uint8Array(await readFile(doc.path)));
2690
+ const bytes = renderReportBytes(readOdbReportContent(pkg, { report: options.reportName }), format, fonts, options);
2691
+ await writeFile(destinationPath, bytes);
2692
+ }
2693
+ //#endregion
2694
+ //#region src/tui/screens/editors/odb/report-render.tsx
2695
+ function defaultReportRenderDestination(odbPath, reportName) {
2696
+ return join(dirname(odbPath), `${reportName}.pdf`);
2697
+ }
2698
+ function parseFontFileField$1(value) {
2699
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
2700
+ }
2701
+ function OdbReportRenderScreen() {
2702
+ const state = useAppState();
2703
+ const dispatch = useAppDispatch();
2704
+ const isActive = !anyOverlayOpen(state);
2705
+ const doc = requireOdbDocument(state.openDocument);
2706
+ const screen = currentScreen(state);
2707
+ if (screen.kind !== "odbReportRender") throw new Error(`OdbReportRenderScreen rendered while the current screen is "${screen.kind}", not "odbReportRender".`);
2708
+ const reportName = screen.reportName;
2709
+ const [destination, setDestination] = useState(() => defaultReportRenderDestination(doc.path, reportName));
2710
+ const [fontFiles, setFontFiles] = useState("");
2711
+ const [field, setField] = useState("destination");
2712
+ const cancel = () => {
2713
+ dispatch({ type: "POP_SCREEN" });
2714
+ };
2715
+ const submit = () => {
2716
+ (async () => {
2717
+ let diagnosticCount = 0;
2718
+ try {
2719
+ await renderOdbReportTo(doc, destination, {
2720
+ reportName,
2721
+ fontFiles: parseFontFileField$1(fontFiles),
2722
+ onDiagnostic: (diagnostic) => {
2723
+ diagnosticCount += 1;
2724
+ dispatch({
2725
+ type: "APPEND_DIAGNOSTIC",
2726
+ diagnostic
2727
+ });
2728
+ }
2729
+ });
2730
+ dispatch({
2731
+ type: "SET_STATUS",
2732
+ severity: "info",
2733
+ text: `Rendered "${reportName}" to ${destination}`
2734
+ });
2735
+ if (diagnosticCount > 0) dispatch({
2736
+ type: "OPEN_OVERLAY",
2737
+ overlay: "diagnosticsPanel"
2738
+ });
2739
+ dispatch({ type: "POP_SCREEN" });
2740
+ } catch (error) {
2741
+ dispatch({
2742
+ type: "OPEN_FILE_ERROR",
2743
+ message: `Could not render "${reportName}" to ${destination}`,
2744
+ detail: describeError(error)
2745
+ });
2746
+ }
2747
+ })();
2748
+ };
2749
+ return /* @__PURE__ */ jsxs(Box, {
2750
+ flexDirection: "column",
2751
+ children: [
2752
+ /* @__PURE__ */ jsxs(Text, {
2753
+ bold: true,
2754
+ children: ["Render report: ", reportName]
2755
+ }),
2756
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
2757
+ color: "cyan",
2758
+ children: "Path: "
2759
+ }), /* @__PURE__ */ jsx(TextField, {
2760
+ value: destination,
2761
+ isFocused: isActive && field === "destination",
2762
+ placeholder: "destination path (.docx, .odt, or .pdf)",
2763
+ onChange: setDestination,
2764
+ onSubmit: () => {
2765
+ setField("fonts");
2766
+ },
2767
+ onCancel: cancel
2768
+ })] }),
2769
+ /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
2770
+ color: "cyan",
2771
+ children: "Fonts: "
2772
+ }), /* @__PURE__ */ jsx(TextField, {
2773
+ value: fontFiles,
2774
+ isFocused: isActive && field === "fonts",
2775
+ placeholder: "optional .ttf/.otf paths, comma-separated -- pdf only",
2776
+ onChange: setFontFiles,
2777
+ onSubmit: submit,
2778
+ onCancel: cancel
2779
+ })] }),
2780
+ /* @__PURE__ */ jsx(Text, {
2781
+ dimColor: true,
2782
+ children: field === "destination" ? "Enter for fonts, Esc to cancel" : "Enter to render, Esc to cancel. The destination's own extension picks docx/odt/pdf"
2783
+ })
2784
+ ]
2785
+ });
2786
+ }
2787
+ //#endregion
2639
2788
  //#region src/tui/screens/editors/odb/table-list.tsx
2640
2789
  const TABLE_LIST_RESERVED_ROWS = 5;
2641
2790
  function OdbTableListScreen() {
@@ -6061,6 +6210,7 @@ function ScreenBody({ screen }) {
6061
6210
  case "odbFormDetail": return /* @__PURE__ */ jsx(OdbFormDetailScreen, {});
6062
6211
  case "odbReportList": return /* @__PURE__ */ jsx(OdbReportListScreen, {});
6063
6212
  case "odbReportDetail": return /* @__PURE__ */ jsx(OdbReportDetailScreen, {});
6213
+ case "odbReportRender": return /* @__PURE__ */ jsx(OdbReportRenderScreen, {});
6064
6214
  case "markdownLineList": return /* @__PURE__ */ jsx(MarkdownLineListScreen, {});
6065
6215
  case "markdownLineEditor": return /* @__PURE__ */ jsx(MarkdownLineEditorScreen, {});
6066
6216
  case "pdfPageList": return /* @__PURE__ */ jsx(PdfPageListScreen, {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-cli",
3
- "version": "1.2.11",
3
+ "version": "1.3.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": {