use camino::Utf8Path;
use std::str::Utf8Error;
#[derive(Clone, Debug, Eq, Ord, PartialOrd, PartialEq)]
pub struct Utf8Paths0 {
buf: Box<str>,
}
impl Utf8Paths0 {
pub fn new(buf: impl Into<String>) -> Self {
Self::strip_trailing_null_byte(buf.into())
}
pub fn from_bytes(buf: impl Into<Vec<u8>>) -> Result<Self, (Vec<u8>, Utf8Error)> {
let buf = buf.into();
let buf = Self::validate_utf8(buf)?;
Ok(Self::strip_trailing_null_byte(buf))
}
pub fn new_forward_slashes(buf: impl Into<String>) -> Self {
let mut buf = buf.into();
if std::path::MAIN_SEPARATOR == '\\' {
buf = buf.replace('/', "\\");
}
Self::strip_trailing_null_byte(buf)
}
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Utf8Path> + 'a> {
self.into_iter()
}
fn validate_utf8(buf: Vec<u8>) -> Result<String, (Vec<u8>, Utf8Error)> {
match String::from_utf8(buf) {
Ok(s) => Ok(s),
Err(err) => {
let buf = err.into_bytes();
buf.split(|b| *b == 0)
.try_for_each(|path| match std::str::from_utf8(path) {
Ok(_) => Ok(()),
Err(utf8_error) => Err((path.to_vec(), utf8_error)),
})?;
unreachable!("full buffer failed utf-8 validation => at least one path failed");
}
}
}
fn strip_trailing_null_byte(mut buf: String) -> Self {
if buf.as_bytes().last() == Some(&0) {
buf.pop();
}
Self { buf: buf.into() }
}
}
impl<'a> IntoIterator for &'a Utf8Paths0 {
type Item = &'a Utf8Path;
type IntoIter = Box<dyn Iterator<Item = &'a Utf8Path> + 'a>;
fn into_iter(self) -> Self::IntoIter {
if self.buf.is_empty() {
return Box::new(std::iter::empty());
}
Box::new(self.buf.split('\0').map(Utf8Path::new))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
paths_eq(*b"", &[]);
paths_eq(*b"a/b/c", &["a/b/c"]);
paths_eq(*b"a/b\0a/c", &["a/b", "a/c"]);
paths_eq(*b"a/b\0a/c\0", &["a/b", "a/c"]);
paths_eq(*b"a/b\xF0\x9F\x98\x81\0c/d", &["a/b😁", "c/d"]);
}
#[test]
fn backslashes() {
paths_eq(*b"a\\b\\c", &["a\\b\\c"]);
paths_eq(*b"a\\b\0a\\c", &["a\\b", "a\\c"]);
paths_eq(*b"a\\b\0a\\c\0", &["a\\b", "a\\c"]);
}
#[cfg(windows)]
#[test]
fn forward_slashes() {
paths_eq_fwd(*b"a/b/c", &["a\\b\\c"]);
paths_eq_fwd(*b"a/b\0a/c", &["a\\b", "a\\c"]);
paths_eq_fwd(*b"a/b\0a/c\0", &["a\\b", "a\\c"]);
paths_eq_fwd(*b"a/b\0a\\c", &["a\\b", "a\\c"]);
}
fn paths_eq(bytes: impl Into<Vec<u8>>, expected: &[&str]) {
let paths = Utf8Paths0::from_bytes(bytes.into()).expect("null-separated paths are valid");
let actual: Vec<_> = paths.iter().collect();
let expected: Vec<_> = expected.iter().map(Utf8Path::new).collect();
assert_eq!(actual, expected, "paths match");
}
#[cfg(windows)]
fn paths_eq_fwd(bytes: impl Into<Vec<u8>>, expected: &[&str]) {
let s = String::from_utf8(bytes.into()).expect("valid UTF-8");
let paths = Utf8Paths0::new_forward_slashes(s);
let actual: Vec<_> = paths.iter().collect();
let expected: Vec<_> = expected.iter().map(Utf8Path::new).collect();
assert_eq!(actual, expected, "paths match");
}
}