// Copyright 2012 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. /*! Cross-platform file path handling */ #[allow(missing_doc)]; use c_str::ToCStr; use c_str; use clone::Clone; use cmp::Eq; use container::Container; use iter::{Iterator, range}; use libc; use num; use option::{None, Option, Some}; use str::{OwnedStr, Str, StrSlice, StrVector}; use to_str::ToStr; use ascii::{AsciiCast, AsciiStr}; use vec::{Vector, OwnedVector, ImmutableVector, OwnedCopyableVector}; #[cfg(windows)] pub use Path = self::WindowsPath; #[cfg(unix)] pub use Path = self::PosixPath; #[deriving(Clone, Eq)] pub struct WindowsPath { host: Option<~str>, device: Option<~str>, is_absolute: bool, components: ~[~str], } pub fn WindowsPath(s: &str) -> WindowsPath { GenericPath::from_str(s) } #[deriving(Clone, Eq)] pub struct PosixPath { is_absolute: bool, components: ~[~str], } pub fn PosixPath(s: &str) -> PosixPath { GenericPath::from_str(s) } pub trait GenericPath : Clone + Eq + ToStr { /// Converts a string to a path. fn from_str(&str) -> Self; /// Returns the directory component of `self`, as a string. fn dirname(&self) -> ~str { let s = self.dir_path().to_str(); match s.len() { 0 => ~".", _ => s, } } /// Returns the file component of `self`, as a string option. /// Returns None if `self` names a directory. fn filename<'a>(&'a self) -> Option<&'a str> { match self.components().len() { 0 => None, n => Some(self.components()[n - 1].as_slice()), } } /// Returns the stem of the file component of `self`, as a string option. /// The stem is the slice of a filename starting at 0 and ending just before /// the last '.' in the name. /// Returns None if `self` names a directory. fn filestem<'a>(&'a self) -> Option<&'a str> { match self.filename() { None => None, Some(ref f) => { match f.rfind('.') { Some(p) => Some(f.slice_to(p)), None => Some((*f)), } } } } /// Returns the type of the file component of `self`, as a string option. /// The file type is the slice of a filename starting just after the last /// '.' in the name and ending at the last index in the filename. /// Returns None if `self` names a directory. fn filetype<'a>(&'a self) -> Option<&'a str> { match self.filename() { None => None, Some(ref f) => { match f.rfind('.') { Some(p) if p < f.len() => Some(f.slice_from(p)), _ => None, } } } } /// Returns a new path consisting of `self` with the parent directory component replaced /// with the given string. fn with_dirname(&self, (&str)) -> Self; /// Returns a new path consisting of `self` with the file component replaced /// with the given string. fn with_filename(&self, (&str)) -> Self; /// Returns a new path consisting of `self` with the file stem replaced /// with the given string. fn with_filestem(&self, s: &str) -> Self { match self.filetype() { None => self.with_filename(s), Some(ref t) => self.with_filename(s.to_owned() + *t), } } /// Returns a new path consisting of `self` with the file type replaced /// with the given string. fn with_filetype(&self, t: &str) -> Self { match (t.len(), self.filestem()) { (0, None) => (*self).clone(), (0, Some(ref s)) => self.with_filename(*s), (_, None) => self.with_filename(fmt!(".%s", t)), (_, Some(ref s)) => self.with_filename(fmt!("%s.%s", *s, t)), } } /// Returns the directory component of `self`, as a new path. /// If `self` has no parent, returns `self`. fn dir_path(&self) -> Self { match self.components().len() { 0 => (*self).clone(), _ => self.pop(), } } /// Returns the file component of `self`, as a new path. /// If `self` names a directory, returns the empty path. fn file_path(&self) -> Self; /// Returns a new path whose parent directory is `self` and whose /// file component is the given string. fn push(&self, (&str)) -> Self; /// Returns a new path consisting of the given path, made relative to `self`. fn push_rel(&self, other: &Self) -> Self { assert!(!other.is_absolute()); self.push_many(other.components()) } /// Returns a new path consisting of the path given by the given vector /// of strings, relative to `self`. fn push_many(&self, (&[S])) -> Self; /// Identical to `dir_path` except in the case where `self` has only one /// component. In this case, `pop` returns the empty path. fn pop(&self) -> Self; /// The same as `push_rel`, except that the directory argument must not /// contain directory separators in any of its components. fn unsafe_join(&self, (&Self)) -> Self; /// On Unix, always returns `false`. On Windows, returns `true` iff `self`'s /// file stem is one of: `con` `aux` `com1` `com2` `com3` `com4` /// `lpt1` `lpt2` `lpt3` `prn` `nul`. fn is_restricted(&self) -> bool; /// Returns a new path that names the same file as `self`, without containing /// any '.', '..', or empty components. On Windows, uppercases the drive letter /// as well. fn normalize(&self) -> Self; /// Returns `true` if `self` is an absolute path. fn is_absolute(&self) -> bool; /// True if `self` is an ancestor of `other`. // See `test_is_ancestor_of` for examples. fn is_ancestor_of(&self, other: &Self) -> bool { debug!("%s / %s %? %?", self.to_str(), other.to_str(), self.is_absolute(), self.components().len()); self == other || (!other.components().is_empty() && !(self.components().is_empty() && !self.is_absolute()) && self.is_ancestor_of(&other.pop())) } /// Finds the relative path from one file to another. fn get_relative_to(&self, abs2: (&Self)) -> Self { assert!(self.is_absolute()); assert!(abs2.is_absolute()); let abs1 = self.normalize(); let abs2 = abs2.normalize(); let split1: &[~str] = abs1.components(); let split2: &[~str] = abs2.components(); let len1 = split1.len(); let len2 = split2.len(); assert!(len1 > 0); assert!(len2 > 0); let max_common_path = num::min(len1, len2) - 1; let mut start_idx = 0; while start_idx < max_common_path && split1[start_idx] == split2[start_idx] { start_idx += 1; } let mut path: ~[~str] = ~[]; for _ in range(start_idx, len1 - 1) { path.push(~".."); }; path.push_all(split2.slice(start_idx, len2 - 1)); let mut result: Self = GenericPath::from_str("."); if !path.is_empty() { // Without this type hint, the typechecker doesn't seem to like it let p: Self = GenericPath::from_str(""); result = p.push_many(path); }; result } /// Returns `true` iff `child` is a suffix of `parent`. See the test /// case for examples. pub fn is_parent_of(parent: &Path, child: &Path) -> bool { if !parent.is_absolute() || child.is_absolute() || parent.components.len() < child.components.len() || parent.components.is_empty() { return false; } let child_components = child.components().len(); let parent_components = parent.components().len(); let to_drop = parent.components.len() - child_components; parent.components.slice(to_drop, parent_components) == child.components } fn components<'a>(&'a self) -> &'a [~str]; } #[cfg(target_os = "linux")] #[cfg(target_os = "android")] mod stat { #[cfg(target_arch = "x86")] pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, __pad1: 0, st_ino: 0, st_mode: 0, st_nlink: 0, st_uid: 0, st_gid: 0, st_rdev: 0, __pad2: 0, st_size: 0, st_blksize: 0, st_blocks: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, __unused4: 0, __unused5: 0, } } } #[cfg(target_arch = "arm")] pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, __pad0: [0, ..4], __st_ino: 0, st_mode: 0, st_nlink: 0, st_uid: 0, st_gid: 0, st_rdev: 0, __pad3: [0, ..4], st_size: 0, st_blksize: 0, st_blocks: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, st_ino: 0 } } } #[cfg(target_arch = "mips")] pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, st_pad1: [0, ..3], st_ino: 0, st_mode: 0, st_nlink: 0, st_uid: 0, st_gid: 0, st_rdev: 0, st_pad2: [0, ..2], st_size: 0, st_pad3: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, st_blksize: 0, st_blocks: 0, st_pad5: [0, ..14], } } } #[cfg(target_arch = "x86_64")] pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, st_ino: 0, st_nlink: 0, st_mode: 0, st_uid: 0, st_gid: 0, __pad0: 0, st_rdev: 0, st_size: 0, st_blksize: 0, st_blocks: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, __unused: [0, 0, 0], } } } } #[cfg(target_os = "freebsd")] mod stat { #[cfg(target_arch = "x86_64")] pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, st_ino: 0, st_mode: 0, st_nlink: 0, st_uid: 0, st_gid: 0, st_rdev: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, st_size: 0, st_blocks: 0, st_blksize: 0, st_flags: 0, st_gen: 0, st_lspare: 0, st_birthtime: 0, st_birthtime_nsec: 0, __unused: [0, 0], } } } } #[cfg(target_os = "macos")] mod stat { pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, st_mode: 0, st_nlink: 0, st_ino: 0, st_uid: 0, st_gid: 0, st_rdev: 0, st_atime: 0, st_atime_nsec: 0, st_mtime: 0, st_mtime_nsec: 0, st_ctime: 0, st_ctime_nsec: 0, st_birthtime: 0, st_birthtime_nsec: 0, st_size: 0, st_blocks: 0, st_blksize: 0, st_flags: 0, st_gen: 0, st_lspare: 0, st_qspare: [0, 0], } } } } #[cfg(target_os = "win32")] mod stat { pub mod arch { use libc; pub fn default_stat() -> libc::stat { libc::stat { st_dev: 0, st_ino: 0, st_mode: 0, st_nlink: 0, st_uid: 0, st_gid: 0, st_rdev: 0, st_size: 0, st_atime: 0, st_mtime: 0, st_ctime: 0, } } } } #[cfg(target_os = "win32")] impl WindowsPath { pub fn stat(&self) -> Option { #[fixed_stack_segment]; #[inline(never)]; do self.with_c_str |buf| { let mut st = stat::arch::default_stat(); match unsafe { libc::stat(buf, &mut st) } { 0 => Some(st), _ => None, } } } pub fn exists(&self) -> bool { match self.stat() { None => false, Some(_) => true, } } pub fn get_size(&self) -> Option { match self.stat() { None => None, Some(ref st) => Some(st.st_size as i64), } } pub fn get_mode(&self) -> Option { match self.stat() { None => None, Some(ref st) => Some(st.st_mode as uint), } } } #[cfg(not(target_os = "win32"))] impl PosixPath { pub fn stat(&self) -> Option { #[fixed_stack_segment]; #[inline(never)]; do self.with_c_str |buf| { let mut st = stat::arch::default_stat(); match unsafe { libc::stat(buf as *libc::c_char, &mut st) } { 0 => Some(st), _ => None, } } } pub fn exists(&self) -> bool { match self.stat() { None => false, Some(_) => true, } } pub fn get_size(&self) -> Option { match self.stat() { None => None, Some(ref st) => Some(st.st_size as i64), } } pub fn get_mode(&self) -> Option { match self.stat() { None => None, Some(ref st) => Some(st.st_mode as uint), } } /// Executes a function `f` on `self` as well as on all of its ancestors. pub fn each_parent(&self, f: &fn(&Path)) { if !self.components.is_empty() { f(self); self.pop().each_parent(f); } } } #[cfg(target_os = "freebsd")] #[cfg(target_os = "linux")] #[cfg(target_os = "macos")] impl PosixPath { pub fn get_atime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_atime as i64, st.st_atime_nsec as int)) } } } pub fn get_mtime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_mtime as i64, st.st_mtime_nsec as int)) } } } pub fn get_ctime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_ctime as i64, st.st_ctime_nsec as int)) } } } } #[cfg(unix)] impl PosixPath { pub fn lstat(&self) -> Option { #[fixed_stack_segment]; #[inline(never)]; do self.with_c_str |buf| { let mut st = stat::arch::default_stat(); match unsafe { libc::lstat(buf, &mut st) } { 0 => Some(st), _ => None, } } } } #[cfg(target_os = "freebsd")] #[cfg(target_os = "macos")] impl PosixPath { pub fn get_birthtime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_birthtime as i64, st.st_birthtime_nsec as int)) } } } } #[cfg(target_os = "win32")] impl WindowsPath { pub fn get_atime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_atime as i64, 0)) } } } pub fn get_mtime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_mtime as i64, 0)) } } } pub fn get_ctime(&self) -> Option<(i64, int)> { match self.stat() { None => None, Some(ref st) => { Some((st.st_ctime as i64, 0)) } } } /// Executes a function `f` on `self` as well as on all of its ancestors. pub fn each_parent(&self, f: &fn(&Path)) { if !self.components.is_empty() { f(self); self.pop().each_parent(f); } } } impl ToStr for PosixPath { fn to_str(&self) -> ~str { let mut s = ~""; if self.is_absolute { s.push_str("/"); } s + self.components.connect("/") } } impl ToCStr for PosixPath { fn to_c_str(&self) -> c_str::CString { self.to_str().to_c_str() } unsafe fn to_c_str_unchecked(&self) -> c_str::CString { self.to_str().to_c_str_unchecked() } } impl GenericPath for PosixPath { fn from_str(s: &str) -> PosixPath { let components = s.split_iter('/') .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())}) .collect(); let is_absolute = (s.len() != 0 && s[0] == '/' as u8); PosixPath { is_absolute: is_absolute, components: components, } } fn with_dirname(&self, d: &str) -> PosixPath { let dpath = PosixPath(d); match self.filename() { Some(ref f) => dpath.push(*f), None => dpath, } } fn with_filename(&self, f: &str) -> PosixPath { assert!(!f.iter().all(posix::is_sep)); self.dir_path().push(f) } fn file_path(&self) -> PosixPath { let cs = match self.filename() { None => ~[], Some(ref f) => ~[(*f).to_owned()] }; PosixPath { is_absolute: false, components: cs, } } fn push(&self, s: &str) -> PosixPath { let mut v = self.components.clone(); for s in s.split_iter(posix::is_sep) { if !s.is_empty() { v.push(s.to_owned()) } } PosixPath { components: v, ..(*self).clone() } } fn push_many(&self, cs: &[S]) -> PosixPath { let mut v = self.components.clone(); for e in cs.iter() { for s in e.as_slice().split_iter(posix::is_sep) { if !s.is_empty() { v.push(s.to_owned()) } } } PosixPath { is_absolute: self.is_absolute, components: v, } } fn pop(&self) -> PosixPath { let mut cs = self.components.clone(); if cs.len() != 0 { cs.pop(); } PosixPath { is_absolute: self.is_absolute, components: cs, } //..self } } fn unsafe_join(&self, other: &PosixPath) -> PosixPath { if other.is_absolute { PosixPath { is_absolute: true, components: other.components.clone(), } } else { self.push_rel(other) } } fn is_restricted(&self) -> bool { false } fn normalize(&self) -> PosixPath { PosixPath { is_absolute: self.is_absolute, components: normalize(self.components), } // ..self } } fn is_absolute(&self) -> bool { self.is_absolute } fn components<'a>(&'a self) -> &'a [~str] { self.components.as_slice() } } impl ToStr for WindowsPath { fn to_str(&self) -> ~str { let mut s = ~""; match self.host { Some(ref h) => { s.push_str("\\\\"); s.push_str(*h); } None => { } } match self.device { Some(ref d) => { s.push_str(*d); s.push_str(":"); } None => { } } if self.is_absolute { s.push_str("\\"); } s + self.components.connect("\\") } } impl c_str::ToCStr for WindowsPath { fn to_c_str(&self) -> c_str::CString { self.to_str().to_c_str() } unsafe fn to_c_str_unchecked(&self) -> c_str::CString { self.to_str().to_c_str_unchecked() } } impl GenericPath for WindowsPath { fn from_str(s: &str) -> WindowsPath { let host; let device; let rest; match ( windows::extract_drive_prefix(s), windows::extract_unc_prefix(s), ) { (Some((ref d, ref r)), _) => { host = None; device = Some((*d).clone()); rest = (*r).clone(); } (None, Some((ref h, ref r))) => { host = Some((*h).clone()); device = None; rest = (*r).clone(); } (None, None) => { host = None; device = None; rest = s.to_owned(); } } let components = rest.split_iter(windows::is_sep) .filter_map(|s| if s.is_empty() {None} else {Some(s.to_owned())}) .collect(); let is_absolute = (rest.len() != 0 && windows::is_sep(rest[0] as char)); WindowsPath { host: host, device: device, is_absolute: is_absolute, components: components, } } fn with_dirname(&self, d: &str) -> WindowsPath { let dpath = WindowsPath(d); match self.filename() { Some(ref f) => dpath.push(*f), None => dpath, } } fn with_filename(&self, f: &str) -> WindowsPath { assert!(! f.iter().all(windows::is_sep)); self.dir_path().push(f) } fn file_path(&self) -> WindowsPath { WindowsPath { host: None, device: None, is_absolute: false, components: match self.filename() { None => ~[], Some(ref f) => ~[(*f).to_owned()], } } } fn push(&self, s: &str) -> WindowsPath { let mut v = self.components.clone(); for s in s.split_iter(windows::is_sep) { if !s.is_empty() { v.push(s.to_owned()) } } WindowsPath { components: v, ..(*self).clone() } } fn push_many(&self, cs: &[S]) -> WindowsPath { let mut v = self.components.clone(); for e in cs.iter() { for s in e.as_slice().split_iter(windows::is_sep) { if !s.is_empty() { v.push(s.to_owned()) } } } // tedious, but as-is, we can't use ..self WindowsPath { host: self.host.clone(), device: self.device.clone(), is_absolute: self.is_absolute, components: v } } fn pop(&self) -> WindowsPath { let mut cs = self.components.clone(); if cs.len() != 0 { cs.pop(); } WindowsPath { host: self.host.clone(), device: self.device.clone(), is_absolute: self.is_absolute, components: cs, } } fn unsafe_join(&self, other: &WindowsPath) -> WindowsPath { /* rhs not absolute is simple push */ if !other.is_absolute { return self.push_many(other.components); } /* if rhs has a host set, then the whole thing wins */ match other.host { Some(ref host) => { return WindowsPath { host: Some((*host).clone()), device: other.device.clone(), is_absolute: true, components: other.components.clone(), }; } _ => {} } /* if rhs has a device set, then a part wins */ match other.device { Some(ref device) => { return WindowsPath { host: None, device: Some((*device).clone()), is_absolute: true, components: other.components.clone(), }; } _ => {} } /* fallback: host and device of lhs win, but the whole path of the right */ WindowsPath { host: self.host.clone(), device: self.device.clone(), is_absolute: self.is_absolute || other.is_absolute, components: other.components.clone(), } } fn is_restricted(&self) -> bool { match self.filestem() { Some(stem) => { // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use // to_ascii_move and to_str_move to not do a unnecessary copy. match stem.to_ascii().to_lower().to_str_ascii() { ~"con" | ~"aux" | ~"com1" | ~"com2" | ~"com3" | ~"com4" | ~"lpt1" | ~"lpt2" | ~"lpt3" | ~"prn" | ~"nul" => true, _ => false } }, None => false } } fn normalize(&self) -> WindowsPath { WindowsPath { host: self.host.clone(), device: match self.device { None => None, // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use // to_ascii_move and to_str_move to not do a unnecessary copy. Some(ref device) => Some(device.to_ascii().to_upper().to_str_ascii()) }, is_absolute: self.is_absolute, components: normalize(self.components) } } fn is_absolute(&self) -> bool { self.is_absolute } fn components<'a>(&'a self) -> &'a [~str] { self.components.as_slice() } } pub fn normalize(components: &[~str]) -> ~[~str] { let mut cs = ~[]; for c in components.iter() { if *c == ~"." && components.len() > 1 { loop; } if *c == ~"" { loop; } if *c == ~".." && cs.len() != 0 { cs.pop(); loop; } cs.push((*c).clone()); } cs } // Various posix helpers. pub mod posix { #[inline] pub fn is_sep(u: char) -> bool { u == '/' } } // Various windows helpers. pub mod windows { use libc; use option::{None, Option, Some}; #[inline] pub fn is_sep(u: char) -> bool { u == '/' || u == '\\' } pub fn extract_unc_prefix(s: &str) -> Option<(~str,~str)> { if (s.len() > 1 && (s[0] == '\\' as u8 || s[0] == '/' as u8) && s[0] == s[1]) { let mut i = 2; while i < s.len() { if is_sep(s[i] as char) { let pre = s.slice(2, i).to_owned(); let rest = s.slice(i, s.len()).to_owned(); return Some((pre, rest)); } i += 1; } } None } pub fn extract_drive_prefix(s: &str) -> Option<(~str,~str)> { #[fixed_stack_segment]; #[inline(never)]; unsafe { if (s.len() > 1 && libc::isalpha(s[0] as libc::c_int) != 0 && s[1] == ':' as u8) { let rest = if s.len() == 2 { ~"" } else { s.slice(2, s.len()).to_owned() }; return Some((s.slice(0,1).to_owned(), rest)); } None } } } #[cfg(test)] mod tests { use option::{None, Some}; use path::{PosixPath, WindowsPath, windows}; #[test] fn test_double_slash_collapsing() { let path = PosixPath("tmp/"); let path = path.push("/hmm"); let path = path.normalize(); assert_eq!(~"tmp/hmm", path.to_str()); let path = WindowsPath("tmp/"); let path = path.push("/hmm"); let path = path.normalize(); assert_eq!(~"tmp\\hmm", path.to_str()); } #[test] fn test_filetype_foo_bar() { let wp = PosixPath("foo.bar"); assert_eq!(wp.filetype(), Some(".bar")); let wp = WindowsPath("foo.bar"); assert_eq!(wp.filetype(), Some(".bar")); } #[test] fn test_filetype_foo() { let wp = PosixPath("foo"); assert_eq!(wp.filetype(), None); let wp = WindowsPath("foo"); assert_eq!(wp.filetype(), None); } #[test] fn test_posix_paths() { fn t(wp: &PosixPath, s: &str) { let ss = wp.to_str(); let sss = s.to_owned(); if (ss != sss) { debug!("got %s", ss); debug!("expected %s", sss); assert_eq!(ss, sss); } } t(&(PosixPath("hi")), "hi"); t(&(PosixPath("/lib")), "/lib"); t(&(PosixPath("hi/there")), "hi/there"); t(&(PosixPath("hi/there.txt")), "hi/there.txt"); t(&(PosixPath("hi/there.txt")), "hi/there.txt"); t(&(PosixPath("hi/there.txt") .with_filetype("")), "hi/there"); t(&(PosixPath("/a/b/c/there.txt") .with_dirname("hi")), "hi/there.txt"); t(&(PosixPath("hi/there.txt") .with_dirname(".")), "./there.txt"); t(&(PosixPath("a/b/c") .push("..")), "a/b/c/.."); t(&(PosixPath("there.txt") .with_filetype("o")), "there.o"); t(&(PosixPath("hi/there.txt") .with_filetype("o")), "hi/there.o"); t(&(PosixPath("hi/there.txt") .with_filetype("o") .with_dirname("/usr/lib")), "/usr/lib/there.o"); t(&(PosixPath("hi/there.txt") .with_filetype("o") .with_dirname("/usr/lib/")), "/usr/lib/there.o"); t(&(PosixPath("hi/there.txt") .with_filetype("o") .with_dirname("/usr//lib//")), "/usr/lib/there.o"); t(&(PosixPath("/usr/bin/rust") .push_many([~"lib", ~"thingy.so"]) .with_filestem("librustc")), "/usr/bin/rust/lib/librustc.so"); } #[test] fn test_posix_push_with_backslash() { let a = PosixPath("/aaa/bbb"); let b = a.push("x\\y"); // \ is not a file separator for posix paths assert_eq!(a.components.len(), 2); assert_eq!(b.components.len(), 3); } #[test] fn test_normalize() { fn t(wp: &PosixPath, s: &str) { let ss = wp.to_str(); let sss = s.to_owned(); if (ss != sss) { debug!("got %s", ss); debug!("expected %s", sss); assert_eq!(ss, sss); } } t(&(PosixPath("hi/there.txt") .with_dirname(".").normalize()), "there.txt"); t(&(PosixPath("a/b/../c/././/../foo.txt/").normalize()), "a/foo.txt"); t(&(PosixPath("a/b/c") .push("..").normalize()), "a/b"); } #[test] fn test_extract_unc_prefixes() { assert!(windows::extract_unc_prefix("\\\\").is_none()); assert!(windows::extract_unc_prefix("//").is_none()); assert!(windows::extract_unc_prefix("\\\\hi").is_none()); assert!(windows::extract_unc_prefix("//hi").is_none()); assert!(windows::extract_unc_prefix("\\\\hi\\") == Some((~"hi", ~"\\"))); assert!(windows::extract_unc_prefix("//hi\\") == Some((~"hi", ~"\\"))); assert!(windows::extract_unc_prefix("\\\\hi\\there") == Some((~"hi", ~"\\there"))); assert!(windows::extract_unc_prefix("//hi/there") == Some((~"hi", ~"/there"))); assert!(windows::extract_unc_prefix( "\\\\hi\\there\\friends.txt") == Some((~"hi", ~"\\there\\friends.txt"))); assert!(windows::extract_unc_prefix( "//hi\\there\\friends.txt") == Some((~"hi", ~"\\there\\friends.txt"))); } #[test] fn test_extract_drive_prefixes() { assert!(windows::extract_drive_prefix("c").is_none()); assert!(windows::extract_drive_prefix("c:") == Some((~"c", ~""))); assert!(windows::extract_drive_prefix("d:") == Some((~"d", ~""))); assert!(windows::extract_drive_prefix("z:") == Some((~"z", ~""))); assert!(windows::extract_drive_prefix("c:\\hi") == Some((~"c", ~"\\hi"))); assert!(windows::extract_drive_prefix("d:hi") == Some((~"d", ~"hi"))); assert!(windows::extract_drive_prefix("c:hi\\there.txt") == Some((~"c", ~"hi\\there.txt"))); assert!(windows::extract_drive_prefix("c:\\hi\\there.txt") == Some((~"c", ~"\\hi\\there.txt"))); } #[test] fn test_windows_paths() { fn t(wp: &WindowsPath, s: &str) { let ss = wp.to_str(); let sss = s.to_owned(); if (ss != sss) { debug!("got %s", ss); debug!("expected %s", sss); assert_eq!(ss, sss); } } t(&(WindowsPath("hi")), "hi"); t(&(WindowsPath("hi/there")), "hi\\there"); t(&(WindowsPath("hi/there.txt")), "hi\\there.txt"); t(&(WindowsPath("there.txt") .with_filetype("o")), "there.o"); t(&(WindowsPath("hi/there.txt") .with_filetype("o")), "hi\\there.o"); t(&(WindowsPath("hi/there.txt") .with_filetype("o") .with_dirname("c:\\program files A")), "c:\\program files A\\there.o"); t(&(WindowsPath("hi/there.txt") .with_filetype("o") .with_dirname("c:\\program files B\\")), "c:\\program files B\\there.o"); t(&(WindowsPath("hi/there.txt") .with_filetype("o") .with_dirname("c:\\program files C\\/")), "c:\\program files C\\there.o"); t(&(WindowsPath("c:\\program files (x86)\\rust") .push_many([~"lib", ~"thingy.dll"]) .with_filename("librustc.dll")), "c:\\program files (x86)\\rust\\lib\\librustc.dll"); t(&(WindowsPath("\\\\computer\\share") .unsafe_join(&WindowsPath("\\a"))), "\\\\computer\\a"); t(&(WindowsPath("//computer/share") .unsafe_join(&WindowsPath("\\a"))), "\\\\computer\\a"); t(&(WindowsPath("//computer/share") .unsafe_join(&WindowsPath("\\\\computer\\share"))), "\\\\computer\\share"); t(&(WindowsPath("C:/whatever") .unsafe_join(&WindowsPath("//computer/share/a/b"))), "\\\\computer\\share\\a\\b"); t(&(WindowsPath("C:") .unsafe_join(&WindowsPath("D:/foo"))), "D:\\foo"); t(&(WindowsPath("C:") .unsafe_join(&WindowsPath("B"))), "C:B"); t(&(WindowsPath("C:") .unsafe_join(&WindowsPath("/foo"))), "C:\\foo"); t(&(WindowsPath("C:\\") .unsafe_join(&WindowsPath("\\bar"))), "C:\\bar"); t(&(WindowsPath("") .unsafe_join(&WindowsPath(""))), ""); t(&(WindowsPath("") .unsafe_join(&WindowsPath("a"))), "a"); t(&(WindowsPath("") .unsafe_join(&WindowsPath("C:\\a"))), "C:\\a"); t(&(WindowsPath("c:\\foo") .normalize()), "C:\\foo"); } #[test] fn test_windows_path_restrictions() { assert_eq!(WindowsPath("hi").is_restricted(), false); assert_eq!(WindowsPath("C:\\NUL").is_restricted(), true); assert_eq!(WindowsPath("C:\\COM1.TXT").is_restricted(), true); assert_eq!(WindowsPath("c:\\prn.exe").is_restricted(), true); } #[test] fn test_is_ancestor_of() { assert!(&PosixPath("/a/b").is_ancestor_of(&PosixPath("/a/b/c/d"))); assert!(!&PosixPath("/a/b/c/d").is_ancestor_of(&PosixPath("/a/b"))); assert!(!&PosixPath("/a/b").is_ancestor_of(&PosixPath("/c/d"))); assert!(&PosixPath("/a/b").is_ancestor_of(&PosixPath("/a/b/c/d"))); assert!(&PosixPath("/").is_ancestor_of(&PosixPath("/a/b/c"))); assert!(!&PosixPath("/").is_ancestor_of(&PosixPath(""))); assert!(!&PosixPath("/a/b/c").is_ancestor_of(&PosixPath(""))); assert!(!&PosixPath("").is_ancestor_of(&PosixPath("/a/b/c"))); assert!(&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\a\\b\\c\\d"))); assert!(!&WindowsPath("C:\\a\\b\\c\\d").is_ancestor_of(&WindowsPath("C:\\a\\b"))); assert!(!&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\c\\d"))); assert!(&WindowsPath("C:\\a\\b").is_ancestor_of(&WindowsPath("C:\\a\\b\\c\\d"))); assert!(&WindowsPath("C:\\").is_ancestor_of(&WindowsPath("C:\\a\\b\\c"))); assert!(!&WindowsPath("C:\\").is_ancestor_of(&WindowsPath(""))); assert!(!&WindowsPath("C:\\a\\b\\c").is_ancestor_of(&WindowsPath(""))); assert!(!&WindowsPath("").is_ancestor_of(&WindowsPath("C:\\a\\b\\c"))); } #[test] fn test_relative_to1() { let p1 = PosixPath("/usr/bin/rustc"); let p2 = PosixPath("/usr/lib/mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("../lib")); let p1 = WindowsPath("C:\\usr\\bin\\rustc"); let p2 = WindowsPath("C:\\usr\\lib\\mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..\\lib")); } #[test] fn test_relative_to2() { let p1 = PosixPath("/usr/bin/rustc"); let p2 = PosixPath("/usr/bin/../lib/mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("../lib")); let p1 = WindowsPath("C:\\usr\\bin\\rustc"); let p2 = WindowsPath("C:\\usr\\bin\\..\\lib\\mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..\\lib")); } #[test] fn test_relative_to3() { let p1 = PosixPath("/usr/bin/whatever/rustc"); let p2 = PosixPath("/usr/lib/whatever/mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("../../lib/whatever")); let p1 = WindowsPath("C:\\usr\\bin\\whatever\\rustc"); let p2 = WindowsPath("C:\\usr\\lib\\whatever\\mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..\\..\\lib\\whatever")); } #[test] fn test_relative_to4() { let p1 = PosixPath("/usr/bin/whatever/../rustc"); let p2 = PosixPath("/usr/lib/whatever/mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("../lib/whatever")); let p1 = WindowsPath("C:\\usr\\bin\\whatever\\..\\rustc"); let p2 = WindowsPath("C:\\usr\\lib\\whatever\\mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..\\lib\\whatever")); } #[test] fn test_relative_to5() { let p1 = PosixPath("/usr/bin/whatever/../rustc"); let p2 = PosixPath("/usr/lib/whatever/../mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("../lib")); let p1 = WindowsPath("C:\\usr\\bin/whatever\\..\\rustc"); let p2 = WindowsPath("C:\\usr\\lib\\whatever\\..\\mylib"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..\\lib")); } #[test] fn test_relative_to6() { let p1 = PosixPath("/1"); let p2 = PosixPath("/2/3"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("2")); let p1 = WindowsPath("C:\\1"); let p2 = WindowsPath("C:\\2\\3"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("2")); } #[test] fn test_relative_to7() { let p1 = PosixPath("/1/2"); let p2 = PosixPath("/3"); let res = p1.get_relative_to(&p2); assert_eq!(res, PosixPath("..")); let p1 = WindowsPath("C:\\1\\2"); let p2 = WindowsPath("C:\\3"); let res = p1.get_relative_to(&p2); assert_eq!(res, WindowsPath("..")); } #[test] fn test_relative_to8() { let p1 = PosixPath("/home/brian/Dev/rust/build/").push_rel( &PosixPath("stage2/lib/rustc/i686-unknown-linux-gnu/lib/librustc.so")); let p2 = PosixPath("/home/brian/Dev/rust/build/stage2/bin/..").push_rel( &PosixPath("lib/rustc/i686-unknown-linux-gnu/lib/libstd.so")); let res = p1.get_relative_to(&p2); debug!("test_relative_to8: %s vs. %s", res.to_str(), PosixPath(".").to_str()); assert_eq!(res, PosixPath(".")); let p1 = WindowsPath("C:\\home\\brian\\Dev\\rust\\build\\").push_rel( &WindowsPath("stage2\\lib\\rustc\\i686-unknown-linux-gnu\\lib\\librustc.so")); let p2 = WindowsPath("\\home\\brian\\Dev\\rust\\build\\stage2\\bin\\..").push_rel( &WindowsPath("lib\\rustc\\i686-unknown-linux-gnu\\lib\\libstd.so")); let res = p1.get_relative_to(&p2); debug!("test_relative_to8: %s vs. %s", res.to_str(), WindowsPath(".").to_str()); assert_eq!(res, WindowsPath(".")); } #[test] fn test_is_parent_of() { assert!(is_parent_of(&PosixPath("/a/b/c/d/e"), &PosixPath("c/d/e"))); assert!(!is_parent_of(&PosixPath("a/b/c/d/e"), &PosixPath("c/d/e"))); assert!(!is_parent_of(&PosixPath("/a/b/c/d/e"), &PosixPath("/c/d/e"))); assert!(!is_parent_of(&PosixPath(""), &PosixPath(""))); assert!(!is_parent_of(&PosixPath(""), &PosixPath("a/b/c"))); assert!(is_parent_of(&PosixPath("/a/b/c"), &PosixPath(""))); assert!(is_parent_of(&PosixPath("/a/b/c"), &PosixPath("a/b/c"))); assert!(!is_parent_of(&PosixPath("/a/b/c"), &PosixPath("d/e/f"))); let abcde = WindowsPath("C:\\a\\b\\c\\d\\e"); let rel_abcde = WindowsPath("a\\b\\c\\d\\e"); let cde = WindowsPath("c\\d\\e"); let slashcde = WindowsPath("C:\\c\\d\\e"); let empty = WindowsPath(""); let abc = WindowsPath("C:\\a\\b\\c"); let rel_abc = WindowsPath("a\\b\\c"); let def = WindowsPath("d\\e\\f"); assert!(is_parent_of(&abcde, &cde)); assert!(!is_parent_of(&rel_abcde, &cde)); assert!(!is_parent_of(&abcde, &slashcde)); assert!(!is_parent_of(&empty, &empty)); assert!(is_parent_of(&abc, &empty); assert!(is_parent_of(&abc, &rel_abc)); assert!(!is_parent_of(&abc, &def)); } }