diff options
| author | Kevin Ballard <kevin@sb.org> | 2013-09-01 12:44:07 -0700 |
|---|---|---|
| committer | Kevin Ballard <kevin@sb.org> | 2013-10-15 20:10:11 -0700 |
| commit | 6d29142219d92f886124057e9ecfdb51ffca19f2 (patch) | |
| tree | 872594a3d1b4e7181597f374baec15ebbeb11992 /src/libstd | |
| parent | 17ca6f0dfac37196ae1fa8e4d7674431534437d3 (diff) | |
| download | rust-6d29142219d92f886124057e9ecfdb51ffca19f2.tar.gz rust-6d29142219d92f886124057e9ecfdb51ffca19f2.zip | |
path2: Extract posix/windows into their own files
Move PosixPath into posix::Path.
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/path2.rs | 1639 | ||||
| -rw-r--r-- | src/libstd/path2/mod.rs | 519 | ||||
| -rw-r--r-- | src/libstd/path2/posix.rs | 1152 | ||||
| -rw-r--r-- | src/libstd/path2/windows.rs | 22 |
4 files changed, 1693 insertions, 1639 deletions
diff --git a/src/libstd/path2.rs b/src/libstd/path2.rs deleted file mode 100644 index c10b6eeeda8..00000000000 --- a/src/libstd/path2.rs +++ /dev/null @@ -1,1639 +0,0 @@ -// Copyright 2013 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 (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 util; -use vec; -use vec::{CopyableVector, OwnedCopyableVector, OwnedVector}; -use vec::{ImmutableEqVector, ImmutableVector, Vector, VectorVector}; - -/// Typedef for the platform-native path type -#[cfg(unix)] -pub type Path = PosixPath; -// /// Typedef for the platform-native path type -//#[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> = vec::SplitIterator<'self, u8>; - -// Condition that is raised when a NUL is found in a byte vector given to a Path function -condition! { - // this should be a &[u8] but there's a lifetime issue - null_byte: ~[u8] -> ~[u8]; -} - -/// Represents a POSIX file path -#[deriving(Clone, DeepClone)] -pub struct PosixPath { - priv repr: ~[u8], // assumed to never be empty or contain NULs - priv sepidx: Option<uint> // index of the final separator in repr -} - -impl Eq for PosixPath { - fn eq(&self, other: &PosixPath) -> bool { - self.repr == other.repr - } -} - -impl FromStr for PosixPath { - fn from_str(s: &str) -> Option<PosixPath> { - let v = s.as_bytes(); - if contains_nul(v) { - None - } else { - Some(unsafe { GenericPathUnsafe::from_vec_unchecked(v) }) - } - } -} - -/// A trait that represents the generic operations available on paths -pub trait GenericPath: Clone + GenericPathUnsafe { - /// Creates a new Path from a byte vector. - /// The resulting Path will always be normalized. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the path contains a NUL. - #[inline] - fn from_vec(path: &[u8]) -> Self { - if contains_nul(path) { - let path = self::null_byte::cond.raise(path.to_owned()); - assert!(!contains_nul(path)); - unsafe { GenericPathUnsafe::from_vec_unchecked(path) } - } else { - unsafe { GenericPathUnsafe::from_vec_unchecked(path) } - } - } - - /// Creates a new Path from a string. - /// The resulting Path will always be normalized. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the path contains a NUL. - #[inline] - fn from_str(path: &str) -> Self { - GenericPath::from_vec(path.as_bytes()) - } - - /// Creates a new Path from a CString. - /// The resulting Path will always be normalized. - #[inline] - fn from_c_str(path: CString) -> Self { - // CStrings can't contain NULs - unsafe { GenericPathUnsafe::from_vec_unchecked(path.as_bytes()) } - } - - /// Returns the path as a string, if possible. - /// If the path is not representable in utf-8, this returns None. - #[inline] - fn as_str<'a>(&'a self) -> Option<&'a str> { - str::from_bytes_slice_opt(self.as_vec()) - } - - /// Returns the path as a byte vector - fn as_vec<'a>(&'a self) -> &'a [u8]; - - /// Returns the directory component of `self`, as a byte vector (with no trailing separator). - /// If `self` has no directory component, returns ['.']. - fn dirname<'a>(&'a self) -> &'a [u8]; - /// Returns the directory component of `self`, as a string, if possible. - /// See `dirname` for details. - #[inline] - fn dirname_str<'a>(&'a self) -> Option<&'a str> { - str::from_bytes_slice_opt(self.dirname()) - } - /// Returns the file component of `self`, as a byte vector. - /// If `self` represents the root of the file hierarchy, returns the empty vector. - /// If `self` is ".", returns the empty vector. - fn filename<'a>(&'a self) -> &'a [u8]; - /// Returns the file component of `self`, as a string, if possible. - /// See `filename` for details. - #[inline] - fn filename_str<'a>(&'a self) -> Option<&'a str> { - str::from_bytes_slice_opt(self.filename()) - } - /// Returns the stem of the filename of `self`, as a byte vector. - /// The stem is the portion of the filename just before the last '.'. - /// If there is no '.', the entire filename is returned. - fn filestem<'a>(&'a self) -> &'a [u8] { - let name = self.filename(); - let dot = '.' as u8; - match name.rposition_elem(&dot) { - None | Some(0) => name, - Some(1) if name == bytes!("..") => name, - Some(pos) => name.slice_to(pos) - } - } - /// Returns the stem of the filename of `self`, as a string, if possible. - /// See `filestem` for details. - #[inline] - fn filestem_str<'a>(&'a self) -> Option<&'a str> { - str::from_bytes_slice_opt(self.filestem()) - } - /// Returns the extension of the filename of `self`, as an optional byte vector. - /// The extension is the portion of the filename just after the last '.'. - /// If there is no extension, None is returned. - /// If the filename ends in '.', the empty vector is returned. - fn extension<'a>(&'a self) -> Option<&'a [u8]> { - let name = self.filename(); - let dot = '.' as u8; - match name.rposition_elem(&dot) { - None | Some(0) => None, - Some(1) if name == bytes!("..") => None, - Some(pos) => Some(name.slice_from(pos+1)) - } - } - /// Returns the extension of the filename of `self`, as a string, if possible. - /// See `extension` for details. - #[inline] - fn extension_str<'a>(&'a self) -> Option<&'a str> { - self.extension().chain(|v| str::from_bytes_slice_opt(v)) - } - - /// Replaces the directory portion of the path with the given byte vector. - /// If `self` represents the root of the filesystem hierarchy, the last path component - /// of the given byte vector becomes the filename. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the dirname contains a NUL. - #[inline] - fn set_dirname(&mut self, dirname: &[u8]) { - if contains_nul(dirname) { - let dirname = self::null_byte::cond.raise(dirname.to_owned()); - assert!(!contains_nul(dirname)); - unsafe { self.set_dirname_unchecked(dirname) } - } else { - unsafe { self.set_dirname_unchecked(dirname) } - } - } - /// Replaces the directory portion of the path with the given string. - /// See `set_dirname` for details. - #[inline] - fn set_dirname_str(&mut self, dirname: &str) { - self.set_dirname(dirname.as_bytes()) - } - /// Replaces the filename portion of the path with the given byte vector. - /// If the replacement name is [], this is equivalent to popping the path. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the filename contains a NUL. - #[inline] - fn set_filename(&mut self, filename: &[u8]) { - if contains_nul(filename) { - let filename = self::null_byte::cond.raise(filename.to_owned()); - assert!(!contains_nul(filename)); - unsafe { self.set_filename_unchecked(filename) } - } else { - unsafe { self.set_filename_unchecked(filename) } - } - } - /// Replaces the filename portion of the path with the given string. - /// See `set_filename` for details. - #[inline] - fn set_filename_str(&mut self, filename: &str) { - self.set_filename(filename.as_bytes()) - } - /// Replaces the filestem with the given byte vector. - /// If there is no extension in `self` (or `self` has no filename), this is equivalent - /// to `set_filename`. Otherwise, if the given byte vector is [], the extension (including - /// the preceding '.') becomes the new filename. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the filestem contains a NUL. - fn set_filestem(&mut self, filestem: &[u8]) { - // borrowck is being a pain here - let val = { - let name = self.filename(); - if !name.is_empty() { - let dot = '.' as u8; - match name.rposition_elem(&dot) { - None | Some(0) => None, - Some(idx) => { - let mut v; - if contains_nul(filestem) { - let filestem = self::null_byte::cond.raise(filestem.to_owned()); - assert!(!contains_nul(filestem)); - v = vec::with_capacity(filestem.len() + name.len() - idx); - v.push_all(filestem); - } else { - v = vec::with_capacity(filestem.len() + name.len() - idx); - v.push_all(filestem); - } - v.push_all(name.slice_from(idx)); - Some(v) - } - } - } else { None } - }; - match val { - None => self.set_filename(filestem), - Some(v) => unsafe { self.set_filename_unchecked(v) } - } - } - /// Replaces the filestem with the given string. - /// See `set_filestem` for details. - #[inline] - fn set_filestem_str(&mut self, filestem: &str) { - self.set_filestem(filestem.as_bytes()) - } - /// Replaces the extension with the given byte vector. - /// If there is no extension in `self`, this adds one. - /// If the given byte vector is [], this removes the extension. - /// If `self` has no filename, this is a no-op. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the extension contains a NUL. - fn set_extension(&mut self, extension: &[u8]) { - // borrowck causes problems here too - let val = { - let name = self.filename(); - if !name.is_empty() { - let dot = '.' as u8; - match name.rposition_elem(&dot) { - None | Some(0) => { - if extension.is_empty() { - None - } else { - let mut v; - if contains_nul(extension) { - let extension = self::null_byte::cond.raise(extension.to_owned()); - assert!(!contains_nul(extension)); - v = vec::with_capacity(name.len() + extension.len() + 1); - v.push_all(name); - v.push(dot); - v.push_all(extension); - } else { - v = vec::with_capacity(name.len() + extension.len() + 1); - v.push_all(name); - v.push(dot); - v.push_all(extension); - } - Some(v) - } - } - Some(idx) => { - if extension.is_empty() { - Some(name.slice_to(idx).to_owned()) - } else { - let mut v; - if contains_nul(extension) { - let extension = self::null_byte::cond.raise(extension.to_owned()); - assert!(!contains_nul(extension)); - v = vec::with_capacity(idx + extension.len() + 1); - v.push_all(name.slice_to(idx+1)); - v.push_all(extension); - } else { - v = vec::with_capacity(idx + extension.len() + 1); - v.push_all(name.slice_to(idx+1)); - v.push_all(extension); - } - Some(v) - } - } - } - } else { None } - }; - match val { - None => (), - Some(v) => unsafe { self.set_filename_unchecked(v) } - } - } - /// Replaces the extension with the given string. - /// See `set_extension` for details. - #[inline] - fn set_extension_str(&mut self, extension: &str) { - self.set_extension(extension.as_bytes()) - } - - /// Returns a new Path constructed by replacing the dirname with the given byte vector. - /// See `set_dirname` for details. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the dirname contains a NUL. - #[inline] - fn with_dirname(&self, dirname: &[u8]) -> Self { - let mut p = self.clone(); - p.set_dirname(dirname); - p - } - /// Returns a new Path constructed by replacing the dirname with the given string. - /// See `set_dirname` for details. - #[inline] - fn with_dirname_str(&self, dirname: &str) -> Self { - self.with_dirname(dirname.as_bytes()) - } - /// Returns a new Path constructed by replacing the filename with the given byte vector. - /// See `set_filename` for details. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the filename contains a NUL. - #[inline] - fn with_filename(&self, filename: &[u8]) -> Self { - let mut p = self.clone(); - p.set_filename(filename); - p - } - /// Returns a new Path constructed by replacing the filename with the given string. - /// See `set_filename` for details. - #[inline] - fn with_filename_str(&self, filename: &str) -> Self { - self.with_filename(filename.as_bytes()) - } - /// Returns a new Path constructed by setting the filestem to the given byte vector. - /// See `set_filestem` for details. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the filestem contains a NUL. - #[inline] - fn with_filestem(&self, filestem: &[u8]) -> Self { - let mut p = self.clone(); - p.set_filestem(filestem); - p - } - /// Returns a new Path constructed by setting the filestem to the given string. - /// See `set_filestem` for details. - #[inline] - fn with_filestem_str(&self, filestem: &str) -> Self { - self.with_filestem(filestem.as_bytes()) - } - /// Returns a new Path constructed by setting the extension to the given byte vector. - /// See `set_extension` for details. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the extension contains a NUL. - #[inline] - fn with_extension(&self, extension: &[u8]) -> Self { - let mut p = self.clone(); - p.set_extension(extension); - p - } - /// Returns a new Path constructed by setting the extension to the given string. - /// See `set_extension` for details. - #[inline] - fn with_extension_str(&self, extension: &str) -> Self { - self.with_extension(extension.as_bytes()) - } - - /// Returns the directory component of `self`, as a Path. - /// If `self` represents the root of the filesystem hierarchy, returns `self`. - fn dir_path(&self) -> Self { - GenericPath::from_vec(self.dirname()) - } - /// Returns the file component of `self`, as a relative Path. - /// If `self` represents the root of the filesystem hierarchy, returns None. - fn file_path(&self) -> Option<Self> { - match self.filename() { - [] => None, - v => Some(GenericPath::from_vec(v)) - } - } - - /// Pushes a path (as a byte vector) onto `self`. - /// If the argument represents an absolute path, it replaces `self`. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the path contains a NUL. - #[inline] - fn push(&mut self, path: &[u8]) { - if contains_nul(path) { - let path = self::null_byte::cond.raise(path.to_owned()); - assert!(!contains_nul(path)); - unsafe { self.push_unchecked(path) } - } else { - unsafe { self.push_unchecked(path) } - } - } - /// Pushes a path (as a string) onto `self. - /// See `push` for details. - #[inline] - fn push_str(&mut self, path: &str) { - self.push(path.as_bytes()) - } - /// Pushes a Path onto `self`. - /// If the argument represents an absolute path, it replaces `self`. - #[inline] - fn push_path(&mut self, path: &Self) { - self.push(path.as_vec()) - } - /// Pops the last path component off of `self` and returns it. - /// If `self` represents the root of the file hierarchy, None is returned. - fn pop_opt(&mut self) -> Option<~[u8]>; - /// Pops the last path component off of `self` and returns it as a string, if possible. - /// `self` will still be modified even if None is returned. - /// See `pop_opt` for details. - #[inline] - fn pop_opt_str(&mut self) -> Option<~str> { - self.pop_opt().chain(|v| str::from_bytes_owned_opt(v)) - } - - /// Returns a new Path constructed by joining `self` with the given path (as a byte vector). - /// If the given path is absolute, the new Path will represent just that. - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the path contains a NUL. - #[inline] - fn join(&self, path: &[u8]) -> Self { - let mut p = self.clone(); - p.push(path); - p - } - /// Returns a new Path constructed by joining `self` with the given path (as a string). - /// See `join` for details. - #[inline] - fn join_str(&self, path: &str) -> Self { - self.join(path.as_bytes()) - } - /// Returns a new Path constructed by joining `self` with the given path. - /// If the given path is absolute, the new Path will represent just that. - #[inline] - fn join_path(&self, path: &Self) -> Self { - let mut p = self.clone(); - p.push_path(path); - p - } - - /// Returns whether `self` represents an absolute path. - fn is_absolute(&self) -> bool; - - /// Returns whether `self` is equal to, or is an ancestor of, the given path. - /// If both paths are relative, they are compared as though they are relative - /// to the same parent path. - fn is_ancestor_of(&self, other: &Self) -> bool; - - /// Returns the Path that, were it joined to `base`, would yield `self`. - /// If no such path exists, None is returned. - /// If `self` is absolute and `base` is relative, or on Windows if both - /// paths refer to separate drives, an absolute path is returned. - fn path_relative_from(&self, base: &Self) -> Option<Self>; -} - -/// A trait that represents the unsafe operations on GenericPaths -pub trait GenericPathUnsafe { - /// Creates a new Path from a byte vector without checking for null bytes. - /// The resulting Path will always be normalized. - unsafe fn from_vec_unchecked(path: &[u8]) -> Self; - - /// Replaces the directory portion of the path with the given byte vector without - /// checking for null bytes. - /// See `set_dirname` for details. - unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]); - - /// Replaces the filename portion of the path with the given byte vector without - /// checking for null bytes. - /// See `set_filename` for details. - unsafe fn set_filename_unchecked(&mut self, filename: &[u8]); - - /// Pushes a path onto `self` without checking for null bytes. - /// See `push` for details. - unsafe fn push_unchecked(&mut self, path: &[u8]); -} - -#[inline(always)] -fn contains_nul(v: &[u8]) -> bool { - v.iter().any(|&x| x == 0) -} - -impl ToCStr for PosixPath { - #[inline] - fn to_c_str(&self) -> CString { - // The Path impl guarantees no internal NUL - unsafe { self.as_vec().to_c_str_unchecked() } - } - - #[inline] - unsafe fn to_c_str_unchecked(&self) -> CString { - self.as_vec().to_c_str_unchecked() - } -} - -impl GenericPathUnsafe for PosixPath { - unsafe fn from_vec_unchecked(path: &[u8]) -> PosixPath { - let path = PosixPath::normalize(path); - assert!(!path.is_empty()); - let idx = path.rposition_elem(&posix::sep); - PosixPath{ repr: path, sepidx: idx } - } - - unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]) { - match self.sepidx { - None if bytes!(".") == self.repr || bytes!("..") == self.repr => { - self.repr = PosixPath::normalize(dirname); - } - None => { - let mut v = vec::with_capacity(dirname.len() + self.repr.len() + 1); - v.push_all(dirname); - v.push(posix::sep); - v.push_all(self.repr); - self.repr = PosixPath::normalize(v); - } - Some(0) if self.repr.len() == 1 && self.repr[0] == posix::sep => { - self.repr = PosixPath::normalize(dirname); - } - Some(idx) if dirname.is_empty() => { - let v = PosixPath::normalize(self.repr.slice_from(idx+1)); - self.repr = v; - } - Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { - self.repr = PosixPath::normalize(dirname); - } - Some(idx) => { - let mut v = vec::with_capacity(dirname.len() + self.repr.len() - idx); - v.push_all(dirname); - v.push_all(self.repr.slice_from(idx)); - self.repr = PosixPath::normalize(v); - } - } - self.sepidx = self.repr.rposition_elem(&posix::sep); - } - - unsafe fn set_filename_unchecked(&mut self, filename: &[u8]) { - match self.sepidx { - None if bytes!("..") == self.repr => { - let mut v = vec::with_capacity(3 + filename.len()); - v.push_all(dot_dot_static); - v.push(posix::sep); - v.push_all(filename); - self.repr = PosixPath::normalize(v); - } - None => { - self.repr = PosixPath::normalize(filename); - } - Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { - let mut v = vec::with_capacity(self.repr.len() + 1 + filename.len()); - v.push_all(self.repr); - v.push(posix::sep); - v.push_all(filename); - self.repr = PosixPath::normalize(v); - } - Some(idx) => { - let mut v = vec::with_capacity(self.repr.len() - idx + filename.len()); - v.push_all(self.repr.slice_to(idx+1)); - v.push_all(filename); - self.repr = PosixPath::normalize(v); - } - } - self.sepidx = self.repr.rposition_elem(&posix::sep); - } - - unsafe fn push_unchecked(&mut self, path: &[u8]) { - if !path.is_empty() { - if path[0] == posix::sep { - self.repr = PosixPath::normalize(path); - } else { - let mut v = vec::with_capacity(self.repr.len() + path.len() + 1); - v.push_all(self.repr); - v.push(posix::sep); - v.push_all(path); - self.repr = PosixPath::normalize(v); - } - self.sepidx = self.repr.rposition_elem(&posix::sep); - } - } -} - -impl GenericPath for PosixPath { - #[inline] - fn as_vec<'a>(&'a self) -> &'a [u8] { - self.repr.as_slice() - } - - fn dirname<'a>(&'a self) -> &'a [u8] { - match self.sepidx { - None if bytes!("..") == self.repr => self.repr.as_slice(), - None => dot_static, - Some(0) => self.repr.slice_to(1), - Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => self.repr.as_slice(), - Some(idx) => self.repr.slice_to(idx) - } - } - - fn filename<'a>(&'a self) -> &'a [u8] { - match self.sepidx { - None if bytes!(".") == self.repr || bytes!("..") == self.repr => &[], - None => self.repr.as_slice(), - Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => &[], - Some(idx) => self.repr.slice_from(idx+1) - } - } - - fn pop_opt(&mut self) -> Option<~[u8]> { - match self.sepidx { - None if bytes!(".") == self.repr => None, - None => { - let mut v = ~['.' as u8]; - util::swap(&mut v, &mut self.repr); - self.sepidx = None; - Some(v) - } - Some(0) if bytes!("/") == self.repr => None, - Some(idx) => { - let v = 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.rposition_elem(&posix::sep); - Some(v) - } - } - } - - #[inline] - fn is_absolute(&self) -> bool { - self.repr[0] == posix::sep - } - - 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 bytes!(".") == self.repr { - return itb.next() != Some(bytes!("..")); - } - loop { - match (ita.next(), itb.next()) { - (None, _) => break, - (Some(a), Some(b)) if a == b => { loop }, - (Some(a), _) if a == bytes!("..") => { - // if ita contains only .. components, it's an ancestor - return ita.all(|x| x == bytes!("..")); - } - _ => return false - } - } - true - } - } - - fn path_relative_from(&self, base: &PosixPath) -> Option<PosixPath> { - 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(dot_dot_static), - (Some(a), Some(b)) if comps.is_empty() && a == b => (), - (Some(a), Some(b)) if b == bytes!(".") => comps.push(a), - (Some(_), Some(b)) if b == bytes!("..") => return None, - (Some(a), Some(_)) => { - comps.push(dot_dot_static); - for _ in itb { - comps.push(dot_dot_static); - } - comps.push(a); - comps.extend(&mut ita); - break; - } - } - } - Some(PosixPath::new(comps.connect_vec(&posix::sep))) - } - } -} - -impl PosixPath { - /// Returns a new PosixPath from a byte vector - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the vector contains a NUL. - #[inline] - pub fn new(v: &[u8]) -> PosixPath { - GenericPath::from_vec(v) - } - - /// Returns a new PosixPath from a string - /// - /// # Failure - /// - /// Raises the `null_byte` condition if the str contains a NUL. - #[inline] - pub fn from_str(s: &str) -> PosixPath { - GenericPath::from_str(s) - } - - /// Converts the PosixPath into an owned byte vector - pub fn into_vec(self) -> ~[u8] { - self.repr - } - - /// Converts the PosixPath into an owned string, if possible - pub fn into_str(self) -> Option<~str> { - str::from_bytes_owned_opt(self.repr) - } - - /// Returns a normalized byte vector representation of a path, by removing all empty - /// components, and unnecessary . and .. components. - pub fn normalize<V: Vector<u8>+CopyableVector<u8>>(v: V) -> ~[u8] { - // borrowck is being very picky - let val = { - let is_abs = !v.as_slice().is_empty() && v.as_slice()[0] == posix::sep; - let v_ = if is_abs { v.as_slice().slice_from(1) } else { v.as_slice() }; - let comps = normalize_helper(v_, is_abs, posix::is_sep); - match comps { - None => None, - Some(comps) => { - if is_abs && comps.is_empty() { - Some(~[posix::sep]) - } else { - let n = if is_abs { comps.len() } else { comps.len() - 1} + - comps.iter().map(|v| v.len()).sum(); - let mut v = vec::with_capacity(n); - let mut it = comps.move_iter(); - if !is_abs { - match it.next() { - None => (), - Some(comp) => v.push_all(comp) - } - } - for comp in it { - v.push(posix::sep); - v.push_all(comp); - } - Some(v) - } - } - } - }; - match val { - None => v.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 v = if self.repr[0] == posix::sep { - self.repr.slice_from(1) - } else { self.repr.as_slice() }; - let mut ret = v.split_iter(posix::is_sep); - if v.is_empty() { - // consume the empty "" component - ret.next(); - } - ret - } -} - -// None result means the byte vector didn't need normalizing -fn normalize_helper<'a>(v: &'a [u8], is_abs: bool, f: &'a fn(&u8) -> bool) -> Option<~[&'a [u8]]> { - if is_abs && v.as_slice().is_empty() { - return None; - } - let mut comps: ~[&'a [u8]] = ~[]; - let mut n_up = 0u; - let mut changed = false; - for comp in v.split_iter(f) { - if comp.is_empty() { changed = true } - else if comp == bytes!(".") { changed = true } - else if comp == bytes!("..") { - if is_abs && comps.is_empty() { changed = true } - else if comps.len() == n_up { comps.push(dot_dot_static); n_up += 1 } - else { comps.pop_opt(); changed = true } - } else { comps.push(comp) } - } - if changed { - if comps.is_empty() && !is_abs { - if v == bytes!(".") { - return None; - } - comps.push(dot_static); - } - Some(comps) - } else { - None - } -} - -static dot_static: &'static [u8] = &'static ['.' as u8]; -static dot_dot_static: &'static [u8] = &'static ['.' as u8, '.' as u8]; - -/// Various POSIX helpers -pub mod posix { - /// The standard path separator character - pub static sep: u8 = '/' as u8; - - /// Returns whether the given byte is a path separator - #[inline] - pub fn is_sep(u: &u8) -> bool { - *u == sep - } -} - -/// Various Windows helpers -pub mod windows { - /// The standard path separator character - pub static sep: u8 = '\\' as u8; - /// The alternative path separator character - pub static sep2: u8 = '/' as u8; - - /// Returns whether the given byte is a path separator - #[inline] - pub fn is_sep(u: &u8) -> bool { - *u == sep || *u == sep2 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use option::{Some, None}; - use iterator::Iterator; - use str; - use vec::Vector; - - macro_rules! t( - (s: $path:expr, $exp:expr) => ( - { - let path = $path; - assert_eq!(path.as_str(), Some($exp)); - } - ); - (v: $path:expr, $exp:expr) => ( - { - let path = $path; - assert_eq!(path.as_vec(), $exp); - } - ) - ) - - macro_rules! b( - ($($arg:expr),+) => ( - bytes!($($arg),+) - ) - ) - - #[test] - fn test_posix_paths() { - t!(v: PosixPath::new([]), b!(".")); - t!(v: PosixPath::new(b!("/")), b!("/")); - t!(v: PosixPath::new(b!("a/b/c")), b!("a/b/c")); - t!(v: PosixPath::new(b!("a/b/c", 0xff)), b!("a/b/c", 0xff)); - t!(v: PosixPath::new(b!(0xff, "/../foo", 0x80)), b!("foo", 0x80)); - let p = PosixPath::new(b!("a/b/c", 0xff)); - assert_eq!(p.as_str(), None); - - t!(s: PosixPath::from_str(""), "."); - t!(s: PosixPath::from_str("/"), "/"); - t!(s: PosixPath::from_str("hi"), "hi"); - t!(s: PosixPath::from_str("/lib"), "/lib"); - t!(s: PosixPath::from_str("hi/there"), "hi/there"); - t!(s: PosixPath::from_str("hi/there.txt"), "hi/there.txt"); - - t!(s: PosixPath::from_str("hi/there/"), "hi/there"); - t!(s: PosixPath::from_str("hi/../there"), "there"); - t!(s: PosixPath::from_str("../hi/there"), "../hi/there"); - t!(s: PosixPath::from_str("/../hi/there"), "/hi/there"); - t!(s: PosixPath::from_str("foo/.."), "."); - t!(s: PosixPath::from_str("/foo/.."), "/"); - t!(s: PosixPath::from_str("/foo/../.."), "/"); - t!(s: PosixPath::from_str("/foo/../../bar"), "/bar"); - t!(s: PosixPath::from_str("/./hi/./there/."), "/hi/there"); - t!(s: PosixPath::from_str("/./hi/./there/./.."), "/hi"); - t!(s: PosixPath::from_str("foo/../.."), ".."); - t!(s: PosixPath::from_str("foo/../../.."), "../.."); - t!(s: PosixPath::from_str("foo/../../bar"), "../bar"); - - assert_eq!(PosixPath::new(b!("foo/bar")).into_vec(), b!("foo/bar").to_owned()); - assert_eq!(PosixPath::new(b!("/foo/../../bar")).into_vec(), - b!("/bar").to_owned()); - assert_eq!(PosixPath::from_str("foo/bar").into_str(), Some(~"foo/bar")); - assert_eq!(PosixPath::from_str("/foo/../../bar").into_str(), Some(~"/bar")); - - let p = PosixPath::new(b!("foo/bar", 0x80)); - assert_eq!(p.as_str(), None); - assert_eq!(PosixPath::new(b!("foo", 0xff, "/bar")).into_str(), None); - } - - #[test] - fn test_posix_null_byte() { - use super::null_byte::cond; - - let mut handled = false; - let mut p = do cond.trap(|v| { - handled = true; - assert_eq!(v.as_slice(), b!("foo/bar", 0)); - (b!("/bar").to_owned()) - }).inside { - PosixPath::new(b!("foo/bar", 0)) - }; - assert!(handled); - assert_eq!(p.as_vec(), b!("/bar")); - - handled = false; - do cond.trap(|v| { - handled = true; - assert_eq!(v.as_slice(), b!("f", 0, "o")); - (b!("foo").to_owned()) - }).inside { - p.set_filename(b!("f", 0, "o")) - }; - assert!(handled); - assert_eq!(p.as_vec(), b!("/foo")); - - handled = false; - do cond.trap(|v| { - handled = true; - assert_eq!(v.as_slice(), b!("null/", 0, "/byte")); - (b!("null/byte").to_owned()) - }).inside { - p.set_dirname(b!("null/", 0, "/byte")); - }; - assert!(handled); - assert_eq!(p.as_vec(), b!("null/byte/foo")); - - handled = false; - do cond.trap(|v| { - handled = true; - assert_eq!(v.as_slice(), b!("f", 0, "o")); - (b!("foo").to_owned()) - }).inside { - p.push(b!("f", 0, "o")); - }; - assert!(handled); - assert_eq!(p.as_vec(), b!("null/byte/foo/foo")); - } - - #[test] - fn test_posix_null_byte_fail() { - use super::null_byte::cond; - use task; - - macro_rules! t( - ($name:expr => $code:block) => ( - { - let mut t = task::task(); - t.supervised(); - t.name($name); - let res = do t.try $code; - assert!(res.is_err()); - } - ) - ) - - t!(~"new() w/nul" => { - do cond.trap(|_| { - (b!("null", 0).to_owned()) - }).inside { - PosixPath::new(b!("foo/bar", 0)) - }; - }) - - t!(~"set_filename w/nul" => { - let mut p = PosixPath::new(b!("foo/bar")); - do cond.trap(|_| { - (b!("null", 0).to_owned()) - }).inside { - p.set_filename(b!("foo", 0)) - }; - }) - - t!(~"set_dirname w/nul" => { - let mut p = PosixPath::new(b!("foo/bar")); - do cond.trap(|_| { - (b!("null", 0).to_owned()) - }).inside { - p.set_dirname(b!("foo", 0)) - }; - }) - - t!(~"push w/nul" => { - let mut p = PosixPath::new(b!("foo/bar")); - do cond.trap(|_| { - (b!("null", 0).to_owned()) - }).inside { - p.push(b!("foo", 0)) - }; - }) - } - - #[test] - fn test_posix_components() { - macro_rules! t( - (s: $path:expr, $op:ident, $exp:expr) => ( - { - let path = PosixPath::from_str($path); - assert_eq!(path.$op(), ($exp).as_bytes()); - } - ); - (s: $path:expr, $op:ident, $exp:expr, opt) => ( - { - let path = PosixPath::from_str($path); - let left = path.$op().map(|&x| str::from_bytes_slice(x)); - assert_eq!(left, $exp); - } - ); - (v: $path:expr, $op:ident, $exp:expr) => ( - { - let path = PosixPath::new($path); - assert_eq!(path.$op(), $exp); - } - ) - ) - - t!(v: b!("a/b/c"), filename, b!("c")); - t!(v: b!("a/b/c", 0xff), filename, b!("c", 0xff)); - t!(v: b!("a/b", 0xff, "/c"), filename, b!("c")); - t!(s: "a/b/c", filename, "c"); - t!(s: "/a/b/c", filename, "c"); - t!(s: "a", filename, "a"); - t!(s: "/a", filename, "a"); - t!(s: ".", filename, ""); - t!(s: "/", filename, ""); - t!(s: "..", filename, ""); - t!(s: "../..", filename, ""); - - t!(v: b!("a/b/c"), dirname, b!("a/b")); - t!(v: b!("a/b/c", 0xff), dirname, b!("a/b")); - t!(v: b!("a/b", 0xff, "/c"), dirname, b!("a/b", 0xff)); - t!(s: "a/b/c", dirname, "a/b"); - t!(s: "/a/b/c", dirname, "/a/b"); - t!(s: "a", dirname, "."); - t!(s: "/a", dirname, "/"); - t!(s: ".", dirname, "."); - t!(s: "/", dirname, "/"); - t!(s: "..", dirname, ".."); - t!(s: "../..", dirname, "../.."); - - t!(v: b!("hi/there.txt"), filestem, b!("there")); - t!(v: b!("hi/there", 0x80, ".txt"), filestem, b!("there", 0x80)); - t!(v: b!("hi/there.t", 0x80, "xt"), filestem, b!("there")); - t!(s: "hi/there.txt", filestem, "there"); - t!(s: "hi/there", filestem, "there"); - t!(s: "there.txt", filestem, "there"); - t!(s: "there", filestem, "there"); - t!(s: ".", filestem, ""); - t!(s: "/", filestem, ""); - t!(s: "foo/.bar", filestem, ".bar"); - t!(s: ".bar", filestem, ".bar"); - t!(s: "..bar", filestem, "."); - t!(s: "hi/there..txt", filestem, "there."); - t!(s: "..", filestem, ""); - t!(s: "../..", filestem, ""); - - t!(v: b!("hi/there.txt"), extension, Some(b!("txt"))); - t!(v: b!("hi/there", 0x80, ".txt"), extension, Some(b!("txt"))); - t!(v: b!("hi/there.t", 0x80, "xt"), extension, Some(b!("t", 0x80, "xt"))); - t!(v: b!("hi/there"), extension, None); - t!(v: b!("hi/there", 0x80), extension, None); - t!(s: "hi/there.txt", extension, Some("txt"), opt); - t!(s: "hi/there", extension, None, opt); - t!(s: "there.txt", extension, Some("txt"), opt); - t!(s: "there", extension, None, opt); - t!(s: ".", extension, None, opt); - t!(s: "/", extension, None, opt); - t!(s: "foo/.bar", extension, None, opt); - t!(s: ".bar", extension, None, opt); - t!(s: "..bar", extension, Some("bar"), opt); - t!(s: "hi/there..txt", extension, Some("txt"), opt); - t!(s: "..", extension, None, opt); - t!(s: "../..", extension, None, opt); - } - - #[test] - fn test_posix_push() { - macro_rules! t( - (s: $path:expr, $join:expr) => ( - { - let path = ($path); - let join = ($join); - let mut p1 = PosixPath::from_str(path); - let p2 = p1.clone(); - p1.push_str(join); - assert_eq!(p1, p2.join_str(join)); - } - ) - ) - - t!(s: "a/b/c", ".."); - t!(s: "/a/b/c", "d"); - t!(s: "a/b", "c/d"); - t!(s: "a/b", "/c/d"); - } - - #[test] - fn test_posix_push_path() { - macro_rules! t( - (s: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = PosixPath::from_str($path); - let push = PosixPath::from_str($push); - p.push_path(&push); - assert_eq!(p.as_str(), Some($exp)); - } - ) - ) - - t!(s: "a/b/c", "d", "a/b/c/d"); - t!(s: "/a/b/c", "d", "/a/b/c/d"); - t!(s: "a/b", "c/d", "a/b/c/d"); - t!(s: "a/b", "/c/d", "/c/d"); - t!(s: "a/b", ".", "a/b"); - t!(s: "a/b", "../c", "a/c"); - } - - #[test] - fn test_posix_pop() { - macro_rules! t( - (s: $path:expr, $left:expr, $right:expr) => ( - { - let mut p = PosixPath::from_str($path); - let file = p.pop_opt_str(); - assert_eq!(p.as_str(), Some($left)); - assert_eq!(file.map(|s| s.as_slice()), $right); - } - ); - (v: [$($path:expr),+], [$($left:expr),+], Some($($right:expr),+)) => ( - { - let mut p = PosixPath::new(b!($($path),+)); - let file = p.pop_opt(); - assert_eq!(p.as_vec(), b!($($left),+)); - assert_eq!(file.map(|v| v.as_slice()), Some(b!($($right),+))); - } - ); - (v: [$($path:expr),+], [$($left:expr),+], None) => ( - { - let mut p = PosixPath::new(b!($($path),+)); - let file = p.pop_opt(); - assert_eq!(p.as_vec(), b!($($left),+)); - assert_eq!(file, None); - } - ) - ) - - t!(v: ["a/b/c"], ["a/b"], Some("c")); - t!(v: ["a"], ["."], Some("a")); - t!(v: ["."], ["."], None); - t!(v: ["/a"], ["/"], Some("a")); - t!(v: ["/"], ["/"], None); - t!(v: ["a/b/c", 0x80], ["a/b"], Some("c", 0x80)); - t!(v: ["a/b", 0x80, "/c"], ["a/b", 0x80], Some("c")); - t!(v: [0xff], ["."], Some(0xff)); - t!(v: ["/", 0xff], ["/"], Some(0xff)); - t!(s: "a/b/c", "a/b", Some("c")); - t!(s: "a", ".", Some("a")); - t!(s: ".", ".", None); - t!(s: "/a", "/", Some("a")); - t!(s: "/", "/", None); - - assert_eq!(PosixPath::new(b!("foo/bar", 0x80)).pop_opt_str(), None); - assert_eq!(PosixPath::new(b!("foo", 0x80, "/bar")).pop_opt_str(), Some(~"bar")); - } - - #[test] - fn test_posix_join() { - t!(v: PosixPath::new(b!("a/b/c")).join(b!("..")), b!("a/b")); - t!(v: PosixPath::new(b!("/a/b/c")).join(b!("d")), b!("/a/b/c/d")); - t!(v: PosixPath::new(b!("a/", 0x80, "/c")).join(b!(0xff)), b!("a/", 0x80, "/c/", 0xff)); - t!(s: PosixPath::from_str("a/b/c").join_str(".."), "a/b"); - t!(s: PosixPath::from_str("/a/b/c").join_str("d"), "/a/b/c/d"); - t!(s: PosixPath::from_str("a/b").join_str("c/d"), "a/b/c/d"); - t!(s: PosixPath::from_str("a/b").join_str("/c/d"), "/c/d"); - t!(s: PosixPath::from_str(".").join_str("a/b"), "a/b"); - t!(s: PosixPath::from_str("/").join_str("a/b"), "/a/b"); - } - - #[test] - fn test_posix_join_path() { - macro_rules! t( - (s: $path:expr, $join:expr, $exp:expr) => ( - { - let path = PosixPath::from_str($path); - let join = PosixPath::from_str($join); - let res = path.join_path(&join); - assert_eq!(res.as_str(), Some($exp)); - } - ) - ) - - t!(s: "a/b/c", "..", "a/b"); - t!(s: "/a/b/c", "d", "/a/b/c/d"); - t!(s: "a/b", "c/d", "a/b/c/d"); - t!(s: "a/b", "/c/d", "/c/d"); - t!(s: ".", "a/b", "a/b"); - t!(s: "/", "a/b", "/a/b"); - } - - #[test] - fn test_posix_with_helpers() { - t!(v: PosixPath::new(b!("a/b/c")).with_dirname(b!("d")), b!("d/c")); - t!(v: PosixPath::new(b!("a/b/c")).with_dirname(b!("d/e")), b!("d/e/c")); - t!(v: PosixPath::new(b!("a/", 0x80, "b/c")).with_dirname(b!(0xff)), b!(0xff, "/c")); - t!(v: PosixPath::new(b!("a/b/", 0x80)).with_dirname(b!("/", 0xcd)), - b!("/", 0xcd, "/", 0x80)); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str("d"), "d/c"); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str("d/e"), "d/e/c"); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str(""), "c"); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str("/"), "/c"); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str("."), "c"); - t!(s: PosixPath::from_str("a/b/c").with_dirname_str(".."), "../c"); - t!(s: PosixPath::from_str("/").with_dirname_str("foo"), "foo"); - t!(s: PosixPath::from_str("/").with_dirname_str(""), "."); - t!(s: PosixPath::from_str("/foo").with_dirname_str("bar"), "bar/foo"); - t!(s: PosixPath::from_str("..").with_dirname_str("foo"), "foo"); - t!(s: PosixPath::from_str("../..").with_dirname_str("foo"), "foo"); - t!(s: PosixPath::from_str("foo").with_dirname_str(".."), "../foo"); - t!(s: PosixPath::from_str("foo").with_dirname_str("../.."), "../../foo"); - - t!(v: PosixPath::new(b!("a/b/c")).with_filename(b!("d")), b!("a/b/d")); - t!(v: PosixPath::new(b!("a/b/c", 0xff)).with_filename(b!(0x80)), b!("a/b/", 0x80)); - t!(v: PosixPath::new(b!("/", 0xff, "/foo")).with_filename(b!(0xcd)), - b!("/", 0xff, "/", 0xcd)); - t!(s: PosixPath::from_str("a/b/c").with_filename_str("d"), "a/b/d"); - t!(s: PosixPath::from_str(".").with_filename_str("foo"), "foo"); - t!(s: PosixPath::from_str("/a/b/c").with_filename_str("d"), "/a/b/d"); - t!(s: PosixPath::from_str("/").with_filename_str("foo"), "/foo"); - t!(s: PosixPath::from_str("/a").with_filename_str("foo"), "/foo"); - t!(s: PosixPath::from_str("foo").with_filename_str("bar"), "bar"); - t!(s: PosixPath::from_str("a/b/c").with_filename_str(""), "a/b"); - t!(s: PosixPath::from_str("a/b/c").with_filename_str("."), "a/b"); - t!(s: PosixPath::from_str("a/b/c").with_filename_str(".."), "a"); - t!(s: PosixPath::from_str("/a").with_filename_str(""), "/"); - t!(s: PosixPath::from_str("foo").with_filename_str(""), "."); - t!(s: PosixPath::from_str("a/b/c").with_filename_str("d/e"), "a/b/d/e"); - t!(s: PosixPath::from_str("a/b/c").with_filename_str("/d"), "a/b/d"); - t!(s: PosixPath::from_str("..").with_filename_str("foo"), "../foo"); - t!(s: PosixPath::from_str("../..").with_filename_str("foo"), "../../foo"); - - t!(v: PosixPath::new(b!("hi/there", 0x80, ".txt")).with_filestem(b!(0xff)), - b!("hi/", 0xff, ".txt")); - t!(v: PosixPath::new(b!("hi/there.txt", 0x80)).with_filestem(b!(0xff)), - b!("hi/", 0xff, ".txt", 0x80)); - t!(v: PosixPath::new(b!("hi/there", 0xff)).with_filestem(b!(0x80)), b!("hi/", 0x80)); - t!(v: PosixPath::new(b!("hi", 0x80, "/there")).with_filestem([]), b!("hi", 0x80)); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str("here"), "hi/here.txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str(""), "hi/.txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str("."), "hi/..txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str(".."), "hi/...txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str("/"), "hi/.txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_filestem_str("foo/bar"), "hi/foo/bar.txt"); - t!(s: PosixPath::from_str("hi/there.foo.txt").with_filestem_str("here"), "hi/here.txt"); - t!(s: PosixPath::from_str("hi/there").with_filestem_str("here"), "hi/here"); - t!(s: PosixPath::from_str("hi/there").with_filestem_str(""), "hi"); - t!(s: PosixPath::from_str("hi").with_filestem_str(""), "."); - t!(s: PosixPath::from_str("/hi").with_filestem_str(""), "/"); - t!(s: PosixPath::from_str("hi/there").with_filestem_str(".."), "."); - t!(s: PosixPath::from_str("hi/there").with_filestem_str("."), "hi"); - t!(s: PosixPath::from_str("hi/there.").with_filestem_str("foo"), "hi/foo."); - t!(s: PosixPath::from_str("hi/there.").with_filestem_str(""), "hi"); - t!(s: PosixPath::from_str("hi/there.").with_filestem_str("."), "."); - t!(s: PosixPath::from_str("hi/there.").with_filestem_str(".."), "hi/..."); - t!(s: PosixPath::from_str("/").with_filestem_str("foo"), "/foo"); - t!(s: PosixPath::from_str(".").with_filestem_str("foo"), "foo"); - t!(s: PosixPath::from_str("hi/there..").with_filestem_str("here"), "hi/here."); - t!(s: PosixPath::from_str("hi/there..").with_filestem_str(""), "hi"); - - t!(v: PosixPath::new(b!("hi/there", 0x80, ".txt")).with_extension(b!("exe")), - b!("hi/there", 0x80, ".exe")); - t!(v: PosixPath::new(b!("hi/there.txt", 0x80)).with_extension(b!(0xff)), - b!("hi/there.", 0xff)); - t!(v: PosixPath::new(b!("hi/there", 0x80)).with_extension(b!(0xff)), - b!("hi/there", 0x80, ".", 0xff)); - t!(v: PosixPath::new(b!("hi/there.", 0xff)).with_extension([]), b!("hi/there")); - t!(s: PosixPath::from_str("hi/there.txt").with_extension_str("exe"), "hi/there.exe"); - t!(s: PosixPath::from_str("hi/there.txt").with_extension_str(""), "hi/there"); - t!(s: PosixPath::from_str("hi/there.txt").with_extension_str("."), "hi/there.."); - t!(s: PosixPath::from_str("hi/there.txt").with_extension_str(".."), "hi/there..."); - t!(s: PosixPath::from_str("hi/there").with_extension_str("txt"), "hi/there.txt"); - t!(s: PosixPath::from_str("hi/there").with_extension_str("."), "hi/there.."); - t!(s: PosixPath::from_str("hi/there").with_extension_str(".."), "hi/there..."); - t!(s: PosixPath::from_str("hi/there.").with_extension_str("txt"), "hi/there.txt"); - t!(s: PosixPath::from_str("hi/.foo").with_extension_str("txt"), "hi/.foo.txt"); - t!(s: PosixPath::from_str("hi/there.txt").with_extension_str(".foo"), "hi/there..foo"); - t!(s: PosixPath::from_str("/").with_extension_str("txt"), "/"); - t!(s: PosixPath::from_str("/").with_extension_str("."), "/"); - t!(s: PosixPath::from_str("/").with_extension_str(".."), "/"); - t!(s: PosixPath::from_str(".").with_extension_str("txt"), "."); - } - - #[test] - fn test_posix_setters() { - macro_rules! t( - (s: $path:expr, $set:ident, $with:ident, $arg:expr) => ( - { - let path = $path; - let arg = $arg; - let mut p1 = PosixPath::from_str(path); - p1.$set(arg); - let p2 = PosixPath::from_str(path); - assert_eq!(p1, p2.$with(arg)); - } - ); - (v: $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!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d")); - t!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d/e")); - t!(v: b!("a/", 0x80, "/c"), set_dirname, with_dirname, b!(0xff)); - t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d"); - t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d/e"); - t!(s: "/", set_dirname_str, with_dirname_str, "foo"); - t!(s: "/foo", set_dirname_str, with_dirname_str, "bar"); - t!(s: "a/b/c", set_dirname_str, with_dirname_str, ""); - t!(s: "../..", set_dirname_str, with_dirname_str, "x"); - t!(s: "foo", set_dirname_str, with_dirname_str, "../.."); - - t!(v: b!("a/b/c"), set_filename, with_filename, b!("d")); - t!(v: b!("/"), set_filename, with_filename, b!("foo")); - t!(v: b!(0x80), set_filename, with_filename, b!(0xff)); - t!(s: "a/b/c", set_filename_str, with_filename_str, "d"); - t!(s: "/", set_filename_str, with_filename_str, "foo"); - t!(s: ".", set_filename_str, with_filename_str, "foo"); - t!(s: "a/b", set_filename_str, with_filename_str, ""); - t!(s: "a", set_filename_str, with_filename_str, ""); - - t!(v: b!("hi/there.txt"), set_filestem, with_filestem, b!("here")); - t!(v: b!("hi/there", 0x80, ".txt"), set_filestem, with_filestem, b!("here", 0xff)); - t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, "here"); - t!(s: "hi/there.", set_filestem_str, with_filestem_str, "here"); - t!(s: "hi/there", set_filestem_str, with_filestem_str, "here"); - t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, ""); - t!(s: "hi/there", set_filestem_str, with_filestem_str, ""); - - t!(v: b!("hi/there.txt"), set_extension, with_extension, b!("exe")); - t!(v: b!("hi/there.t", 0x80, "xt"), set_extension, with_extension, b!("exe", 0xff)); - t!(s: "hi/there.txt", set_extension_str, with_extension_str, "exe"); - t!(s: "hi/there.", set_extension_str, with_extension_str, "txt"); - t!(s: "hi/there", set_extension_str, with_extension_str, "txt"); - t!(s: "hi/there.txt", set_extension_str, with_extension_str, ""); - t!(s: "hi/there", set_extension_str, with_extension_str, ""); - t!(s: ".", set_extension_str, with_extension_str, "txt"); - } - - #[test] - fn test_posix_getters() { - macro_rules! t( - (s: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( - { - let path = $path; - assert_eq!(path.filename_str(), $filename); - assert_eq!(path.dirname_str(), $dirname); - assert_eq!(path.filestem_str(), $filestem); - assert_eq!(path.extension_str(), $ext); - } - ); - (v: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( - { - let path = $path; - assert_eq!(path.filename(), $filename); - assert_eq!(path.dirname(), $dirname); - assert_eq!(path.filestem(), $filestem); - assert_eq!(path.extension(), $ext); - } - ) - ) - - t!(v: PosixPath::new(b!("a/b/c")), b!("c"), b!("a/b"), b!("c"), None); - t!(v: PosixPath::new(b!("a/b/", 0xff)), b!(0xff), b!("a/b"), b!(0xff), None); - t!(v: PosixPath::new(b!("hi/there.", 0xff)), b!("there.", 0xff), b!("hi"), - b!("there"), Some(b!(0xff))); - t!(s: PosixPath::from_str("a/b/c"), Some("c"), Some("a/b"), Some("c"), None); - t!(s: PosixPath::from_str("."), Some(""), Some("."), Some(""), None); - t!(s: PosixPath::from_str("/"), Some(""), Some("/"), Some(""), None); - t!(s: PosixPath::from_str(".."), Some(""), Some(".."), Some(""), None); - t!(s: PosixPath::from_str("../.."), Some(""), Some("../.."), Some(""), None); - t!(s: PosixPath::from_str("hi/there.txt"), Some("there.txt"), Some("hi"), - Some("there"), Some("txt")); - t!(s: PosixPath::from_str("hi/there"), Some("there"), Some("hi"), Some("there"), None); - t!(s: PosixPath::from_str("hi/there."), Some("there."), Some("hi"), - Some("there"), Some("")); - t!(s: PosixPath::from_str("hi/.there"), Some(".there"), Some("hi"), Some(".there"), None); - t!(s: PosixPath::from_str("hi/..there"), Some("..there"), Some("hi"), - Some("."), Some("there")); - t!(s: PosixPath::new(b!("a/b/", 0xff)), None, Some("a/b"), None, None); - t!(s: PosixPath::new(b!("a/b/", 0xff, ".txt")), None, Some("a/b"), None, Some("txt")); - t!(s: PosixPath::new(b!("a/b/c.", 0x80)), None, Some("a/b"), Some("c"), None); - t!(s: PosixPath::new(b!(0xff, "/b")), Some("b"), None, Some("b"), None); - } - - #[test] - fn test_posix_dir_file_path() { - t!(v: PosixPath::new(b!("hi/there", 0x80)).dir_path(), b!("hi")); - t!(v: PosixPath::new(b!("hi", 0xff, "/there")).dir_path(), b!("hi", 0xff)); - t!(s: PosixPath::from_str("hi/there").dir_path(), "hi"); - t!(s: PosixPath::from_str("hi").dir_path(), "."); - t!(s: PosixPath::from_str("/hi").dir_path(), "/"); - t!(s: PosixPath::from_str("/").dir_path(), "/"); - t!(s: PosixPath::from_str("..").dir_path(), ".."); - t!(s: PosixPath::from_str("../..").dir_path(), "../.."); - - macro_rules! t( - (s: $path:expr, $exp:expr) => ( - { - let path = $path; - let left = path.chain_ref(|p| p.as_str()); - assert_eq!(left, $exp); - } - ); - (v: $path:expr, $exp:expr) => ( - { - let path = $path; - let left = path.map(|p| p.as_vec()); - assert_eq!(left, $exp); - } - ) - ) - - t!(v: PosixPath::new(b!("hi/there", 0x80)).file_path(), Some(b!("there", 0x80))); - t!(v: PosixPath::new(b!("hi", 0xff, "/there")).file_path(), Some(b!("there"))); - t!(s: PosixPath::from_str("hi/there").file_path(), Some("there")); - t!(s: PosixPath::from_str("hi").file_path(), Some("hi")); - t!(s: PosixPath::from_str(".").file_path(), None); - t!(s: PosixPath::from_str("/").file_path(), None); - t!(s: PosixPath::from_str("..").file_path(), None); - t!(s: PosixPath::from_str("../..").file_path(), None); - } - - #[test] - fn test_posix_is_absolute() { - assert_eq!(PosixPath::from_str("a/b/c").is_absolute(), false); - assert_eq!(PosixPath::from_str("/a/b/c").is_absolute(), true); - assert_eq!(PosixPath::from_str("a").is_absolute(), false); - assert_eq!(PosixPath::from_str("/a").is_absolute(), true); - assert_eq!(PosixPath::from_str(".").is_absolute(), false); - assert_eq!(PosixPath::from_str("/").is_absolute(), true); - assert_eq!(PosixPath::from_str("..").is_absolute(), false); - assert_eq!(PosixPath::from_str("../..").is_absolute(), false); - } - - #[test] - fn test_posix_is_ancestor_of() { - macro_rules! t( - (s: $path:expr, $dest:expr, $exp:expr) => ( - { - let path = PosixPath::from_str($path); - let dest = PosixPath::from_str($dest); - assert_eq!(path.is_ancestor_of(&dest), $exp); - } - ) - ) - - t!(s: "a/b/c", "a/b/c/d", true); - t!(s: "a/b/c", "a/b/c", true); - t!(s: "a/b/c", "a/b", false); - t!(s: "/a/b/c", "/a/b/c", true); - t!(s: "/a/b", "/a/b/c", true); - t!(s: "/a/b/c/d", "/a/b/c", false); - t!(s: "/a/b", "a/b/c", false); - t!(s: "a/b", "/a/b/c", false); - t!(s: "a/b/c", "a/b/d", false); - t!(s: "../a/b/c", "a/b/c", false); - t!(s: "a/b/c", "../a/b/c", false); - t!(s: "a/b/c", "a/b/cd", false); - t!(s: "a/b/cd", "a/b/c", false); - t!(s: "../a/b", "../a/b/c", true); - t!(s: ".", "a/b", true); - t!(s: ".", ".", true); - t!(s: "/", "/", true); - t!(s: "/", "/a/b", true); - t!(s: "..", "a/b", true); - t!(s: "../..", "a/b", true); - } - - #[test] - fn test_posix_path_relative_from() { - macro_rules! t( - (s: $path:expr, $other:expr, $exp:expr) => ( - { - let path = PosixPath::from_str($path); - let other = PosixPath::from_str($other); - let res = path.path_relative_from(&other); - assert_eq!(res.chain_ref(|x| x.as_str()), $exp); - } - ) - ) - - t!(s: "a/b/c", "a/b", Some("c")); - t!(s: "a/b/c", "a/b/d", Some("../c")); - t!(s: "a/b/c", "a/b/c/d", Some("..")); - t!(s: "a/b/c", "a/b/c", Some(".")); - t!(s: "a/b/c", "a/b/c/d/e", Some("../..")); - t!(s: "a/b/c", "a/d/e", Some("../../b/c")); - t!(s: "a/b/c", "d/e/f", Some("../../../a/b/c")); - t!(s: "a/b/c", "/a/b/c", None); - t!(s: "/a/b/c", "a/b/c", Some("/a/b/c")); - t!(s: "/a/b/c", "/a/b/c/d", Some("..")); - t!(s: "/a/b/c", "/a/b", Some("c")); - t!(s: "/a/b/c", "/a/b/c/d/e", Some("../..")); - t!(s: "/a/b/c", "/a/d/e", Some("../../b/c")); - t!(s: "/a/b/c", "/d/e/f", Some("../../../a/b/c")); - t!(s: "hi/there.txt", "hi/there", Some("../there.txt")); - t!(s: ".", "a", Some("..")); - t!(s: ".", "a/b", Some("../..")); - t!(s: ".", ".", Some(".")); - t!(s: "a", ".", Some("a")); - t!(s: "a/b", ".", Some("a/b")); - t!(s: "..", ".", Some("..")); - t!(s: "a/b/c", "a/b/c", Some(".")); - t!(s: "/a/b/c", "/a/b/c", Some(".")); - t!(s: "/", "/", Some(".")); - t!(s: "/", ".", Some("/")); - t!(s: "../../a", "b", Some("../../../a")); - t!(s: "a", "../../b", None); - t!(s: "../../a", "../../b", Some("../a")); - t!(s: "../../a", "../../a/b", Some("..")); - t!(s: "../../a/b", "../../a", Some("b")); - } - - #[test] - fn test_posix_component_iter() { - macro_rules! t( - (s: $path:expr, $exp:expr) => ( - { - let path = PosixPath::from_str($path); - let comps = path.component_iter().to_owned_vec(); - let exp: &[&str] = $exp; - let exps = exp.iter().map(|x| x.as_bytes()).to_owned_vec(); - assert_eq!(comps, exps); - } - ); - (v: [$($arg:expr),+], [$([$($exp:expr),*]),*]) => ( - { - let path = PosixPath::new(b!($($arg),+)); - let comps = path.component_iter().to_owned_vec(); - let exp: &[&[u8]] = [$(b!($($exp),*)),*]; - assert_eq!(comps.as_slice(), exp); - } - ) - ) - - t!(v: ["a/b/c"], [["a"], ["b"], ["c"]]); - t!(v: ["/", 0xff, "/a/", 0x80], [[0xff], ["a"], [0x80]]); - t!(v: ["../../foo", 0xcd, "bar"], [[".."], [".."], ["foo", 0xcd, "bar"]]); - t!(s: "a/b/c", ["a", "b", "c"]); - t!(s: "a/b/d", ["a", "b", "d"]); - t!(s: "a/b/cd", ["a", "b", "cd"]); - t!(s: "/a/b/c", ["a", "b", "c"]); - t!(s: "a", ["a"]); - t!(s: "/a", ["a"]); - t!(s: "/", []); - t!(s: ".", ["."]); - t!(s: "..", [".."]); - t!(s: "../..", ["..", ".."]); - t!(s: "../../foo", ["..", "..", "foo"]); - } -} diff --git a/src/libstd/path2/mod.rs b/src/libstd/path2/mod.rs new file mode 100644 index 00000000000..148af0057f5 --- /dev/null +++ b/src/libstd/path2/mod.rs @@ -0,0 +1,519 @@ +// Copyright 2013 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 (re-write) + +use container::Container; +use c_str::CString; +use clone::Clone; +use iterator::Iterator; +use option::{Option, None, Some}; +use str; +use str::StrSlice; +use vec; +use vec::{CopyableVector, OwnedCopyableVector, OwnedVector}; +use vec::{ImmutableEqVector, ImmutableVector}; + +pub mod posix; +pub mod windows; + +/// Typedef for POSIX file paths. +/// See `posix::Path` for more info. +pub type PosixPath = posix::Path; + +// /// Typedef for Windows file paths. +// /// See `windows::Path` for more info. +// pub type WindowsPath = windows::Path; + +/// Typedef for the platform-native path type +#[cfg(unix)] +pub type Path = PosixPath; +// /// Typedef for the platform-native path type +//#[cfg(windows)] +//pub type Path = WindowsPath; + +/// Typedef for the POSIX path component iterator. +/// See `posix::ComponentIter` for more info. +pub type PosixComponentIter<'self> = posix::ComponentIter<'self>; + +// /// Typedef for the Windows path component iterator. +// /// See `windows::ComponentIter` for more info. +// pub type WindowsComponentIter<'self> = windows::ComponentIter<'self>; + +/// 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>; + +// Condition that is raised when a NUL is found in a byte vector given to a Path function +condition! { + // this should be a &[u8] but there's a lifetime issue + null_byte: ~[u8] -> ~[u8]; +} + +/// A trait that represents the generic operations available on paths +pub trait GenericPath: Clone + GenericPathUnsafe { + /// Creates a new Path from a byte vector. + /// The resulting Path will always be normalized. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the path contains a NUL. + #[inline] + fn from_vec(path: &[u8]) -> Self { + if contains_nul(path) { + let path = self::null_byte::cond.raise(path.to_owned()); + assert!(!contains_nul(path)); + unsafe { GenericPathUnsafe::from_vec_unchecked(path) } + } else { + unsafe { GenericPathUnsafe::from_vec_unchecked(path) } + } + } + + /// Creates a new Path from a string. + /// The resulting Path will always be normalized. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the path contains a NUL. + #[inline] + fn from_str(path: &str) -> Self { + GenericPath::from_vec(path.as_bytes()) + } + + /// Creates a new Path from a CString. + /// The resulting Path will always be normalized. + #[inline] + fn from_c_str(path: CString) -> Self { + // CStrings can't contain NULs + unsafe { GenericPathUnsafe::from_vec_unchecked(path.as_bytes()) } + } + + /// Returns the path as a string, if possible. + /// If the path is not representable in utf-8, this returns None. + #[inline] + fn as_str<'a>(&'a self) -> Option<&'a str> { + str::from_bytes_slice_opt(self.as_vec()) + } + + /// Returns the path as a byte vector + fn as_vec<'a>(&'a self) -> &'a [u8]; + + /// Returns the directory component of `self`, as a byte vector (with no trailing separator). + /// If `self` has no directory component, returns ['.']. + fn dirname<'a>(&'a self) -> &'a [u8]; + /// Returns the directory component of `self`, as a string, if possible. + /// See `dirname` for details. + #[inline] + fn dirname_str<'a>(&'a self) -> Option<&'a str> { + str::from_bytes_slice_opt(self.dirname()) + } + /// Returns the file component of `self`, as a byte vector. + /// If `self` represents the root of the file hierarchy, returns the empty vector. + /// If `self` is ".", returns the empty vector. + fn filename<'a>(&'a self) -> &'a [u8]; + /// Returns the file component of `self`, as a string, if possible. + /// See `filename` for details. + #[inline] + fn filename_str<'a>(&'a self) -> Option<&'a str> { + str::from_bytes_slice_opt(self.filename()) + } + /// Returns the stem of the filename of `self`, as a byte vector. + /// The stem is the portion of the filename just before the last '.'. + /// If there is no '.', the entire filename is returned. + fn filestem<'a>(&'a self) -> &'a [u8] { + let name = self.filename(); + let dot = '.' as u8; + match name.rposition_elem(&dot) { + None | Some(0) => name, + Some(1) if name == bytes!("..") => name, + Some(pos) => name.slice_to(pos) + } + } + /// Returns the stem of the filename of `self`, as a string, if possible. + /// See `filestem` for details. + #[inline] + fn filestem_str<'a>(&'a self) -> Option<&'a str> { + str::from_bytes_slice_opt(self.filestem()) + } + /// Returns the extension of the filename of `self`, as an optional byte vector. + /// The extension is the portion of the filename just after the last '.'. + /// If there is no extension, None is returned. + /// If the filename ends in '.', the empty vector is returned. + fn extension<'a>(&'a self) -> Option<&'a [u8]> { + let name = self.filename(); + let dot = '.' as u8; + match name.rposition_elem(&dot) { + None | Some(0) => None, + Some(1) if name == bytes!("..") => None, + Some(pos) => Some(name.slice_from(pos+1)) + } + } + /// Returns the extension of the filename of `self`, as a string, if possible. + /// See `extension` for details. + #[inline] + fn extension_str<'a>(&'a self) -> Option<&'a str> { + self.extension().chain(|v| str::from_bytes_slice_opt(v)) + } + + /// Replaces the directory portion of the path with the given byte vector. + /// If `self` represents the root of the filesystem hierarchy, the last path component + /// of the given byte vector becomes the filename. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the dirname contains a NUL. + #[inline] + fn set_dirname(&mut self, dirname: &[u8]) { + if contains_nul(dirname) { + let dirname = self::null_byte::cond.raise(dirname.to_owned()); + assert!(!contains_nul(dirname)); + unsafe { self.set_dirname_unchecked(dirname) } + } else { + unsafe { self.set_dirname_unchecked(dirname) } + } + } + /// Replaces the directory portion of the path with the given string. + /// See `set_dirname` for details. + #[inline] + fn set_dirname_str(&mut self, dirname: &str) { + self.set_dirname(dirname.as_bytes()) + } + /// Replaces the filename portion of the path with the given byte vector. + /// If the replacement name is [], this is equivalent to popping the path. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the filename contains a NUL. + #[inline] + fn set_filename(&mut self, filename: &[u8]) { + if contains_nul(filename) { + let filename = self::null_byte::cond.raise(filename.to_owned()); + assert!(!contains_nul(filename)); + unsafe { self.set_filename_unchecked(filename) } + } else { + unsafe { self.set_filename_unchecked(filename) } + } + } + /// Replaces the filename portion of the path with the given string. + /// See `set_filename` for details. + #[inline] + fn set_filename_str(&mut self, filename: &str) { + self.set_filename(filename.as_bytes()) + } + /// Replaces the filestem with the given byte vector. + /// If there is no extension in `self` (or `self` has no filename), this is equivalent + /// to `set_filename`. Otherwise, if the given byte vector is [], the extension (including + /// the preceding '.') becomes the new filename. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the filestem contains a NUL. + fn set_filestem(&mut self, filestem: &[u8]) { + // borrowck is being a pain here + let val = { + let name = self.filename(); + if !name.is_empty() { + let dot = '.' as u8; + match name.rposition_elem(&dot) { + None | Some(0) => None, + Some(idx) => { + let mut v; + if contains_nul(filestem) { + let filestem = self::null_byte::cond.raise(filestem.to_owned()); + assert!(!contains_nul(filestem)); + v = vec::with_capacity(filestem.len() + name.len() - idx); + v.push_all(filestem); + } else { + v = vec::with_capacity(filestem.len() + name.len() - idx); + v.push_all(filestem); + } + v.push_all(name.slice_from(idx)); + Some(v) + } + } + } else { None } + }; + match val { + None => self.set_filename(filestem), + Some(v) => unsafe { self.set_filename_unchecked(v) } + } + } + /// Replaces the filestem with the given string. + /// See `set_filestem` for details. + #[inline] + fn set_filestem_str(&mut self, filestem: &str) { + self.set_filestem(filestem.as_bytes()) + } + /// Replaces the extension with the given byte vector. + /// If there is no extension in `self`, this adds one. + /// If the given byte vector is [], this removes the extension. + /// If `self` has no filename, this is a no-op. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the extension contains a NUL. + fn set_extension(&mut self, extension: &[u8]) { + // borrowck causes problems here too + let val = { + let name = self.filename(); + if !name.is_empty() { + let dot = '.' as u8; + match name.rposition_elem(&dot) { + None | Some(0) => { + if extension.is_empty() { + None + } else { + let mut v; + if contains_nul(extension) { + let extension = self::null_byte::cond.raise(extension.to_owned()); + assert!(!contains_nul(extension)); + v = vec::with_capacity(name.len() + extension.len() + 1); + v.push_all(name); + v.push(dot); + v.push_all(extension); + } else { + v = vec::with_capacity(name.len() + extension.len() + 1); + v.push_all(name); + v.push(dot); + v.push_all(extension); + } + Some(v) + } + } + Some(idx) => { + if extension.is_empty() { + Some(name.slice_to(idx).to_owned()) + } else { + let mut v; + if contains_nul(extension) { + let extension = self::null_byte::cond.raise(extension.to_owned()); + assert!(!contains_nul(extension)); + v = vec::with_capacity(idx + extension.len() + 1); + v.push_all(name.slice_to(idx+1)); + v.push_all(extension); + } else { + v = vec::with_capacity(idx + extension.len() + 1); + v.push_all(name.slice_to(idx+1)); + v.push_all(extension); + } + Some(v) + } + } + } + } else { None } + }; + match val { + None => (), + Some(v) => unsafe { self.set_filename_unchecked(v) } + } + } + /// Replaces the extension with the given string. + /// See `set_extension` for details. + #[inline] + fn set_extension_str(&mut self, extension: &str) { + self.set_extension(extension.as_bytes()) + } + + /// Returns a new Path constructed by replacing the dirname with the given byte vector. + /// See `set_dirname` for details. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the dirname contains a NUL. + #[inline] + fn with_dirname(&self, dirname: &[u8]) -> Self { + let mut p = self.clone(); + p.set_dirname(dirname); + p + } + /// Returns a new Path constructed by replacing the dirname with the given string. + /// See `set_dirname` for details. + #[inline] + fn with_dirname_str(&self, dirname: &str) -> Self { + self.with_dirname(dirname.as_bytes()) + } + /// Returns a new Path constructed by replacing the filename with the given byte vector. + /// See `set_filename` for details. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the filename contains a NUL. + #[inline] + fn with_filename(&self, filename: &[u8]) -> Self { + let mut p = self.clone(); + p.set_filename(filename); + p + } + /// Returns a new Path constructed by replacing the filename with the given string. + /// See `set_filename` for details. + #[inline] + fn with_filename_str(&self, filename: &str) -> Self { + self.with_filename(filename.as_bytes()) + } + /// Returns a new Path constructed by setting the filestem to the given byte vector. + /// See `set_filestem` for details. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the filestem contains a NUL. + #[inline] + fn with_filestem(&self, filestem: &[u8]) -> Self { + let mut p = self.clone(); + p.set_filestem(filestem); + p + } + /// Returns a new Path constructed by setting the filestem to the given string. + /// See `set_filestem` for details. + #[inline] + fn with_filestem_str(&self, filestem: &str) -> Self { + self.with_filestem(filestem.as_bytes()) + } + /// Returns a new Path constructed by setting the extension to the given byte vector. + /// See `set_extension` for details. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the extension contains a NUL. + #[inline] + fn with_extension(&self, extension: &[u8]) -> Self { + let mut p = self.clone(); + p.set_extension(extension); + p + } + /// Returns a new Path constructed by setting the extension to the given string. + /// See `set_extension` for details. + #[inline] + fn with_extension_str(&self, extension: &str) -> Self { + self.with_extension(extension.as_bytes()) + } + + /// Returns the directory component of `self`, as a Path. + /// If `self` represents the root of the filesystem hierarchy, returns `self`. + fn dir_path(&self) -> Self { + GenericPath::from_vec(self.dirname()) + } + /// Returns the file component of `self`, as a relative Path. + /// If `self` represents the root of the filesystem hierarchy, returns None. + fn file_path(&self) -> Option<Self> { + match self.filename() { + [] => None, + v => Some(GenericPath::from_vec(v)) + } + } + + /// Pushes a path (as a byte vector) onto `self`. + /// If the argument represents an absolute path, it replaces `self`. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the path contains a NUL. + #[inline] + fn push(&mut self, path: &[u8]) { + if contains_nul(path) { + let path = self::null_byte::cond.raise(path.to_owned()); + assert!(!contains_nul(path)); + unsafe { self.push_unchecked(path) } + } else { + unsafe { self.push_unchecked(path) } + } + } + /// Pushes a path (as a string) onto `self. + /// See `push` for details. + #[inline] + fn push_str(&mut self, path: &str) { + self.push(path.as_bytes()) + } + /// Pushes a Path onto `self`. + /// If the argument represents an absolute path, it replaces `self`. + #[inline] + fn push_path(&mut self, path: &Self) { + self.push(path.as_vec()) + } + /// Pops the last path component off of `self` and returns it. + /// If `self` represents the root of the file hierarchy, None is returned. + fn pop_opt(&mut self) -> Option<~[u8]>; + /// Pops the last path component off of `self` and returns it as a string, if possible. + /// `self` will still be modified even if None is returned. + /// See `pop_opt` for details. + #[inline] + fn pop_opt_str(&mut self) -> Option<~str> { + self.pop_opt().chain(|v| str::from_bytes_owned_opt(v)) + } + + /// Returns a new Path constructed by joining `self` with the given path (as a byte vector). + /// If the given path is absolute, the new Path will represent just that. + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the path contains a NUL. + #[inline] + fn join(&self, path: &[u8]) -> Self { + let mut p = self.clone(); + p.push(path); + p + } + /// Returns a new Path constructed by joining `self` with the given path (as a string). + /// See `join` for details. + #[inline] + fn join_str(&self, path: &str) -> Self { + self.join(path.as_bytes()) + } + /// Returns a new Path constructed by joining `self` with the given path. + /// If the given path is absolute, the new Path will represent just that. + #[inline] + fn join_path(&self, path: &Self) -> Self { + let mut p = self.clone(); + p.push_path(path); + p + } + + /// Returns whether `self` represents an absolute path. + fn is_absolute(&self) -> bool; + + /// Returns whether `self` is equal to, or is an ancestor of, the given path. + /// If both paths are relative, they are compared as though they are relative + /// to the same parent path. + fn is_ancestor_of(&self, other: &Self) -> bool; + + /// Returns the Path that, were it joined to `base`, would yield `self`. + /// If no such path exists, None is returned. + /// If `self` is absolute and `base` is relative, or on Windows if both + /// paths refer to separate drives, an absolute path is returned. + fn path_relative_from(&self, base: &Self) -> Option<Self>; +} + +/// A trait that represents the unsafe operations on GenericPaths +pub trait GenericPathUnsafe { + /// Creates a new Path from a byte vector without checking for null bytes. + /// The resulting Path will always be normalized. + unsafe fn from_vec_unchecked(path: &[u8]) -> Self; + + /// Replaces the directory portion of the path with the given byte vector without + /// checking for null bytes. + /// See `set_dirname` for details. + unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]); + + /// Replaces the filename portion of the path with the given byte vector without + /// checking for null bytes. + /// See `set_filename` for details. + unsafe fn set_filename_unchecked(&mut self, filename: &[u8]); + + /// Pushes a path onto `self` without checking for null bytes. + /// See `push` for details. + unsafe fn push_unchecked(&mut self, path: &[u8]); +} + +#[inline(always)] +fn contains_nul(v: &[u8]) -> bool { + v.iter().any(|&x| x == 0) +} diff --git a/src/libstd/path2/posix.rs b/src/libstd/path2/posix.rs new file mode 100644 index 00000000000..fa6d1e32ebd --- /dev/null +++ b/src/libstd/path2/posix.rs @@ -0,0 +1,1152 @@ +// Copyright 2013 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. + +//! POSIX file path handling + +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::Str; +use util; +use vec; +use vec::CopyableVector; +use vec::{Vector, VectorVector}; +use super::{GenericPath, GenericPathUnsafe}; + +/// Iterator that yields successive components of a Path +pub type ComponentIter<'self> = vec::SplitIterator<'self, u8>; + +/// Represents a POSIX file path +#[deriving(Clone, DeepClone)] +pub struct Path { + priv repr: ~[u8], // assumed to never be empty or contain NULs + priv sepidx: Option<uint> // index of the final separator in repr +} + +/// The standard path separator character +pub static sep: u8 = '/' as u8; + +/// Returns whether the given byte is a path separator +#[inline] +pub fn is_sep(u: &u8) -> bool { + *u == sep +} + +impl Eq for Path { + fn eq(&self, other: &Path) -> bool { + self.repr == other.repr + } +} + +impl FromStr for Path { + fn from_str(s: &str) -> Option<Path> { + let v = s.as_bytes(); + if contains_nul(v) { + None + } else { + Some(unsafe { GenericPathUnsafe::from_vec_unchecked(v) }) + } + } +} + +impl ToCStr for Path { + #[inline] + fn to_c_str(&self) -> CString { + // The Path impl guarantees no internal NUL + unsafe { self.as_vec().to_c_str_unchecked() } + } + + #[inline] + unsafe fn to_c_str_unchecked(&self) -> CString { + self.as_vec().to_c_str_unchecked() + } +} + +impl GenericPathUnsafe for Path { + unsafe fn from_vec_unchecked(path: &[u8]) -> Path { + let path = Path::normalize(path); + assert!(!path.is_empty()); + let idx = path.rposition_elem(&sep); + Path{ repr: path, sepidx: idx } + } + + unsafe fn set_dirname_unchecked(&mut self, dirname: &[u8]) { + match self.sepidx { + None if bytes!(".") == self.repr || bytes!("..") == self.repr => { + self.repr = Path::normalize(dirname); + } + None => { + let mut v = vec::with_capacity(dirname.len() + self.repr.len() + 1); + v.push_all(dirname); + v.push(sep); + v.push_all(self.repr); + self.repr = Path::normalize(v); + } + Some(0) if self.repr.len() == 1 && self.repr[0] == sep => { + self.repr = Path::normalize(dirname); + } + Some(idx) if dirname.is_empty() => { + let v = Path::normalize(self.repr.slice_from(idx+1)); + self.repr = v; + } + Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { + self.repr = Path::normalize(dirname); + } + Some(idx) => { + let mut v = vec::with_capacity(dirname.len() + self.repr.len() - idx); + v.push_all(dirname); + v.push_all(self.repr.slice_from(idx)); + self.repr = Path::normalize(v); + } + } + self.sepidx = self.repr.rposition_elem(&sep); + } + + unsafe fn set_filename_unchecked(&mut self, filename: &[u8]) { + match self.sepidx { + None if bytes!("..") == self.repr => { + let mut v = vec::with_capacity(3 + filename.len()); + v.push_all(dot_dot_static); + v.push(sep); + v.push_all(filename); + self.repr = Path::normalize(v); + } + None => { + self.repr = Path::normalize(filename); + } + Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => { + let mut v = vec::with_capacity(self.repr.len() + 1 + filename.len()); + v.push_all(self.repr); + v.push(sep); + v.push_all(filename); + self.repr = Path::normalize(v); + } + Some(idx) => { + let mut v = vec::with_capacity(self.repr.len() - idx + filename.len()); + v.push_all(self.repr.slice_to(idx+1)); + v.push_all(filename); + self.repr = Path::normalize(v); + } + } + self.sepidx = self.repr.rposition_elem(&sep); + } + + unsafe fn push_unchecked(&mut self, path: &[u8]) { + if !path.is_empty() { + if path[0] == sep { + self.repr = Path::normalize(path); + } else { + let mut v = vec::with_capacity(self.repr.len() + path.len() + 1); + v.push_all(self.repr); + v.push(sep); + v.push_all(path); + self.repr = Path::normalize(v); + } + self.sepidx = self.repr.rposition_elem(&sep); + } + } +} + +impl GenericPath for Path { + #[inline] + fn as_vec<'a>(&'a self) -> &'a [u8] { + self.repr.as_slice() + } + + fn dirname<'a>(&'a self) -> &'a [u8] { + match self.sepidx { + None if bytes!("..") == self.repr => self.repr.as_slice(), + None => dot_static, + Some(0) => self.repr.slice_to(1), + Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => self.repr.as_slice(), + Some(idx) => self.repr.slice_to(idx) + } + } + + fn filename<'a>(&'a self) -> &'a [u8] { + match self.sepidx { + None if bytes!(".") == self.repr || bytes!("..") == self.repr => &[], + None => self.repr.as_slice(), + Some(idx) if self.repr.slice_from(idx+1) == bytes!("..") => &[], + Some(idx) => self.repr.slice_from(idx+1) + } + } + + fn pop_opt(&mut self) -> Option<~[u8]> { + match self.sepidx { + None if bytes!(".") == self.repr => None, + None => { + let mut v = ~['.' as u8]; + util::swap(&mut v, &mut self.repr); + self.sepidx = None; + Some(v) + } + Some(0) if bytes!("/") == self.repr => None, + Some(idx) => { + let v = 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.rposition_elem(&sep); + Some(v) + } + } + } + + #[inline] + fn is_absolute(&self) -> bool { + self.repr[0] == sep + } + + fn is_ancestor_of(&self, other: &Path) -> bool { + if self.is_absolute() != other.is_absolute() { + false + } else { + let mut ita = self.component_iter(); + let mut itb = other.component_iter(); + if bytes!(".") == self.repr { + return itb.next() != Some(bytes!("..")); + } + loop { + match (ita.next(), itb.next()) { + (None, _) => break, + (Some(a), Some(b)) if a == b => { loop }, + (Some(a), _) if a == bytes!("..") => { + // if ita contains only .. components, it's an ancestor + return ita.all(|x| x == bytes!("..")); + } + _ => return false + } + } + true + } + } + + fn path_relative_from(&self, base: &Path) -> Option<Path> { + 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(dot_dot_static), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(b)) if b == bytes!(".") => comps.push(a), + (Some(_), Some(b)) if b == bytes!("..") => return None, + (Some(a), Some(_)) => { + comps.push(dot_dot_static); + for _ in itb { + comps.push(dot_dot_static); + } + comps.push(a); + comps.extend(&mut ita); + break; + } + } + } + Some(Path::new(comps.connect_vec(&sep))) + } + } +} + +impl Path { + /// Returns a new Path from a byte vector + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the vector contains a NUL. + #[inline] + pub fn new(v: &[u8]) -> Path { + GenericPath::from_vec(v) + } + + /// Returns a new Path from a string + /// + /// # Failure + /// + /// Raises the `null_byte` condition if the str contains a NUL. + #[inline] + pub fn from_str(s: &str) -> Path { + GenericPath::from_str(s) + } + + /// Converts the Path into an owned byte vector + pub fn into_vec(self) -> ~[u8] { + self.repr + } + + /// Converts the Path into an owned string, if possible + pub fn into_str(self) -> Option<~str> { + str::from_bytes_owned_opt(self.repr) + } + + /// Returns a normalized byte vector representation of a path, by removing all empty + /// components, and unnecessary . and .. components. + pub fn normalize<V: Vector<u8>+CopyableVector<u8>>(v: V) -> ~[u8] { + // borrowck is being very picky + let val = { + let is_abs = !v.as_slice().is_empty() && v.as_slice()[0] == sep; + let v_ = if is_abs { v.as_slice().slice_from(1) } else { v.as_slice() }; + let comps = normalize_helper(v_, is_abs, is_sep); + match comps { + None => None, + Some(comps) => { + if is_abs && comps.is_empty() { + Some(~[sep]) + } else { + let n = if is_abs { comps.len() } else { comps.len() - 1} + + comps.iter().map(|v| v.len()).sum(); + let mut v = vec::with_capacity(n); + let mut it = comps.move_iter(); + if !is_abs { + match it.next() { + None => (), + Some(comp) => v.push_all(comp) + } + } + for comp in it { + v.push(sep); + v.push_all(comp); + } + Some(v) + } + } + } + }; + match val { + None => v.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) -> ComponentIter<'a> { + let v = if self.repr[0] == sep { + self.repr.slice_from(1) + } else { self.repr.as_slice() }; + let mut ret = v.split_iter(is_sep); + if v.is_empty() { + // consume the empty "" component + ret.next(); + } + ret + } +} + +// None result means the byte vector didn't need normalizing +// FIXME (#8169): Pull this into parent module once visibility works +fn normalize_helper<'a>(v: &'a [u8], is_abs: bool, f: &'a fn(&u8) -> bool) -> Option<~[&'a [u8]]> { + if is_abs && v.as_slice().is_empty() { + return None; + } + let mut comps: ~[&'a [u8]] = ~[]; + let mut n_up = 0u; + let mut changed = false; + for comp in v.split_iter(f) { + if comp.is_empty() { changed = true } + else if comp == bytes!(".") { changed = true } + else if comp == bytes!("..") { + if is_abs && comps.is_empty() { changed = true } + else if comps.len() == n_up { comps.push(dot_dot_static); n_up += 1 } + else { comps.pop_opt(); changed = true } + } else { comps.push(comp) } + } + if changed { + if comps.is_empty() && !is_abs { + if v == bytes!(".") { + return None; + } + comps.push(dot_static); + } + Some(comps) + } else { + None + } +} + +// FIXME (#8169): Pull this into parent module once visibility works +#[inline(always)] +fn contains_nul(v: &[u8]) -> bool { + v.iter().any(|&x| x == 0) +} + +static dot_static: &'static [u8] = &'static ['.' as u8]; +static dot_dot_static: &'static [u8] = &'static ['.' as u8, '.' as u8]; + +#[cfg(test)] +mod tests { + use super::*; + use option::{Some, None}; + use iterator::Iterator; + use str; + use vec::Vector; + + macro_rules! t( + (s: $path:expr, $exp:expr) => ( + { + let path = $path; + assert_eq!(path.as_str(), Some($exp)); + } + ); + (v: $path:expr, $exp:expr) => ( + { + let path = $path; + assert_eq!(path.as_vec(), $exp); + } + ) + ) + + macro_rules! b( + ($($arg:expr),+) => ( + bytes!($($arg),+) + ) + ) + + #[test] + fn test_paths() { + t!(v: Path::new([]), b!(".")); + t!(v: Path::new(b!("/")), b!("/")); + t!(v: Path::new(b!("a/b/c")), b!("a/b/c")); + t!(v: Path::new(b!("a/b/c", 0xff)), b!("a/b/c", 0xff)); + t!(v: Path::new(b!(0xff, "/../foo", 0x80)), b!("foo", 0x80)); + let p = Path::new(b!("a/b/c", 0xff)); + assert_eq!(p.as_str(), None); + + t!(s: Path::from_str(""), "."); + t!(s: Path::from_str("/"), "/"); + t!(s: Path::from_str("hi"), "hi"); + t!(s: Path::from_str("/lib"), "/lib"); + t!(s: Path::from_str("hi/there"), "hi/there"); + t!(s: Path::from_str("hi/there.txt"), "hi/there.txt"); + + t!(s: Path::from_str("hi/there/"), "hi/there"); + t!(s: Path::from_str("hi/../there"), "there"); + t!(s: Path::from_str("../hi/there"), "../hi/there"); + t!(s: Path::from_str("/../hi/there"), "/hi/there"); + t!(s: Path::from_str("foo/.."), "."); + t!(s: Path::from_str("/foo/.."), "/"); + t!(s: Path::from_str("/foo/../.."), "/"); + t!(s: Path::from_str("/foo/../../bar"), "/bar"); + t!(s: Path::from_str("/./hi/./there/."), "/hi/there"); + t!(s: Path::from_str("/./hi/./there/./.."), "/hi"); + t!(s: Path::from_str("foo/../.."), ".."); + t!(s: Path::from_str("foo/../../.."), "../.."); + t!(s: Path::from_str("foo/../../bar"), "../bar"); + + assert_eq!(Path::new(b!("foo/bar")).into_vec(), b!("foo/bar").to_owned()); + assert_eq!(Path::new(b!("/foo/../../bar")).into_vec(), + b!("/bar").to_owned()); + assert_eq!(Path::from_str("foo/bar").into_str(), Some(~"foo/bar")); + assert_eq!(Path::from_str("/foo/../../bar").into_str(), Some(~"/bar")); + + let p = Path::new(b!("foo/bar", 0x80)); + assert_eq!(p.as_str(), None); + assert_eq!(Path::new(b!("foo", 0xff, "/bar")).into_str(), None); + } + + #[test] + fn test_null_byte() { + use path2::null_byte::cond; + + let mut handled = false; + let mut p = do cond.trap(|v| { + handled = true; + assert_eq!(v.as_slice(), b!("foo/bar", 0)); + (b!("/bar").to_owned()) + }).inside { + Path::new(b!("foo/bar", 0)) + }; + assert!(handled); + assert_eq!(p.as_vec(), b!("/bar")); + + handled = false; + do cond.trap(|v| { + handled = true; + assert_eq!(v.as_slice(), b!("f", 0, "o")); + (b!("foo").to_owned()) + }).inside { + p.set_filename(b!("f", 0, "o")) + }; + assert!(handled); + assert_eq!(p.as_vec(), b!("/foo")); + + handled = false; + do cond.trap(|v| { + handled = true; + assert_eq!(v.as_slice(), b!("null/", 0, "/byte")); + (b!("null/byte").to_owned()) + }).inside { + p.set_dirname(b!("null/", 0, "/byte")); + }; + assert!(handled); + assert_eq!(p.as_vec(), b!("null/byte/foo")); + + handled = false; + do cond.trap(|v| { + handled = true; + assert_eq!(v.as_slice(), b!("f", 0, "o")); + (b!("foo").to_owned()) + }).inside { + p.push(b!("f", 0, "o")); + }; + assert!(handled); + assert_eq!(p.as_vec(), b!("null/byte/foo/foo")); + } + + #[test] + fn test_null_byte_fail() { + use path2::null_byte::cond; + use task; + + macro_rules! t( + ($name:expr => $code:block) => ( + { + let mut t = task::task(); + t.supervised(); + t.name($name); + let res = do t.try $code; + assert!(res.is_err()); + } + ) + ) + + t!(~"new() w/nul" => { + do cond.trap(|_| { + (b!("null", 0).to_owned()) + }).inside { + Path::new(b!("foo/bar", 0)) + }; + }) + + t!(~"set_filename w/nul" => { + let mut p = Path::new(b!("foo/bar")); + do cond.trap(|_| { + (b!("null", 0).to_owned()) + }).inside { + p.set_filename(b!("foo", 0)) + }; + }) + + t!(~"set_dirname w/nul" => { + let mut p = Path::new(b!("foo/bar")); + do cond.trap(|_| { + (b!("null", 0).to_owned()) + }).inside { + p.set_dirname(b!("foo", 0)) + }; + }) + + t!(~"push w/nul" => { + let mut p = Path::new(b!("foo/bar")); + do cond.trap(|_| { + (b!("null", 0).to_owned()) + }).inside { + p.push(b!("foo", 0)) + }; + }) + } + + #[test] + fn test_components() { + macro_rules! t( + (s: $path:expr, $op:ident, $exp:expr) => ( + { + let path = Path::from_str($path); + assert_eq!(path.$op(), ($exp).as_bytes()); + } + ); + (s: $path:expr, $op:ident, $exp:expr, opt) => ( + { + let path = Path::from_str($path); + let left = path.$op().map(|&x| str::from_bytes_slice(x)); + assert_eq!(left, $exp); + } + ); + (v: $path:expr, $op:ident, $exp:expr) => ( + { + let path = Path::new($path); + assert_eq!(path.$op(), $exp); + } + ) + ) + + t!(v: b!("a/b/c"), filename, b!("c")); + t!(v: b!("a/b/c", 0xff), filename, b!("c", 0xff)); + t!(v: b!("a/b", 0xff, "/c"), filename, b!("c")); + t!(s: "a/b/c", filename, "c"); + t!(s: "/a/b/c", filename, "c"); + t!(s: "a", filename, "a"); + t!(s: "/a", filename, "a"); + t!(s: ".", filename, ""); + t!(s: "/", filename, ""); + t!(s: "..", filename, ""); + t!(s: "../..", filename, ""); + + t!(v: b!("a/b/c"), dirname, b!("a/b")); + t!(v: b!("a/b/c", 0xff), dirname, b!("a/b")); + t!(v: b!("a/b", 0xff, "/c"), dirname, b!("a/b", 0xff)); + t!(s: "a/b/c", dirname, "a/b"); + t!(s: "/a/b/c", dirname, "/a/b"); + t!(s: "a", dirname, "."); + t!(s: "/a", dirname, "/"); + t!(s: ".", dirname, "."); + t!(s: "/", dirname, "/"); + t!(s: "..", dirname, ".."); + t!(s: "../..", dirname, "../.."); + + t!(v: b!("hi/there.txt"), filestem, b!("there")); + t!(v: b!("hi/there", 0x80, ".txt"), filestem, b!("there", 0x80)); + t!(v: b!("hi/there.t", 0x80, "xt"), filestem, b!("there")); + t!(s: "hi/there.txt", filestem, "there"); + t!(s: "hi/there", filestem, "there"); + t!(s: "there.txt", filestem, "there"); + t!(s: "there", filestem, "there"); + t!(s: ".", filestem, ""); + t!(s: "/", filestem, ""); + t!(s: "foo/.bar", filestem, ".bar"); + t!(s: ".bar", filestem, ".bar"); + t!(s: "..bar", filestem, "."); + t!(s: "hi/there..txt", filestem, "there."); + t!(s: "..", filestem, ""); + t!(s: "../..", filestem, ""); + + t!(v: b!("hi/there.txt"), extension, Some(b!("txt"))); + t!(v: b!("hi/there", 0x80, ".txt"), extension, Some(b!("txt"))); + t!(v: b!("hi/there.t", 0x80, "xt"), extension, Some(b!("t", 0x80, "xt"))); + t!(v: b!("hi/there"), extension, None); + t!(v: b!("hi/there", 0x80), extension, None); + t!(s: "hi/there.txt", extension, Some("txt"), opt); + t!(s: "hi/there", extension, None, opt); + t!(s: "there.txt", extension, Some("txt"), opt); + t!(s: "there", extension, None, opt); + t!(s: ".", extension, None, opt); + t!(s: "/", extension, None, opt); + t!(s: "foo/.bar", extension, None, opt); + t!(s: ".bar", extension, None, opt); + t!(s: "..bar", extension, Some("bar"), opt); + t!(s: "hi/there..txt", extension, Some("txt"), opt); + t!(s: "..", extension, None, opt); + t!(s: "../..", extension, None, opt); + } + + #[test] + fn test_push() { + macro_rules! t( + (s: $path:expr, $join:expr) => ( + { + let path = ($path); + let join = ($join); + let mut p1 = Path::from_str(path); + let p2 = p1.clone(); + p1.push_str(join); + assert_eq!(p1, p2.join_str(join)); + } + ) + ) + + t!(s: "a/b/c", ".."); + t!(s: "/a/b/c", "d"); + t!(s: "a/b", "c/d"); + t!(s: "a/b", "/c/d"); + } + + #[test] + fn test_push_path() { + macro_rules! t( + (s: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::from_str($path); + let push = Path::from_str($push); + p.push_path(&push); + assert_eq!(p.as_str(), Some($exp)); + } + ) + ) + + t!(s: "a/b/c", "d", "a/b/c/d"); + t!(s: "/a/b/c", "d", "/a/b/c/d"); + t!(s: "a/b", "c/d", "a/b/c/d"); + t!(s: "a/b", "/c/d", "/c/d"); + t!(s: "a/b", ".", "a/b"); + t!(s: "a/b", "../c", "a/c"); + } + + #[test] + fn test_pop() { + macro_rules! t( + (s: $path:expr, $left:expr, $right:expr) => ( + { + let mut p = Path::from_str($path); + let file = p.pop_opt_str(); + assert_eq!(p.as_str(), Some($left)); + assert_eq!(file.map(|s| s.as_slice()), $right); + } + ); + (v: [$($path:expr),+], [$($left:expr),+], Some($($right:expr),+)) => ( + { + let mut p = Path::new(b!($($path),+)); + let file = p.pop_opt(); + assert_eq!(p.as_vec(), b!($($left),+)); + assert_eq!(file.map(|v| v.as_slice()), Some(b!($($right),+))); + } + ); + (v: [$($path:expr),+], [$($left:expr),+], None) => ( + { + let mut p = Path::new(b!($($path),+)); + let file = p.pop_opt(); + assert_eq!(p.as_vec(), b!($($left),+)); + assert_eq!(file, None); + } + ) + ) + + t!(v: ["a/b/c"], ["a/b"], Some("c")); + t!(v: ["a"], ["."], Some("a")); + t!(v: ["."], ["."], None); + t!(v: ["/a"], ["/"], Some("a")); + t!(v: ["/"], ["/"], None); + t!(v: ["a/b/c", 0x80], ["a/b"], Some("c", 0x80)); + t!(v: ["a/b", 0x80, "/c"], ["a/b", 0x80], Some("c")); + t!(v: [0xff], ["."], Some(0xff)); + t!(v: ["/", 0xff], ["/"], Some(0xff)); + t!(s: "a/b/c", "a/b", Some("c")); + t!(s: "a", ".", Some("a")); + t!(s: ".", ".", None); + t!(s: "/a", "/", Some("a")); + t!(s: "/", "/", None); + + assert_eq!(Path::new(b!("foo/bar", 0x80)).pop_opt_str(), None); + assert_eq!(Path::new(b!("foo", 0x80, "/bar")).pop_opt_str(), Some(~"bar")); + } + + #[test] + fn test_join() { + t!(v: Path::new(b!("a/b/c")).join(b!("..")), b!("a/b")); + t!(v: Path::new(b!("/a/b/c")).join(b!("d")), b!("/a/b/c/d")); + t!(v: Path::new(b!("a/", 0x80, "/c")).join(b!(0xff)), b!("a/", 0x80, "/c/", 0xff)); + t!(s: Path::from_str("a/b/c").join_str(".."), "a/b"); + t!(s: Path::from_str("/a/b/c").join_str("d"), "/a/b/c/d"); + t!(s: Path::from_str("a/b").join_str("c/d"), "a/b/c/d"); + t!(s: Path::from_str("a/b").join_str("/c/d"), "/c/d"); + t!(s: Path::from_str(".").join_str("a/b"), "a/b"); + t!(s: Path::from_str("/").join_str("a/b"), "/a/b"); + } + + #[test] + fn test_join_path() { + macro_rules! t( + (s: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::from_str($path); + let join = Path::from_str($join); + let res = path.join_path(&join); + assert_eq!(res.as_str(), Some($exp)); + } + ) + ) + + t!(s: "a/b/c", "..", "a/b"); + t!(s: "/a/b/c", "d", "/a/b/c/d"); + t!(s: "a/b", "c/d", "a/b/c/d"); + t!(s: "a/b", "/c/d", "/c/d"); + t!(s: ".", "a/b", "a/b"); + t!(s: "/", "a/b", "/a/b"); + } + + #[test] + fn test_with_helpers() { + t!(v: Path::new(b!("a/b/c")).with_dirname(b!("d")), b!("d/c")); + t!(v: Path::new(b!("a/b/c")).with_dirname(b!("d/e")), b!("d/e/c")); + t!(v: Path::new(b!("a/", 0x80, "b/c")).with_dirname(b!(0xff)), b!(0xff, "/c")); + t!(v: Path::new(b!("a/b/", 0x80)).with_dirname(b!("/", 0xcd)), + b!("/", 0xcd, "/", 0x80)); + t!(s: Path::from_str("a/b/c").with_dirname_str("d"), "d/c"); + t!(s: Path::from_str("a/b/c").with_dirname_str("d/e"), "d/e/c"); + t!(s: Path::from_str("a/b/c").with_dirname_str(""), "c"); + t!(s: Path::from_str("a/b/c").with_dirname_str("/"), "/c"); + t!(s: Path::from_str("a/b/c").with_dirname_str("."), "c"); + t!(s: Path::from_str("a/b/c").with_dirname_str(".."), "../c"); + t!(s: Path::from_str("/").with_dirname_str("foo"), "foo"); + t!(s: Path::from_str("/").with_dirname_str(""), "."); + t!(s: Path::from_str("/foo").with_dirname_str("bar"), "bar/foo"); + t!(s: Path::from_str("..").with_dirname_str("foo"), "foo"); + t!(s: Path::from_str("../..").with_dirname_str("foo"), "foo"); + t!(s: Path::from_str("foo").with_dirname_str(".."), "../foo"); + t!(s: Path::from_str("foo").with_dirname_str("../.."), "../../foo"); + + t!(v: Path::new(b!("a/b/c")).with_filename(b!("d")), b!("a/b/d")); + t!(v: Path::new(b!("a/b/c", 0xff)).with_filename(b!(0x80)), b!("a/b/", 0x80)); + t!(v: Path::new(b!("/", 0xff, "/foo")).with_filename(b!(0xcd)), + b!("/", 0xff, "/", 0xcd)); + t!(s: Path::from_str("a/b/c").with_filename_str("d"), "a/b/d"); + t!(s: Path::from_str(".").with_filename_str("foo"), "foo"); + t!(s: Path::from_str("/a/b/c").with_filename_str("d"), "/a/b/d"); + t!(s: Path::from_str("/").with_filename_str("foo"), "/foo"); + t!(s: Path::from_str("/a").with_filename_str("foo"), "/foo"); + t!(s: Path::from_str("foo").with_filename_str("bar"), "bar"); + t!(s: Path::from_str("a/b/c").with_filename_str(""), "a/b"); + t!(s: Path::from_str("a/b/c").with_filename_str("."), "a/b"); + t!(s: Path::from_str("a/b/c").with_filename_str(".."), "a"); + t!(s: Path::from_str("/a").with_filename_str(""), "/"); + t!(s: Path::from_str("foo").with_filename_str(""), "."); + t!(s: Path::from_str("a/b/c").with_filename_str("d/e"), "a/b/d/e"); + t!(s: Path::from_str("a/b/c").with_filename_str("/d"), "a/b/d"); + t!(s: Path::from_str("..").with_filename_str("foo"), "../foo"); + t!(s: Path::from_str("../..").with_filename_str("foo"), "../../foo"); + + t!(v: Path::new(b!("hi/there", 0x80, ".txt")).with_filestem(b!(0xff)), + b!("hi/", 0xff, ".txt")); + t!(v: Path::new(b!("hi/there.txt", 0x80)).with_filestem(b!(0xff)), + b!("hi/", 0xff, ".txt", 0x80)); + t!(v: Path::new(b!("hi/there", 0xff)).with_filestem(b!(0x80)), b!("hi/", 0x80)); + t!(v: Path::new(b!("hi", 0x80, "/there")).with_filestem([]), b!("hi", 0x80)); + t!(s: Path::from_str("hi/there.txt").with_filestem_str("here"), "hi/here.txt"); + t!(s: Path::from_str("hi/there.txt").with_filestem_str(""), "hi/.txt"); + t!(s: Path::from_str("hi/there.txt").with_filestem_str("."), "hi/..txt"); + t!(s: Path::from_str("hi/there.txt").with_filestem_str(".."), "hi/...txt"); + t!(s: Path::from_str("hi/there.txt").with_filestem_str("/"), "hi/.txt"); + t!(s: Path::from_str("hi/there.txt").with_filestem_str("foo/bar"), "hi/foo/bar.txt"); + t!(s: Path::from_str("hi/there.foo.txt").with_filestem_str("here"), "hi/here.txt"); + t!(s: Path::from_str("hi/there").with_filestem_str("here"), "hi/here"); + t!(s: Path::from_str("hi/there").with_filestem_str(""), "hi"); + t!(s: Path::from_str("hi").with_filestem_str(""), "."); + t!(s: Path::from_str("/hi").with_filestem_str(""), "/"); + t!(s: Path::from_str("hi/there").with_filestem_str(".."), "."); + t!(s: Path::from_str("hi/there").with_filestem_str("."), "hi"); + t!(s: Path::from_str("hi/there.").with_filestem_str("foo"), "hi/foo."); + t!(s: Path::from_str("hi/there.").with_filestem_str(""), "hi"); + t!(s: Path::from_str("hi/there.").with_filestem_str("."), "."); + t!(s: Path::from_str("hi/there.").with_filestem_str(".."), "hi/..."); + t!(s: Path::from_str("/").with_filestem_str("foo"), "/foo"); + t!(s: Path::from_str(".").with_filestem_str("foo"), "foo"); + t!(s: Path::from_str("hi/there..").with_filestem_str("here"), "hi/here."); + t!(s: Path::from_str("hi/there..").with_filestem_str(""), "hi"); + + t!(v: Path::new(b!("hi/there", 0x80, ".txt")).with_extension(b!("exe")), + b!("hi/there", 0x80, ".exe")); + t!(v: Path::new(b!("hi/there.txt", 0x80)).with_extension(b!(0xff)), + b!("hi/there.", 0xff)); + t!(v: Path::new(b!("hi/there", 0x80)).with_extension(b!(0xff)), + b!("hi/there", 0x80, ".", 0xff)); + t!(v: Path::new(b!("hi/there.", 0xff)).with_extension([]), b!("hi/there")); + t!(s: Path::from_str("hi/there.txt").with_extension_str("exe"), "hi/there.exe"); + t!(s: Path::from_str("hi/there.txt").with_extension_str(""), "hi/there"); + t!(s: Path::from_str("hi/there.txt").with_extension_str("."), "hi/there.."); + t!(s: Path::from_str("hi/there.txt").with_extension_str(".."), "hi/there..."); + t!(s: Path::from_str("hi/there").with_extension_str("txt"), "hi/there.txt"); + t!(s: Path::from_str("hi/there").with_extension_str("."), "hi/there.."); + t!(s: Path::from_str("hi/there").with_extension_str(".."), "hi/there..."); + t!(s: Path::from_str("hi/there.").with_extension_str("txt"), "hi/there.txt"); + t!(s: Path::from_str("hi/.foo").with_extension_str("txt"), "hi/.foo.txt"); + t!(s: Path::from_str("hi/there.txt").with_extension_str(".foo"), "hi/there..foo"); + t!(s: Path::from_str("/").with_extension_str("txt"), "/"); + t!(s: Path::from_str("/").with_extension_str("."), "/"); + t!(s: Path::from_str("/").with_extension_str(".."), "/"); + t!(s: Path::from_str(".").with_extension_str("txt"), "."); + } + + #[test] + fn test_setters() { + macro_rules! t( + (s: $path:expr, $set:ident, $with:ident, $arg:expr) => ( + { + let path = $path; + let arg = $arg; + let mut p1 = Path::from_str(path); + p1.$set(arg); + let p2 = Path::from_str(path); + assert_eq!(p1, p2.$with(arg)); + } + ); + (v: $path:expr, $set:ident, $with:ident, $arg:expr) => ( + { + let path = $path; + let arg = $arg; + let mut p1 = Path::new(path); + p1.$set(arg); + let p2 = Path::new(path); + assert_eq!(p1, p2.$with(arg)); + } + ) + ) + + t!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d")); + t!(v: b!("a/b/c"), set_dirname, with_dirname, b!("d/e")); + t!(v: b!("a/", 0x80, "/c"), set_dirname, with_dirname, b!(0xff)); + t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d"); + t!(s: "a/b/c", set_dirname_str, with_dirname_str, "d/e"); + t!(s: "/", set_dirname_str, with_dirname_str, "foo"); + t!(s: "/foo", set_dirname_str, with_dirname_str, "bar"); + t!(s: "a/b/c", set_dirname_str, with_dirname_str, ""); + t!(s: "../..", set_dirname_str, with_dirname_str, "x"); + t!(s: "foo", set_dirname_str, with_dirname_str, "../.."); + + t!(v: b!("a/b/c"), set_filename, with_filename, b!("d")); + t!(v: b!("/"), set_filename, with_filename, b!("foo")); + t!(v: b!(0x80), set_filename, with_filename, b!(0xff)); + t!(s: "a/b/c", set_filename_str, with_filename_str, "d"); + t!(s: "/", set_filename_str, with_filename_str, "foo"); + t!(s: ".", set_filename_str, with_filename_str, "foo"); + t!(s: "a/b", set_filename_str, with_filename_str, ""); + t!(s: "a", set_filename_str, with_filename_str, ""); + + t!(v: b!("hi/there.txt"), set_filestem, with_filestem, b!("here")); + t!(v: b!("hi/there", 0x80, ".txt"), set_filestem, with_filestem, b!("here", 0xff)); + t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, "here"); + t!(s: "hi/there.", set_filestem_str, with_filestem_str, "here"); + t!(s: "hi/there", set_filestem_str, with_filestem_str, "here"); + t!(s: "hi/there.txt", set_filestem_str, with_filestem_str, ""); + t!(s: "hi/there", set_filestem_str, with_filestem_str, ""); + + t!(v: b!("hi/there.txt"), set_extension, with_extension, b!("exe")); + t!(v: b!("hi/there.t", 0x80, "xt"), set_extension, with_extension, b!("exe", 0xff)); + t!(s: "hi/there.txt", set_extension_str, with_extension_str, "exe"); + t!(s: "hi/there.", set_extension_str, with_extension_str, "txt"); + t!(s: "hi/there", set_extension_str, with_extension_str, "txt"); + t!(s: "hi/there.txt", set_extension_str, with_extension_str, ""); + t!(s: "hi/there", set_extension_str, with_extension_str, ""); + t!(s: ".", set_extension_str, with_extension_str, "txt"); + } + + #[test] + fn test_getters() { + macro_rules! t( + (s: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( + { + let path = $path; + assert_eq!(path.filename_str(), $filename); + assert_eq!(path.dirname_str(), $dirname); + assert_eq!(path.filestem_str(), $filestem); + assert_eq!(path.extension_str(), $ext); + } + ); + (v: $path:expr, $filename:expr, $dirname:expr, $filestem:expr, $ext:expr) => ( + { + let path = $path; + assert_eq!(path.filename(), $filename); + assert_eq!(path.dirname(), $dirname); + assert_eq!(path.filestem(), $filestem); + assert_eq!(path.extension(), $ext); + } + ) + ) + + t!(v: Path::new(b!("a/b/c")), b!("c"), b!("a/b"), b!("c"), None); + t!(v: Path::new(b!("a/b/", 0xff)), b!(0xff), b!("a/b"), b!(0xff), None); + t!(v: Path::new(b!("hi/there.", 0xff)), b!("there.", 0xff), b!("hi"), + b!("there"), Some(b!(0xff))); + t!(s: Path::from_str("a/b/c"), Some("c"), Some("a/b"), Some("c"), None); + t!(s: Path::from_str("."), Some(""), Some("."), Some(""), None); + t!(s: Path::from_str("/"), Some(""), Some("/"), Some(""), None); + t!(s: Path::from_str(".."), Some(""), Some(".."), Some(""), None); + t!(s: Path::from_str("../.."), Some(""), Some("../.."), Some(""), None); + t!(s: Path::from_str("hi/there.txt"), Some("there.txt"), Some("hi"), + Some("there"), Some("txt")); + t!(s: Path::from_str("hi/there"), Some("there"), Some("hi"), Some("there"), None); + t!(s: Path::from_str("hi/there."), Some("there."), Some("hi"), + Some("there"), Some("")); + t!(s: Path::from_str("hi/.there"), Some(".there"), Some("hi"), Some(".there"), None); + t!(s: Path::from_str("hi/..there"), Some("..there"), Some("hi"), + Some("."), Some("there")); + t!(s: Path::new(b!("a/b/", 0xff)), None, Some("a/b"), None, None); + t!(s: Path::new(b!("a/b/", 0xff, ".txt")), None, Some("a/b"), None, Some("txt")); + t!(s: Path::new(b!("a/b/c.", 0x80)), None, Some("a/b"), Some("c"), None); + t!(s: Path::new(b!(0xff, "/b")), Some("b"), None, Some("b"), None); + } + + #[test] + fn test_dir_file_path() { + t!(v: Path::new(b!("hi/there", 0x80)).dir_path(), b!("hi")); + t!(v: Path::new(b!("hi", 0xff, "/there")).dir_path(), b!("hi", 0xff)); + t!(s: Path::from_str("hi/there").dir_path(), "hi"); + t!(s: Path::from_str("hi").dir_path(), "."); + t!(s: Path::from_str("/hi").dir_path(), "/"); + t!(s: Path::from_str("/").dir_path(), "/"); + t!(s: Path::from_str("..").dir_path(), ".."); + t!(s: Path::from_str("../..").dir_path(), "../.."); + + macro_rules! t( + (s: $path:expr, $exp:expr) => ( + { + let path = $path; + let left = path.chain_ref(|p| p.as_str()); + assert_eq!(left, $exp); + } + ); + (v: $path:expr, $exp:expr) => ( + { + let path = $path; + let left = path.map(|p| p.as_vec()); + assert_eq!(left, $exp); + } + ) + ) + + t!(v: Path::new(b!("hi/there", 0x80)).file_path(), Some(b!("there", 0x80))); + t!(v: Path::new(b!("hi", 0xff, "/there")).file_path(), Some(b!("there"))); + t!(s: Path::from_str("hi/there").file_path(), Some("there")); + t!(s: Path::from_str("hi").file_path(), Some("hi")); + t!(s: Path::from_str(".").file_path(), None); + t!(s: Path::from_str("/").file_path(), None); + t!(s: Path::from_str("..").file_path(), None); + t!(s: Path::from_str("../..").file_path(), None); + } + + #[test] + fn test_is_absolute() { + assert_eq!(Path::from_str("a/b/c").is_absolute(), false); + assert_eq!(Path::from_str("/a/b/c").is_absolute(), true); + assert_eq!(Path::from_str("a").is_absolute(), false); + assert_eq!(Path::from_str("/a").is_absolute(), true); + assert_eq!(Path::from_str(".").is_absolute(), false); + assert_eq!(Path::from_str("/").is_absolute(), true); + assert_eq!(Path::from_str("..").is_absolute(), false); + assert_eq!(Path::from_str("../..").is_absolute(), false); + } + + #[test] + fn test_is_ancestor_of() { + macro_rules! t( + (s: $path:expr, $dest:expr, $exp:expr) => ( + { + let path = Path::from_str($path); + let dest = Path::from_str($dest); + assert_eq!(path.is_ancestor_of(&dest), $exp); + } + ) + ) + + t!(s: "a/b/c", "a/b/c/d", true); + t!(s: "a/b/c", "a/b/c", true); + t!(s: "a/b/c", "a/b", false); + t!(s: "/a/b/c", "/a/b/c", true); + t!(s: "/a/b", "/a/b/c", true); + t!(s: "/a/b/c/d", "/a/b/c", false); + t!(s: "/a/b", "a/b/c", false); + t!(s: "a/b", "/a/b/c", false); + t!(s: "a/b/c", "a/b/d", false); + t!(s: "../a/b/c", "a/b/c", false); + t!(s: "a/b/c", "../a/b/c", false); + t!(s: "a/b/c", "a/b/cd", false); + t!(s: "a/b/cd", "a/b/c", false); + t!(s: "../a/b", "../a/b/c", true); + t!(s: ".", "a/b", true); + t!(s: ".", ".", true); + t!(s: "/", "/", true); + t!(s: "/", "/a/b", true); + t!(s: "..", "a/b", true); + t!(s: "../..", "a/b", true); + } + + #[test] + fn test_path_relative_from() { + macro_rules! t( + (s: $path:expr, $other:expr, $exp:expr) => ( + { + let path = Path::from_str($path); + let other = Path::from_str($other); + let res = path.path_relative_from(&other); + assert_eq!(res.chain_ref(|x| x.as_str()), $exp); + } + ) + ) + + t!(s: "a/b/c", "a/b", Some("c")); + t!(s: "a/b/c", "a/b/d", Some("../c")); + t!(s: "a/b/c", "a/b/c/d", Some("..")); + t!(s: "a/b/c", "a/b/c", Some(".")); + t!(s: "a/b/c", "a/b/c/d/e", Some("../..")); + t!(s: "a/b/c", "a/d/e", Some("../../b/c")); + t!(s: "a/b/c", "d/e/f", Some("../../../a/b/c")); + t!(s: "a/b/c", "/a/b/c", None); + t!(s: "/a/b/c", "a/b/c", Some("/a/b/c")); + t!(s: "/a/b/c", "/a/b/c/d", Some("..")); + t!(s: "/a/b/c", "/a/b", Some("c")); + t!(s: "/a/b/c", "/a/b/c/d/e", Some("../..")); + t!(s: "/a/b/c", "/a/d/e", Some("../../b/c")); + t!(s: "/a/b/c", "/d/e/f", Some("../../../a/b/c")); + t!(s: "hi/there.txt", "hi/there", Some("../there.txt")); + t!(s: ".", "a", Some("..")); + t!(s: ".", "a/b", Some("../..")); + t!(s: ".", ".", Some(".")); + t!(s: "a", ".", Some("a")); + t!(s: "a/b", ".", Some("a/b")); + t!(s: "..", ".", Some("..")); + t!(s: "a/b/c", "a/b/c", Some(".")); + t!(s: "/a/b/c", "/a/b/c", Some(".")); + t!(s: "/", "/", Some(".")); + t!(s: "/", ".", Some("/")); + t!(s: "../../a", "b", Some("../../../a")); + t!(s: "a", "../../b", None); + t!(s: "../../a", "../../b", Some("../a")); + t!(s: "../../a", "../../a/b", Some("..")); + t!(s: "../../a/b", "../../a", Some("b")); + } + + #[test] + fn test_component_iter() { + macro_rules! t( + (s: $path:expr, $exp:expr) => ( + { + let path = Path::from_str($path); + let comps = path.component_iter().to_owned_vec(); + let exp: &[&str] = $exp; + let exps = exp.iter().map(|x| x.as_bytes()).to_owned_vec(); + assert_eq!(comps, exps); + } + ); + (v: [$($arg:expr),+], [$([$($exp:expr),*]),*]) => ( + { + let path = Path::new(b!($($arg),+)); + let comps = path.component_iter().to_owned_vec(); + let exp: &[&[u8]] = [$(b!($($exp),*)),*]; + assert_eq!(comps.as_slice(), exp); + } + ) + ) + + t!(v: ["a/b/c"], [["a"], ["b"], ["c"]]); + t!(v: ["/", 0xff, "/a/", 0x80], [[0xff], ["a"], [0x80]]); + t!(v: ["../../foo", 0xcd, "bar"], [[".."], [".."], ["foo", 0xcd, "bar"]]); + t!(s: "a/b/c", ["a", "b", "c"]); + t!(s: "a/b/d", ["a", "b", "d"]); + t!(s: "a/b/cd", ["a", "b", "cd"]); + t!(s: "/a/b/c", ["a", "b", "c"]); + t!(s: "a", ["a"]); + t!(s: "/a", ["a"]); + t!(s: "/", []); + t!(s: ".", ["."]); + t!(s: "..", [".."]); + t!(s: "../..", ["..", ".."]); + t!(s: "../../foo", ["..", "..", "foo"]); + } +} diff --git a/src/libstd/path2/windows.rs b/src/libstd/path2/windows.rs new file mode 100644 index 00000000000..1c3eb5c291d --- /dev/null +++ b/src/libstd/path2/windows.rs @@ -0,0 +1,22 @@ +// Copyright 2013 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. + +//! Windows file path handling + +/// The standard path separator character +pub static sep: u8 = '\\' as u8; +/// The alternative path separator character +pub static sep2: u8 = '/' as u8; + +/// Returns whether the given byte is a path separator +#[inline] +pub fn is_sep(u: &u8) -> bool { + *u == sep || *u == sep2 +} |
