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}