Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

9.4KB

CLI Render Contract

The desktop app (Story: “Launch a text-only render from the desktop app”) integrates against this contract, not against EnvelopeRenderer.Cli's internals. Treat it as the stable interface between the two; changing it is a cross-cutting decision, not a local one.

Implementation: src/EnvelopeRenderer.Cli.

Arguments

All three are required. There is no short form and no = form in this MVP — keep the surface intentionally small per the story's conversation notes.

Argument Value Notes
--template <path> Local or UNC path to the XML template Must exist and be readable.
--csv <path> Local or UNC path to the CSV data file Must exist and be readable.
--output <path> Local or UNC path to write the rendered PDF to Parent directory must already exist; the CLI does not create directories.
-h, --help Prints usage to stdout and exits 0. Overrides everything else on the command line.

Unknown arguments, a flag given without a value, a flag given twice, or an empty value are all usage errors (exit 2).

stdout / stderr rules

  • stdout is reserved for machine-readable output: --help text (human-readable by exception, since it's explicitly for a human at a terminal), and — once argument/path validation has succeeded and rendering begins — one line-oriented PROGRESS event per line (see “Progress events” below). The desktop app should be able to treat stdout as parseable and never need to filter noise out of it.
  • stderr carries every error, one per line, each prefixed ERROR: . Multiple problems (e.g. two missing arguments) are each reported as their own line rather than stopping at the first. A render-stage failure still writes its ERROR: line(s) here exactly as before — the stdout failure progress event described below supplements this, it never replaces it, and it never changes the exit code.

Progress events

Once argument parsing, input-path validation, and output-path validation have all succeeded (i.e. the CLI is past every exit-2/3/4 case and has started the actual render), it emits one line per event to stdout in this fixed, space-separated, key=value format:

PROGRESS <kind> elapsedMs=<integer> completed=<integer> [reason=<text to end of line>]
  • <kind> is one of startup, render, complete, failure.
  • elapsedMs — milliseconds since the render phase started, as an integer. Always present.
  • completed — number of CSV records successfully rendered to a page so far. Always present (0 for startup and for a failure that happened before any page was rendered).
  • reason — present only on failure; a short, human-readable description of what went wrong. It is always the last field on the line and takes everything remaining on the line verbatim (embedded newlines are stripped to a single space first), so it never needs quoting or escaping and a consumer can safely Split(' ', 5)-style parse the fixed fields first.

Why this shape instead of one JSON object per line: it stays trivially parseable with a plain string split (no JSON library dependency needed in the desktop shell), it stays readable at a terminal or in a redirected log file — matching the plain-text style already used for stderr's ERROR: lines — and every numeric field is a culture-invariant integer, so there's no decimal-separator ambiguity across locales. Implementation: Progress/ProgressEventFormatter.cs.

Event kinds, each emitted at most as documented:

Kind When Count per run
startup Immediately when the render phase begins, before template parsing, CSV header validation, or Debenu setup. Exactly 1
render After a record is successfully rendered to a page. Throttled to at least once per second — see below — not once per row. 0 or more
complete The run finished successfully; completed is the final record count. Exactly 1, only on success
failure The run failed at any point during the render phase (template parse, CSV read, Debenu setup, or the per-record render/save loop); completed is however many records were successfully rendered before the failure, and reason summarizes the same problem reported to stderr. Exactly 1, only on failure

A run therefore always starts with exactly one startup line and ends with exactly one complete or failure line, with zero or more render lines in between. Argument parsing and path-validation failures (exit 2/3/4) produce no progress events at all — that failure mode is entirely a pre-render validation error, unaffected by this contract.

Throttling

render events are throttled to at least once per second of wall-clock time, not once per CSV row, so a fast render of thousands of rows doesn't flood stdout with one line per row. The first render event after startup is always written immediately (so a slow run shows something right away); after that, an event is only written once at least 1000ms have passed since the last one that was actually written. startup, complete, and failure are never throttled — each happens exactly once, so there's nothing to throttle. Implementation: Progress/ConsoleProgressReporter.cs.

Exit codes

Code Meaning
0 Success — PDF written to --output.
1 Template, CSV, or render error: malformed/invalid template XML, a template column not present in the CSV header row, an empty CSV, a missing/unresolvable font, or a Debenu render/save failure (including a missing or invalid license key — see below). Read stderr for which.
2 Invalid usage — missing, unknown, duplicate, or empty-valued argument.
3 Input not found — --template or --csv path does not exist.
4 Output path invalid — output directory does not exist, or the output path is itself an existing directory.

Validation runs in this order and stops at the first failing stage: usage → input paths → output path → render. A caller can rely on, e.g., never seeing an output-path error (4) while an input path is still missing.

Rendering

The text-only template format (--template) is documented separately in TEMPLATE_FORMAT.md.

Debenu license key

Rendering (not argument validation) requires a Debenu Quick PDF Library 10.13 license key at runtime, read from the DEBENU_LICENSE_KEY environment variable. Without it, every render fails at the save step with exit 1 — confirmed directly against the vendor DLL: page creation, font embedding, and text drawing all succeed, but SaveToFile/SaveToString both return error code 999 regardless of content. A working key for local development is kept at the project root in key.txt (untracked — never commit it or hardcode it into source).

UNC paths

Local and UNC paths are both accepted and validated identically — System.IO.File.Exists / Directory.Exists handle UNC paths natively, so there's no special-case code. What is not yet defined is timeout/retry behavior against a slow or unreachable UNC share; that's an open impediment (logs/impediment_log.md, logged 2026-09-04) carried as an explicit risk into the Debenu render story, not solved here.

Smoke examples

Run from code/:

$ dotnet run --project src/EnvelopeRenderer.Cli -- --help
(usage text)
exit=0

$ dotnet run --project src/EnvelopeRenderer.Cli --
ERROR: Missing required argument '--template'.
ERROR: Missing required argument '--csv'.
ERROR: Missing required argument '--output'.
(usage text on stderr)
exit=2

$ dotnet run --project src/EnvelopeRenderer.Cli -- --template nope.xml --csv sample-data/wilson.csv --output out.pdf
ERROR: Template not found: 'nope.xml'.
exit=3

$ dotnet run --project src/EnvelopeRenderer.Cli -- --template envelope.xml --csv sample-data/wilson.csv --output nosuchdir/out.pdf
ERROR: Output directory does not exist: 'nosuchdir'.
exit=4

$ dotnet run --project src/EnvelopeRenderer.Cli -- --template sample-data/sample-envelope-template.xml --csv "sample-data/87700 - 999999 - Wilson Township.csv" --output out.pdf
# stdout:
PROGRESS startup elapsedMs=0 completed=0
PROGRESS render elapsedMs=802 completed=1
PROGRESS render elapsedMs=1804 completed=392
PROGRESS failure elapsedMs=1805 completed=392 reason=Failed to save PDF to 'out.pdf' (error code 999).
# stderr:
ERROR: Failed to save PDF to 'out.pdf' (error code 999).
exit=1
# (DEBENU_LICENSE_KEY not set — see "Debenu license key" above. Note the throttling in action:
#  392 records were each individually reported to RenderEngine, but only 2 `render` lines were
#  actually written to stdout because the whole render took under 2 seconds.)

$ DEBENU_LICENSE_KEY="$(cat ../key.txt)" dotnet run --project src/EnvelopeRenderer.Cli -- --template sample-data/sample-envelope-template.xml --csv "sample-data/87700 - 999999 - Wilson Township.csv" --output out.pdf
exit=0
# stdout (illustrative — not re-captured for this story since no license key was available in
#  this dev environment; the render loop that produces `startup`/`render` is identical to the
#  failure example above, a valid key only changes the final Save() outcome, so the only actual
#  difference is the last line):
PROGRESS startup elapsedMs=0 completed=0
PROGRESS render elapsedMs=802 completed=1
PROGRESS render elapsedMs=1804 completed=392
PROGRESS complete elapsedMs=<final> completed=392

Powered by TurnKey Linux.