diff options
Diffstat (limited to 'src/libstd/path.rs')
| -rw-r--r-- | src/libstd/path.rs | 1200 |
1 files changed, 1200 insertions, 0 deletions
diff --git a/src/libstd/path.rs b/src/libstd/path.rs new file mode 100644 index 00000000000..ed9ef864f80 --- /dev/null +++ b/src/libstd/path.rs @@ -0,0 +1,1200 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! + +Cross-platform file path handling + +*/ + +use container::Container; +use cmp::Eq; +use libc; +use option::{None, Option, Some}; +use str; +use str::StrSlice; +use to_str::ToStr; +use ascii::{AsciiCast, AsciiStr}; +use old_iter::BaseIter; +use vec::OwnedVector; + +#[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 { + /// Converts a string to a Path + fn from_str(&str) -> Self; + + /// Returns the directory component of `self`, as a string + fn dirname(&self) -> ~str; + /// Returns the file component of `self`, as a string option. + /// Returns None if `self` names a directory. + fn filename(&self) -> Option<~str>; + /// 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(&self) -> Option<~str>; + /// 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(&self) -> Option<~str>; + + /// 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, (&str)) -> Self; + /// Returns a new path consisting of `self` with the file type replaced + /// with the given string. + fn with_filetype(&self, (&str)) -> Self; + + /// Returns the directory component of `self`, as a new path. + /// If `self` has no parent, returns `self`. + fn dir_path(&self) -> Self; + /// 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, (&Self)) -> Self; + /// Returns a new Path consisting of the path given by the given vector + /// of strings, relative to `self`. + fn push_many(&self, (&[~str])) -> 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; +} + +#[cfg(target_os = "linux")] +#[cfg(target_os = "android")] +mod stat { + #[cfg(target_arch = "x86")] + #[cfg(target_arch = "arm")] + 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 = "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, + } + } + } +} + + +pub impl Path { + fn stat(&self) -> Option<libc::stat> { + unsafe { + do str::as_c_str(self.to_str()) |buf| { + let mut st = stat::arch::default_stat(); + match libc::stat(buf, &mut st) { + 0 => Some(st), + _ => None, + } + } + } + } + + #[cfg(unix)] + fn lstat(&self) -> Option<libc::stat> { + unsafe { + do str::as_c_str(self.to_str()) |buf| { + let mut st = stat::arch::default_stat(); + match libc::lstat(buf, &mut st) { + 0 => Some(st), + _ => None, + } + } + } + } + + fn exists(&self) -> bool { + match self.stat() { + None => false, + Some(_) => true, + } + } + + fn get_size(&self) -> Option<i64> { + match self.stat() { + None => None, + Some(ref st) => Some(st.st_size as i64), + } + } + + fn get_mode(&self) -> Option<uint> { + match self.stat() { + None => None, + Some(ref st) => Some(st.st_mode as uint), + } + } +} + +#[cfg(target_os = "freebsd")] +#[cfg(target_os = "linux")] +#[cfg(target_os = "macos")] +pub impl Path { + 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)) + } + } + } + + 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)) + } + } + } + + 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(target_os = "freebsd")] +#[cfg(target_os = "macos")] +pub impl Path { + 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")] +pub impl Path { + fn get_atime(&self) -> Option<(i64, int)> { + match self.stat() { + None => None, + Some(ref st) => { + Some((st.st_atime as i64, 0)) + } + } + } + + fn get_mtime(&self) -> Option<(i64, int)> { + match self.stat() { + None => None, + Some(ref st) => { + Some((st.st_mtime as i64, 0)) + } + } + } + + fn get_ctime(&self) -> Option<(i64, int)> { + match self.stat() { + None => None, + Some(ref st) => { + Some((st.st_ctime as i64, 0)) + } + } + } +} + +impl ToStr for PosixPath { + fn to_str(&self) -> ~str { + let mut s = ~""; + if self.is_absolute { + s += "/"; + } + s + str::connect(self.components, "/") + } +} + +// FIXME (#3227): when default methods in traits are working, de-duplicate +// PosixPath and WindowsPath, most of their methods are common. +impl GenericPath for PosixPath { + fn from_str(s: &str) -> PosixPath { + let mut components = ~[]; + for str::each_split_nonempty(s, |c| c == '/') |s| { + components.push(s.to_owned()) + } + let is_absolute = (s.len() != 0 && s[0] == '/' as u8); + PosixPath { + is_absolute: is_absolute, + components: components, + } + } + + fn dirname(&self) -> ~str { + let s = self.dir_path().to_str(); + match s.len() { + 0 => ~".", + _ => s, + } + } + + fn filename(&self) -> Option<~str> { + match self.components.len() { + 0 => None, + n => Some(copy self.components[n - 1]), + } + } + + fn filestem(&self) -> Option<~str> { + match self.filename() { + None => None, + Some(ref f) => { + match str::rfind_char(*f, '.') { + Some(p) => Some(f.slice(0, p).to_owned()), + None => Some(copy *f), + } + } + } + } + + fn filetype(&self) -> Option<~str> { + match self.filename() { + None => None, + Some(ref f) => { + match str::rfind_char(*f, '.') { + Some(p) if p < f.len() => Some(f.slice(p, f.len()).to_owned()), + _ => None, + } + } + } + } + + 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!(! str::any(f, |c| windows::is_sep(c as u8))); + self.dir_path().push(f) + } + + fn with_filestem(&self, s: &str) -> PosixPath { + match self.filetype() { + None => self.with_filename(s), + Some(ref t) => self.with_filename(str::to_owned(s) + *t), + } + } + + fn with_filetype(&self, t: &str) -> PosixPath { + match (t.len(), self.filestem()) { + (0, None) => copy *self, + (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)), + } + } + + fn dir_path(&self) -> PosixPath { + match self.components.len() { + 0 => copy *self, + _ => self.pop(), + } + } + + fn file_path(&self) -> PosixPath { + let cs = match self.filename() { + None => ~[], + Some(ref f) => ~[copy *f] + }; + PosixPath { + is_absolute: false, + components: cs, + } + } + + fn push_rel(&self, other: &PosixPath) -> PosixPath { + assert!(!other.is_absolute); + self.push_many(other.components) + } + + fn unsafe_join(&self, other: &PosixPath) -> PosixPath { + if other.is_absolute { + PosixPath { + is_absolute: true, + components: copy other.components, + } + } else { + self.push_rel(other) + } + } + + fn is_restricted(&self) -> bool { + false + } + + fn push_many(&self, cs: &[~str]) -> PosixPath { + let mut v = copy self.components; + for cs.each |e| { + let mut ss = ~[]; + for str::each_split_nonempty(*e, |c| windows::is_sep(c as u8)) |s| { + ss.push(s.to_owned()) + } + v.push_all_move(ss); + } + PosixPath { + is_absolute: self.is_absolute, + components: v, + } + } + + fn push(&self, s: &str) -> PosixPath { + let mut v = copy self.components; + let mut ss = ~[]; + for str::each_split_nonempty(s, |c| windows::is_sep(c as u8)) |s| { + ss.push(s.to_owned()) + } + v.push_all_move(ss); + PosixPath { components: v, ..copy *self } + } + + fn pop(&self) -> PosixPath { + let mut cs = copy self.components; + if cs.len() != 0 { + cs.pop(); + } + PosixPath { + is_absolute: self.is_absolute, + components: cs, + } //..self } + } + + fn normalize(&self) -> PosixPath { + PosixPath { + is_absolute: self.is_absolute, + components: normalize(self.components), + } // ..self } + } + + fn is_absolute(&self) -> bool { + self.is_absolute + } +} + + +impl ToStr for WindowsPath { + fn to_str(&self) -> ~str { + let mut s = ~""; + match self.host { + Some(ref h) => { s += "\\\\"; s += *h; } + None => { } + } + match self.device { + Some(ref d) => { s += *d; s += ":"; } + None => { } + } + if self.is_absolute { + s += "\\"; + } + s + str::connect(self.components, "\\") + } +} + + +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(copy *d); + rest = copy *r; + } + (None, Some((ref h, ref r))) => { + host = Some(copy *h); + device = None; + rest = copy *r; + } + (None, None) => { + host = None; + device = None; + rest = str::to_owned(s); + } + } + + let mut components = ~[]; + for str::each_split_nonempty(rest, |c| windows::is_sep(c as u8)) |s| { + components.push(s.to_owned()) + } + let is_absolute = (rest.len() != 0 && windows::is_sep(rest[0])); + WindowsPath { + host: host, + device: device, + is_absolute: is_absolute, + components: components, + } + } + + fn dirname(&self) -> ~str { + let s = self.dir_path().to_str(); + match s.len() { + 0 => ~".", + _ => s, + } + } + + fn filename(&self) -> Option<~str> { + match self.components.len() { + 0 => None, + n => Some(copy self.components[n - 1]), + } + } + + fn filestem(&self) -> Option<~str> { + match self.filename() { + None => None, + Some(ref f) => { + match str::rfind_char(*f, '.') { + Some(p) => Some(f.slice(0, p).to_owned()), + None => Some(copy *f), + } + } + } + } + + fn filetype(&self) -> Option<~str> { + match self.filename() { + None => None, + Some(ref f) => { + match str::rfind_char(*f, '.') { + Some(p) if p < f.len() => Some(f.slice(p, f.len()).to_owned()), + _ => None, + } + } + } + } + + 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!(! str::any(f, |c| windows::is_sep(c as u8))); + self.dir_path().push(f) + } + + fn with_filestem(&self, s: &str) -> WindowsPath { + match self.filetype() { + None => self.with_filename(s), + Some(ref t) => self.with_filename(str::to_owned(s) + *t), + } + } + + fn with_filetype(&self, t: &str) -> WindowsPath { + match (t.len(), self.filestem()) { + (0, None) => copy *self, + (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)), + } + } + + fn dir_path(&self) -> WindowsPath { + match self.components.len() { + 0 => copy *self, + _ => self.pop(), + } + } + + fn file_path(&self) -> WindowsPath { + WindowsPath { + host: None, + device: None, + is_absolute: false, + components: match self.filename() { + None => ~[], + Some(ref f) => ~[copy *f], + } + } + } + + fn push_rel(&self, other: &WindowsPath) -> WindowsPath { + assert!(!other.is_absolute); + self.push_many(other.components) + } + + 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(copy host) => { + return WindowsPath { + host: Some(host), + device: copy other.device, + is_absolute: true, + components: copy other.components, + }; + } + _ => {} + } + + /* if rhs has a device set, then a part wins */ + match other.device { + Some(copy device) => { + return WindowsPath { + host: None, + device: Some(device), + is_absolute: true, + components: copy other.components, + }; + } + _ => {} + } + + /* fallback: host and device of lhs win, but the + whole path of the right */ + WindowsPath { + host: copy self.host, + device: copy self.device, + is_absolute: self.is_absolute || other.is_absolute, + components: copy other.components, + } + } + + fn is_restricted(&self) -> bool { + match self.filestem() { + Some(stem) => { + // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use + // to_ascii_consume and to_str_consume 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 push_many(&self, cs: &[~str]) -> WindowsPath { + let mut v = copy self.components; + for cs.each |e| { + let mut ss = ~[]; + for str::each_split_nonempty(*e, |c| windows::is_sep(c as u8)) |s| { + ss.push(s.to_owned()) + } + v.push_all_move(ss); + } + // tedious, but as-is, we can't use ..self + WindowsPath { + host: copy self.host, + device: copy self.device, + is_absolute: self.is_absolute, + components: v + } + } + + fn push(&self, s: &str) -> WindowsPath { + let mut v = copy self.components; + let mut ss = ~[]; + for str::each_split_nonempty(s, |c| windows::is_sep(c as u8)) |s| { + ss.push(s.to_owned()) + } + v.push_all_move(ss); + WindowsPath { components: v, ..copy *self } + } + + fn pop(&self) -> WindowsPath { + let mut cs = copy self.components; + if cs.len() != 0 { + cs.pop(); + } + WindowsPath { + host: copy self.host, + device: copy self.device, + is_absolute: self.is_absolute, + components: cs, + } + } + + fn normalize(&self) -> WindowsPath { + WindowsPath { + host: copy self.host, + device: match self.device { + None => None, + + // FIXME: #4318 Instead of to_ascii and to_str_ascii, could use + // to_ascii_consume and to_str_consume 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 + } +} + + +pub fn normalize(components: &[~str]) -> ~[~str] { + let mut cs = ~[]; + for components.each |c| { + if *c == ~"." && components.len() > 1 { loop; } + if *c == ~"" { loop; } + if *c == ~".." && cs.len() != 0 { + cs.pop(); + loop; + } + cs.push(copy *c); + } + cs +} + +// Various windows helpers, and tests for the impl. +pub mod windows { + use libc; + use option::{None, Option, Some}; + + #[inline(always)] + pub fn is_sep(u: u8) -> bool { + u == '/' as u8 || u == '\\' as u8 + } + + 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]) { + 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)> { + 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}; + use str; + + #[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 = str::to_owned(s); + 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_normalize() { + fn t(wp: &PosixPath, s: &str) { + let ss = wp.to_str(); + let sss = str::to_owned(s); + 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 = str::to_owned(s); + 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); + } +} |
