1use std::io::{self, Write};
4
5use serde::Serialize;
6
7use crate::context::{ColorMode, Context, LogLevel};
8use crate::error::{Error, Result};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub enum OutputMode {
13 #[default]
15 Human,
16 Json,
18}
19
20impl OutputMode {
21 pub(crate) fn parse(raw: Option<&str>) -> Self {
22 match raw.map(str::trim) {
23 Some("json") => Self::Json,
24 _ => Self::Human,
26 }
27 }
28}
29
30pub struct Output {
33 mode: OutputMode,
34 log_level: LogLevel,
35 color: ColorMode,
36 writer: Box<dyn Write + Send>,
37}
38
39impl Output {
40 pub fn for_context(ctx: &Context, writer: impl Write + Send + 'static) -> Self {
42 Self {
43 mode: ctx.output_mode(),
44 log_level: ctx.log_level(),
45 color: ctx.color(),
46 writer: Box::new(writer),
47 }
48 }
49
50 #[must_use]
52 pub fn with_writer(mut self, writer: impl Write + Send + 'static) -> Self {
53 self.writer = Box::new(writer);
54 self
55 }
56
57 #[must_use]
59 pub const fn mode(&self) -> OutputMode {
60 self.mode
61 }
62
63 #[must_use]
65 pub const fn color(&self) -> ColorMode {
66 self.color
67 }
68
69 pub fn human(&mut self, msg: &str) {
74 if self.mode == OutputMode::Json || self.log_level == LogLevel::Quiet {
75 return;
76 }
77 drop(writeln!(self.writer, "{msg}"));
79 }
80
81 pub fn json<T: Serialize>(&mut self, value: &T) -> Result<()> {
91 let line = serde_json::to_string(value)?;
92 writeln!(self.writer, "{line}").map_err(Error::Io)?;
93 Ok(())
94 }
95
96 pub fn error(&mut self, err: &dyn std::error::Error) {
98 match self.mode {
99 OutputMode::Json => {
100 drop(self.json(&serde_json::json!({
101 "error": err.to_string(),
102 })));
103 },
104 OutputMode::Human => {
105 drop(writeln!(self.writer, "error: {err}"));
106 },
107 }
108 }
109
110 pub fn flush(&mut self) -> io::Result<()> {
116 self.writer.flush()
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 struct Sink(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
125
126 impl Write for Sink {
127 fn write(&mut self, b: &[u8]) -> io::Result<usize> {
128 self.0.lock().unwrap().extend_from_slice(b);
129 Ok(b.len())
130 }
131 fn flush(&mut self) -> io::Result<()> {
132 Ok(())
133 }
134 }
135
136 fn captured(ctx: &Context) -> (Output, std::sync::Arc<std::sync::Mutex<Vec<u8>>>) {
137 let buf: std::sync::Arc<std::sync::Mutex<Vec<u8>>> =
138 std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
139 (Output::for_context(ctx, Sink(buf.clone())), buf)
140 }
141
142 #[test]
143 fn human_writes_one_line() {
144 let ctx = Context::default_for_tests();
145 let (mut out, buf) = captured(&ctx);
146 out.human("hello");
147 let s = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
148 assert_eq!(s, "hello\n");
149 }
150
151 #[test]
152 fn json_serializes_value() {
153 let ctx = Context::default_for_tests();
154 let (mut out, buf) = captured(&ctx);
155 out.json(&serde_json::json!({"k": 1})).unwrap();
156 let s = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
157 assert_eq!(s, "{\"k\":1}\n");
158 }
159}