1use std::io::{self, Write};
4use std::process::ExitCode;
5
6use anstream::AutoStream;
7use serde::Serialize;
8
9use crate::color::ColorMode;
10use crate::document::{Document, Text};
11use crate::format::OutputFormat;
12use crate::model::{Envelope, ErrorBody};
13use crate::render::{DEFAULT_COLUMN_BUFFER_ENVS, DEFAULT_MINIMUM_AUTOMATIC_WIDTH, RenderOptions};
14
15pub trait Present: Serialize {
17 fn present(&self) -> Document;
19
20 fn message_kind(&self) -> MessageKind {
22 MessageKind::Success
23 }
24
25 fn exit_code(&self) -> u8 {
28 self.message_kind().default_exit_code()
29 }
30}
31
32#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
34pub enum MessageKind {
35 #[default]
37 Success,
38 Error,
40}
41
42impl MessageKind {
43 #[must_use]
45 pub const fn default_exit_code(self) -> u8 {
46 match self {
47 Self::Success => 0,
48 Self::Error => 1,
49 }
50 }
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55pub enum Stream {
56 None,
58 Stdout,
60 Stderr,
62}
63
64#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct Captured {
67 stream: Stream,
68 content: String,
69 exit_code: u8,
70}
71
72impl Captured {
73 #[must_use]
75 pub const fn stream(&self) -> Stream {
76 self.stream
77 }
78
79 #[must_use]
81 pub fn bytes(&self) -> &[u8] {
82 self.content.as_bytes()
83 }
84
85 #[must_use]
87 pub fn text(&self) -> &str {
88 &self.content
89 }
90
91 #[must_use]
93 pub fn exit_code(&self) -> ExitCode {
94 ExitCode::from(self.exit_code)
95 }
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
100pub struct View {
101 pub format: OutputFormat,
103 pub color: ColorMode,
105 pub quiet: bool,
107 width: Option<u16>,
108 automatic_width_buffer: Option<u16>,
109 automatic_width_buffer_envs: &'static [&'static str],
110 minimum_automatic_width: u16,
111}
112
113impl View {
114 #[must_use]
116 pub const fn new(format: OutputFormat, color: ColorMode) -> Self {
117 Self {
118 format,
119 color,
120 quiet: false,
121 width: None,
122 automatic_width_buffer: None,
123 automatic_width_buffer_envs: DEFAULT_COLUMN_BUFFER_ENVS,
124 minimum_automatic_width: DEFAULT_MINIMUM_AUTOMATIC_WIDTH,
125 }
126 }
127
128 #[must_use]
130 pub const fn quiet(mut self, quiet: bool) -> Self {
131 self.quiet = quiet;
132 self
133 }
134
135 #[must_use]
137 pub const fn width(mut self, width: u16) -> Self {
138 self.width = Some(width);
139 self
140 }
141
142 #[must_use]
145 pub const fn automatic_width_buffer(mut self, columns: u16) -> Self {
146 self.automatic_width_buffer = Some(columns);
147 self
148 }
149
150 #[must_use]
152 pub const fn automatic_width_buffer_envs(mut self, names: &'static [&'static str]) -> Self {
153 self.automatic_width_buffer_envs = names;
154 self
155 }
156
157 #[must_use]
159 pub const fn minimum_automatic_width(mut self, columns: u16) -> Self {
160 self.minimum_automatic_width = columns;
161 self
162 }
163
164 #[must_use]
166 pub const fn explicit_automatic_width_buffer(self) -> Option<u16> {
167 self.automatic_width_buffer
168 }
169
170 #[must_use]
172 pub const fn automatic_width_buffer_env_names(self) -> &'static [&'static str] {
173 self.automatic_width_buffer_envs
174 }
175
176 #[must_use]
178 pub const fn automatic_width_minimum(self) -> u16 {
179 self.minimum_automatic_width
180 }
181
182 pub(crate) fn render_options(self) -> RenderOptions {
183 let mut options = RenderOptions::new(self.color)
184 .automatic_width_buffer_envs(self.automatic_width_buffer_envs)
185 .minimum_automatic_width(self.minimum_automatic_width);
186 if let Some(width) = self.width {
187 options = options.width(width);
188 }
189 if let Some(buffer) = self.automatic_width_buffer {
190 options = options.automatic_width_buffer(buffer);
191 }
192 options
193 }
194
195 pub fn capture(self, value: &impl Present) -> io::Result<Captured> {
197 let kind = value.message_kind();
198 let exit_code = value.exit_code();
199 if self.format.is_json() {
200 let mut content = serde_json::to_string(value)?;
201 content.push('\n');
202 return Ok(Captured {
203 stream: Stream::Stdout,
204 content,
205 exit_code,
206 });
207 }
208 if self.quiet && kind == MessageKind::Success {
209 return Ok(Captured {
210 stream: Stream::None,
211 content: String::new(),
212 exit_code,
213 });
214 }
215 let content = value.present().render(self.render_options());
216 Ok(Captured {
217 stream: match kind {
218 MessageKind::Success => Stream::Stdout,
219 MessageKind::Error => Stream::Stderr,
220 },
221 content,
222 exit_code,
223 })
224 }
225
226 pub fn show(self, value: &impl Present) -> io::Result<ExitCode> {
228 let captured = self.capture(value)?;
229 match captured.stream() {
230 Stream::None => {}
231 Stream::Stdout => write_stdout(captured.bytes(), self.color)?,
232 Stream::Stderr => write_stderr(captured.bytes(), self.color)?,
233 }
234 Ok(captured.exit_code())
235 }
236
237 pub fn emit_err(self, bin: &str, message: &str) -> io::Result<ExitCode> {
239 if self.format.is_json() {
240 emit_json(&Envelope::<()>::err(ErrorBody::new(bin, message)))?;
241 return Ok(ExitCode::FAILURE);
242 }
243 let document =
244 Document::new().paragraph(Text::new().error(bin).then(": ").then(message.to_owned()));
245 write_stderr(
246 document.render(self.render_options()).as_bytes(),
247 self.color,
248 )?;
249 Ok(ExitCode::FAILURE)
250 }
251}
252
253pub(crate) fn emit_json<T: Serialize>(value: &T) -> io::Result<()> {
255 let stdout = io::stdout();
256 let mut lock = stdout.lock();
257 serde_json::to_writer(&mut lock, value)?;
258 lock.write_all(b"\n")?;
259 lock.flush()
260}
261
262pub(crate) fn write_stdout(bytes: &[u8], color: ColorMode) -> io::Result<()> {
264 let mut stream = AutoStream::new(io::stdout().lock(), color.choice());
265 stream.write_all(bytes)?;
266 stream.flush()
267}
268
269pub(crate) fn write_stderr(bytes: &[u8], color: ColorMode) -> io::Result<()> {
271 let mut stream = AutoStream::new(io::stderr().lock(), color.choice());
272 stream.write_all(bytes)?;
273 stream.flush()
274}