use std::{borrow::Cow, fmt::Write as WriteStr, io, str};
#[cfg(any(feature = "svg", feature = "test"))]
pub use self::rgb_color::RgbColor;
#[cfg(feature = "svg")]
pub use self::rgb_color::RgbColorParseError;
pub(crate) struct WriteAdapter<'a> {
inner: &'a mut dyn WriteStr,
}
impl<'a> WriteAdapter<'a> {
pub fn new(output: &'a mut dyn WriteStr) -> Self {
Self { inner: output }
}
}
impl io::Write for WriteAdapter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let segment =
str::from_utf8(buf).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
self.inner
.write_str(segment)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) fn normalize_newlines(s: &str) -> Cow<'_, str> {
if s.contains("\r\n") {
Cow::Owned(s.replace("\r\n", "\n"))
} else {
Cow::Borrowed(s)
}
}
#[cfg(not(windows))]
pub(crate) fn is_recoverable_kill_error(err: &io::Error) -> bool {
matches!(err.kind(), io::ErrorKind::InvalidInput)
}
#[cfg(windows)]
pub(crate) fn is_recoverable_kill_error(err: &io::Error) -> bool {
matches!(
err.kind(),
io::ErrorKind::InvalidInput | io::ErrorKind::PermissionDenied
)
}
#[cfg(any(feature = "svg", feature = "test"))]
mod rgb_color {
use std::{error::Error as StdError, fmt, num::ParseIntError, str::FromStr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RgbColor(pub u8, pub u8, pub u8);
impl fmt::LowerHex for RgbColor {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "#{:02x}{:02x}{:02x}", self.0, self.1, self.2)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum RgbColorParseError {
NotAscii,
NoHashPrefix,
IncorrectLen(usize),
IncorrectDigit(ParseIntError),
}
impl fmt::Display for RgbColorParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotAscii => formatter.write_str("color string contains non-ASCII chars"),
Self::NoHashPrefix => formatter.write_str("missing '#' prefix"),
Self::IncorrectLen(len) => write!(
formatter,
"unexpected byte length {len} of color string, expected 4 or 7"
),
Self::IncorrectDigit(err) => write!(formatter, "error parsing hex digit: {err}"),
}
}
}
impl StdError for RgbColorParseError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::IncorrectDigit(err) => Some(err),
_ => None,
}
}
}
impl FromStr for RgbColor {
type Err = RgbColorParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() || s.as_bytes()[0] != b'#' {
Err(RgbColorParseError::NoHashPrefix)
} else if s.len() == 4 {
if !s.is_ascii() {
return Err(RgbColorParseError::NotAscii);
}
let r =
u8::from_str_radix(&s[1..2], 16).map_err(RgbColorParseError::IncorrectDigit)?;
let g =
u8::from_str_radix(&s[2..3], 16).map_err(RgbColorParseError::IncorrectDigit)?;
let b =
u8::from_str_radix(&s[3..], 16).map_err(RgbColorParseError::IncorrectDigit)?;
Ok(Self(r * 17, g * 17, b * 17))
} else if s.len() == 7 {
if !s.is_ascii() {
return Err(RgbColorParseError::NotAscii);
}
let r =
u8::from_str_radix(&s[1..3], 16).map_err(RgbColorParseError::IncorrectDigit)?;
let g =
u8::from_str_radix(&s[3..5], 16).map_err(RgbColorParseError::IncorrectDigit)?;
let b =
u8::from_str_radix(&s[5..], 16).map_err(RgbColorParseError::IncorrectDigit)?;
Ok(Self(r, g, b))
} else {
Err(RgbColorParseError::IncorrectLen(s.len()))
}
}
}
}
#[cfg(all(test, any(feature = "svg", feature = "test")))]
mod tests {
use assert_matches::assert_matches;
use super::*;
#[test]
fn parsing_color() {
let RgbColor(r, g, b) = "#fed".parse().unwrap();
assert_eq!((r, g, b), (0xff, 0xee, 0xdd));
let RgbColor(r, g, b) = "#c0ffee".parse().unwrap();
assert_eq!((r, g, b), (0xc0, 0xff, 0xee));
}
#[test]
fn errors_parsing_color() {
let err = "123".parse::<RgbColor>().unwrap_err();
assert_matches!(err, RgbColorParseError::NoHashPrefix);
let err = "#12".parse::<RgbColor>().unwrap_err();
assert_matches!(err, RgbColorParseError::IncorrectLen(3));
let err = "#тэг".parse::<RgbColor>().unwrap_err();
assert_matches!(err, RgbColorParseError::NotAscii);
let err = "#coffee".parse::<RgbColor>().unwrap_err();
assert_matches!(err, RgbColorParseError::IncorrectDigit(_));
}
}