ui.rs
105 lines
| 2.9 KiB
| application/rls-services+xml
|
RustLexer
Antoine Cezar
|
r45592 | use std::io; | ||
Antoine Cezar
|
r45921 | use std::io::{ErrorKind, Write}; | ||
Antoine Cezar
|
r45592 | |||
Antoine Cezar
|
r45921 | #[derive(Debug)] | ||
pub struct Ui { | ||||
stdout: std::io::Stdout, | ||||
stderr: std::io::Stderr, | ||||
} | ||||
Antoine Cezar
|
r45592 | |||
/// The kind of user interface error | ||||
pub enum UiError { | ||||
/// The standard output stream cannot be written to | ||||
StdoutError(io::Error), | ||||
/// The standard error stream cannot be written to | ||||
StderrError(io::Error), | ||||
} | ||||
/// The commandline user interface | ||||
impl Ui { | ||||
pub fn new() -> Self { | ||||
Antoine Cezar
|
r45921 | Ui { | ||
stdout: std::io::stdout(), | ||||
stderr: std::io::stderr(), | ||||
} | ||||
} | ||||
/// Returns a buffered handle on stdout for faster batch printing | ||||
/// operations. | ||||
pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> { | ||||
StdoutBuffer::new(self.stdout.lock()) | ||||
Antoine Cezar
|
r45592 | } | ||
/// Write bytes to stdout | ||||
pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> { | ||||
Antoine Cezar
|
r45921 | let mut stdout = self.stdout.lock(); | ||
Antoine Cezar
|
r45592 | |||
Antoine Cezar
|
r46010 | stdout.write_all(bytes).or_else(handle_stdout_error)?; | ||
Antoine Cezar
|
r45592 | |||
Antoine Cezar
|
r46010 | stdout.flush().or_else(handle_stdout_error) | ||
Antoine Cezar
|
r45592 | } | ||
/// Write bytes to stderr | ||||
pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> { | ||||
Antoine Cezar
|
r45921 | let mut stderr = self.stderr.lock(); | ||
Antoine Cezar
|
r45592 | |||
Antoine Cezar
|
r46010 | stderr.write_all(bytes).or_else(handle_stderr_error)?; | ||
Antoine Cezar
|
r45592 | |||
Antoine Cezar
|
r46010 | stderr.flush().or_else(handle_stderr_error) | ||
Antoine Cezar
|
r45592 | } | ||
Antoine Cezar
|
r46011 | |||
/// Write string line to stderr | ||||
pub fn writeln_stderr_str(&self, s: &str) -> Result<(), UiError> { | ||||
self.write_stderr(&format!("{}\n", s).as_bytes()) | ||||
} | ||||
Antoine Cezar
|
r45592 | } | ||
Antoine Cezar
|
r45921 | |||
/// A buffered stdout writer for faster batch printing operations. | ||||
pub struct StdoutBuffer<W: Write> { | ||||
buf: io::BufWriter<W>, | ||||
} | ||||
impl<W: Write> StdoutBuffer<W> { | ||||
pub fn new(writer: W) -> Self { | ||||
let buf = io::BufWriter::new(writer); | ||||
Self { buf } | ||||
} | ||||
/// Write bytes to stdout buffer | ||||
pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> { | ||||
Antoine Cezar
|
r46010 | self.buf.write_all(bytes).or_else(handle_stdout_error) | ||
Antoine Cezar
|
r45921 | } | ||
/// Flush bytes to stdout | ||||
pub fn flush(&mut self) -> Result<(), UiError> { | ||||
Antoine Cezar
|
r46010 | self.buf.flush().or_else(handle_stdout_error) | ||
Antoine Cezar
|
r45921 | } | ||
} | ||||
Antoine Cezar
|
r45925 | |||
/// Sometimes writing to stdout is not possible, try writing to stderr to | ||||
/// signal that failure, otherwise just bail. | ||||
fn handle_stdout_error(error: io::Error) -> Result<(), UiError> { | ||||
if let ErrorKind::BrokenPipe = error.kind() { | ||||
// This makes `| head` work for example | ||||
return Ok(()); | ||||
} | ||||
let mut stderr = io::stderr(); | ||||
stderr | ||||
.write_all(&[b"abort: ", error.to_string().as_bytes(), b"\n"].concat()) | ||||
Antoine Cezar
|
r46010 | .map_err(UiError::StderrError)?; | ||
Antoine Cezar
|
r45925 | |||
Antoine Cezar
|
r46010 | stderr.flush().map_err(UiError::StderrError)?; | ||
Antoine Cezar
|
r45925 | |||
Err(UiError::StdoutError(error)) | ||||
} | ||||
Antoine Cezar
|
r45926 | |||
/// Sometimes writing to stderr is not possible. | ||||
fn handle_stderr_error(error: io::Error) -> Result<(), UiError> { | ||||
// A broken pipe should not result in a error | ||||
// like with `| head` for example | ||||
if let ErrorKind::BrokenPipe = error.kind() { | ||||
return Ok(()); | ||||
} | ||||
Err(UiError::StdoutError(error)) | ||||
} | ||||