dsp_cli/render/format.rs
1//! `Format` enum and renderer factory.
2//!
3//! `Format` is the render-layer concept that maps a user-chosen output format
4//! to a concrete renderer instance. It is also the `ValueEnum` that clap uses
5//! for the `--format` flag on the six vre leaf commands. See dsp-cli/ADR-0003 for the
6//! output-format specification and dsp-cli/ADR-0008 for the renderer-layer placement.
7
8use clap::ValueEnum;
9
10use super::csv::CsvRenderer;
11use super::json::JsonRenderer;
12use super::lines::LinesRenderer;
13use super::progress::{HumanProgress, JsonProgress, ProgressReporter};
14use super::prose::ProseRenderer;
15use super::tsv::TsvRenderer;
16use super::{Renderer, TableOptions};
17
18/// Output format for a command.
19///
20/// `Prose` is the default (per dsp-cli/ADR-0003). The other variants map to the
21/// machine-readable formats; their renderers began as Phase 1 stubs (only
22/// `diagnostic`) and grow per-noun methods with real data (Phase 3 `dump`,
23/// Phase 4 `projects`, Phase 5 onward).
24#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
25pub enum Format {
26 /// Rich human-readable prose output with contextual hints (default).
27 #[value(name = "prose")]
28 Prose,
29
30 /// Newline-delimited JSON (one object per line).
31 #[value(name = "json")]
32 Json,
33
34 /// One identifier per line, for shell pipeline consumption.
35 #[value(name = "lines")]
36 Lines,
37
38 /// Comma-separated values with a header row.
39 #[value(name = "csv")]
40 Csv,
41
42 /// Tab-separated values with a header row.
43 #[value(name = "tsv")]
44 Tsv,
45}
46
47impl Format {
48 /// Construct the renderer for this format, writing to stdout.
49 ///
50 /// Returns a heap-allocated `Box<dyn Renderer>`. Per-noun methods grow as
51 /// real data lands (Phase 3 `dump`, Phase 4 `projects`, Phase 5 onward).
52 /// Use `into_renderer_with_writer` in tests to capture output.
53 ///
54 /// Uses `TableOptions::default()`. Call [`Format::into_renderer_with_options`]
55 /// to supply user-specified column projection or header-control options.
56 pub fn into_renderer(self) -> Box<dyn Renderer> {
57 self.into_renderer_with_options(TableOptions::default())
58 }
59
60 /// Construct the renderer for this format with the given tabular options.
61 ///
62 /// For `csv`, `tsv`, and `lines`, `opts` controls column projection
63 /// (`--columns`) and header emission (`--no-header` / `--header-only`).
64 /// For `prose` and `json`, `opts` is ignored — those formats are not column-
65 /// structured and do not honour projection or header flags.
66 pub fn into_renderer_with_options(self, opts: TableOptions) -> Box<dyn Renderer> {
67 match self {
68 Format::Prose => Box::new(ProseRenderer::new()),
69 Format::Json => Box::new(JsonRenderer::new()),
70 Format::Lines => Box::new(LinesRenderer::new().with_options(opts)),
71 Format::Csv => Box::new(CsvRenderer::new().with_options(opts)),
72 Format::Tsv => Box::new(TsvRenderer::new().with_options(opts)),
73 }
74 }
75
76 /// Construct the progress reporter for this format, writing to stderr.
77 ///
78 /// `Format::Json` → `JsonProgress` (NDJSON events on stderr so a JSON
79 /// consumer can parse both the final stdout object and the stderr event
80 /// stream with the same parser). All other formats → `HumanProgress`
81 /// (prose/lines/csv/tsv share human-readable stderr — stderr is non-data
82 /// output so the format distinction does not apply there).
83 ///
84 /// `Format` is `Copy`, so a caller can do:
85 /// ```
86 /// # use dsp_cli::render::{Format, ProgressReporter};
87 /// # let fmt = Format::Prose;
88 /// let mut renderer = fmt.into_renderer();
89 /// let mut reporter = fmt.into_progress_reporter();
90 /// ```
91 pub fn into_progress_reporter(self) -> Box<dyn ProgressReporter> {
92 match self {
93 Format::Json => Box::new(JsonProgress::new()),
94 Format::Prose | Format::Lines | Format::Csv | Format::Tsv => Box::new(HumanProgress::new()),
95 }
96 }
97}