From e97d61672b3f31e4d54589bed20286aca02bf42b Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Fri, 23 Aug 2013 23:30:17 -0700 Subject: [PATCH] path2: Implement PosixPath Fixes #5389 (new conventions for Path constructor) --- src/libstd/path2.rs | 771 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 770 insertions(+), 1 deletion(-) diff --git a/src/libstd/path2.rs b/src/libstd/path2.rs index b9f25209834..a98fcc40e18 100644 --- a/src/libstd/path2.rs +++ b/src/libstd/path2.rs @@ -10,14 +10,18 @@ //! Cross-platform file path handling (re-write) +use container::Container; use c_str::{CString, ToCStr}; use clone::Clone; use cmp::Eq; use from_str::FromStr; +use iterator::{AdditiveIterator, Extendable, Iterator}; use option::{Option, None, Some}; use str; -use str::{OwnedStr, Str, StrSlice}; +use str::{OwnedStr, Str, StrSlice, StrVector}; use to_str::ToStr; +use util; +use vec::{ImmutableVector, OwnedVector}; /// Typedef for the platform-native path type #[cfg(unix)] @@ -26,6 +30,16 @@ pub type Path = PosixPath; //#[cfg(windows)] //pub type Path = WindowsPath; +/// Typedef for the platform-native component iterator +#[cfg(unix)] +pub type ComponentIter<'self> = PosixComponentIter<'self>; +// /// Typedef for the platform-native component iterator +//#[cfg(windows)] +//pub type ComponentIter<'self> = WindowsComponentIter<'self>; + +/// Iterator that yields successive components of a PosixPath +type PosixComponentIter<'self> = str::CharSplitIterator<'self, char>; + /// Represents a POSIX file path #[deriving(Clone, DeepClone)] pub struct PosixPath { @@ -260,6 +274,312 @@ impl ToCStr for PosixPath { } } +impl GenericPath for PosixPath { + #[inline] + fn from_str(s: &str) -> PosixPath { + PosixPath::new(s) + } + + #[inline] + fn as_str<'a>(&'a self) -> &'a str { + self.repr.as_slice() + } + + fn dirname<'a>(&'a self) -> &'a str { + match self.sepidx { + None if ".." == self.repr => "..", + None => ".", + Some(0) => self.repr.slice_to(1), + Some(idx) if self.repr.slice_from(idx+1) == ".." => self.repr.as_slice(), + Some(idx) => self.repr.slice_to(idx) + } + } + + fn filename<'a>(&'a self) -> &'a str { + match self.sepidx { + None if "." == self.repr || ".." == self.repr => "", + None => self.repr.as_slice(), + Some(idx) if self.repr.slice_from(idx+1) == ".." => "", + Some(idx) => self.repr.slice_from(idx+1) + } + } + + fn set_dirname(&mut self, dirname: &str) { + match self.sepidx { + None if "." == self.repr || ".." == self.repr => { + self.repr = PosixPath::normalize(dirname); + } + None => { + let mut s = str::with_capacity(dirname.len() + self.repr.len() + 1); + s.push_str(dirname); + s.push_char(posix::sep); + s.push_str(self.repr); + self.repr = PosixPath::normalize(s); + } + Some(0) if self.repr.len() == 1 && self.repr[0] == posix::sep as u8 => { + self.repr = PosixPath::normalize(dirname); + } + Some(idx) if dirname == "" => { + let s = PosixPath::normalize(self.repr.slice_from(idx+1)); + self.repr = s; + } + Some(idx) if self.repr.slice_from(idx+1) == ".." => { + self.repr = PosixPath::normalize(dirname); + } + Some(idx) => { + let mut s = str::with_capacity(dirname.len() + self.repr.len() - idx); + s.push_str(dirname); + s.push_str(self.repr.slice_from(idx)); + self.repr = PosixPath::normalize(s); + } + } + self.sepidx = self.repr.rfind(posix::sep); + } + + fn set_filename(&mut self, filename: &str) { + match self.sepidx { + None if ".." == self.repr => { + let mut s = str::with_capacity(3 + filename.len()); + s.push_str(".."); + s.push_char(posix::sep); + s.push_str(filename); + self.repr = PosixPath::normalize(s); + } + None => { + self.repr = PosixPath::normalize(filename); + } + Some(idx) if self.repr.slice_from(idx+1) == ".." => { + let mut s = str::with_capacity(self.repr.len() + 1 + filename.len()); + s.push_str(self.repr); + s.push_char(posix::sep); + s.push_str(filename); + self.repr = PosixPath::normalize(s); + } + Some(idx) => { + let mut s = str::with_capacity(self.repr.len() - idx + filename.len()); + s.push_str(self.repr.slice_to(idx+1)); + s.push_str(filename); + self.repr = PosixPath::normalize(s); + } + } + self.sepidx = self.repr.rfind(posix::sep); + } + + fn push(&mut self, path: &str) { + if !path.is_empty() { + if path[0] == posix::sep as u8 { + self.repr = PosixPath::normalize(path); + } else { + let mut s = str::with_capacity(self.repr.len() + path.len() + 1); + s.push_str(self.repr); + s.push_char(posix::sep); + s.push_str(path); + self.repr = PosixPath::normalize(s); + } + self.sepidx = self.repr.rfind(posix::sep); + } + } + + fn push_path(&mut self, path: &PosixPath) { + self.push(path.as_str()); + } + + fn pop_opt(&mut self) -> Option<~str> { + match self.sepidx { + None if "." == self.repr => None, + None => { + let mut s = ~"."; + util::swap(&mut s, &mut self.repr); + self.sepidx = None; + Some(s) + } + Some(0) if "/" == self.repr => None, + Some(idx) => { + let s = self.repr.slice_from(idx+1).to_owned(); + if idx == 0 { + self.repr.truncate(idx+1); + } else { + self.repr.truncate(idx); + } + self.sepidx = self.repr.rfind(posix::sep); + Some(s) + } + } + } + + #[inline] + fn is_absolute(&self) -> bool { + self.repr[0] == posix::sep as u8 + } + + fn is_ancestor_of(&self, other: &PosixPath) -> bool { + if self.is_absolute() != other.is_absolute() { + false + } else { + let mut ita = self.component_iter(); + let mut itb = other.component_iter(); + if "." == self.repr { + return match itb.next() { + Some("..") => false, + _ => true + }; + } + loop { + match (ita.next(), itb.next()) { + (None, _) => break, + (Some(a), Some(b)) if a == b => { loop }, + (Some(".."), _) => { + // if ita contains only .. components, it's an ancestor + return ita.all(|x| x == ".."); + } + _ => return false + } + } + true + } + } + + fn path_relative_from(&self, base: &PosixPath) -> Option { + if self.is_absolute() != base.is_absolute() { + if self.is_absolute() { + Some(self.clone()) + } else { + None + } + } else { + let mut ita = self.component_iter(); + let mut itb = base.component_iter(); + let mut comps = ~[]; + loop { + match (ita.next(), itb.next()) { + (None, None) => break, + (Some(a), None) => { + comps.push(a); + comps.extend(&mut ita); + break; + } + (None, _) => comps.push(".."), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(".")) => comps.push(a), + (Some(_), Some("..")) => return None, + (Some(a), Some(_)) => { + comps.push(".."); + for _ in itb { + comps.push(".."); + } + comps.push(a); + comps.extend(&mut ita); + break; + } + } + } + Some(PosixPath::new(comps.connect(str::from_char(posix::sep)))) + } + } +} + +impl PosixPath { + /// Returns a new PosixPath from a string + pub fn new(s: &str) -> PosixPath { + let s = PosixPath::normalize(s); + assert!(!s.is_empty()); + let idx = s.rfind(posix::sep); + PosixPath{ repr: s, sepidx: idx } + } + + /// Converts the PosixPath into an owned string + pub fn into_str(self) -> ~str { + self.repr + } + + /// Returns a normalized string representation of a path, by removing all empty + /// components, and unnecessary . and .. components. + pub fn normalize(s: S) -> ~str { + // borrowck is being very picky + let val = { + let is_abs = !s.as_slice().is_empty() && s.as_slice()[0] == posix::sep as u8; + let s_ = if is_abs { s.as_slice().slice_from(1) } else { s.as_slice() }; + let comps = normalize_helper(s_, is_abs, posix::sep); + match comps { + None => None, + Some(comps) => { + let sepstr = str::from_char(posix::sep); + if is_abs && comps.is_empty() { + Some(sepstr) + } else { + let n = if is_abs { comps.len() } else { comps.len() - 1} + + comps.iter().map(|s| s.len()).sum(); + let mut s = str::with_capacity(n); + let mut it = comps.move_iter(); + if !is_abs { + match it.next() { + None => (), + Some(comp) => s.push_str(comp) + } + } + for comp in it { + s.push_str(sepstr); + s.push_str(comp); + } + Some(s) + } + } + } + }; + match val { + None => s.into_owned(), + Some(val) => val + } + } + + /// Returns an iterator that yields each component of the path in turn. + /// Does not distinguish between absolute and relative paths, e.g. + /// /a/b/c and a/b/c yield the same set of components. + /// A path of "/" yields no components. A path of "." yields one component. + pub fn component_iter<'a>(&'a self) -> PosixComponentIter<'a> { + let s = if self.repr[0] == posix::sep as u8 { + self.repr.slice_from(1) + } else { self.repr.as_slice() }; + let mut ret = s.split_iter(posix::sep); + if s.is_empty() { + // consume the empty "" component + ret.next(); + } + ret + } +} + +// None result means the string didn't need normalizing +fn normalize_helper<'a, Sep: str::CharEq>(s: &'a str, is_abs: bool, sep: Sep) -> Option<~[&'a str]> { + if is_abs && s.as_slice().is_empty() { + return None; + } + let mut comps: ~[&'a str] = ~[]; + let mut n_up = 0u; + let mut changed = false; + for comp in s.split_iter(sep) { + match comp { + "" => { changed = true; } + "." => { changed = true; } + ".." if is_abs && comps.is_empty() => { changed = true; } + ".." if comps.len() == n_up => { comps.push(".."); n_up += 1; } + ".." => { comps.pop_opt(); changed = true; } + x => comps.push(x) + } + } + if changed { + if comps.is_empty() && !is_abs { + if s == "." { + return None; + } + comps.push("."); + } + Some(comps) + } else { + None + } +} + /// Various POSIX helpers pub mod posix { /// The standard path separator character @@ -283,3 +603,452 @@ pub mod windows { u == sep || u == '/' } } + +#[cfg(test)] +mod tests { + use super::*; + use option::{Some, None}; + use iterator::Iterator; + use vec::Vector; + + macro_rules! t( + ($path:expr, $exp:expr) => ( + { + let path = $path; + assert_eq!(path.as_str(), $exp); + } + ) + ) + + #[test] + fn test_posix_paths() { + t!(PosixPath::new(""), "."); + t!(PosixPath::new("/"), "/"); + t!(PosixPath::new("hi"), "hi"); + t!(PosixPath::new("/lib"), "/lib"); + t!(PosixPath::new("hi/there"), "hi/there"); + t!(PosixPath::new("hi/there.txt"), "hi/there.txt"); + + t!(PosixPath::new("hi/there/"), "hi/there"); + t!(PosixPath::new("hi/../there"), "there"); + t!(PosixPath::new("../hi/there"), "../hi/there"); + t!(PosixPath::new("/../hi/there"), "/hi/there"); + t!(PosixPath::new("foo/.."), "."); + t!(PosixPath::new("/foo/.."), "/"); + t!(PosixPath::new("/foo/../.."), "/"); + t!(PosixPath::new("/foo/../../bar"), "/bar"); + t!(PosixPath::new("/./hi/./there/."), "/hi/there"); + t!(PosixPath::new("/./hi/./there/./.."), "/hi"); + t!(PosixPath::new("foo/../.."), ".."); + t!(PosixPath::new("foo/../../.."), "../.."); + t!(PosixPath::new("foo/../../bar"), "../bar"); + + assert_eq!(PosixPath::new("foo/bar").into_str(), ~"foo/bar"); + assert_eq!(PosixPath::new("/foo/../../bar").into_str(), ~"/bar"); + } + + #[test] + fn test_posix_components() { + macro_rules! t( + ($path:expr, $op:ident, $exp:expr) => ( + { + let path = PosixPath::new($path); + assert_eq!(path.$op(), $exp); + } + ) + ) + + t!("a/b/c", filename, "c"); + t!("/a/b/c", filename, "c"); + t!("a", filename, "a"); + t!("/a", filename, "a"); + t!(".", filename, ""); + t!("/", filename, ""); + t!("..", filename, ""); + t!("../..", filename, ""); + + t!("a/b/c", dirname, "a/b"); + t!("/a/b/c", dirname, "/a/b"); + t!("a", dirname, "."); + t!("/a", dirname, "/"); + t!(".", dirname, "."); + t!("/", dirname, "/"); + t!("..", dirname, ".."); + t!("../..", dirname, "../.."); + + t!("hi/there.txt", filestem, "there"); + t!("hi/there", filestem, "there"); + t!("there.txt", filestem, "there"); + t!("there", filestem, "there"); + t!(".", filestem, ""); + t!("/", filestem, ""); + t!("foo/.bar", filestem, ".bar"); + t!(".bar", filestem, ".bar"); + t!("..bar", filestem, "."); + t!("hi/there..txt", filestem, "there."); + t!("..", filestem, ""); + t!("../..", filestem, ""); + + t!("hi/there.txt", extension, Some("txt")); + t!("hi/there", extension, None); + t!("there.txt", extension, Some("txt")); + t!("there", extension, None); + t!(".", extension, None); + t!("/", extension, None); + t!("foo/.bar", extension, None); + t!(".bar", extension, None); + t!("..bar", extension, Some("bar")); + t!("hi/there..txt", extension, Some("txt")); + t!("..", extension, None); + t!("../..", extension, None); + } + + #[test] + fn test_posix_push() { + macro_rules! t( + ($path:expr, $join:expr) => ( + { + let path = ($path); + let join = ($join); + let mut p1 = PosixPath::new(path); + p1.push(join); + let p2 = PosixPath::new(path); + assert_eq!(p1, p2.join(join)); + } + ) + ) + + t!("a/b/c", ".."); + t!("/a/b/c", "d"); + t!("a/b", "c/d"); + t!("a/b", "/c/d"); + } + + #[test] + fn test_posix_push_path() { + macro_rules! t( + ($path:expr, $push:expr, $exp:expr) => ( + { + let mut p = PosixPath::new($path); + let push = PosixPath::new($push); + p.push_path(&push); + assert_eq!(p.as_str(), $exp); + } + ) + ) + + t!("a/b/c", "d", "a/b/c/d"); + t!("/a/b/c", "d", "/a/b/c/d"); + t!("a/b", "c/d", "a/b/c/d"); + t!("a/b", "/c/d", "/c/d"); + t!("a/b", ".", "a/b"); + t!("a/b", "../c", "a/c"); + } + + #[test] + fn test_posix_pop() { + macro_rules! t( + ($path:expr, $left:expr, $right:expr) => ( + { + let mut p = PosixPath::new($path); + let file = p.pop_opt(); + assert_eq!(p.as_str(), $left); + assert_eq!(file.map(|s| s.as_slice()), $right); + } + ) + ) + + t!("a/b/c", "a/b", Some("c")); + t!("a", ".", Some("a")); + t!(".", ".", None); + t!("/a", "/", Some("a")); + t!("/", "/", None); + } + + #[test] + fn test_posix_join() { + t!(PosixPath::new("a/b/c").join(".."), "a/b"); + t!(PosixPath::new("/a/b/c").join("d"), "/a/b/c/d"); + t!(PosixPath::new("a/b").join("c/d"), "a/b/c/d"); + t!(PosixPath::new("a/b").join("/c/d"), "/c/d"); + t!(PosixPath::new(".").join("a/b"), "a/b"); + t!(PosixPath::new("/").join("a/b"), "/a/b"); + } + + #[test] + fn test_posix_join_path() { + macro_rules! t( + ($path:expr, $join:expr, $exp:expr) => ( + { + let path = PosixPath::new($path); + let join = PosixPath::new($join); + let res = path.join_path(&join); + assert_eq!(res.as_str(), $exp); + } + ) + ) + + t!("a/b/c", "..", "a/b"); + t!("/a/b/c", "d", "/a/b/c/d"); + t!("a/b", "c/d", "a/b/c/d"); + t!("a/b", "/c/d", "/c/d"); + t!(".", "a/b", "a/b"); + t!("/", "a/b", "/a/b"); + } + + #[test] + fn test_posix_with_helpers() { + t!(PosixPath::new("a/b/c").with_dirname("d"), "d/c"); + t!(PosixPath::new("a/b/c").with_dirname("d/e"), "d/e/c"); + t!(PosixPath::new("a/b/c").with_dirname(""), "c"); + t!(PosixPath::new("a/b/c").with_dirname("/"), "/c"); + t!(PosixPath::new("a/b/c").with_dirname("."), "c"); + t!(PosixPath::new("a/b/c").with_dirname(".."), "../c"); + t!(PosixPath::new("/").with_dirname("foo"), "foo"); + t!(PosixPath::new("/").with_dirname(""), "."); + t!(PosixPath::new("/foo").with_dirname("bar"), "bar/foo"); + t!(PosixPath::new("..").with_dirname("foo"), "foo"); + t!(PosixPath::new("../..").with_dirname("foo"), "foo"); + t!(PosixPath::new("foo").with_dirname(".."), "../foo"); + t!(PosixPath::new("foo").with_dirname("../.."), "../../foo"); + + t!(PosixPath::new("a/b/c").with_filename("d"), "a/b/d"); + t!(PosixPath::new(".").with_filename("foo"), "foo"); + t!(PosixPath::new("/a/b/c").with_filename("d"), "/a/b/d"); + t!(PosixPath::new("/").with_filename("foo"), "/foo"); + t!(PosixPath::new("/a").with_filename("foo"), "/foo"); + t!(PosixPath::new("foo").with_filename("bar"), "bar"); + t!(PosixPath::new("a/b/c").with_filename(""), "a/b"); + t!(PosixPath::new("a/b/c").with_filename("."), "a/b"); + t!(PosixPath::new("a/b/c").with_filename(".."), "a"); + t!(PosixPath::new("/a").with_filename(""), "/"); + t!(PosixPath::new("foo").with_filename(""), "."); + t!(PosixPath::new("a/b/c").with_filename("d/e"), "a/b/d/e"); + t!(PosixPath::new("a/b/c").with_filename("/d"), "a/b/d"); + t!(PosixPath::new("..").with_filename("foo"), "../foo"); + t!(PosixPath::new("../..").with_filename("foo"), "../../foo"); + + t!(PosixPath::new("hi/there.txt").with_filestem("here"), "hi/here.txt"); + t!(PosixPath::new("hi/there.txt").with_filestem(""), "hi/.txt"); + t!(PosixPath::new("hi/there.txt").with_filestem("."), "hi/..txt"); + t!(PosixPath::new("hi/there.txt").with_filestem(".."), "hi/...txt"); + t!(PosixPath::new("hi/there.txt").with_filestem("/"), "hi/.txt"); + t!(PosixPath::new("hi/there.txt").with_filestem("foo/bar"), "hi/foo/bar.txt"); + t!(PosixPath::new("hi/there.foo.txt").with_filestem("here"), "hi/here.txt"); + t!(PosixPath::new("hi/there").with_filestem("here"), "hi/here"); + t!(PosixPath::new("hi/there").with_filestem(""), "hi"); + t!(PosixPath::new("hi").with_filestem(""), "."); + t!(PosixPath::new("/hi").with_filestem(""), "/"); + t!(PosixPath::new("hi/there").with_filestem(".."), "."); + t!(PosixPath::new("hi/there").with_filestem("."), "hi"); + t!(PosixPath::new("hi/there.").with_filestem("foo"), "hi/foo."); + t!(PosixPath::new("hi/there.").with_filestem(""), "hi"); + t!(PosixPath::new("hi/there.").with_filestem("."), "."); + t!(PosixPath::new("hi/there.").with_filestem(".."), "hi/..."); + t!(PosixPath::new("/").with_filestem("foo"), "/foo"); + t!(PosixPath::new(".").with_filestem("foo"), "foo"); + t!(PosixPath::new("hi/there..").with_filestem("here"), "hi/here."); + t!(PosixPath::new("hi/there..").with_filestem(""), "hi"); + + t!(PosixPath::new("hi/there.txt").with_extension("exe"), "hi/there.exe"); + t!(PosixPath::new("hi/there.txt").with_extension(""), "hi/there"); + t!(PosixPath::new("hi/there.txt").with_extension("."), "hi/there.."); + t!(PosixPath::new("hi/there.txt").with_extension(".."), "hi/there..."); + t!(PosixPath::new("hi/there").with_extension("txt"), "hi/there.txt"); + t!(PosixPath::new("hi/there").with_extension("."), "hi/there.."); + t!(PosixPath::new("hi/there").with_extension(".."), "hi/there..."); + t!(PosixPath::new("hi/there.").with_extension("txt"), "hi/there.txt"); + t!(PosixPath::new("hi/.foo").with_extension("txt"), "hi/.foo.txt"); + t!(PosixPath::new("hi/there.txt").with_extension(".foo"), "hi/there..foo"); + t!(PosixPath::new("/").with_extension("txt"), "/"); + t!(PosixPath::new("/").with_extension("."), "/"); + t!(PosixPath::new("/").with_extension(".."), "/"); + t!(PosixPath::new(".").with_extension("txt"), "."); + } + + #[test] + fn test_posix_setters() { + macro_rules! t( + ($path:expr, $set:ident, $with:ident, $arg:expr) => ( + { + let path = ($path); + let arg = ($arg); + let mut p1 = PosixPath::new(path); + p1.$set(arg); + let p2 = PosixPath::new(path); + assert_eq!(p1, p2.$with(arg)); + } + ) + ) + + t!("a/b/c", set_dirname, with_dirname, "d"); + t!("a/b/c", set_dirname, with_dirname, "d/e"); + t!("/", set_dirname, with_dirname, "foo"); + t!("/foo", set_dirname, with_dirname, "bar"); + t!("a/b/c", set_dirname, with_dirname, ""); + t!("../..", set_dirname, with_dirname, "x"); + t!("foo", set_dirname, with_dirname, "../.."); + + t!("a/b/c", set_filename, with_filename, "d"); + t!("/", set_filename, with_filename, "foo"); + t!(".", set_filename, with_filename, "foo"); + t!("a/b", set_filename, with_filename, ""); + t!("a", set_filename, with_filename, ""); + + t!("hi/there.txt", set_filestem, with_filestem, "here"); + t!("hi/there.", set_filestem, with_filestem, "here"); + t!("hi/there", set_filestem, with_filestem, "here"); + t!("hi/there.txt", set_filestem, with_filestem, ""); + t!("hi/there", set_filestem, with_filestem, ""); + + t!("hi/there.txt", set_extension, with_extension, "exe"); + t!("hi/there.", set_extension, with_extension, "txt"); + t!("hi/there", set_extension, with_extension, "txt"); + t!("hi/there.txt", set_extension, with_extension, ""); + t!("hi/there", set_extension, with_extension, ""); + t!(".", set_extension, with_extension, "txt"); + } + + #[test] + fn test_posix_dir_file_path() { + t!(PosixPath::new("hi/there").dir_path(), "hi"); + t!(PosixPath::new("hi").dir_path(), "."); + t!(PosixPath::new("/hi").dir_path(), "/"); + t!(PosixPath::new("/").dir_path(), "/"); + t!(PosixPath::new("..").dir_path(), ".."); + t!(PosixPath::new("../..").dir_path(), "../.."); + + macro_rules! t( + ($path:expr, $exp:expr) => ( + { + let path = $path; + let left = path.map(|p| p.as_str()); + assert_eq!(left, $exp); + } + ) + ) + + t!(PosixPath::new("hi/there").file_path(), Some("there")); + t!(PosixPath::new("hi").file_path(), Some("hi")); + t!(PosixPath::new(".").file_path(), None); + t!(PosixPath::new("/").file_path(), None); + t!(PosixPath::new("..").file_path(), None); + t!(PosixPath::new("../..").file_path(), None); + } + + #[test] + fn test_posix_is_absolute() { + assert_eq!(PosixPath::new("a/b/c").is_absolute(), false); + assert_eq!(PosixPath::new("/a/b/c").is_absolute(), true); + assert_eq!(PosixPath::new("a").is_absolute(), false); + assert_eq!(PosixPath::new("/a").is_absolute(), true); + assert_eq!(PosixPath::new(".").is_absolute(), false); + assert_eq!(PosixPath::new("/").is_absolute(), true); + assert_eq!(PosixPath::new("..").is_absolute(), false); + assert_eq!(PosixPath::new("../..").is_absolute(), false); + } + + #[test] + fn test_posix_is_ancestor_of() { + macro_rules! t( + ($path:expr, $dest:expr, $exp:expr) => ( + { + let path = PosixPath::new($path); + let dest = PosixPath::new($dest); + assert_eq!(path.is_ancestor_of(&dest), $exp); + } + ) + ) + + t!("a/b/c", "a/b/c/d", true); + t!("a/b/c", "a/b/c", true); + t!("a/b/c", "a/b", false); + t!("/a/b/c", "/a/b/c", true); + t!("/a/b", "/a/b/c", true); + t!("/a/b/c/d", "/a/b/c", false); + t!("/a/b", "a/b/c", false); + t!("a/b", "/a/b/c", false); + t!("a/b/c", "a/b/d", false); + t!("../a/b/c", "a/b/c", false); + t!("a/b/c", "../a/b/c", false); + t!("a/b/c", "a/b/cd", false); + t!("a/b/cd", "a/b/c", false); + t!("../a/b", "../a/b/c", true); + t!(".", "a/b", true); + t!(".", ".", true); + t!("/", "/", true); + t!("/", "/a/b", true); + t!("..", "a/b", true); + t!("../..", "a/b", true); + } + + #[test] + fn test_posix_path_relative_from() { + macro_rules! t( + ($path:expr, $other:expr, $exp:expr) => ( + { + let path = PosixPath::new($path); + let other = PosixPath::new($other); + let res = path.path_relative_from(&other); + assert_eq!(res.map(|x| x.as_str()), $exp); + } + ) + ) + + t!("a/b/c", "a/b", Some("c")); + t!("a/b/c", "a/b/d", Some("../c")); + t!("a/b/c", "a/b/c/d", Some("..")); + t!("a/b/c", "a/b/c", Some(".")); + t!("a/b/c", "a/b/c/d/e", Some("../..")); + t!("a/b/c", "a/d/e", Some("../../b/c")); + t!("a/b/c", "d/e/f", Some("../../../a/b/c")); + t!("a/b/c", "/a/b/c", None); + t!("/a/b/c", "a/b/c", Some("/a/b/c")); + t!("/a/b/c", "/a/b/c/d", Some("..")); + t!("/a/b/c", "/a/b", Some("c")); + t!("/a/b/c", "/a/b/c/d/e", Some("../..")); + t!("/a/b/c", "/a/d/e", Some("../../b/c")); + t!("/a/b/c", "/d/e/f", Some("../../../a/b/c")); + t!("hi/there.txt", "hi/there", Some("../there.txt")); + t!(".", "a", Some("..")); + t!(".", "a/b", Some("../..")); + t!(".", ".", Some(".")); + t!("a", ".", Some("a")); + t!("a/b", ".", Some("a/b")); + t!("..", ".", Some("..")); + t!("a/b/c", "a/b/c", Some(".")); + t!("/a/b/c", "/a/b/c", Some(".")); + t!("/", "/", Some(".")); + t!("/", ".", Some("/")); + t!("../../a", "b", Some("../../../a")); + t!("a", "../../b", None); + t!("../../a", "../../b", Some("../a")); + t!("../../a", "../../a/b", Some("..")); + t!("../../a/b", "../../a", Some("b")); + } + + #[test] + fn test_posix_component_iter() { + macro_rules! t( + ($path:expr, $exp:expr) => ( + { + let path = PosixPath::new($path); + let comps = path.component_iter().to_owned_vec(); + assert_eq!(comps.as_slice(), $exp); + } + ) + ) + + t!("a/b/c", ["a", "b", "c"]); + t!("a/b/d", ["a", "b", "d"]); + t!("a/b/cd", ["a", "b", "cd"]); + t!("/a/b/c", ["a", "b", "c"]); + t!("a", ["a"]); + t!("/a", ["a"]); + t!("/", []); + t!(".", ["."]); + t!("..", [".."]); + t!("../..", ["..", ".."]); + t!("../../foo", ["..", "..", "foo"]); + } +}