Add a --output text|json flag¶
Define one row type with both derives¶
render_table needs Tabled; render_json needs Serialize. A type
that feeds both carries both:
use serde::Serialize;
use tabled::Tabled;
#[derive(Tabled, Serialize)]
pub struct RepoRow {
#[tabled(rename = "repository")]
#[serde(rename = "repository")]
pub name: String,
pub branch: String,
pub dirty: bool,
}
The two rename attributes are independent. Set both, or the table header and the JSON key will disagree — which is exactly the sort of thing nobody notices until a script starts parsing the JSON.
Pick the renderer from the flag¶
Keep the choice in one place. The command produces rows; something downstream of it decides how they are written:
use rtb_tui::{render_json, render_table, RenderError};
use serde::Serialize;
use tabled::Tabled;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
}
pub fn render<R: Tabled + Serialize>(
rows: &[R],
format: OutputFormat,
) -> Result<String, RenderError> {
match format {
OutputFormat::Text => Ok(render_table(rows)),
OutputFormat::Json => render_json(rows),
}
}
Print the result with print!, not println!. Both helpers already
append a trailing newline, so println! adds a blank line to every
command's output.
Handle the empty case before rendering¶
An empty slice is not an error, and neither helper treats it as one.
render_table renders the header and the rule with no body rows;
render_json renders []. That asymmetry usually wants a decision at
the call site rather than in the renderer:
if rows.is_empty() && format == OutputFormat::Text {
eprintln!("no repositories matched");
return Ok(());
}
Note the eprintln!. A "nothing found" message on stdout ends up in
whatever is parsing the output; on stderr it reaches the human and
leaves the pipe clean. And leave the JSON branch alone — [] is the
correct answer to "give me the list" and a script can handle it.
Deal with the one type that will not render as a table¶
Serialize is happy with nested structs; Tabled is not. A row type
with a Vec<String> field, or a field whose type is another struct,
will not derive Tabled without a per-field tabled attribute telling
it how to flatten the value into a cell.
When that happens you have two honest options, and inventing a third usually ends in a table nobody can read:
- flatten for the table (
tags: Stringholding a comma-joined list) and accept that the JSON is flat too; or - keep two types — a rich one for JSON and a flat projection for the
table — and write the
Fromimpl between them.
What this does not give you¶
There is no third format. rtb-tui renders text and JSON, and nothing
else; there is no YAML, CSV or template renderer, and adding one means
writing it in your own crate. The table style is fixed as well — see
Why the table style is not configurable.
Next¶
- Render helpers reference — exact
output, trailing newlines, and the cases where
render_jsonfails.