struct LazyBuf<'a> {
s: &'a str,
buf: Option<Vec<u8>>,
w: usize,
}
impl<'a> LazyBuf<'a> {
pub fn new(s: &'a str) -> Self {
Self { s, buf: None, w: 0 }
}
fn index(&self, i: usize) -> u8 {
self.buf.as_ref().map(|x| x[i]).unwrap_or_else(|| self.s.as_bytes()[i])
}
fn append(&mut self, c: u8) {
if self.buf.is_none() {
if self.w < self.s.len() && self.s.as_bytes()[self.w] == c {
self.w += 1;
return;
}
self.buf = Some({
let mut buf = vec![0u8; self.s.len()];
buf[..self.w].copy_from_slice(&self.s.as_bytes()[..self.w]);
buf
});
}
self.buf.as_mut().unwrap()[self.w] = c;
self.w += 1;
}
fn string(&self) -> String {
match self.buf {
Some(ref s) => String::from_utf8_lossy(&s[..self.w]).to_string(),
None => String::from_utf8_lossy(&self.s.as_bytes()[..self.w]).to_string(),
}
}
}
pub fn clean(path: &str) -> String {
if path.is_empty() {
return ".".into();
}
let p = path.as_bytes();
let (rooted, n, mut out, mut r, mut dotdot) = (p[0] == b'/', path.len(), LazyBuf::new(path), 0, 0);
if rooted {
out.append(b'/');
r = 1;
dotdot = 1;
}
while r < n {
if p[r] == b'/' || (p[r] == b'.' && (r + 1 == n || p[r + 1] == b'/')) {
r += 1;
} else if p[r] == b'.' && p[r + 1] == b'.' && (r + 2 == n || p[r + 2] == b'/') {
r += 2;
if out.w > dotdot {
out.w -= 1;
while out.w > dotdot && out.index(out.w) != b'/' {
out.w -= 1;
}
} else if !rooted {
if out.w > 0 {
out.append(b'/');
}
out.append(b'.');
out.append(b'.');
dotdot = out.w;
}
} else {
if rooted && out.w != 1 || !rooted && out.w != 0 {
out.append(b'/');
}
while r < n && p[r] != b'/' {
out.append(p[r]);
r += 1;
}
}
}
if out.w == 0 { ".".into() } else { out.string() }
}
#[cfg(test)]
mod tests {
use super::clean;
#[test_case::test_case("", "."; "1")]
#[test_case::test_case("abc", "abc"; "2")]
#[test_case::test_case("abc/def", "abc/def"; "3")]
#[test_case::test_case("a/b/c", "a/b/c"; "4")]
#[test_case::test_case(".", "."; "5")]
#[test_case::test_case("..", ".."; "6")]
#[test_case::test_case("../..", "../.."; "7")]
#[test_case::test_case("../../abc", "../../abc"; "8")]
#[test_case::test_case("/abc", "/abc"; "9")]
#[test_case::test_case("/", "/"; "10")]
#[test_case::test_case("abc/", "abc"; "11")]
#[test_case::test_case("abc/def/", "abc/def"; "12")]
#[test_case::test_case("a/b/c/", "a/b/c"; "13")]
#[test_case::test_case("./", "."; "14")]
#[test_case::test_case("../", ".."; "15")]
#[test_case::test_case("../../", "../.."; "16")]
#[test_case::test_case("/abc/", "/abc"; "17")]
#[test_case::test_case("abc//def//ghi", "abc/def/ghi"; "18")]
#[test_case::test_case("//abc", "/abc"; "19")]
#[test_case::test_case("///abc", "/abc"; "20")]
#[test_case::test_case("//abc//", "/abc"; "21")]
#[test_case::test_case("abc//", "abc"; "22")]
#[test_case::test_case("abc/./def", "abc/def"; "23")]
#[test_case::test_case("/./abc/def", "/abc/def"; "24")]
#[test_case::test_case("abc/.", "abc"; "25")]
#[test_case::test_case("abc/def/ghi/../jkl", "abc/def/jkl"; "26")]
#[test_case::test_case("abc/def/../ghi/../jkl", "abc/jkl"; "27")]
#[test_case::test_case("abc/def/..", "abc"; "28")]
#[test_case::test_case("abc/def/../..", "."; "29")]
#[test_case::test_case("/abc/def/../..", "/"; "30")]
#[test_case::test_case("abc/def/../../..", ".."; "31")]
#[test_case::test_case("/abc/def/../../..", "/"; "32")]
#[test_case::test_case("abc/def/../../../ghi/jkl/../../../mno", "../../mno"; "33")]
#[test_case::test_case("abc/./../def", "def"; "34")]
#[test_case::test_case("abc//./../def", "def"; "35")]
#[test_case::test_case("abc/../../././../def", "../../def"; "36")]
fn test_clean(path: &str, result: &str) {
assert_eq!(clean(path), result.to_owned());
assert_eq!(clean(result), result.to_owned());
}
}