use std::{io, io::Write};
use tracing::{Level, Metadata};
use tracing_subscriber::fmt::MakeWriter;
enum ErrorAndWarnState {
Init,
Normal,
LastCharWasSpecial(u8),
}
fn char_is_special(ch: u8) -> bool {
ch == b'\n' || ch == b'\r'
}
fn escape_special(ch: u8) -> &'static [u8] {
match ch {
b'\n' => b"\\n",
b'\r' => b"\\r",
_ => unreachable!(),
}
}
enum BuildScriptWriterInner {
Informational(io::Stderr),
ErrorsAndWarnings {
state: ErrorAndWarnState,
writer: io::Stdout,
},
}
pub struct BuildScriptWriter(BuildScriptWriterInner);
impl BuildScriptWriter {
pub fn informational() -> Self {
Self(BuildScriptWriterInner::Informational(io::stderr()))
}
pub fn errors_and_warnings() -> Self {
Self(BuildScriptWriterInner::ErrorsAndWarnings { state: ErrorAndWarnState::Init, writer: io::stdout() })
}
}
impl Drop for BuildScriptWriter {
fn drop(&mut self) {
if let BuildScriptWriterInner::ErrorsAndWarnings { state: ErrorAndWarnState::LastCharWasSpecial(ch), writer } =
&mut self.0
{
let _ = writer.write(&[*ch]);
}
}
}
impl Write for BuildScriptWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_all(buf)?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
match &mut self.0 {
BuildScriptWriterInner::Informational(writer) => writer.flush(),
BuildScriptWriterInner::ErrorsAndWarnings { writer, state: _ } => writer.flush(),
}
}
fn write_all(&mut self, mut buf: &[u8]) -> io::Result<()> {
match &mut self.0 {
BuildScriptWriterInner::Informational(writer) => writer.write_all(buf),
BuildScriptWriterInner::ErrorsAndWarnings { state, writer } => {
let mut writer = writer.lock();
match *state {
ErrorAndWarnState::Init => {
writer.write_all(b"cargo::warning=")?;
},
ErrorAndWarnState::LastCharWasSpecial(ch) => {
writer.write_all(escape_special(ch))?;
},
ErrorAndWarnState::Normal => {},
}
match buf.last().copied() {
Some(ch) if char_is_special(ch) => {
buf = &buf[..buf.len() - 1];
*state = ErrorAndWarnState::LastCharWasSpecial(ch);
},
_ => {
*state = ErrorAndWarnState::Normal;
},
}
let mut last_special_char = match buf.iter().position(|ch| char_is_special(*ch)) {
Some(pos) => {
writer.write_all(&buf[..pos])?;
let ret = buf[pos];
buf = &buf[pos + 1..];
ret
},
None => {
writer.write_all(buf)?;
return Ok(());
},
};
loop {
writer.write_all(escape_special(last_special_char))?;
match buf.iter().position(|ch| char_is_special(*ch)) {
Some(pos) => {
writer.write_all(&buf[..pos])?;
last_special_char = buf[pos];
buf = &buf[pos + 1..];
},
None => {
writer.write_all(buf)?;
break;
},
}
}
Ok(())
},
}
}
}
pub struct BuildScriptMakeWriter;
impl<'a> MakeWriter<'a> for BuildScriptMakeWriter {
type Writer = BuildScriptWriter;
fn make_writer(&'a self) -> Self::Writer {
BuildScriptWriter::informational()
}
fn make_writer_for(&'a self, meta: &Metadata) -> Self::Writer {
if meta.level() == &Level::ERROR || meta.level() == &Level::WARN {
BuildScriptWriter::errors_and_warnings()
} else {
BuildScriptWriter::informational()
}
}
}