Skip to main content

pimalaya_cli/
printer.rs

1//! Command output rendering to stdout.
2//!
3//! The [`Printer`] trait abstracts how a command emits its result, the
4//! [`StdoutPrinter`] implementation writes plain text or JSON to
5//! stdout, and [`PrintTable`] renders a value as a table.
6
7use std::{
8    fmt,
9    io::{IsTerminal, Stdout, Write, stdout},
10};
11
12use anyhow::{Context, Result};
13use serde::Serialize;
14
15use crate::clap::args::JsonFlag;
16
17/// A value that can render itself as a table.
18pub trait PrintTable {
19    /// Writes the table to the given writer, capped at an optional
20    /// maximum width.
21    fn print(&self, writer: &mut dyn Write, table_max_width: Option<u16>) -> Result<()>;
22}
23
24/// Sink a command writes its output to.
25pub trait Printer {
26    /// Writes one piece of command output, as text or JSON.
27    fn out<T: fmt::Display + Serialize>(&mut self, data: T) -> Result<()>;
28
29    /// Whether the printer emits JSON.
30    fn is_json(&self) -> bool {
31        false
32    }
33}
34
35/// A [`Printer`] writing plain text or JSON to stdout.
36pub struct StdoutPrinter {
37    stdout: Stdout,
38    json: bool,
39}
40
41impl StdoutPrinter {
42    /// Builds a stdout printer, emitting JSON when the flag is set.
43    pub fn new(json: &JsonFlag) -> Self {
44        Self {
45            stdout: stdout(),
46            json: json.enabled,
47        }
48    }
49}
50
51impl Printer for StdoutPrinter {
52    fn out<T: fmt::Display + serde::Serialize>(&mut self, data: T) -> Result<()> {
53        if self.json {
54            if self.stdout.is_terminal() {
55                serde_json::to_writer_pretty(&mut self.stdout, &data)
56                    .context("Print pretty JSON to stdout error")?;
57                writeln!(self.stdout)?;
58            } else {
59                serde_json::to_writer(&mut self.stdout, &data)
60                    .context("Print JSON to stdout error")?;
61            }
62        } else {
63            write!(self.stdout, "{data}")?;
64        }
65
66        Ok(())
67    }
68
69    fn is_json(&self) -> bool {
70        self.json
71    }
72}
73
74/// A plain message wrapper providing text and JSON output.
75#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
76pub struct Message {
77    message: String,
78}
79
80impl Message {
81    /// Wraps the given message.
82    pub fn new(message: impl ToString) -> Self {
83        Self {
84            message: message.to_string(),
85        }
86    }
87}
88
89impl fmt::Display for Message {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        writeln!(f, "{}", &self.message)
92    }
93}