// Invariants: // - 'orig' is a valid path, and in particular, the '/'-split components are non-empty; // - 'comps' describes a non-empty prefix of 'orig'. #[derive(Debug)] pub struct Path<'a> { orig: &'a str, comps: Vec<(usize, &'a str)>, // (offset, component) } impl<'a> Path<'a> { /// Returns the split path if the path is valid. A valid path has the following requirements: /// * It does not contain unicode control characters; /// * When split on '/', the resulting components are all non-empty and neither start nor end /// with unicode whitespace characters. pub fn split(s: &str) -> Option { let mut comps = Vec::new(); let mut start = 0; // of current component for (i, c) in s.char_indices() { match c { '/' if i == start => return None, // empty component '/' => { let comp = &s[start..i]; if comp.starts_with(|c2: char| c2.is_whitespace()) || comp.ends_with(|c2: char| c2.is_whitespace()) { return None; } comps.push((start, comp)); start = i + 1; }, _ if c.is_control() => return None, _ => {}, // include in current component } } // check and add the last component if start == s.len() { return None; // slash at end of input } comps.push((start, &s[start..s.len()])); Some(Path { orig: s, comps }) } pub fn without_last(&self) -> Option<(Path, &str)> { let n = self.comps.len(); if n > 1 { Some((Path { orig: self.orig, comps: self.comps[0 .. n - 1].into() }, self.comps[n - 1].1)) } else { None } } pub fn join(&self) -> &str { if self.comps.len() == 0 { "" } else { let (lastoff, lastcomp) = self.comps[self.comps.len() - 1]; &self.orig[0 .. lastoff + lastcomp.len()] } } }