Skip to main content

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