pub mod error;
pub mod human;
pub mod reports;
use std::io::Write;
use human::{Human, Theme};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format {
Json,
Human,
}
pub trait CliReport: erased_serde::Serialize {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()>;
fn exit_code(&self) -> i32 {
0
}
}
erased_serde::serialize_trait_object!(CliReport);
pub struct Output {
fmt: Format,
}
impl Output {
pub fn new(fmt: Format) -> Self {
Self { fmt }
}
pub fn from_flags(json: bool, human: bool) -> Self {
Self::new(if human && !json {
Format::Human
} else {
Format::Json
})
}
pub fn format(&self) -> Format {
self.fmt
}
pub fn emit(&self, r: &dyn CliReport) -> std::io::Result<()> {
let mut out = std::io::stdout().lock();
self.write(&mut out, Theme::for_stdout(), r)
}
pub fn render_to_vec(&self, r: &dyn CliReport) -> Vec<u8> {
let mut v = Vec::new();
self.write(&mut v, Theme::none(), r)
.expect("render to vec is infallible");
v
}
fn write(&self, out: &mut dyn Write, theme: Theme, r: &dyn CliReport) -> std::io::Result<()> {
match self.fmt {
Format::Json => {
serde_json::to_writer_pretty(&mut *out, r).map_err(std::io::Error::other)?;
writeln!(out)
}
Format::Human => r.render(&mut Human::new(&mut *out, theme)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(serde::Serialize)]
struct Dummy {
ok: bool,
}
impl CliReport for Dummy {
fn render(&self, h: &mut Human<'_>) -> std::io::Result<()> {
h.line(&format!("ok={}", self.ok))
}
}
#[test]
fn emit_json_writes_pretty_object() {
let buf = Output::new(Format::Json).render_to_vec(&Dummy { ok: true });
assert_eq!(String::from_utf8(buf).unwrap(), "{\n \"ok\": true\n}\n");
}
#[test]
fn emit_human_uses_render() {
let buf = Output::new(Format::Human).render_to_vec(&Dummy { ok: false });
assert_eq!(String::from_utf8(buf).unwrap(), "ok=false\n");
}
}