1 | use std::{io, sync::Mutex}; |
2 | |
3 | use crate::fmt::writer::WriteStyle; |
4 | |
5 | #[derive (Debug)] |
6 | pub(in crate::fmt::writer) struct BufferWriter { |
7 | target: WritableTarget, |
8 | write_style: WriteStyle, |
9 | } |
10 | |
11 | impl BufferWriter { |
12 | pub(in crate::fmt::writer) fn stderr(is_test: bool, write_style: WriteStyle) -> Self { |
13 | BufferWriter { |
14 | target: if is_test { |
15 | WritableTarget::PrintStderr |
16 | } else { |
17 | WritableTarget::WriteStderr |
18 | }, |
19 | write_style, |
20 | } |
21 | } |
22 | |
23 | pub(in crate::fmt::writer) fn stdout(is_test: bool, write_style: WriteStyle) -> Self { |
24 | BufferWriter { |
25 | target: if is_test { |
26 | WritableTarget::PrintStdout |
27 | } else { |
28 | WritableTarget::WriteStdout |
29 | }, |
30 | write_style, |
31 | } |
32 | } |
33 | |
34 | pub(in crate::fmt::writer) fn pipe( |
35 | pipe: Box<Mutex<dyn io::Write + Send + 'static>>, |
36 | write_style: WriteStyle, |
37 | ) -> Self { |
38 | BufferWriter { |
39 | target: WritableTarget::Pipe(pipe), |
40 | write_style, |
41 | } |
42 | } |
43 | |
44 | pub(in crate::fmt::writer) fn write_style(&self) -> WriteStyle { |
45 | self.write_style |
46 | } |
47 | |
48 | pub(in crate::fmt::writer) fn buffer(&self) -> Buffer { |
49 | Buffer(Vec::new()) |
50 | } |
51 | |
52 | pub(in crate::fmt::writer) fn print(&self, buf: &Buffer) -> io::Result<()> { |
53 | #![allow (clippy::print_stdout)] // enabled for tests only |
54 | #![allow (clippy::print_stderr)] // enabled for tests only |
55 | |
56 | use std::io::Write as _; |
57 | |
58 | let buf = buf.as_bytes(); |
59 | match &self.target { |
60 | WritableTarget::WriteStdout => { |
61 | let stream = io::stdout(); |
62 | #[cfg (feature = "color" )] |
63 | let stream = anstream::AutoStream::new(stream, self.write_style.into()); |
64 | let mut stream = stream.lock(); |
65 | stream.write_all(buf)?; |
66 | stream.flush()?; |
67 | } |
68 | WritableTarget::PrintStdout => { |
69 | #[cfg (feature = "color" )] |
70 | let buf = adapt(buf, self.write_style)?; |
71 | #[cfg (feature = "color" )] |
72 | let buf = &buf; |
73 | let buf = String::from_utf8_lossy(buf); |
74 | print!(" {buf}" ); |
75 | } |
76 | WritableTarget::WriteStderr => { |
77 | let stream = io::stderr(); |
78 | #[cfg (feature = "color" )] |
79 | let stream = anstream::AutoStream::new(stream, self.write_style.into()); |
80 | let mut stream = stream.lock(); |
81 | stream.write_all(buf)?; |
82 | stream.flush()?; |
83 | } |
84 | WritableTarget::PrintStderr => { |
85 | #[cfg (feature = "color" )] |
86 | let buf = adapt(buf, self.write_style)?; |
87 | #[cfg (feature = "color" )] |
88 | let buf = &buf; |
89 | let buf = String::from_utf8_lossy(buf); |
90 | eprint!(" {buf}" ); |
91 | } |
92 | WritableTarget::Pipe(pipe) => { |
93 | #[cfg (feature = "color" )] |
94 | let buf = adapt(buf, self.write_style)?; |
95 | #[cfg (feature = "color" )] |
96 | let buf = &buf; |
97 | let mut stream = pipe.lock().expect("no panics while held" ); |
98 | stream.write_all(buf)?; |
99 | stream.flush()?; |
100 | } |
101 | } |
102 | |
103 | Ok(()) |
104 | } |
105 | } |
106 | |
107 | #[cfg (feature = "color" )] |
108 | fn adapt(buf: &[u8], write_style: WriteStyle) -> io::Result<Vec<u8>> { |
109 | use std::io::Write as _; |
110 | |
111 | let adapted: Vec = Vec::with_capacity(buf.len()); |
112 | let mut stream: AutoStream> = anstream::AutoStream::new(raw:adapted, choice:write_style.into()); |
113 | stream.write_all(buf)?; |
114 | let adapted: Vec = stream.into_inner(); |
115 | Ok(adapted) |
116 | } |
117 | |
118 | pub(in crate::fmt) struct Buffer(Vec<u8>); |
119 | |
120 | impl Buffer { |
121 | pub(in crate::fmt) fn clear(&mut self) { |
122 | self.0.clear(); |
123 | } |
124 | |
125 | pub(in crate::fmt) fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
126 | self.0.extend(iter:buf); |
127 | Ok(buf.len()) |
128 | } |
129 | |
130 | pub(in crate::fmt) fn flush(&mut self) -> io::Result<()> { |
131 | Ok(()) |
132 | } |
133 | |
134 | pub(in crate::fmt) fn as_bytes(&self) -> &[u8] { |
135 | &self.0 |
136 | } |
137 | } |
138 | |
139 | impl std::fmt::Debug for Buffer { |
140 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
141 | String::from_utf8_lossy(self.as_bytes()).fmt(f) |
142 | } |
143 | } |
144 | |
145 | /// Log target, either `stdout`, `stderr` or a custom pipe. |
146 | /// |
147 | /// Same as `Target`, except the pipe is wrapped in a mutex for interior mutability. |
148 | pub(super) enum WritableTarget { |
149 | /// Logs will be written to standard output. |
150 | WriteStdout, |
151 | /// Logs will be printed to standard output. |
152 | PrintStdout, |
153 | /// Logs will be written to standard error. |
154 | WriteStderr, |
155 | /// Logs will be printed to standard error. |
156 | PrintStderr, |
157 | /// Logs will be sent to a custom pipe. |
158 | Pipe(Box<Mutex<dyn io::Write + Send + 'static>>), |
159 | } |
160 | |
161 | impl std::fmt::Debug for WritableTarget { |
162 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
163 | write!( |
164 | f, |
165 | " {}" , |
166 | match self { |
167 | Self::WriteStdout => "stdout" , |
168 | Self::PrintStdout => "stdout" , |
169 | Self::WriteStderr => "stderr" , |
170 | Self::PrintStderr => "stderr" , |
171 | Self::Pipe(_) => "pipe" , |
172 | } |
173 | ) |
174 | } |
175 | } |
176 | |