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 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}