1use crate::color::{spec_bold_color, spec_color};
2use crate::utils::str_from_utf8;
3use std::fmt;
4use std::io::{Error, ErrorKind, Result, Write};
5use termcolor::{Color, ColorSpec, WriteColor};
6
7pub fn unpack_io_error(error: Error) -> (ErrorKind, String) {
8 (error.kind(), error.to_string())
9}
10
11#[derive(Default)]
12pub struct ColoredOuput {
13 spec: ColorSpec,
14 chunks: Vec<OutputChunk>,
15}
16
17impl ColoredOuput {
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 pub fn chunks(&self) -> &Vec<OutputChunk> {
23 &self.chunks
24 }
25}
26
27impl Write for ColoredOuput {
28 fn write(&mut self, buf: &[u8]) -> Result<usize> {
29 let spec = &self.spec;
30 let value = str_from_utf8(buf)?;
31
32 if let Some(chunk) = self.chunks.last_mut().filter(|chunk| &chunk.spec == spec) {
33 chunk.value += value;
34 } else {
35 self.chunks.push(OutputChunk {
36 spec: self.spec.clone(),
37 value: value.into(),
38 })
39 }
40
41 Ok(buf.len())
42 }
43
44 fn flush(&mut self) -> Result<()> {
45 Ok(())
46 }
47}
48
49impl WriteColor for ColoredOuput {
50 fn supports_color(&self) -> bool {
51 true
52 }
53
54 fn set_color(&mut self, spec: &ColorSpec) -> Result<()> {
55 self.spec = spec.clone();
56 Ok(())
57 }
58
59 fn reset(&mut self) -> Result<()> {
60 self.spec = ColorSpec::new();
61 Ok(())
62 }
63}
64
65#[derive(PartialEq, Clone)]
66pub struct OutputChunk {
67 pub spec: ColorSpec,
68 pub value: String,
69}
70
71impl OutputChunk {
72 pub fn plain(value: &str) -> Self {
73 Self {
74 spec: ColorSpec::new(),
75 value: value.into(),
76 }
77 }
78
79 pub fn color(color: Color, value: &str) -> Self {
80 Self {
81 spec: spec_color(color),
82 value: value.into(),
83 }
84 }
85
86 pub fn bold_color(color: Color, value: &str) -> Self {
87 Self {
88 spec: spec_bold_color(color),
89 value: value.into(),
90 }
91 }
92}
93
94impl fmt::Debug for OutputChunk {
95 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(fmt, "OutputChunk::")?;
97
98 match (self.spec.fg(), self.spec.bold()) {
99 (None, _) => write!(fmt, "plain(")?,
100 (Some(color), true) => write!(fmt, "bold_color(Color::{:?}, ", color)?,
101 (Some(color), false) => write!(fmt, "color(Color::{:?}, ", color)?,
102 }
103
104 write!(fmt, "{:?})", self.value.replace("\n", "\\n"))
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use ntest::*;
112
113 #[test]
114 fn unpack_io_error() {
115 assert_eq!(
116 super::unpack_io_error(Error::new(ErrorKind::Other, "test")),
117 (ErrorKind::Other, "test".into())
118 );
119 }
120
121 mod colored_output {
122 use super::*;
123
124 #[test]
125 fn supports_color() {
126 assert_true!(ColoredOuput::new().supports_color());
127 }
128
129 #[test]
130 fn write() {
131 let mut output = ColoredOuput::new();
132
133 write!(output, "a").unwrap();
134 write!(output, "b").unwrap();
135 output.set_color(&spec_color(Color::Red)).unwrap();
136 write!(output, "c").unwrap();
137 write!(output, "d").unwrap();
138 output.set_color(&spec_bold_color(Color::Blue)).unwrap();
139 write!(output, "e").unwrap();
140 write!(output, "f").unwrap();
141 output.reset().unwrap();
142 write!(output, "g").unwrap();
143 output.flush().unwrap();
144
145 assert_eq!(
146 output.chunks,
147 &[
148 OutputChunk::plain("ab"),
149 OutputChunk::color(Color::Red, "cd"),
150 OutputChunk::bold_color(Color::Blue, "ef"),
151 OutputChunk::plain("g"),
152 ]
153 );
154 }
155 }
156
157 mod output_chunk {
158 use super::*;
159 use test_case::test_case;
160
161 #[test_case(OutputChunk::plain("ab"), ColorSpec::new(), "ab" ; "plain")]
162 #[test_case(OutputChunk::color(Color::Red, "cd"), spec_color(Color::Red), "cd" ; "color")]
163 #[test_case(OutputChunk::bold_color(Color::Blue, "ef"), spec_bold_color(Color::Blue), "ef" ; "bold color")]
164 fn create(chunk: OutputChunk, spec: ColorSpec, value: &str) {
165 assert_eq!(chunk.spec, spec);
166 assert_eq!(chunk.value, value);
167 }
168
169 #[test_case(OutputChunk::plain("a\nb"), r#"OutputChunk::plain("a\\nb")"# ; "plain")]
170 #[test_case(OutputChunk::color(Color::Red, "c\nd"), r#"OutputChunk::color(Color::Red, "c\\nd")"# ; "color")]
171 #[test_case(OutputChunk::bold_color(Color::Blue, "e\nf"), r#"OutputChunk::bold_color(Color::Blue, "e\\nf")"# ; "bold color")]
172 fn debug(chunk: OutputChunk, result: &str) {
173 assert_eq!(format!("{:?}", chunk), result);
174 }
175 }
176}