1use std::{
2 fs::File,
3 io::{self, LineWriter, Write},
4 path::{Path, PathBuf},
5 str::FromStr,
6 sync::{Arc, Mutex, MutexGuard},
7};
8
9#[track_caller]
10fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<T> {
11 mutex.lock().unwrap_or_else(|e| e.into_inner())
12}
13
14#[derive(Debug, Clone)]
44pub struct Output(OutputInner);
45
46#[derive(Debug, Clone)]
47enum OutputInner {
48 Stdout,
49 File {
50 path: Arc<PathBuf>,
51 writer: Arc<Mutex<LineWriter<File>>>,
52 },
53}
54
55impl Output {
56 pub fn stdout() -> Self {
58 Self(OutputInner::Stdout)
59 }
60
61 pub fn create(path: PathBuf) -> io::Result<Self> {
63 let path = Arc::new(path);
64 let file = File::create(&*path)?;
65 let writer = Arc::new(Mutex::new(LineWriter::new(file)));
66 Ok(Self(OutputInner::File { path, writer }))
67 }
68
69 pub fn is_stdout(&self) -> bool {
71 matches!(self.0, OutputInner::Stdout)
72 }
73
74 pub fn is_file(&self) -> bool {
77 matches!(self.0, OutputInner::File { .. })
78 }
79
80 pub fn path(&self) -> Option<&Path> {
84 match &self.0 {
85 OutputInner::Stdout => None,
86 OutputInner::File { path, .. } => Some(path),
87 }
88 }
89
90 pub fn lock(&self) -> LockedOutput<'_> {
96 let inner = match &self.0 {
97 OutputInner::Stdout => {
98 let writer = io::stdout().lock();
99 LockedOutputInner::Stdout { writer }
100 }
101 OutputInner::File { path, writer: file } => {
102 let writer = lock(file);
103 LockedOutputInner::File {
104 path: Arc::clone(path),
105 writer,
106 }
107 }
108 };
109 LockedOutput(inner)
110 }
111}
112
113impl FromStr for Output {
114 type Err = io::Error;
115
116 fn from_str(s: &str) -> Result<Self, Self::Err> {
117 if s == "-" {
118 return Ok(Self::stdout());
119 }
120 Self::create(PathBuf::from(s))
121 }
122}
123
124macro_rules! with_writer {
125 ($inner:expr, $var:ident => $e:expr) => {
126 match $inner {
127 OutputInner::Stdout => {
128 let mut $var = io::stdout();
129 $e
130 }
131 OutputInner::File { writer, .. } => {
132 let mut $var = lock(writer);
133 $e
134 }
135 }
136 };
137}
138
139impl Write for Output {
140 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
141 with_writer!(&self.0, writer => writer.write(buf))
142 }
143
144 fn flush(&mut self) -> io::Result<()> {
145 with_writer!(&self.0, writer => writer.flush())
146 }
147
148 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
149 with_writer!(&self.0, writer => writer.write_vectored(bufs))
150 }
151
152 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
158 with_writer!(&self.0, writer => writer.write_all(buf))
159 }
160
161 }
166
167#[derive(Debug)]
169pub struct LockedOutput<'a>(LockedOutputInner<'a>);
170
171impl LockedOutput<'_> {
172 pub fn is_stdin(&self) -> bool {
174 matches!(self.0, LockedOutputInner::Stdout { .. })
175 }
176
177 pub fn is_file(&self) -> bool {
179 matches!(self.0, LockedOutputInner::File { .. })
180 }
181
182 pub fn path(&self) -> Option<&Path> {
186 match &self.0 {
187 LockedOutputInner::Stdout { .. } => None,
188 LockedOutputInner::File { path, .. } => Some(path),
189 }
190 }
191}
192
193#[derive(Debug)]
194enum LockedOutputInner<'a> {
195 Stdout {
196 writer: io::StdoutLock<'a>,
197 },
198 File {
199 path: Arc<PathBuf>,
200 writer: MutexGuard<'a, LineWriter<File>>,
201 },
202}
203
204macro_rules! with_locked_writer {
205 ($inner:expr, $var:ident => $e:expr) => {
206 match $inner {
207 LockedOutputInner::Stdout { writer } => {
208 let $var = writer;
209 $e
210 }
211 LockedOutputInner::File { writer, .. } => {
212 let $var = writer;
213 $e
214 }
215 }
216 };
217}
218
219impl Write for LockedOutput<'_> {
220 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
221 with_locked_writer!(&mut self.0, writer => writer.write(buf))
222 }
223
224 fn flush(&mut self) -> io::Result<()> {
225 with_locked_writer!(&mut self.0, writer => writer.flush())
226 }
227
228 fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
229 with_locked_writer!(&mut self.0, writer => writer.write_vectored(bufs))
230 }
231
232 fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
238 with_locked_writer!(&mut self.0, writer => writer.write_all(buf))
239 }
240
241 }