From 3449751ff7fa21aedd1352cdbe9f7d77e2bc0d91 Mon Sep 17 00:00:00 2001 From: Valerii Hiora Date: Tue, 3 Feb 2015 15:30:10 +0200 Subject: iOS: fixed build --- src/libstd/sys/unix/c.rs | 1 + src/libstd/sys/unix/os.rs | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) (limited to 'src/libstd') diff --git a/src/libstd/sys/unix/c.rs b/src/libstd/sys/unix/c.rs index 89bd9a23406..50a8e6b73e3 100644 --- a/src/libstd/sys/unix/c.rs +++ b/src/libstd/sys/unix/c.rs @@ -143,6 +143,7 @@ extern { pub fn sigdelset(set: *mut sigset_t, signum: libc::c_int) -> libc::c_int; pub fn sigemptyset(set: *mut sigset_t) -> libc::c_int; + #[cfg(not(target_os = "ios"))] pub fn getpwuid_r(uid: libc::uid_t, pwd: *mut passwd, buf: *mut libc::c_char, diff --git a/src/libstd/sys/unix/os.rs b/src/libstd/sys/unix/os.rs index b3f37962945..5004ff713c4 100644 --- a/src/libstd/sys/unix/os.rs +++ b/src/libstd/sys/unix/os.rs @@ -307,23 +307,23 @@ pub fn args() -> Args { let mut res = Vec::new(); unsafe { - let processInfoSel = sel_registerName("processInfo\0".as_ptr()); - let argumentsSel = sel_registerName("arguments\0".as_ptr()); - let utf8Sel = sel_registerName("UTF8String\0".as_ptr()); - let countSel = sel_registerName("count\0".as_ptr()); - let objectAtSel = sel_registerName("objectAtIndex:\0".as_ptr()); + let process_info_sel = sel_registerName("processInfo\0".as_ptr()); + let arguments_sel = sel_registerName("arguments\0".as_ptr()); + let utf8_sel = sel_registerName("UTF8String\0".as_ptr()); + let count_sel = sel_registerName("count\0".as_ptr()); + let object_at_sel = sel_registerName("objectAtIndex:\0".as_ptr()); let klass = objc_getClass("NSProcessInfo\0".as_ptr()); - let info = objc_msgSend(klass, processInfoSel); - let args = objc_msgSend(info, argumentsSel); + let info = objc_msgSend(klass, process_info_sel); + let args = objc_msgSend(info, arguments_sel); - let cnt: int = mem::transmute(objc_msgSend(args, countSel)); + let cnt: int = mem::transmute(objc_msgSend(args, count_sel)); for i in range(0, cnt) { - let tmp = objc_msgSend(args, objectAtSel, i); + let tmp = objc_msgSend(args, object_at_sel, i); let utf_c_str: *const libc::c_char = - mem::transmute(objc_msgSend(tmp, utf8Sel)); - let bytes = ffi::c_str_to_bytes(&utf_c_str).to_vec(); - res.push(OsString::from_vec(bytes)) + mem::transmute(objc_msgSend(tmp, utf8_sel)); + let bytes = ffi::c_str_to_bytes(&utf_c_str); + res.push(OsString::from_str(str::from_utf8(bytes).unwrap())) } } @@ -455,9 +455,11 @@ pub fn home_dir() -> Option { Path::new(os.into_vec()) }); - #[cfg(target_os = "android")] + #[cfg(any(target_os = "android", + target_os = "ios"))] unsafe fn fallback() -> Option { None } - #[cfg(not(target_os = "android"))] + #[cfg(not(any(target_os = "android", + target_os = "ios")))] unsafe fn fallback() -> Option { let mut amt = match libc::sysconf(c::_SC_GETPW_R_SIZE_MAX) { n if n < 0 => 512 as usize, -- cgit 1.4.1-3-g733a5 From 5cf9905e257ddeeadaf493a705a230081a6c7da3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 31 Jan 2015 20:24:36 -0800 Subject: std: Add `io` module again This commit is an implementation of [RFC 576][rfc] which adds back the `std::io` module to the standard library. No functionality in `std::old_io` has been deprecated just yet, and the new `std::io` module is behind the same `io` feature gate. [rfc]: https://github.com/rust-lang/rfcs/pull/576 A good bit of functionality was copied over from `std::old_io`, but many tweaks were required for the new method signatures. Behavior such as precisely when buffered objects call to the underlying object may have been tweaked slightly in the transition. All implementations were audited to use composition wherever possible. For example the custom `pos` and `cap` cursors in `BufReader` were removed in favor of just using `Cursor>`. A few liberties were taken during this implementation which were not explicitly spelled out in the RFC: * The old `LineBufferedWriter` is now named `LineWriter` * The internal representation of `Error` now favors OS error codes (a 0-allocation path) and contains a `Box` for extra semantic data. * The io prelude currently reexports `Seek` as `NewSeek` to prevent conflicts with the real prelude reexport of `old_io::Seek` * The `chars` method was moved from `BufReadExt` to `ReadExt`. * The `chars` iterator returns a custom error with a variant that explains that the data was not valid UTF-8. --- src/libstd/io/buffered.rs | 676 ++++++++++++++++++++++++++++++ src/libstd/io/cursor.rs | 408 ++++++++++++++++++ src/libstd/io/error.rs | 183 ++++++++ src/libstd/io/impls.rs | 88 ++++ src/libstd/io/mod.rs | 948 ++++++++++++++++++++++++++++++++++++++++++ src/libstd/io/prelude.rs | 27 ++ src/libstd/io/util.rs | 153 +++++++ src/libstd/lib.rs | 6 +- src/libstd/sys/unix/mod.rs | 32 +- src/libstd/sys/windows/mod.rs | 29 ++ 10 files changed, 2546 insertions(+), 4 deletions(-) create mode 100644 src/libstd/io/buffered.rs create mode 100644 src/libstd/io/cursor.rs create mode 100644 src/libstd/io/error.rs create mode 100644 src/libstd/io/impls.rs create mode 100644 src/libstd/io/mod.rs create mode 100644 src/libstd/io/prelude.rs create mode 100644 src/libstd/io/util.rs (limited to 'src/libstd') diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs new file mode 100644 index 00000000000..2fd6631ecc4 --- /dev/null +++ b/src/libstd/io/buffered.rs @@ -0,0 +1,676 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// ignore-lexer-test FIXME #15883 + +//! Buffering wrappers for I/O traits + +use prelude::v1::*; +use io::prelude::*; + +use cmp; +use error::Error as StdError; +use error::FromError; +use fmt; +use io::{self, Cursor, DEFAULT_BUF_SIZE, Error, ErrorKind}; +use ptr; + +/// Wraps a `Read` and buffers input from it +/// +/// It can be excessively inefficient to work directly with a `Read` instance. +/// For example, every call to `read` on `TcpStream` results in a system call. +/// A `BufReader` performs large, infrequent reads on the underlying `Read` +/// and maintains an in-memory buffer of the results. +pub struct BufReader { + inner: R, + buf: Cursor>, +} + +impl BufReader { + /// Creates a new `BufReader` with a default buffer capacity + pub fn new(inner: R) -> BufReader { + BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) + } + + /// Creates a new `BufReader` with the specified buffer capacity + pub fn with_capacity(cap: usize, inner: R) -> BufReader { + BufReader { + inner: inner, + buf: Cursor::new(Vec::with_capacity(cap)), + } + } + + /// Gets a reference to the underlying reader. + pub fn get_ref<'a>(&self) -> &R { &self.inner } + + /// Gets a mutable reference to the underlying reader. + /// + /// # Warning + /// + /// It is inadvisable to directly read from the underlying reader. + pub fn get_mut(&mut self) -> &mut R { &mut self.inner } + + /// Unwraps this `BufReader`, returning the underlying reader. + /// + /// Note that any leftover data in the internal buffer is lost. + pub fn into_inner(self) -> R { self.inner } +} + +impl Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // If we don't have any buffered data and we're doing a massive read + // (larger than our internal buffer), bypass our internal buffer + // entirely. + if self.buf.get_ref().len() == self.buf.position() as usize && + buf.len() >= self.buf.get_ref().capacity() { + return self.inner.read(buf); + } + try!(self.fill_buf()); + self.buf.read(buf) + } +} + +impl BufRead for BufReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + // If we've reached the end of our internal buffer then we need to fetch + // some more data from the underlying reader. + if self.buf.position() as usize == self.buf.get_ref().len() { + self.buf.set_position(0); + let v = self.buf.get_mut(); + v.truncate(0); + let inner = &mut self.inner; + try!(super::with_end_to_cap(v, |b| inner.read(b))); + } + self.buf.fill_buf() + } + + fn consume(&mut self, amt: uint) { + self.buf.consume(amt) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for BufReader where R: fmt::Debug { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "BufReader {{ reader: {:?}, buffer: {}/{} }}", + self.inner, self.buf.position(), self.buf.get_ref().len()) + } +} + +/// Wraps a Writer and buffers output to it +/// +/// It can be excessively inefficient to work directly with a `Write`. For +/// example, every call to `write` on `TcpStream` results in a system call. A +/// `BufWriter` keeps an in memory buffer of data and writes it to the +/// underlying `Write` in large, infrequent batches. +/// +/// This writer will be flushed when it is dropped. +pub struct BufWriter { + inner: Option, + buf: Vec, +} + +/// An error returned by `into_inner` which indicates whether a flush error +/// happened or not. +#[derive(Debug)] +pub struct IntoInnerError(W, Error); + +impl BufWriter { + /// Creates a new `BufWriter` with a default buffer capacity + pub fn new(inner: W) -> BufWriter { + BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) + } + + /// Creates a new `BufWriter` with the specified buffer capacity + pub fn with_capacity(cap: usize, inner: W) -> BufWriter { + BufWriter { + inner: Some(inner), + buf: Vec::with_capacity(cap), + } + } + + fn flush_buf(&mut self) -> io::Result<()> { + let mut written = 0; + let len = self.buf.len(); + let mut ret = Ok(()); + while written < len { + match self.inner.as_mut().unwrap().write(&self.buf[written..]) { + Ok(0) => { + ret = Err(Error::new(ErrorKind::WriteZero, + "failed to flush", None)); + break; + } + Ok(n) => written += n, + Err(e) => { ret = Err(e); break } + + } + } + if written > 0 { + // NB: would be better expressed as .remove(0..n) if it existed + unsafe { + ptr::copy_memory(self.buf.as_mut_ptr(), + self.buf.as_ptr().offset(written as isize), + len - written); + } + } + self.buf.truncate(len - written); + ret + } + + /// Gets a reference to the underlying writer. + pub fn get_ref(&self) -> &W { self.inner.as_ref().unwrap() } + + /// Gets a mutable reference to the underlying write. + /// + /// # Warning + /// + /// It is inadvisable to directly read from the underlying writer. + pub fn get_mut(&mut self) -> &mut W { self.inner.as_mut().unwrap() } + + /// Unwraps this `BufWriter`, returning the underlying writer. + /// + /// The buffer is flushed before returning the writer. + pub fn into_inner(mut self) -> Result>> { + match self.flush_buf() { + Err(e) => Err(IntoInnerError(self, e)), + Ok(()) => Ok(self.inner.take().unwrap()) + } + } +} + +impl Write for BufWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + if self.buf.len() + buf.len() > self.buf.capacity() { + try!(self.flush_buf()); + } + if buf.len() >= self.buf.capacity() { + self.inner.as_mut().unwrap().write(buf) + } else { + let amt = cmp::min(buf.len(), self.buf.capacity()); + Write::write(&mut self.buf, &buf[..amt]) + } + } + fn flush(&mut self) -> io::Result<()> { + self.flush_buf().and_then(|()| self.get_mut().flush()) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for BufWriter where W: fmt::Debug { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "BufWriter {{ writer: {:?}, buffer: {}/{} }}", + self.inner.as_ref().unwrap(), self.buf.len(), self.buf.capacity()) + } +} + +#[unsafe_destructor] +impl Drop for BufWriter { + fn drop(&mut self) { + if self.inner.is_some() { + // dtors should not panic, so we ignore a failed flush + let _r = self.flush_buf(); + } + } +} + +impl IntoInnerError { + /// Returns the error which caused the call to `into_inner` to fail. + /// + /// This error was returned when attempting to flush the internal buffer. + pub fn error(&self) -> &Error { &self.1 } + + /// Returns the underlying `BufWriter` instance which generated the error. + /// + /// The returned object can be used to retry a flush or re-inspect the + /// buffer. + pub fn into_inner(self) -> W { self.0 } +} + +impl FromError> for Error { + fn from_error(iie: IntoInnerError) -> Error { iie.1 } +} + +impl StdError for IntoInnerError { + fn description(&self) -> &str { self.error().description() } +} + +impl fmt::Display for IntoInnerError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.error().fmt(f) + } +} + +/// Wraps a Writer and buffers output to it, flushing whenever a newline +/// (`0x0a`, `'\n'`) is detected. +/// +/// This writer will be flushed when it is dropped. +pub struct LineWriter { + inner: BufWriter, +} + +impl LineWriter { + /// Creates a new `LineWriter` + pub fn new(inner: W) -> LineWriter { + // Lines typically aren't that long, don't use a giant buffer + LineWriter { inner: BufWriter::with_capacity(1024, inner) } + } + + /// Gets a reference to the underlying writer. + /// + /// This type does not expose the ability to get a mutable reference to the + /// underlying reader because that could possibly corrupt the buffer. + pub fn get_ref<'a>(&'a self) -> &'a W { self.inner.get_ref() } + + /// Unwraps this `LineWriter`, returning the underlying writer. + /// + /// The internal buffer is flushed before returning the writer. + pub fn into_inner(self) -> Result>> { + self.inner.into_inner().map_err(|IntoInnerError(buf, e)| { + IntoInnerError(LineWriter { inner: buf }, e) + }) + } +} + +impl Write for LineWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + match buf.rposition_elem(&b'\n') { + Some(i) => { + let n = try!(self.inner.write(&buf[..i + 1])); + if n != i + 1 { return Ok(n) } + try!(self.inner.flush()); + self.inner.write(&buf[i + 1..]).map(|i| n + i) + } + None => self.inner.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { self.inner.flush() } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for LineWriter where W: fmt::Debug { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "LineWriter {{ writer: {:?}, buffer: {}/{} }}", + self.inner.inner, self.inner.buf.len(), + self.inner.buf.capacity()) + } +} + +struct InternalBufWriter(BufWriter); + +impl InternalBufWriter { + fn get_mut(&mut self) -> &mut BufWriter { + let InternalBufWriter(ref mut w) = *self; + return w; + } +} + +impl Read for InternalBufWriter { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.get_mut().inner.as_mut().unwrap().read(buf) + } +} + +/// Wraps a Stream and buffers input and output to and from it. +/// +/// It can be excessively inefficient to work directly with a `Stream`. For +/// example, every call to `read` or `write` on `TcpStream` results in a system +/// call. A `BufStream` keeps in memory buffers of data, making large, +/// infrequent calls to `read` and `write` on the underlying `Stream`. +/// +/// The output half will be flushed when this stream is dropped. +pub struct BufStream { + inner: BufReader> +} + +impl BufStream { + /// Creates a new buffered stream with explicitly listed capacities for the + /// reader/writer buffer. + pub fn with_capacities(reader_cap: usize, writer_cap: usize, inner: S) + -> BufStream { + let writer = BufWriter::with_capacity(writer_cap, inner); + let internal_writer = InternalBufWriter(writer); + let reader = BufReader::with_capacity(reader_cap, internal_writer); + BufStream { inner: reader } + } + + /// Creates a new buffered stream with the default reader/writer buffer + /// capacities. + pub fn new(inner: S) -> BufStream { + BufStream::with_capacities(DEFAULT_BUF_SIZE, DEFAULT_BUF_SIZE, inner) + } + + /// Gets a reference to the underlying stream. + pub fn get_ref(&self) -> &S { + let InternalBufWriter(ref w) = self.inner.inner; + w.get_ref() + } + + /// Gets a mutable reference to the underlying stream. + /// + /// # Warning + /// + /// It is inadvisable to read directly from or write directly to the + /// underlying stream. + pub fn get_mut(&mut self) -> &mut S { + let InternalBufWriter(ref mut w) = self.inner.inner; + w.get_mut() + } + + /// Unwraps this `BufStream`, returning the underlying stream. + /// + /// The internal buffer is flushed before returning the stream. Any leftover + /// data in the read buffer is lost. + pub fn into_inner(self) -> Result>> { + let BufReader { inner: InternalBufWriter(w), buf } = self.inner; + w.into_inner().map_err(|IntoInnerError(w, e)| { + IntoInnerError(BufStream { + inner: BufReader { inner: InternalBufWriter(w), buf: buf }, + }, e) + }) + } +} + +impl BufRead for BufStream { + fn fill_buf(&mut self) -> io::Result<&[u8]> { self.inner.fill_buf() } + fn consume(&mut self, amt: uint) { self.inner.consume(amt) } +} + +impl Read for BufStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl Write for BufStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.inner.get_mut().write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.inner.get_mut().flush() + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for BufStream where S: fmt::Debug { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let reader = &self.inner; + let writer = &self.inner.inner.0; + write!(fmt, "BufStream {{ stream: {:?}, write_buffer: {}/{}, read_buffer: {}/{} }}", + writer.inner, + writer.buf.len(), writer.buf.capacity(), + reader.buf.position(), reader.buf.get_ref().len()) + } +} + +#[cfg(test)] +mod tests { + use prelude::v1::*; + use io::prelude::*; + use io::{self, BufReader, BufWriter, BufStream, Cursor, LineWriter}; + use test; + + /// A dummy reader intended at testing short-reads propagation. + pub struct ShortReader { + lengths: Vec, + } + + impl Read for ShortReader { + fn read(&mut self, _: &mut [u8]) -> io::Result { + if self.lengths.is_empty() { + Ok(0) + } else { + Ok(self.lengths.remove(0)) + } + } + } + + #[test] + fn test_buffered_reader() { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let mut reader = BufReader::with_capacity(2, inner); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(Ok(3), nread); + let b: &[_] = &[5, 6, 7]; + assert_eq!(buf, b); + + let mut buf = [0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(Ok(2), nread); + let b: &[_] = &[0, 1]; + assert_eq!(buf, b); + + let mut buf = [0]; + let nread = reader.read(&mut buf); + assert_eq!(Ok(1), nread); + let b: &[_] = &[2]; + assert_eq!(buf, b); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf); + assert_eq!(Ok(1), nread); + let b: &[_] = &[3, 0, 0]; + assert_eq!(buf, b); + + let nread = reader.read(&mut buf); + assert_eq!(Ok(1), nread); + let b: &[_] = &[4, 0, 0]; + assert_eq!(buf, b); + + assert_eq!(reader.read(&mut buf), Ok(0)); + } + + #[test] + fn test_buffered_writer() { + let inner = Vec::new(); + let mut writer = BufWriter::with_capacity(2, inner); + + writer.write(&[0, 1]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[2]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[3]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[4]).unwrap(); + writer.write(&[5]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[6]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5]); + + writer.write(&[7, 8]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); + + writer.write(&[9, 10, 11]).unwrap(); + let a: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + } + + #[test] + fn test_buffered_writer_inner_flushes() { + let mut w = BufWriter::with_capacity(3, Vec::new()); + w.write(&[0, 1]).unwrap(); + assert_eq!(*w.get_ref(), []); + let w = w.into_inner().unwrap(); + assert_eq!(w, [0, 1]); + } + + // This is just here to make sure that we don't infinite loop in the + // newtype struct autoderef weirdness + #[test] + fn test_buffered_stream() { + struct S; + + impl Write for S { + fn write(&mut self, b: &[u8]) -> io::Result { Ok(b.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } + } + + impl Read for S { + fn read(&mut self, _: &mut [u8]) -> io::Result { Ok(0) } + } + + let mut stream = BufStream::new(S); + assert_eq!(stream.read(&mut [0; 10]), Ok(0)); + stream.write(&[0; 10]).unwrap(); + stream.flush().unwrap(); + } + + #[test] + fn test_read_until() { + let inner: &[u8] = &[0, 1, 2, 1, 0]; + let mut reader = BufReader::with_capacity(2, inner); + let mut v = Vec::new(); + reader.read_until(0, &mut v).unwrap(); + assert_eq!(v, [0]); + v.truncate(0); + reader.read_until(2, &mut v).unwrap(); + assert_eq!(v, [1, 2]); + v.truncate(0); + reader.read_until(1, &mut v).unwrap(); + assert_eq!(v, [1]); + v.truncate(0); + reader.read_until(8, &mut v).unwrap(); + assert_eq!(v, [0]); + v.truncate(0); + reader.read_until(9, &mut v).unwrap(); + assert_eq!(v, []); + } + + #[test] + fn test_line_buffer() { + let mut writer = LineWriter::new(Vec::new()); + writer.write(&[0]).unwrap(); + assert_eq!(*writer.get_ref(), []); + writer.write(&[1]).unwrap(); + assert_eq!(*writer.get_ref(), []); + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + writer.write(&[0, b'\n', 1, b'\n', 2]).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n']); + writer.flush().unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2]); + writer.write(&[3, b'\n']).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2, 3, b'\n']); + } + + #[test] + fn test_read_line() { + let in_buf = b"a\nb\nc"; + let mut reader = BufReader::with_capacity(2, in_buf); + let mut s = String::new(); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "a\n"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "b\n"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, "c"); + s.truncate(0); + reader.read_line(&mut s).unwrap(); + assert_eq!(s, ""); + } + + #[test] + fn test_lines() { + let in_buf = b"a\nb\nc"; + let mut reader = BufReader::with_capacity(2, in_buf); + let mut it = reader.lines(); + assert_eq!(it.next(), Some(Ok("a".to_string()))); + assert_eq!(it.next(), Some(Ok("b".to_string()))); + assert_eq!(it.next(), Some(Ok("c".to_string()))); + assert_eq!(it.next(), None); + } + + #[test] + fn test_short_reads() { + let inner = ShortReader{lengths: vec![0, 1, 2, 0, 1, 0]}; + let mut reader = BufReader::new(inner); + let mut buf = [0, 0]; + assert_eq!(reader.read(&mut buf), Ok(0)); + assert_eq!(reader.read(&mut buf), Ok(1)); + assert_eq!(reader.read(&mut buf), Ok(2)); + assert_eq!(reader.read(&mut buf), Ok(0)); + assert_eq!(reader.read(&mut buf), Ok(1)); + assert_eq!(reader.read(&mut buf), Ok(0)); + assert_eq!(reader.read(&mut buf), Ok(0)); + } + + #[test] + fn read_char_buffered() { + let buf = [195u8, 159u8]; + let mut reader = BufReader::with_capacity(1, &buf[]); + assert_eq!(reader.chars().next(), Some(Ok('ß'))); + } + + #[test] + fn test_chars() { + let buf = [195u8, 159u8, b'a']; + let mut reader = BufReader::with_capacity(1, &buf[]); + let mut it = reader.chars(); + assert_eq!(it.next(), Some(Ok('ß'))); + assert_eq!(it.next(), Some(Ok('a'))); + assert_eq!(it.next(), None); + } + + #[test] + #[should_fail] + fn dont_panic_in_drop_on_panicked_flush() { + struct FailFlushWriter; + + impl Write for FailFlushWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { + Err(io::Error::last_os_error()) + } + } + + let writer = FailFlushWriter; + let _writer = BufWriter::new(writer); + + // If writer panics *again* due to the flush error then the process will + // abort. + panic!(); + } + + #[bench] + fn bench_buffered_reader(b: &mut test::Bencher) { + b.iter(|| { + BufReader::new(io::empty()) + }); + } + + #[bench] + fn bench_buffered_writer(b: &mut test::Bencher) { + b.iter(|| { + BufWriter::new(io::sink()) + }); + } + + #[bench] + fn bench_buffered_stream(b: &mut test::Bencher) { + let mut buf = Cursor::new(Vec::new()); + b.iter(|| { + BufStream::new(&mut buf); + }); + } +} diff --git a/src/libstd/io/cursor.rs b/src/libstd/io/cursor.rs new file mode 100644 index 00000000000..9f3655de20f --- /dev/null +++ b/src/libstd/io/cursor.rs @@ -0,0 +1,408 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(missing_copy_implementations)] + +use prelude::v1::*; +use io::prelude::*; + +use cmp; +use io::{self, SeekFrom, Error, ErrorKind}; +use iter::repeat; +use num::Int; +use slice; + +/// A `Cursor` is a type which wraps another I/O object to provide a `Seek` +/// implementation. +/// +/// Cursors are currently typically used with memory buffer objects in order to +/// allow `Seek` plus `Read` and `Write` implementations. For example, common +/// cursor types include: +/// +/// * `Cursor>` +/// * `Cursor<&[u8]>` +/// +/// Implementations of the I/O traits for `Cursor` are not currently generic +/// over `T` itself. Instead, specific implementations are provided for various +/// in-memory buffer types like `Vec` and `&[u8]`. +pub struct Cursor { + inner: T, + pos: u64, +} + +impl Cursor { + /// Create a new cursor wrapping the provided underlying I/O object. + pub fn new(inner: T) -> Cursor { + Cursor { pos: 0, inner: inner } + } + + /// Consume this cursor, returning the underlying value. + pub fn into_inner(self) -> T { self.inner } + + /// Get a reference to the underlying value in this cursor. + pub fn get_ref(&self) -> &T { &self.inner } + + /// Get a mutable reference to the underlying value in this cursor. + /// + /// Care should be taken to avoid modifying the internal I/O state of the + /// underlying value as it may corrupt this cursor's position. + pub fn get_mut(&mut self) -> &mut T { &mut self.inner } + + /// Returns the current value of this cursor + pub fn position(&self) -> u64 { self.pos } + + /// Sets the value of this cursor + pub fn set_position(&mut self, pos: u64) { self.pos = pos; } +} + +macro_rules! seek { + () => { + fn seek(&mut self, style: SeekFrom) -> io::Result { + let pos = match style { + SeekFrom::Start(n) => { self.pos = n; return Ok(n) } + SeekFrom::End(n) => self.inner.len() as i64 + n, + SeekFrom::Current(n) => self.pos as i64 + n, + }; + + if pos < 0 { + Err(Error::new(ErrorKind::InvalidInput, + "invalid seek to a negative position", + None)) + } else { + self.pos = pos as u64; + Ok(self.pos) + } + } + } +} + +impl<'a> io::Seek for Cursor<&'a [u8]> { seek!(); } +impl<'a> io::Seek for Cursor<&'a mut [u8]> { seek!(); } +impl io::Seek for Cursor> { seek!(); } + +macro_rules! read { + () => { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let n = try!(Read::read(&mut try!(self.fill_buf()), buf)); + self.pos += n as u64; + Ok(n) + } + } +} + +impl<'a> Read for Cursor<&'a [u8]> { read!(); } +impl<'a> Read for Cursor<&'a mut [u8]> { read!(); } +impl Read for Cursor> { read!(); } + +macro_rules! buffer { + () => { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + let amt = cmp::min(self.pos, self.inner.len() as u64); + Ok(&self.inner[(amt as usize)..]) + } + fn consume(&mut self, amt: usize) { self.pos += amt as u64; } + } +} + +impl<'a> BufRead for Cursor<&'a [u8]> { buffer!(); } +impl<'a> BufRead for Cursor<&'a mut [u8]> { buffer!(); } +impl<'a> BufRead for Cursor> { buffer!(); } + +impl<'a> Write for Cursor<&'a mut [u8]> { + fn write(&mut self, data: &[u8]) -> io::Result { + let pos = cmp::min(self.pos, self.inner.len() as u64); + let amt = try!((&mut self.inner[(pos as usize)..]).write(data)); + self.pos += amt as u64; + Ok(amt) + } + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +impl Write for Cursor> { + fn write(&mut self, buf: &[u8]) -> io::Result { + // Make sure the internal buffer is as least as big as where we + // currently are + let pos = self.position(); + let amt = pos.saturating_sub(self.inner.len() as u64); + self.inner.extend(repeat(0).take(amt as usize)); + + // Figure out what bytes will be used to overwrite what's currently + // there (left), and what will be appended on the end (right) + let space = self.inner.len() - pos as usize; + let (left, right) = buf.split_at(cmp::min(space, buf.len())); + slice::bytes::copy_memory(&mut self.inner[(pos as usize)..], left); + self.inner.push_all(right); + + // Bump us forward + self.set_position(pos + buf.len() as u64); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + + +#[cfg(test)] +mod tests { + use core::prelude::*; + + use io::prelude::*; + use io::{Cursor, SeekFrom}; + use vec::Vec; + + #[test] + fn test_vec_writer() { + let mut writer = Vec::new(); + assert_eq!(writer.write(&[0]), Ok(1)); + assert_eq!(writer.write(&[1, 2, 3]), Ok(3)); + assert_eq!(writer.write(&[4, 5, 6, 7]), Ok(4)); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(writer, b); + } + + #[test] + fn test_mem_writer() { + let mut writer = Cursor::new(Vec::new()); + assert_eq!(writer.write(&[0]), Ok(1)); + assert_eq!(writer.write(&[1, 2, 3]), Ok(3)); + assert_eq!(writer.write(&[4, 5, 6, 7]), Ok(4)); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[], b); + } + + #[test] + fn test_buf_writer() { + let mut buf = [0 as u8; 9]; + { + let mut writer = Cursor::new(&mut buf[]); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[0]), Ok(1)); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[1, 2, 3]), Ok(3)); + assert_eq!(writer.write(&[4, 5, 6, 7]), Ok(4)); + assert_eq!(writer.position(), 8); + assert_eq!(writer.write(&[]), Ok(0)); + assert_eq!(writer.position(), 8); + + assert_eq!(writer.write(&[8, 9]), Ok(1)); + assert_eq!(writer.write(&[10]), Ok(0)); + } + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8]; + assert_eq!(buf, b); + } + + #[test] + fn test_buf_writer_seek() { + let mut buf = [0 as u8; 8]; + { + let mut writer = Cursor::new(&mut buf[]); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[1]), Ok(1)); + assert_eq!(writer.position(), 1); + + assert_eq!(writer.seek(SeekFrom::Start(2)), Ok(2)); + assert_eq!(writer.position(), 2); + assert_eq!(writer.write(&[2]), Ok(1)); + assert_eq!(writer.position(), 3); + + assert_eq!(writer.seek(SeekFrom::Current(-2)), Ok(1)); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[3]), Ok(1)); + assert_eq!(writer.position(), 2); + + assert_eq!(writer.seek(SeekFrom::End(-1)), Ok(7)); + assert_eq!(writer.position(), 7); + assert_eq!(writer.write(&[4]), Ok(1)); + assert_eq!(writer.position(), 8); + + } + let b: &[_] = &[1, 3, 2, 0, 0, 0, 0, 4]; + assert_eq!(buf, b); + } + + #[test] + fn test_buf_writer_error() { + let mut buf = [0 as u8; 2]; + let mut writer = Cursor::new(&mut buf[]); + assert_eq!(writer.write(&[0]), Ok(1)); + assert_eq!(writer.write(&[0, 0]), Ok(1)); + assert_eq!(writer.write(&[0, 0]), Ok(0)); + } + + #[test] + fn test_mem_reader() { + let mut reader = Cursor::new(vec!(0u8, 1, 2, 3, 4, 5, 6, 7)); + let mut buf = []; + assert_eq!(reader.read(&mut buf), Ok(0)); + assert_eq!(reader.position(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf), Ok(1)); + assert_eq!(reader.position(), 1); + let b: &[_] = &[0]; + assert_eq!(buf, b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf), Ok(4)); + assert_eq!(reader.position(), 5); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf, b); + assert_eq!(reader.read(&mut buf), Ok(3)); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf), Ok(0)); + } + + #[test] + fn read_to_end() { + let mut reader = Cursor::new(vec!(0u8, 1, 2, 3, 4, 5, 6, 7)); + let mut v = Vec::new(); + reader.read_to_end(&mut v).ok().unwrap(); + assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7]); + } + + #[test] + fn test_slice_reader() { + let in_buf = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let mut reader = &mut in_buf.as_slice(); + let mut buf = []; + assert_eq!(reader.read(&mut buf), Ok(0)); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf), Ok(1)); + assert_eq!(reader.len(), 7); + let b: &[_] = &[0]; + assert_eq!(buf.as_slice(), b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf), Ok(4)); + assert_eq!(reader.len(), 3); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf.as_slice(), b); + assert_eq!(reader.read(&mut buf), Ok(3)); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf), Ok(0)); + } + + #[test] + fn test_buf_reader() { + let in_buf = vec![0u8, 1, 2, 3, 4, 5, 6, 7]; + let mut reader = Cursor::new(in_buf.as_slice()); + let mut buf = []; + assert_eq!(reader.read(&mut buf), Ok(0)); + assert_eq!(reader.position(), 0); + let mut buf = [0]; + assert_eq!(reader.read(&mut buf), Ok(1)); + assert_eq!(reader.position(), 1); + let b: &[_] = &[0]; + assert_eq!(buf, b); + let mut buf = [0; 4]; + assert_eq!(reader.read(&mut buf), Ok(4)); + assert_eq!(reader.position(), 5); + let b: &[_] = &[1, 2, 3, 4]; + assert_eq!(buf, b); + assert_eq!(reader.read(&mut buf), Ok(3)); + let b: &[_] = &[5, 6, 7]; + assert_eq!(&buf[..3], b); + assert_eq!(reader.read(&mut buf), Ok(0)); + } + + #[test] + fn test_read_char() { + let b = b"Vi\xE1\xBB\x87t"; + let mut c = Cursor::new(b).chars(); + assert_eq!(c.next(), Some(Ok('V'))); + assert_eq!(c.next(), Some(Ok('i'))); + assert_eq!(c.next(), Some(Ok('ệ'))); + assert_eq!(c.next(), Some(Ok('t'))); + assert_eq!(c.next(), None); + } + + #[test] + fn test_read_bad_char() { + let b = b"\x80"; + let mut c = Cursor::new(b).chars(); + assert!(c.next().unwrap().is_err()); + } + + #[test] + fn seek_past_end() { + let buf = [0xff]; + let mut r = Cursor::new(&buf[]); + assert_eq!(r.seek(SeekFrom::Start(10)), Ok(10)); + assert_eq!(r.read(&mut [0]), Ok(0)); + + let mut r = Cursor::new(vec!(10u8)); + assert_eq!(r.seek(SeekFrom::Start(10)), Ok(10)); + assert_eq!(r.read(&mut [0]), Ok(0)); + + let mut buf = [0]; + let mut r = Cursor::new(&mut buf[]); + assert_eq!(r.seek(SeekFrom::Start(10)), Ok(10)); + assert_eq!(r.write(&[3]), Ok(0)); + } + + #[test] + fn seek_before_0() { + let buf = [0xff_u8]; + let mut r = Cursor::new(&buf[]); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + + let mut r = Cursor::new(vec!(10u8)); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + + let mut buf = [0]; + let mut r = Cursor::new(&mut buf[]); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + } + + #[test] + fn test_seekable_mem_writer() { + let mut writer = Cursor::new(Vec::::new()); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[0]), Ok(1)); + assert_eq!(writer.position(), 1); + assert_eq!(writer.write(&[1, 2, 3]), Ok(3)); + assert_eq!(writer.write(&[4, 5, 6, 7]), Ok(4)); + assert_eq!(writer.position(), 8); + let b: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[], b); + + assert_eq!(writer.seek(SeekFrom::Start(0)), Ok(0)); + assert_eq!(writer.position(), 0); + assert_eq!(writer.write(&[3, 4]), Ok(2)); + let b: &[_] = &[3, 4, 2, 3, 4, 5, 6, 7]; + assert_eq!(&writer.get_ref()[], b); + + assert_eq!(writer.seek(SeekFrom::Current(1)), Ok(3)); + assert_eq!(writer.write(&[0, 1]), Ok(2)); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 7]; + assert_eq!(&writer.get_ref()[], b); + + assert_eq!(writer.seek(SeekFrom::End(-1)), Ok(7)); + assert_eq!(writer.write(&[1, 2]), Ok(2)); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 1, 2]; + assert_eq!(&writer.get_ref()[], b); + + assert_eq!(writer.seek(SeekFrom::End(1)), Ok(10)); + assert_eq!(writer.write(&[1]), Ok(1)); + let b: &[_] = &[3, 4, 2, 0, 1, 5, 6, 1, 2, 0, 1]; + assert_eq!(&writer.get_ref()[], b); + } + + #[test] + fn vec_seek_past_end() { + let mut r = Cursor::new(Vec::new()); + assert_eq!(r.seek(SeekFrom::Start(10)), Ok(10)); + assert_eq!(r.write(&[3]), Ok(1)); + } + + #[test] + fn vec_seek_before_0() { + let mut r = Cursor::new(Vec::new()); + assert!(r.seek(SeekFrom::End(-2)).is_err()); + } +} diff --git a/src/libstd/io/error.rs b/src/libstd/io/error.rs new file mode 100644 index 00000000000..9f3cd8c8b15 --- /dev/null +++ b/src/libstd/io/error.rs @@ -0,0 +1,183 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use boxed::Box; +use clone::Clone; +use error::Error as StdError; +use fmt; +use option::Option::{self, Some, None}; +use result; +use string::String; +use sys; + +/// A type for results generated by I/O related functions where the `Err` type +/// is hard-wired to `io::Error`. +/// +/// This typedef is generally used to avoid writing out `io::Error` directly and +/// is otherwise a direct mapping to `std::result::Result`. +pub type Result = result::Result; + +/// The error type for I/O operations of the `Read`, `Write`, `Seek`, and +/// associated traits. +/// +/// Errors mostly originate from the underlying OS, but custom instances of +/// `Error` can be created with crafted error messages and a particular value of +/// `ErrorKind`. +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Error { + repr: Repr, +} + +#[derive(PartialEq, Eq, Clone, Debug)] +enum Repr { + Os(i32), + Custom(Box), +} + +#[derive(PartialEq, Eq, Clone, Debug)] +struct Custom { + kind: ErrorKind, + desc: &'static str, + detail: Option +} + +/// A list specifying general categories of I/O error. +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum ErrorKind { + /// The file was not found. + FileNotFound, + /// The file permissions disallowed access to this file. + PermissionDenied, + /// The connection was refused by the remote server. + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// The connection was aborted (terminated) by the remote server. + ConnectionAborted, + /// The network operation failed because it was not connected yet. + NotConnected, + /// The operation failed because a pipe was closed. + BrokenPipe, + /// A file already existed with that name. + PathAlreadyExists, + /// No file exists at that location. + PathDoesntExist, + /// The path did not specify the type of file that this operation required. + /// For example, attempting to copy a directory with the `fs::copy()` + /// operation will fail with this error. + MismatchedFileTypeForOperation, + /// The operation temporarily failed (for example, because a signal was + /// received), and retrying may succeed. + ResourceUnavailable, + /// A parameter was incorrect in a way that caused an I/O error not part of + /// this list. + InvalidInput, + /// The I/O operation's timeout expired, causing it to be canceled. + TimedOut, + /// An error returned when an operation could not be completed because a + /// call to `write` returned `Ok(0)`. + /// + /// This typically means that an operation could only succeed if it wrote a + /// particular number of bytes but only a smaller number of bytes could be + /// written. + WriteZero, + /// This operation was interrupted + Interrupted, + /// Any I/O error not part of this list. + Other, +} + +impl Error { + /// Creates a new custom error from a specified kind/description/detail. + pub fn new(kind: ErrorKind, + description: &'static str, + detail: Option) -> Error { + Error { + repr: Repr::Custom(Box::new(Custom { + kind: kind, + desc: description, + detail: detail, + })) + } + } + + /// Returns an error representing the last OS error which occurred. + /// + /// This function reads the value of `errno` for the target platform (e.g. + /// `GetLastError` on Windows) and will return a corresponding instance of + /// `Error` for the error code. + pub fn last_os_error() -> Error { + Error::from_os_error(sys::os::errno() as i32) + } + + /// Creates a new instance of an `Error` from a particular OS error code. + pub fn from_os_error(code: i32) -> Error { + Error { repr: Repr::Os(code) } + } + + /// Return the corresponding `ErrorKind` for this error. + pub fn kind(&self) -> ErrorKind { + match self.repr { + Repr::Os(code) => sys::decode_error_kind(code), + Repr::Custom(ref c) => c.kind, + } + } + + /// Returns a short description for this error message + pub fn description(&self) -> &str { + match self.repr { + Repr::Os(..) => "os error", + Repr::Custom(ref c) => c.desc, + } + } + + /// Returns a detailed error message for this error (if one is available) + pub fn detail(&self) -> Option { + match self.repr { + Repr::Os(code) => Some(sys::os::error_string(code)), + Repr::Custom(ref s) => s.detail.clone(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self.repr { + Repr::Os(code) => { + let detail = sys::os::error_string(code); + write!(fmt, "{} (os error {})", detail, code) + } + Repr::Custom(ref c) => { + match **c { + Custom { + kind: ErrorKind::Other, + desc: "unknown error", + detail: Some(ref detail) + } => { + write!(fmt, "{}", detail) + } + Custom { detail: None, desc, .. } => + write!(fmt, "{}", desc), + Custom { detail: Some(ref detail), desc, .. } => + write!(fmt, "{} ({})", desc, detail) + } + } + } + } +} + +impl StdError for Error { + fn description(&self) -> &str { + match self.repr { + Repr::Os(..) => "os error", + Repr::Custom(ref c) => c.desc, + } + } +} diff --git a/src/libstd/io/impls.rs b/src/libstd/io/impls.rs new file mode 100644 index 00000000000..7f3ce7924c1 --- /dev/null +++ b/src/libstd/io/impls.rs @@ -0,0 +1,88 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use core::prelude::*; + +use boxed::Box; +use cmp; +use io::{self, SeekFrom, Read, Write, Seek, BufRead}; +use mem; +use slice; +use vec::Vec; + +// ============================================================================= +// Forwarding implementations + +impl<'a, R: Read + ?Sized> Read for &'a mut R { + fn read(&mut self, buf: &mut [u8]) -> io::Result { (**self).read(buf) } +} +impl<'a, W: Write + ?Sized> Write for &'a mut W { + fn write(&mut self, buf: &[u8]) -> io::Result { (**self).write(buf) } + fn flush(&mut self) -> io::Result<()> { (**self).flush() } +} +impl<'a, S: Seek + ?Sized> Seek for &'a mut S { + fn seek(&mut self, pos: SeekFrom) -> io::Result { (**self).seek(pos) } +} +impl<'a, B: BufRead + ?Sized> BufRead for &'a mut B { + fn fill_buf(&mut self) -> io::Result<&[u8]> { (**self).fill_buf() } + fn consume(&mut self, amt: usize) { (**self).consume(amt) } +} + +impl Read for Box { + fn read(&mut self, buf: &mut [u8]) -> io::Result { (**self).read(buf) } +} +impl Write for Box { + fn write(&mut self, buf: &[u8]) -> io::Result { (**self).write(buf) } + fn flush(&mut self) -> io::Result<()> { (**self).flush() } +} +impl Seek for Box { + fn seek(&mut self, pos: SeekFrom) -> io::Result { (**self).seek(pos) } +} +impl BufRead for Box { + fn fill_buf(&mut self) -> io::Result<&[u8]> { (**self).fill_buf() } + fn consume(&mut self, amt: usize) { (**self).consume(amt) } +} + +// ============================================================================= +// In-memory buffer implementations + +impl<'a> Read for &'a [u8] { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let amt = cmp::min(buf.len(), self.len()); + let (a, b) = self.split_at(amt); + slice::bytes::copy_memory(buf, a); + *self = b; + Ok(amt) + } +} + +impl<'a> BufRead for &'a [u8] { + fn fill_buf(&mut self) -> io::Result<&[u8]> { Ok(*self) } + fn consume(&mut self, amt: usize) { *self = &self[amt..]; } +} + +impl<'a> Write for &'a mut [u8] { + fn write(&mut self, data: &[u8]) -> io::Result { + let amt = cmp::min(data.len(), self.len()); + let (a, b) = mem::replace(self, &mut []).split_at_mut(amt); + slice::bytes::copy_memory(a, &data[..amt]); + *self = b; + Ok(amt) + } + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +impl Write for Vec { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.push_all(buf); + Ok(buf.len()) + } + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs new file mode 100644 index 00000000000..0832206a48b --- /dev/null +++ b/src/libstd/io/mod.rs @@ -0,0 +1,948 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Traits, helpers, and type definitions for core I/O functionality. +//! +//! > **NOTE**: This module is very much a work in progress and is under active +//! > development. At this time it is still recommended to use the `old_io` +//! > module while the details of this module shake out. + +#![unstable(feature = "io", + reason = "this new I/O module is still under active deveopment and \ + APIs are subject to tweaks fairly regularly")] + +use cmp; +use unicode::str as core_str; +use error::Error as StdError; +use fmt; +use iter::Iterator; +use marker::Sized; +use mem; +use ops::{Drop, FnOnce}; +use option::Option::{self, Some, None}; +use ptr::PtrExt; +use result::Result::{Ok, Err}; +use result; +use slice::{self, SliceExt}; +use string::String; +use str::{self, StrExt}; +use vec::Vec; + +pub use self::buffered::{BufReader, BufWriter, BufStream, LineWriter}; +pub use self::buffered::IntoInnerError; +pub use self::cursor::Cursor; +pub use self::error::{Result, Error, ErrorKind}; +pub use self::util::{copy, sink, Sink, empty, Empty, repeat, Repeat}; + +pub mod prelude; +mod buffered; +mod cursor; +mod error; +mod impls; +mod util; + +const DEFAULT_BUF_SIZE: usize = 64 * 1024; + +// Acquires a slice of the vector `v` from its length to its capacity +// (uninitialized data), reads into it, and then updates the length. +// +// This function is leveraged to efficiently read some bytes into a destination +// vector without extra copying and taking advantage of the space that's already +// in `v`. +// +// The buffer we're passing down, however, is pointing at uninitialized data +// (the end of a `Vec`), and many operations will be *much* faster if we don't +// have to zero it out. In order to prevent LLVM from generating an `undef` +// value when reads happen from this uninitialized memory, we force LLVM to +// think it's initialized by sending it through a black box. This should prevent +// actual undefined behavior after optimizations. +fn with_end_to_cap(v: &mut Vec, f: F) -> Result + where F: FnOnce(&mut [u8]) -> Result +{ + unsafe { + let n = try!(f({ + let base = v.as_mut_ptr().offset(v.len() as isize); + black_box(slice::from_raw_mut_buf(mem::copy_lifetime(v, &base), + v.capacity() - v.len())) + })); + + // If the closure (typically a `read` implementation) reported that it + // read a larger number of bytes than the vector actually has, we need + // to be sure to clamp the vector to at most its capacity. + let new_len = cmp::min(v.capacity(), v.len() + n); + v.set_len(new_len); + return Ok(n); + } + + // Semi-hack used to prevent LLVM from retaining any assumptions about + // `dummy` over this function call + unsafe fn black_box(mut dummy: T) -> T { + asm!("" :: "r"(&mut dummy) : "memory"); + dummy + } +} + +// A few methods below (read_to_string, read_line) will append data into a +// `String` buffer, but we need to be pretty careful when doing this. The +// implementation will just call `.as_mut_vec()` and then delegate to a +// byte-oriented reading method, but we must ensure that when returning we never +// leave `buf` in a state such that it contains invalid UTF-8 in its bounds. +// +// To this end, we use an RAII guard (to protect against panics) which updates +// the length of the string when it is dropped. This guard initially truncates +// the string to the prior length and only afer we've validated that the +// new contents are valid UTF-8 do we allow it to set a longer length. +// +// The unsafety in this function is twofold: +// +// 1. We're looking at the raw bytes of `buf`, so we take on the burden of UTF-8 +// checks. +// 2. We're passing a raw buffer to the function `f`, and it is expected that +// the function only *appends* bytes to the buffer. We'll get undefined +// behavior if existing bytes are overwritten to have non-UTF-8 data. +fn append_to_string(buf: &mut String, f: F) -> Result<()> + where F: FnOnce(&mut Vec) -> Result<()> +{ + struct Guard<'a> { s: &'a mut Vec, len: usize } + #[unsafe_destructor] + impl<'a> Drop for Guard<'a> { + fn drop(&mut self) { + unsafe { self.s.set_len(self.len); } + } + } + + unsafe { + let mut g = Guard { len: buf.len(), s: buf.as_mut_vec() }; + let ret = f(g.s); + if str::from_utf8(&g.s[g.len..]).is_err() { + ret.and_then(|()| { + Err(Error::new(ErrorKind::InvalidInput, + "stream did not contain valid UTF-8", None)) + }) + } else { + g.len = g.s.len(); + ret + } + } +} + +fn read_to_end(r: &mut R, buf: &mut Vec) -> Result<()> { + loop { + if buf.capacity() == buf.len() { + buf.reserve(DEFAULT_BUF_SIZE); + } + match with_end_to_cap(buf, |b| r.read(b)) { + Ok(0) => return Ok(()), + Ok(_) => {} + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } +} + +/// A trait for objects which are byte-oriented sources. +/// +/// Readers are defined by one method, `read`. Each call to `read` will attempt +/// to pull bytes from this source into a provided buffer. +/// +/// Readers are intended to be composable with one another. Many objects +/// throughout the I/O and related libraries take and provide types which +/// implement the `Read` trait. +pub trait Read { + /// Pull some bytes from this source into the specified buffer, returning + /// how many bytes were read. + /// + /// This function does not provide any guarantees about whether it blocks + /// waiting for data, but if an object needs to block for a read but cannot + /// it will typically signal this via an `Err` return value. + /// + /// If the return value of this method is `Ok(n)`, then it must be + /// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value indicates + /// that the buffer `buf` has ben filled in with `n` bytes of data from this + /// source. If `n` is `0`, then it can indicate one of two scenarios: + /// + /// 1. This reader has reached its "end of file" and will likely no longer + /// be able to produce bytes. Note that this does not mean that the + /// reader will *always* no longer be able to produce bytes. + /// 2. The buffer specified was 0 bytes in length. + /// + /// No guarantees are provided about the contents of `buf` when this + /// function is called, implementations cannot rely on any property of the + /// contents of `buf` being true. It is recommended that implementations + /// only write data to `buf` instead of reading its contents. + /// + /// # Errors + /// + /// If this function encounters any form of I/O or other error, an error + /// variant will be returned. If an error is returned then it must be + /// guaranteed that no bytes were read. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read all bytes until EOF in this source, placing them into `buf`. + /// + /// All bytes read from this source will be appended to the specified buffer + /// `buf`. This function will return a call to `read` either: + /// + /// 1. Returns `Ok(0)`. + /// 2. Returns an error which is not of the kind `ErrorKind::Interrupted`. + /// + /// Until one of these conditions is met the function will continuously + /// invoke `read` to append more data to `buf`. + /// + /// # Errors + /// + /// If this function encounters an error of the kind + /// `ErrorKind::Interrupted` then the error is ignored and the operation + /// will continue. + /// + /// If any other read error is encountered then this function immediately + /// returns. Any bytes which have already been read will be appended to + /// `buf`. + fn read_to_end(&mut self, buf: &mut Vec) -> Result<()> { + read_to_end(self, buf) + } + + /// Read all bytes until EOF in this source, placing them into `buf`. + /// + /// # Errors + /// + /// If the data in this stream is *not* valid UTF-8 then an error is + /// returned and `buf` is unchanged. + /// + /// See `read_to_end` for other error semantics. + fn read_to_string(&mut self, buf: &mut String) -> Result<()> { + // Note that we do *not* call `.read_to_end()` here. We are passing + // `&mut Vec` (the raw contents of `buf`) into the `read_to_end` + // method to fill it up. An arbitrary implementation could overwrite the + // entire contents of the vector, not just append to it (which is what + // we are expecting). + // + // To prevent extraneously checking the UTF-8-ness of the entire buffer + // we pass it to our hardcoded `read_to_end` implementation which we + // know is guaranteed to only read data into the end of the buffer. + append_to_string(buf, |b| read_to_end(self, b)) + } +} + +/// Extension methods for all instances of `Read`, typically imported through +/// `std::io::prelude::*`. +pub trait ReadExt: Read + Sized { + /// Create a "by reference" adaptor for this instance of `Read`. + /// + /// The returned adaptor also implements `Read` and will simply borrow this + /// current reader. + fn by_ref(&mut self) -> &mut Self { self } + + /// Transform this `Read` instance to an `Iterator` over its bytes. + /// + /// The returned type implements `Iterator` where the `Item` is `Result`. The yielded item is `Ok` if a byte was successfully read and + /// `Err` otherwise for I/O errors. EOF is mapped to returning `None` from + /// this iterator. + fn bytes(self) -> Bytes { + Bytes { inner: self } + } + + /// Transform this `Read` instance to an `Iterator` over `char`s. + /// + /// This adaptor will attempt to interpret this reader as an UTF-8 encoded + /// sequence of characters. The returned iterator will return `None` once + /// EOF is reached for this reader. Otherwise each element yielded will be a + /// `Result` where `E` may contain information about what I/O error + /// occurred or where decoding failed. + /// + /// Currently this adaptor will discard intermediate data read, and should + /// be avoided if this is not desired. + fn chars(self) -> Chars { + Chars { inner: self } + } + + /// Create an adaptor which will chain this stream with another. + /// + /// The returned `Read` instance will first read all bytes from this object + /// until EOF is encountered. Afterwards the output is equivalent to the + /// output of `next`. + fn chain(self, next: R) -> Chain { + Chain { first: self, second: next, done_first: false } + } + + /// Create an adaptor which will read at most `limit` bytes from it. + /// + /// This function returns a new instance of `Read` which will read at most + /// `limit` bytes, after which it will always return EOF (`Ok(0)`). Any + /// read errors will not count towards the number of bytes read and future + /// calls to `read` may succeed. + fn take(self, limit: u64) -> Take { + Take { inner: self, limit: limit } + } + + /// Creates a reader adaptor which will write all read data into the given + /// output stream. + /// + /// Whenever the returned `Read` instance is read it will write the read + /// data to `out`. The current semantics of this implementation imply that + /// a `write` error will not report how much data was initially read. + fn tee(self, out: W) -> Tee { + Tee { reader: self, writer: out } + } +} + +impl ReadExt for T {} + +/// A trait for objects which are byte-oriented sinks. +/// +/// The `write` method will attempt to write some data into the object, +/// returning how many bytes were successfully written. +/// +/// The `flush` method is useful for adaptors and explicit buffers themselves +/// for ensuring that all buffered data has been pushed out to the "true sink". +/// +/// Writers are intended to be composable with one another. Many objects +/// throughout the I/O and related libraries take and provide types which +/// implement the `Write` trait. +pub trait Write { + /// Write a buffer into this object, returning how many bytes were written. + /// + /// This function will attempt to write the entire contents of `buf`, but + /// the entire write may not succeed, or the write may also generate an + /// error. A call to `write` represents *at most one* attempt to write to + /// any wrapped object. + /// + /// Calls to `write` are not guaranteed to block waiting for data to be + /// written, and a write which would otherwise block can indicated through + /// an `Err` variant. + /// + /// If the return value is `Ok(n)` then it must be guaranteed that + /// `0 <= n <= buf.len()`. A return value of `0` typically means that the + /// underlying object is no longer able to accept bytes and will likely not + /// be able to in the future as well, or that the buffer provided is empty. + /// + /// # Errors + /// + /// Each call to `write` may generate an I/O error indicating that the + /// operation could not be completed. If an error is returned then no bytes + /// in the buffer were written to this writer. + /// + /// It is **not** considered an error if the entire buffer could not be + /// written to this writer. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + /// + /// # Errors + /// + /// It is considered an error if not all bytes could be written due to + /// I/O errors or EOF being reached. + fn flush(&mut self) -> Result<()>; + + /// Attempts to write an entire buffer into this write. + /// + /// This method will continuously call `write` while there is more data to + /// write. This method will not return until the entire buffer has been + /// successfully written or an error occurs. The first error generated from + /// this method will be returned. + /// + /// # Errors + /// + /// This function will return the first error that `write` returns. + fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { + while buf.len() > 0 { + match self.write(buf) { + Ok(0) => return Err(Error::new(ErrorKind::WriteZero, + "failed to write whole buffer", + None)), + Ok(n) => buf = &buf[n..], + Err(ref e) if e.kind() == ErrorKind::Interrupted => {} + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Writes a formatted string into this writer, returning any error + /// encountered. + /// + /// This method is primarily used to interface with the `format_args!` + /// macro, but it is rare that this should explicitly be called. The + /// `write!` macro should be favored to invoke this method instead. + /// + /// This function internally uses the `write_all` method on this trait and + /// hence will continuously write data so long as no errors are received. + /// This also means that partial writes are not indicated in this signature. + /// + /// # Errors + /// + /// This function will return any I/O error reported while formatting. + fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()> { + // Create a shim which translates a Writer to a fmt::Writer and saves + // off I/O errors. instead of discarding them + struct Adaptor<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl<'a, T: Write + ?Sized> fmt::Writer for Adaptor<'a, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adaptor { inner: self, error: Ok(()) }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => output.error + } + } +} + +/// Extension methods for all instances of `Write`, typically imported through +/// `std::io::prelude::*`. +pub trait WriteExt: Write + Sized { + /// Create a "by reference" adaptor for this instance of `Write`. + /// + /// The returned adaptor also implements `Write` and will simply borrow this + /// current writer. + fn by_ref(&mut self) -> &mut Self { self } + + /// Creates a new writer which will write all data to both this writer and + /// another writer. + /// + /// All data written to the returned writer will both be written to `self` + /// as well as `other`. Note that the error semantics of the current + /// implementation do not precisely track where errors happen. For example + /// an error on the second call to `write` will not report that the first + /// call to `write` succeeded. + fn broadcast(self, other: W) -> Broadcast { + Broadcast { first: self, second: other } + } +} + +impl WriteExt for T {} + +/// An object implementing `Seek` internally has some form of cursor which can +/// be moved within a stream of bytes. +/// +/// The stream typically has a fixed size, allowing seeking relative to either +/// end or the current offset. +pub trait Seek { + /// Seek to an offset, in bytes, in a stream + /// + /// A seek beyond the end of a stream is allowed, but seeking before offset + /// 0 is an error. + /// + /// Seeking past the end of the stream does not modify the underlying + /// stream, but the next write may cause the previous data to be filled in + /// with a bit pattern. + /// + /// This method returns the new position within the stream if the seek + /// operation completed successfully. + /// + /// # Errors + /// + /// Seeking to a negative offset is considered an error + fn seek(&mut self, pos: SeekFrom) -> Result; +} + +/// Enumeration of possible methods to seek within an I/O object. +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum SeekFrom { + /// Set the offset to the provided number of bytes. + Start(u64), + + /// Set the offset to the size of this object plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but is an error to + /// seek before byte 0. + End(i64), + + /// Set the offset to the current position plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but is an error to + /// seek before byte 0. + Current(i64), +} + +fn read_until(r: &mut R, delim: u8, buf: &mut Vec) + -> Result<()> { + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e) + }; + match available.position_elem(&delim) { + Some(i) => { + buf.push_all(&available[..i + 1]); + (true, i + 1) + } + None => { + buf.push_all(available); + (false, available.len()) + } + } + }; + r.consume(used); + if done || used == 0 { + return Ok(()); + } + } +} + +/// A Buffer is a type of reader which has some form of internal buffering to +/// allow certain kinds of reading operations to be more optimized than others. +/// +/// This type extends the `Read` trait with a few methods that are not +/// possible to reasonably implement with purely a read interface. +pub trait BufRead: Read { + /// Fills the internal buffer of this object, returning the buffer contents. + /// + /// None of the contents will be "read" in the sense that later calling + /// `read` may return the same contents. + /// + /// The `consume` function must be called with the number of bytes that are + /// consumed from this buffer returned to ensure that the bytes are never + /// returned twice. + /// + /// An empty buffer returned indicates that the stream has reached EOF. + /// + /// # Errors + /// + /// This function will return an I/O error if the underlying reader was + /// read, but returned an error. + fn fill_buf(&mut self) -> Result<&[u8]>; + + /// Tells this buffer that `amt` bytes have been consumed from the buffer, + /// so they should no longer be returned in calls to `read`. + fn consume(&mut self, amt: usize); + + /// Read all bytes until the delimiter `byte` is reached. + /// + /// This function will continue to read (and buffer) bytes from the + /// underlying stream until the delimiter or EOF is found. Once found, all + /// bytes up to, and including, the delimiter (if found) will be appended to + /// `buf`. + /// + /// If this buffered reader is currently at EOF, then this function will not + /// place any more bytes into `buf` and will return `Ok(())`. + /// + /// # Errors + /// + /// This function will ignore all instances of `ErrorKind::Interrupted` and + /// will otherwise return any errors returned by `fill_buf`. + /// + /// If an I/O error is encountered then all bytes read so far will be + /// present in `buf` and its length will have been adjusted appropriately. + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> Result<()> { + read_until(self, byte, buf) + } + + /// Read all bytes until a newline byte (the 0xA byte) is reached. + /// + /// This function will continue to read (and buffer) bytes from the + /// underlying stream until the newline delimiter (the 0xA byte) or EOF is + /// found. Once found, all bytes up to, and including, the delimiter (if + /// found) will be appended to `buf`. + /// + /// If this reader is currently at EOF then this function will not modify + /// `buf` and will return `Ok(())`. + /// + /// # Errors + /// + /// This function has the same error semantics as `read_until` and will also + /// return an error if the read bytes are not valid UTF-8. If an I/O error + /// is encountered then `buf` may contain some bytes already read in the + /// event that all data read so far was valid UTF-8. + fn read_line(&mut self, buf: &mut String) -> Result<()> { + // Note that we are not calling the `.read_until` method here, but + // rather our hardcoded implementation. For more details as to why, see + // the comments in `read_to_end`. + append_to_string(buf, |b| read_until(self, b'\n', b)) + } +} + +/// Extension methods for all instances of `BufRead`, typically imported through +/// `std::io::prelude::*`. +pub trait BufReadExt: BufRead + Sized { + /// Returns an iterator over the contents of this reader split on the byte + /// `byte`. + /// + /// The iterator returned from this function will return instances of + /// `io::Result>`. Each vector returned will *not* have the + /// delimiter byte at the end. + /// + /// This function will yield errors whenever `read_until` would have also + /// yielded an error. + fn split(self, byte: u8) -> Split { + Split { buf: self, delim: byte } + } + + /// Returns an iterator over the lines of this reader. + /// + /// The iterator returned from this function will yield instances of + /// `io::Result`. Each string returned will *not* have a newline + /// byte (the 0xA byte) at the end. + /// + /// This function will yield errors whenever `read_string` would have also + /// yielded an error. + fn lines(self) -> Lines { + Lines { buf: self } + } +} + +impl BufReadExt for T {} + +/// A `Write` adaptor which will write data to multiple locations. +/// +/// For more information, see `WriteExt::broadcast`. +pub struct Broadcast { + first: T, + second: U, +} + +impl Write for Broadcast { + fn write(&mut self, data: &[u8]) -> Result { + let n = try!(self.first.write(data)); + // FIXME: what if the write fails? (we wrote something) + try!(self.second.write_all(&data[..n])); + Ok(n) + } + + fn flush(&mut self) -> Result<()> { + self.first.flush().and(self.second.flush()) + } +} + +/// Adaptor to chain together two instances of `Read`. +/// +/// For more information, see `ReadExt::chain`. +pub struct Chain { + first: T, + second: U, + done_first: bool, +} + +impl Read for Chain { + fn read(&mut self, buf: &mut [u8]) -> Result { + if !self.done_first { + match try!(self.first.read(buf)) { + 0 => { self.done_first = true; } + n => return Ok(n), + } + } + self.second.read(buf) + } +} + +/// Reader adaptor which limits the bytes read from an underlying reader. +/// +/// For more information, see `ReadExt::take`. +pub struct Take { + inner: T, + limit: u64, +} + +impl Take { + /// Returns the number of bytes that can be read before this instance will + /// return EOF. + /// + /// # Note + /// + /// This instance may reach EOF after reading fewer bytes than indiccated by + /// this method if the underlying `Read` instance reaches EOF. + pub fn limit(&self) -> u64 { self.limit } +} + +impl Read for Take { + fn read(&mut self, buf: &mut [u8]) -> Result { + let max = cmp::min(buf.len() as u64, self.limit) as usize; + let n = try!(self.inner.read(&mut buf[..max])); + self.limit -= n as u64; + Ok(n) + } +} + +/// An adaptor which will emit all read data to a specified writer as well. +/// +/// For more information see `ReadExt::tee` +pub struct Tee { + reader: R, + writer: W, +} + +impl Read for Tee { + fn read(&mut self, buf: &mut [u8]) -> Result { + let n = try!(self.reader.read(buf)); + // FIXME: what if the write fails? (we read something) + try!(self.writer.write_all(&buf[..n])); + Ok(n) + } +} + +/// A bridge from implementations of `Read` to an `Iterator` of `u8`. +/// +/// See `ReadExt::bytes` for more information. +pub struct Bytes { + inner: R, +} + +impl Iterator for Bytes { + type Item = Result; + + fn next(&mut self) -> Option> { + let mut buf = [0]; + match self.inner.read(&mut buf) { + Ok(0) => None, + Ok(..) => Some(Ok(buf[0])), + Err(e) => Some(Err(e)), + } + } +} + +/// A bridge from implementations of `Read` to an `Iterator` of `char`. +/// +/// See `ReadExt::chars` for more information. +pub struct Chars { + inner: R, +} + +/// An enumeration of possible errors that can be generated from the `Chars` +/// adapter. +#[derive(PartialEq, Clone, Debug)] +pub enum CharsError { + /// Variant representing that the underlying stream was read successfully + /// but it did not contain valid utf8 data. + NotUtf8, + + /// Variant representing that an I/O error occurred. + Other(Error), +} + +impl Iterator for Chars { + type Item = result::Result; + + fn next(&mut self) -> Option> { + let mut buf = [0]; + let first_byte = match self.inner.read(&mut buf) { + Ok(0) => return None, + Ok(..) => buf[0], + Err(e) => return Some(Err(CharsError::Other(e))), + }; + let width = core_str::utf8_char_width(first_byte); + if width == 1 { return Some(Ok(first_byte as char)) } + if width == 0 { return Some(Err(CharsError::NotUtf8)) } + let mut buf = [first_byte, 0, 0, 0]; + { + let mut start = 1; + while start < width { + match self.inner.read(&mut buf[start..width]) { + Ok(0) => return Some(Err(CharsError::NotUtf8)), + Ok(n) => start += n, + Err(e) => return Some(Err(CharsError::Other(e))), + } + } + } + Some(match str::from_utf8(&buf[..width]).ok() { + Some(s) => Ok(s.char_at(0)), + None => Err(CharsError::NotUtf8), + }) + } +} + +impl StdError for CharsError { + fn description(&self) -> &str { + match *self { + CharsError::NotUtf8 => "invalid utf8 encoding", + CharsError::Other(ref e) => e.description(), + } + } + fn cause(&self) -> Option<&StdError> { + match *self { + CharsError::NotUtf8 => None, + CharsError::Other(ref e) => e.cause(), + } + } +} + +impl fmt::Display for CharsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CharsError::NotUtf8 => { + "byte stream did not contain valid utf8".fmt(f) + } + CharsError::Other(ref e) => e.fmt(f), + } + } +} + +/// An iterator over the contents of an instance of `BufRead` split on a +/// particular byte. +/// +/// See `BufReadExt::split` for more information. +pub struct Split { + buf: B, + delim: u8, +} + +impl Iterator for Split { + type Item = Result>; + + fn next(&mut self) -> Option>> { + let mut buf = Vec::new(); + match self.buf.read_until(self.delim, &mut buf) { + Ok(()) if buf.len() == 0 => None, + Ok(()) => { + if buf[buf.len() - 1] == self.delim { + buf.pop(); + } + Some(Ok(buf)) + } + Err(e) => Some(Err(e)) + } + } +} + +/// An iterator over the lines of an instance of `BufRead` split on a newline +/// byte. +/// +/// See `BufReadExt::lines` for more information. +pub struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = Result; + + fn next(&mut self) -> Option> { + let mut buf = String::new(); + match self.buf.read_line(&mut buf) { + Ok(()) if buf.len() == 0 => None, + Ok(()) => { + if buf.ends_with("\n") { + buf.pop(); + } + Some(Ok(buf)) + } + Err(e) => Some(Err(e)) + } + } +} + +#[cfg(test)] +mod tests { + use prelude::v1::*; + use io::prelude::*; + use super::Cursor; + + #[test] + fn read_until() { + let mut buf = Cursor::new(b"12"); + let mut v = Vec::new(); + assert_eq!(buf.read_until(b'3', &mut v), Ok(())); + assert_eq!(v, b"12"); + + let mut buf = Cursor::new(b"1233"); + let mut v = Vec::new(); + assert_eq!(buf.read_until(b'3', &mut v), Ok(())); + assert_eq!(v, b"123"); + v.truncate(0); + assert_eq!(buf.read_until(b'3', &mut v), Ok(())); + assert_eq!(v, b"3"); + v.truncate(0); + assert_eq!(buf.read_until(b'3', &mut v), Ok(())); + assert_eq!(v, []); + } + + #[test] + fn split() { + let mut buf = Cursor::new(b"12"); + let mut s = buf.split(b'3'); + assert_eq!(s.next(), Some(Ok(vec![b'1', b'2']))); + assert_eq!(s.next(), None); + + let mut buf = Cursor::new(b"1233"); + let mut s = buf.split(b'3'); + assert_eq!(s.next(), Some(Ok(vec![b'1', b'2']))); + assert_eq!(s.next(), Some(Ok(vec![]))); + assert_eq!(s.next(), None); + } + + #[test] + fn read_line() { + let mut buf = Cursor::new(b"12"); + let mut v = String::new(); + assert_eq!(buf.read_line(&mut v), Ok(())); + assert_eq!(v, "12"); + + let mut buf = Cursor::new(b"12\n\n"); + let mut v = String::new(); + assert_eq!(buf.read_line(&mut v), Ok(())); + assert_eq!(v, "12\n"); + v.truncate(0); + assert_eq!(buf.read_line(&mut v), Ok(())); + assert_eq!(v, "\n"); + v.truncate(0); + assert_eq!(buf.read_line(&mut v), Ok(())); + assert_eq!(v, ""); + } + + #[test] + fn lines() { + let mut buf = Cursor::new(b"12"); + let mut s = buf.lines(); + assert_eq!(s.next(), Some(Ok("12".to_string()))); + assert_eq!(s.next(), None); + + let mut buf = Cursor::new(b"12\n\n"); + let mut s = buf.lines(); + assert_eq!(s.next(), Some(Ok("12".to_string()))); + assert_eq!(s.next(), Some(Ok(String::new()))); + assert_eq!(s.next(), None); + } + + #[test] + fn read_to_end() { + let mut c = Cursor::new(b""); + let mut v = Vec::new(); + assert_eq!(c.read_to_end(&mut v), Ok(())); + assert_eq!(v, []); + + let mut c = Cursor::new(b"1"); + let mut v = Vec::new(); + assert_eq!(c.read_to_end(&mut v), Ok(())); + assert_eq!(v, b"1"); + } + + #[test] + fn read_to_string() { + let mut c = Cursor::new(b""); + let mut v = String::new(); + assert_eq!(c.read_to_string(&mut v), Ok(())); + assert_eq!(v, ""); + + let mut c = Cursor::new(b"1"); + let mut v = String::new(); + assert_eq!(c.read_to_string(&mut v), Ok(())); + assert_eq!(v, "1"); + + let mut c = Cursor::new(b"\xff"); + let mut v = String::new(); + assert!(c.read_to_string(&mut v).is_err()); + } +} diff --git a/src/libstd/io/prelude.rs b/src/libstd/io/prelude.rs new file mode 100644 index 00000000000..475ada2ff84 --- /dev/null +++ b/src/libstd/io/prelude.rs @@ -0,0 +1,27 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The I/O Prelude +//! +//! The purpose of this module is to alleviate imports of many common I/O traits +//! by adding a glob import to the top of I/O heavy modules: +//! +//! ``` +//! use std::io::prelude::*; +//! ``` +//! +//! This module contains reexports of many core I/O traits such as `Read`, +//! `Write`, `ReadExt`, and `WriteExt`. Structures and functions are not +//! contained in this module. + +pub use super::{Read, ReadExt, Write, WriteExt, BufRead, BufReadExt}; + +// FIXME: pub use as `Seek` when the name isn't in the actual prelude any more +pub use super::Seek as NewSeek; diff --git a/src/libstd/io/util.rs b/src/libstd/io/util.rs new file mode 100644 index 00000000000..3d342137c62 --- /dev/null +++ b/src/libstd/io/util.rs @@ -0,0 +1,153 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(missing_copy_implementations)] + +use prelude::v1::*; + +use io::{self, Read, Write, ErrorKind}; + +/// Copies the entire contents of a reader into a writer. +/// +/// This function will continuously read data from `r` and then write it into +/// `w` in a streaming fashion until `r` returns EOF. +/// +/// On success the total number of bytes that were copied from `r` to `w` is +/// returned. +/// +/// # Errors +/// +/// This function will return an error immediately if any call to `read` or +/// `write` returns an error. All instances of `ErrorKind::Interrupted` are +/// handled by this function and the underlying operation is retried. +pub fn copy(r: &mut R, w: &mut W) -> io::Result { + let mut buf = [0; super::DEFAULT_BUF_SIZE]; + let mut written = 0; + loop { + let len = match r.read(&mut buf) { + Ok(0) => return Ok(written), + Ok(len) => len, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + try!(w.write_all(&buf[..len])); + written += len as u64; + } +} + +/// A reader which is always at EOF. +pub struct Empty { _priv: () } + +/// Creates an instance of an empty reader. +/// +/// All reads from the returned reader will return `Ok(0)`. +pub fn empty() -> Empty { Empty { _priv: () } } + +impl Read for Empty { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { Ok(0) } +} + +/// A reader which infinitely yields one byte. +pub struct Repeat { byte: u8 } + +/// Creates an instance of a reader that infinitely repeats one byte. +/// +/// All reads from this reader will succeed by filling the specified buffer with +/// the given byte. +pub fn repeat(byte: u8) -> Repeat { Repeat { byte: byte } } + +impl Read for Repeat { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + for slot in buf.iter_mut() { + *slot = self.byte; + } + Ok(buf.len()) + } +} + +/// A writer which will move data into the void. +pub struct Sink { _priv: () } + +/// Creates an instance of a writer which will successfully consume all data. +/// +/// All calls to `write` on the returned instance will return `Ok(buf.len())` +/// and the contents of the buffer will not be inspected. +pub fn sink() -> Sink { Sink { _priv: () } } + +impl Write for Sink { + fn write(&mut self, buf: &[u8]) -> io::Result { Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +#[cfg(test)] +mod test { + use prelude::v1::*; + + use io::prelude::*; + use io::{sink, empty, repeat}; + + #[test] + fn sink_sinks() { + let mut s = sink(); + assert_eq!(s.write(&[]), Ok(0)); + assert_eq!(s.write(&[0]), Ok(1)); + assert_eq!(s.write(&[0; 1024]), Ok(1024)); + assert_eq!(s.by_ref().write(&[0; 1024]), Ok(1024)); + } + + #[test] + fn empty_reads() { + let mut e = empty(); + assert_eq!(e.read(&mut []), Ok(0)); + assert_eq!(e.read(&mut [0]), Ok(0)); + assert_eq!(e.read(&mut [0; 1024]), Ok(0)); + assert_eq!(e.by_ref().read(&mut [0; 1024]), Ok(0)); + } + + #[test] + fn repeat_repeats() { + let mut r = repeat(4); + let mut b = [0; 1024]; + assert_eq!(r.read(&mut b), Ok(1024)); + assert!(b.iter().all(|b| *b == 4)); + } + + #[test] + fn take_some_bytes() { + assert_eq!(repeat(4).take(100).bytes().count(), 100); + assert_eq!(repeat(4).take(100).bytes().next(), Some(Ok(4))); + assert_eq!(repeat(1).take(10).chain(repeat(2).take(10)).bytes().count(), 20); + } + + #[test] + fn tee() { + let mut buf = [0; 10]; + { + let mut ptr: &mut [u8] = &mut buf; + assert_eq!(repeat(4).tee(&mut ptr).take(5).read(&mut [0; 10]), Ok(5)); + } + assert_eq!(buf, [4, 4, 4, 4, 4, 0, 0, 0, 0, 0]); + } + + #[test] + fn broadcast() { + let mut buf1 = [0; 10]; + let mut buf2 = [0; 10]; + { + let mut ptr1: &mut [u8] = &mut buf1; + let mut ptr2: &mut [u8] = &mut buf2; + + assert_eq!((&mut ptr1).broadcast(&mut ptr2) + .write(&[1, 2, 3]), Ok(3)); + } + assert_eq!(buf1, buf2); + assert_eq!(buf1, [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]); + } +} diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 839983d336d..9efb6fa4247 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -128,9 +128,8 @@ #![deny(missing_docs)] -#[cfg(test)] -#[macro_use] -extern crate log; +#[cfg(test)] extern crate test; +#[cfg(test)] #[macro_use] extern crate log; #[macro_use] #[macro_reexport(assert, assert_eq, debug_assert, debug_assert_eq, @@ -248,6 +247,7 @@ pub mod dynamic_lib; pub mod ffi; pub mod fmt; pub mod old_io; +pub mod io; pub mod os; pub mod env; pub mod path; diff --git a/src/libstd/sys/unix/mod.rs b/src/libstd/sys/unix/mod.rs index b03b9046966..427cf21ac70 100644 --- a/src/libstd/sys/unix/mod.rs +++ b/src/libstd/sys/unix/mod.rs @@ -18,10 +18,11 @@ use prelude::v1::*; use ffi; -use old_io::{self, IoResult, IoError}; +use io::ErrorKind; use libc; use num::{Int, SignedInt}; use num; +use old_io::{self, IoResult, IoError}; use str; use sys_common::mkerr_libc; @@ -133,6 +134,35 @@ pub fn decode_error_detailed(errno: i32) -> IoError { err } +pub fn decode_error_kind(errno: i32) -> ErrorKind { + match errno as libc::c_int { + libc::ECONNREFUSED => ErrorKind::ConnectionRefused, + libc::ECONNRESET => ErrorKind::ConnectionReset, + libc::EPERM | libc::EACCES => ErrorKind::PermissionDenied, + libc::EPIPE => ErrorKind::BrokenPipe, + libc::ENOTCONN => ErrorKind::NotConnected, + libc::ECONNABORTED => ErrorKind::ConnectionAborted, + libc::EADDRNOTAVAIL => ErrorKind::ConnectionRefused, + libc::EADDRINUSE => ErrorKind::ConnectionRefused, + libc::ENOENT => ErrorKind::FileNotFound, + libc::EISDIR => ErrorKind::InvalidInput, + libc::EINTR => ErrorKind::Interrupted, + libc::EINVAL => ErrorKind::InvalidInput, + libc::ENOTTY => ErrorKind::MismatchedFileTypeForOperation, + libc::ETIMEDOUT => ErrorKind::TimedOut, + libc::ECANCELED => ErrorKind::TimedOut, + libc::consts::os::posix88::EEXIST => ErrorKind::PathAlreadyExists, + + // These two constants can have the same value on some systems, + // but different values on others, so we can't use a match + // clause + x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => + ErrorKind::ResourceUnavailable, + + _ => ErrorKind::Other, + } +} + #[inline] pub fn retry (mut f: F) -> T where T: SignedInt, diff --git a/src/libstd/sys/windows/mod.rs b/src/libstd/sys/windows/mod.rs index 8dd467eba9e..f1af70e2cf7 100644 --- a/src/libstd/sys/windows/mod.rs +++ b/src/libstd/sys/windows/mod.rs @@ -15,6 +15,7 @@ use prelude::v1::*; use ffi::OsStr; +use io::ErrorKind; use libc; use mem; use old_io::{self, IoResult, IoError}; @@ -143,6 +144,34 @@ pub fn decode_error_detailed(errno: i32) -> IoError { err } +pub fn decode_error_kind(errno: i32) -> ErrorKind { + match errno as libc::c_int { + libc::ERROR_ACCESS_DENIED => ErrorKind::PermissionDenied, + libc::ERROR_ALREADY_EXISTS => ErrorKind::PathAlreadyExists, + libc::ERROR_BROKEN_PIPE => ErrorKind::BrokenPipe, + libc::ERROR_FILE_NOT_FOUND => ErrorKind::FileNotFound, + libc::ERROR_INVALID_FUNCTION => ErrorKind::InvalidInput, + libc::ERROR_INVALID_HANDLE => ErrorKind::MismatchedFileTypeForOperation, + libc::ERROR_INVALID_NAME => ErrorKind::InvalidInput, + libc::ERROR_NOTHING_TO_TERMINATE => ErrorKind::InvalidInput, + libc::ERROR_NO_DATA => ErrorKind::BrokenPipe, + libc::ERROR_OPERATION_ABORTED => ErrorKind::TimedOut, + + libc::WSAEACCES => ErrorKind::PermissionDenied, + libc::WSAEADDRINUSE => ErrorKind::ConnectionRefused, + libc::WSAEADDRNOTAVAIL => ErrorKind::ConnectionRefused, + libc::WSAECONNABORTED => ErrorKind::ConnectionAborted, + libc::WSAECONNREFUSED => ErrorKind::ConnectionRefused, + libc::WSAECONNRESET => ErrorKind::ConnectionReset, + libc::WSAEINVAL => ErrorKind::InvalidInput, + libc::WSAENOTCONN => ErrorKind::NotConnected, + libc::WSAEWOULDBLOCK => ErrorKind::ResourceUnavailable, + + _ => ErrorKind::Other, + } +} + + #[inline] pub fn retry(f: F) -> I where F: FnOnce() -> I { f() } // PR rust-lang/rust/#17020 -- cgit 1.4.1-3-g733a5 From c6aaea67250d32bde3ef26b7c864c3d6c1e05726 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Wed, 4 Feb 2015 00:15:28 +0530 Subject: Fix some missing stability attrs --- src/libstd/thread_local/mod.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libstd') diff --git a/src/libstd/thread_local/mod.rs b/src/libstd/thread_local/mod.rs index d4d777789dd..9de5fd1c770 100644 --- a/src/libstd/thread_local/mod.rs +++ b/src/libstd/thread_local/mod.rs @@ -45,6 +45,7 @@ pub mod scoped; // Sure wish we had macro hygiene, no? #[doc(hidden)] +#[stable(feature = "rust1", since = "1.0.0")] pub mod __impl { pub use super::imp::Key as KeyInner; pub use super::imp::destroy_value; -- cgit 1.4.1-3-g733a5 From 3e39f0bc0e96f76610104918edaa5912f4f352df Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Thu, 29 Jan 2015 14:03:36 -0800 Subject: Rename std::path to std::old_path As part of [RFC 474](https://github.com/rust-lang/rfcs/pull/474), this commit renames `std::path` to `std::old_path`, leaving the existing path API in place to ease migration to the new one. Updating should be as simple as adjusting imports, and the prelude still maps to the old path APIs for now. [breaking-change] --- src/libcore/error.rs | 2 +- src/libgraphviz/maybe_owned_vec.rs | 2 +- src/librustc_back/target/mod.rs | 2 +- src/librustc_trans/trans/debuginfo.rs | 2 +- src/librustdoc/clean/mod.rs | 2 +- src/libserialize/serialize.rs | 18 +- src/libstd/env.rs | 2 +- src/libstd/lib.rs | 1 + src/libstd/old_io/fs.rs | 6 +- src/libstd/old_io/net/pipe.rs | 2 +- src/libstd/old_io/process.rs | 2 +- src/libstd/old_io/tempfile.rs | 2 +- src/libstd/old_path/mod.rs | 923 +++++++++++++ src/libstd/old_path/posix.rs | 1348 ++++++++++++++++++ src/libstd/old_path/windows.rs | 2329 ++++++++++++++++++++++++++++++++ src/libstd/os.rs | 8 +- src/libstd/path/mod.rs | 923 ------------- src/libstd/path/posix.rs | 1348 ------------------ src/libstd/path/windows.rs | 2329 -------------------------------- src/libstd/prelude/v1.rs | 2 +- src/libstd/rand/os.rs | 2 +- src/libstd/sys/common/mod.rs | 2 +- src/libstd/sys/unix/process.rs | 2 +- src/libstd/sys/windows/backtrace.rs | 2 +- src/libstd/sys/windows/process.rs | 2 +- src/libsyntax/parse/token.rs | 2 +- src/test/compile-fail/range-3.rs | 2 +- src/test/compile-fail/range-4.rs | 2 +- src/test/debuginfo/associated-types.rs | 2 +- 29 files changed, 4636 insertions(+), 4635 deletions(-) create mode 100644 src/libstd/old_path/mod.rs create mode 100644 src/libstd/old_path/posix.rs create mode 100644 src/libstd/old_path/windows.rs delete mode 100644 src/libstd/path/mod.rs delete mode 100644 src/libstd/path/posix.rs delete mode 100644 src/libstd/path/windows.rs (limited to 'src/libstd') diff --git a/src/libcore/error.rs b/src/libcore/error.rs index 71d5e88cccf..161f6c78921 100644 --- a/src/libcore/error.rs +++ b/src/libcore/error.rs @@ -51,7 +51,7 @@ //! use std::error::FromError; //! use std::old_io::{File, IoError}; //! use std::os::{MemoryMap, MapError}; -//! use std::path::Path; +//! use std::old_path::Path; //! //! enum MyError { //! Io(IoError), diff --git a/src/libgraphviz/maybe_owned_vec.rs b/src/libgraphviz/maybe_owned_vec.rs index 71f11783593..1c931856fa1 100644 --- a/src/libgraphviz/maybe_owned_vec.rs +++ b/src/libgraphviz/maybe_owned_vec.rs @@ -17,7 +17,7 @@ use std::cmp::Ordering; use std::default::Default; use std::fmt; use std::iter::FromIterator; -use std::path::BytesContainer; +use std::old_path::BytesContainer; use std::slice; // Note 1: It is not clear whether the flexibility of providing both diff --git a/src/librustc_back/target/mod.rs b/src/librustc_back/target/mod.rs index 4b3833b687c..bffee9d4933 100644 --- a/src/librustc_back/target/mod.rs +++ b/src/librustc_back/target/mod.rs @@ -306,7 +306,7 @@ impl Target { use std::env; use std::ffi::OsString; use std::old_io::File; - use std::path::Path; + use std::old_path::Path; use serialize::json; fn load_file(path: &Path) -> Result { diff --git a/src/librustc_trans/trans/debuginfo.rs b/src/librustc_trans/trans/debuginfo.rs index 66bb299273d..0ced4066f62 100644 --- a/src/librustc_trans/trans/debuginfo.rs +++ b/src/librustc_trans/trans/debuginfo.rs @@ -1590,7 +1590,7 @@ fn compile_unit_metadata(cx: &CrateContext) -> DIDescriptor { Some(ref p) if p.is_relative() => { // prepend "./" if necessary let dotdot = b".."; - let prefix: &[u8] = &[dotdot[0], ::std::path::SEP_BYTE]; + let prefix: &[u8] = &[dotdot[0], ::std::old_path::SEP_BYTE]; let mut path_bytes = p.as_vec().to_vec(); if &path_bytes[..2] != prefix && diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 07679480bfb..57eaf042aa0 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -49,7 +49,7 @@ use rustc::middle::stability; use std::rc::Rc; use std::u32; use std::str::Str as StrTrait; // Conflicts with Str variant -use std::path::Path as FsPath; // Conflicts with Path struct +use std::old_path::Path as FsPath; // Conflicts with Path struct use core::DocContext; use doctree; diff --git a/src/libserialize/serialize.rs b/src/libserialize/serialize.rs index 3d7c91ad188..517907bcf58 100644 --- a/src/libserialize/serialize.rs +++ b/src/libserialize/serialize.rs @@ -14,7 +14,7 @@ Core encoding and decoding interfaces. */ -use std::path; +use std::old_path; use std::rc::Rc; use std::cell::{Cell, RefCell}; use std::sync::Arc; @@ -538,29 +538,29 @@ macro_rules! tuple { tuple! { T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, } -impl Encodable for path::posix::Path { +impl Encodable for old_path::posix::Path { fn encode(&self, e: &mut S) -> Result<(), S::Error> { self.as_vec().encode(e) } } -impl Decodable for path::posix::Path { - fn decode(d: &mut D) -> Result { +impl Decodable for old_path::posix::Path { + fn decode(d: &mut D) -> Result { let bytes: Vec = try!(Decodable::decode(d)); - Ok(path::posix::Path::new(bytes)) + Ok(old_path::posix::Path::new(bytes)) } } -impl Encodable for path::windows::Path { +impl Encodable for old_path::windows::Path { fn encode(&self, e: &mut S) -> Result<(), S::Error> { self.as_vec().encode(e) } } -impl Decodable for path::windows::Path { - fn decode(d: &mut D) -> Result { +impl Decodable for old_path::windows::Path { + fn decode(d: &mut D) -> Result { let bytes: Vec = try!(Decodable::decode(d)); - Ok(path::windows::Path::new(bytes)) + Ok(old_path::windows::Path::new(bytes)) } } diff --git a/src/libstd/env.rs b/src/libstd/env.rs index 5070f8c547a..559a68542ef 100644 --- a/src/libstd/env.rs +++ b/src/libstd/env.rs @@ -57,7 +57,7 @@ pub fn current_dir() -> IoResult { /// /// ```rust /// use std::env; -/// use std::path::Path; +/// use std::old_path::Path; /// /// let root = Path::new("/"); /// assert!(env::set_current_dir(&root).is_ok()); diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 839983d336d..bd4763d7bd4 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -251,6 +251,7 @@ pub mod old_io; pub mod os; pub mod env; pub mod path; +pub mod old_path; pub mod rand; pub mod time; diff --git a/src/libstd/old_io/fs.rs b/src/libstd/old_io/fs.rs index abf215988bb..88ca6667d55 100644 --- a/src/libstd/old_io/fs.rs +++ b/src/libstd/old_io/fs.rs @@ -61,8 +61,8 @@ use old_io; use iter::{Iterator, Extend}; use option::Option; use option::Option::{Some, None}; -use path::{Path, GenericPath}; -use path; +use old_path::{Path, GenericPath}; +use old_path; use result::Result::{Err, Ok}; use slice::SliceExt; use string::String; @@ -782,7 +782,7 @@ pub trait PathExtensions { fn is_dir(&self) -> bool; } -impl PathExtensions for path::Path { +impl PathExtensions for old_path::Path { fn stat(&self) -> IoResult { stat(self) } fn lstat(&self) -> IoResult { lstat(self) } fn exists(&self) -> bool { diff --git a/src/libstd/old_io/net/pipe.rs b/src/libstd/old_io/net/pipe.rs index 0da7670c5b4..8c4a10a55d4 100644 --- a/src/libstd/old_io/net/pipe.rs +++ b/src/libstd/old_io/net/pipe.rs @@ -23,7 +23,7 @@ use prelude::v1::*; use ffi::CString; -use path::BytesContainer; +use old_path::BytesContainer; use old_io::{Listener, Acceptor, IoResult, TimedOut, standard_error}; use sys::pipe::UnixAcceptor as UnixAcceptorImp; use sys::pipe::UnixListener as UnixListenerImp; diff --git a/src/libstd/old_io/process.rs b/src/libstd/old_io/process.rs index 61a07bc8208..27af957e18e 100644 --- a/src/libstd/old_io/process.rs +++ b/src/libstd/old_io/process.rs @@ -25,7 +25,7 @@ use old_io::{IoResult, IoError}; use old_io; use libc; use os; -use path::BytesContainer; +use old_path::BytesContainer; use sync::mpsc::{channel, Receiver}; use sys::fs::FileDesc; use sys::process::Process as ProcessImp; diff --git a/src/libstd/old_io/tempfile.rs b/src/libstd/old_io/tempfile.rs index 83a42549424..a227116dfae 100644 --- a/src/libstd/old_io/tempfile.rs +++ b/src/libstd/old_io/tempfile.rs @@ -17,7 +17,7 @@ use old_io; use ops::Drop; use option::Option::{None, Some}; use option::Option; -use path::{Path, GenericPath}; +use old_path::{Path, GenericPath}; use rand::{Rng, thread_rng}; use result::Result::{Ok, Err}; use str::StrExt; diff --git a/src/libstd/old_path/mod.rs b/src/libstd/old_path/mod.rs new file mode 100644 index 00000000000..0d80258d7e0 --- /dev/null +++ b/src/libstd/old_path/mod.rs @@ -0,0 +1,923 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Cross-platform path support +//! +//! This module implements support for two flavors of paths. `PosixPath` represents a path on any +//! unix-like system, whereas `WindowsPath` represents a path on Windows. This module also exposes +//! a typedef `Path` which is equal to the appropriate platform-specific path variant. +//! +//! Both `PosixPath` and `WindowsPath` implement a trait `GenericPath`, which contains the set of +//! methods that behave the same for both paths. They each also implement some methods that could +//! not be expressed in `GenericPath`, yet behave identically for both path flavors, such as +//! `.components()`. +//! +//! The three main design goals of this module are 1) to avoid unnecessary allocation, 2) to behave +//! the same regardless of which flavor of path is being used, and 3) to support paths that cannot +//! be represented in UTF-8 (as Linux has no restriction on paths beyond disallowing NUL). +//! +//! ## Usage +//! +//! Usage of this module is fairly straightforward. Unless writing platform-specific code, `Path` +//! should be used to refer to the platform-native path. +//! +//! Creation of a path is typically done with either `Path::new(some_str)` or +//! `Path::new(some_vec)`. This path can be modified with `.push()` and `.pop()` (and other +//! setters). The resulting Path can either be passed to another API that expects a path, or can be +//! turned into a `&[u8]` with `.as_vec()` or a `Option<&str>` with `.as_str()`. Similarly, +//! attributes of the path can be queried with methods such as `.filename()`. There are also +//! methods that return a new path instead of modifying the receiver, such as `.join()` or +//! `.dir_path()`. +//! +//! Paths are always kept in normalized form. This means that creating the path +//! `Path::new("a/b/../c")` will return the path `a/c`. Similarly any attempt to mutate the path +//! will always leave it in normalized form. +//! +//! When rendering a path to some form of output, there is a method `.display()` which is +//! compatible with the `format!()` parameter `{}`. This will render the path as a string, +//! replacing all non-utf8 sequences with the Replacement Character (U+FFFD). As such it is not +//! suitable for passing to any API that actually operates on the path; it is only intended for +//! display. +//! +//! ## Example +//! +//! ```rust +//! use std::old_io::fs::PathExtensions; +//! +//! let mut path = Path::new("/tmp/path"); +//! println!("path: {}", path.display()); +//! path.set_filename("foo"); +//! path.push("bar"); +//! println!("new path: {}", path.display()); +//! println!("path exists: {}", path.exists()); +//! ``` + +#![unstable(feature = "path")] + +use core::marker::Sized; +use ffi::CString; +use clone::Clone; +use fmt; +use iter::IteratorExt; +use option::Option; +use option::Option::{None, Some}; +use str; +use str::StrExt; +use string::{String, CowString}; +use slice::SliceExt; +use vec::Vec; + +/// Typedef for POSIX file paths. +/// See `posix::Path` for more info. +pub use self::posix::Path as PosixPath; + +/// Typedef for Windows file paths. +/// See `windows::Path` for more info. +pub use self::windows::Path as WindowsPath; + +/// Typedef for the platform-native path type +#[cfg(unix)] +pub use self::posix::Path as Path; +/// Typedef for the platform-native path type +#[cfg(windows)] +pub use self::windows::Path as Path; + +/// Typedef for the platform-native component iterator +#[cfg(unix)] +pub use self::posix::Components as Components; +/// Typedef for the platform-native component iterator +#[cfg(windows)] +pub use self::windows::Components as Components; + +/// Typedef for the platform-native str component iterator +#[cfg(unix)] +pub use self::posix::StrComponents as StrComponents; +/// Typedef for the platform-native str component iterator +#[cfg(windows)] +pub use self::windows::StrComponents as StrComponents; + +/// Alias for the platform-native separator character. +#[cfg(unix)] +pub use self::posix::SEP as SEP; +/// Alias for the platform-native separator character. +#[cfg(windows)] +pub use self::windows::SEP as SEP; + +/// Alias for the platform-native separator byte. +#[cfg(unix)] +pub use self::posix::SEP_BYTE as SEP_BYTE; +/// Alias for the platform-native separator byte. +#[cfg(windows)] +pub use self::windows::SEP_BYTE as SEP_BYTE; + +/// Typedef for the platform-native separator char func +#[cfg(unix)] +pub use self::posix::is_sep as is_sep; +/// Typedef for the platform-native separator char func +#[cfg(windows)] +pub use self::windows::is_sep as is_sep; +/// Typedef for the platform-native separator byte func +#[cfg(unix)] +pub use self::posix::is_sep_byte as is_sep_byte; +/// Typedef for the platform-native separator byte func +#[cfg(windows)] +pub use self::windows::is_sep_byte as is_sep_byte; + +pub mod posix; +pub mod windows; + +/// A trait that represents the generic operations available on paths +pub trait GenericPath: Clone + GenericPathUnsafe { + /// Creates a new Path from a byte vector or string. + /// The resulting Path will always be normalized. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let path = Path::new("foo/bar"); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the path contains a NUL. + /// + /// See individual Path impls for additional restrictions. + #[inline] + fn new(path: T) -> Self { + assert!(!contains_nul(&path)); + unsafe { GenericPathUnsafe::new_unchecked(path) } + } + + /// Creates a new Path from a byte vector or string, if possible. + /// The resulting Path will always be normalized. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let x: &[u8] = b"foo\0"; + /// assert!(Path::new_opt(x).is_none()); + /// # } + /// ``` + #[inline] + fn new_opt(path: T) -> Option { + if contains_nul(&path) { + None + } else { + Some(unsafe { GenericPathUnsafe::new_unchecked(path) }) + } + } + + /// Returns the path as a string, if possible. + /// If the path is not representable in utf-8, this returns None. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("/abc/def"); + /// assert_eq!(p.as_str(), Some("/abc/def")); + /// # } + /// ``` + #[inline] + fn as_str<'a>(&'a self) -> Option<&'a str> { + str::from_utf8(self.as_vec()).ok() + } + + /// Returns the path as a byte vector + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def"); + /// assert_eq!(p.as_vec(), b"abc/def"); + /// # } + /// ``` + fn as_vec<'a>(&'a self) -> &'a [u8]; + + /// Converts the Path into an owned byte vector + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def"); + /// assert_eq!(p.into_vec(), b"abc/def".to_vec()); + /// // attempting to use p now results in "error: use of moved value" + /// # } + /// ``` + fn into_vec(self) -> Vec; + + /// Returns an object that implements `Show` for printing paths + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def"); + /// println!("{}", p.display()); // prints "abc/def" + /// # } + /// ``` + fn display<'a>(&'a self) -> Display<'a, Self> { + Display{ path: self, filename: false } + } + + /// Returns an object that implements `Show` for printing filenames + /// + /// If there is no filename, nothing will be printed. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def"); + /// println!("{}", p.filename_display()); // prints "def" + /// # } + /// ``` + fn filename_display<'a>(&'a self) -> Display<'a, Self> { + Display{ path: self, filename: true } + } + + /// Returns the directory component of `self`, as a byte vector (with no trailing separator). + /// If `self` has no directory component, returns ['.']. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def/ghi"); + /// assert_eq!(p.dirname(), b"abc/def"); + /// # } + /// ``` + fn dirname<'a>(&'a self) -> &'a [u8]; + + /// Returns the directory component of `self`, as a string, if possible. + /// See `dirname` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def/ghi"); + /// assert_eq!(p.dirname_str(), Some("abc/def")); + /// # } + /// ``` + #[inline] + fn dirname_str<'a>(&'a self) -> Option<&'a str> { + str::from_utf8(self.dirname()).ok() + } + + /// Returns the file component of `self`, as a byte vector. + /// If `self` represents the root of the file hierarchy, returns None. + /// If `self` is "." or "..", returns None. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def/ghi"); + /// assert_eq!(p.filename(), Some(b"ghi")); + /// # } + /// ``` + fn filename<'a>(&'a self) -> Option<&'a [u8]>; + + /// Returns the file component of `self`, as a string, if possible. + /// See `filename` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def/ghi"); + /// assert_eq!(p.filename_str(), Some("ghi")); + /// # } + /// ``` + #[inline] + fn filename_str<'a>(&'a self) -> Option<&'a str> { + self.filename().and_then(|s| str::from_utf8(s).ok()) + } + + /// 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. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("/abc/def.txt"); + /// assert_eq!(p.filestem(), Some(b"def")); + /// # } + /// ``` + fn filestem<'a>(&'a self) -> Option<&'a [u8]> { + match self.filename() { + None => None, + Some(name) => Some({ + let dot = b'.'; + match name.rposition_elem(&dot) { + None | Some(0) => name, + Some(1) if name == b".." => name, + Some(pos) => &name[..pos] + } + }) + } + } + + /// Returns the stem of the filename of `self`, as a string, if possible. + /// See `filestem` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("/abc/def.txt"); + /// assert_eq!(p.filestem_str(), Some("def")); + /// # } + /// ``` + #[inline] + fn filestem_str<'a>(&'a self) -> Option<&'a str> { + self.filestem().and_then(|s| str::from_utf8(s).ok()) + } + + /// 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. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def.txt"); + /// assert_eq!(p.extension(), Some(b"txt")); + /// # } + /// ``` + fn extension<'a>(&'a self) -> Option<&'a [u8]> { + match self.filename() { + None => None, + Some(name) => { + let dot = b'.'; + match name.rposition_elem(&dot) { + None | Some(0) => None, + Some(1) if name == b".." => None, + Some(pos) => Some(&name[pos+1..]) + } + } + } + } + + /// Returns the extension of the filename of `self`, as a string, if possible. + /// See `extension` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def.txt"); + /// assert_eq!(p.extension_str(), Some("txt")); + /// # } + /// ``` + #[inline] + fn extension_str<'a>(&'a self) -> Option<&'a str> { + self.extension().and_then(|s| str::from_utf8(s).ok()) + } + + /// Replaces the filename portion of the path with the given byte vector or string. + /// If the replacement name is [], this is equivalent to popping the path. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("abc/def.txt"); + /// p.set_filename("foo.dat"); + /// assert!(p == Path::new("abc/foo.dat")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the filename contains a NUL. + #[inline] + fn set_filename(&mut self, filename: T) { + assert!(!contains_nul(&filename)); + unsafe { self.set_filename_unchecked(filename) } + } + + /// Replaces the extension with the given byte vector or string. + /// If there is no extension in `self`, this adds one. + /// If the argument is [] or "", this removes the extension. + /// If `self` has no filename, this is a no-op. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("abc/def.txt"); + /// p.set_extension("csv"); + /// assert_eq!(p, Path::new("abc/def.csv")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the extension contains a NUL. + fn set_extension(&mut self, extension: T) { + assert!(!contains_nul(&extension)); + + let val = self.filename().and_then(|name| { + let dot = b'.'; + let extlen = extension.container_as_bytes().len(); + match (name.rposition_elem(&dot), extlen) { + (None, 0) | (Some(0), 0) => None, + (Some(idx), 0) => Some(name[..idx].to_vec()), + (idx, extlen) => { + let idx = match idx { + None | Some(0) => name.len(), + Some(val) => val + }; + + let mut v; + v = Vec::with_capacity(idx + extlen + 1); + v.push_all(&name[..idx]); + v.push(dot); + v.push_all(extension.container_as_bytes()); + Some(v) + } + } + }); + + match val { + None => (), + Some(v) => unsafe { self.set_filename_unchecked(v) } + } + } + + /// Returns a new Path constructed by replacing the filename with the given + /// byte vector or string. + /// See `set_filename` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("abc/def.txt"); + /// assert_eq!(p.with_filename("foo.dat"), Path::new("abc/foo.dat")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the filename contains a NUL. + #[inline] + fn with_filename(&self, filename: T) -> Self { + let mut p = self.clone(); + p.set_filename(filename); + p + } + + /// Returns a new Path constructed by setting the extension to the given + /// byte vector or string. + /// See `set_extension` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("abc/def.txt"); + /// assert_eq!(p.with_extension("csv"), Path::new("abc/def.csv")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the extension contains a NUL. + #[inline] + fn with_extension(&self, extension: T) -> Self { + let mut p = self.clone(); + p.set_extension(extension); + p + } + + /// Returns the directory component of `self`, as a Path. + /// If `self` represents the root of the filesystem hierarchy, returns `self`. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def/ghi"); + /// assert_eq!(p.dir_path(), Path::new("abc/def")); + /// # } + /// ``` + fn dir_path(&self) -> Self { + // self.dirname() returns a NUL-free vector + unsafe { GenericPathUnsafe::new_unchecked(self.dirname()) } + } + + /// Returns a Path that represents the filesystem root that `self` is rooted in. + /// + /// If `self` is not absolute, or vol/cwd-relative in the case of Windows, this returns None. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// assert_eq!(Path::new("abc/def").root_path(), None); + /// assert_eq!(Path::new("/abc/def").root_path(), Some(Path::new("/"))); + /// # } + /// ``` + fn root_path(&self) -> Option; + + /// Pushes a path (as a byte vector or string) onto `self`. + /// If the argument represents an absolute path, it replaces `self`. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("foo/bar"); + /// p.push("baz.txt"); + /// assert_eq!(p, Path::new("foo/bar/baz.txt")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the path contains a NUL. + #[inline] + fn push(&mut self, path: T) { + assert!(!contains_nul(&path)); + unsafe { self.push_unchecked(path) } + } + + /// Pushes multiple paths (as byte vectors or strings) onto `self`. + /// See `push` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("foo"); + /// p.push_many(&["bar", "baz.txt"]); + /// assert_eq!(p, Path::new("foo/bar/baz.txt")); + /// # } + /// ``` + #[inline] + fn push_many(&mut self, paths: &[T]) { + let t: Option<&T> = None; + if BytesContainer::is_str(t) { + for p in paths { + self.push(p.container_as_str().unwrap()) + } + } else { + for p in paths { + self.push(p.container_as_bytes()) + } + } + } + + /// Removes the last path component from the receiver. + /// Returns `true` if the receiver was modified, or `false` if it already + /// represented the root of the file hierarchy. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let mut p = Path::new("foo/bar/baz.txt"); + /// p.pop(); + /// assert_eq!(p, Path::new("foo/bar")); + /// # } + /// ``` + fn pop(&mut self) -> bool; + + /// Returns a new Path constructed by joining `self` with the given path + /// (as a byte vector or string). + /// If the given path is absolute, the new Path will represent just that. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("/foo"); + /// assert_eq!(p.join("bar.txt"), Path::new("/foo/bar.txt")); + /// # } + /// ``` + /// + /// # Panics + /// + /// Panics the task if the path contains a NUL. + #[inline] + fn join(&self, path: T) -> Self { + let mut p = self.clone(); + p.push(path); + p + } + + /// Returns a new Path constructed by joining `self` with the given paths + /// (as byte vectors or strings). + /// See `join` for details. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("foo"); + /// let fbbq = Path::new("foo/bar/baz/quux.txt"); + /// assert_eq!(p.join_many(&["bar", "baz", "quux.txt"]), fbbq); + /// # } + /// ``` + #[inline] + fn join_many(&self, paths: &[T]) -> Self { + let mut p = self.clone(); + p.push_many(paths); + p + } + + /// Returns whether `self` represents an absolute path. + /// An absolute path is defined as one that, when joined to another path, will + /// yield back the same absolute path. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("/abc/def"); + /// assert!(p.is_absolute()); + /// # } + /// ``` + fn is_absolute(&self) -> bool; + + /// Returns whether `self` represents a relative path. + /// Typically this is the inverse of `is_absolute`. + /// But for Windows paths, it also means the path is not volume-relative or + /// relative to the current working directory. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("abc/def"); + /// assert!(p.is_relative()); + /// # } + /// ``` + fn is_relative(&self) -> bool { + !self.is_absolute() + } + + /// 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. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("foo/bar/baz/quux.txt"); + /// let fb = Path::new("foo/bar"); + /// let bq = Path::new("baz/quux.txt"); + /// assert!(fb.is_ancestor_of(&p)); + /// # } + /// ``` + 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. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("foo/bar/baz/quux.txt"); + /// let fb = Path::new("foo/bar"); + /// let bq = Path::new("baz/quux.txt"); + /// assert_eq!(p.path_relative_from(&fb), Some(bq)); + /// # } + /// ``` + fn path_relative_from(&self, base: &Self) -> Option; + + /// Returns whether the relative path `child` is a suffix of `self`. + /// + /// # Example + /// + /// ``` + /// # foo(); + /// # #[cfg(windows)] fn foo() {} + /// # #[cfg(unix)] fn foo() { + /// let p = Path::new("foo/bar/baz/quux.txt"); + /// let bq = Path::new("baz/quux.txt"); + /// assert!(p.ends_with_path(&bq)); + /// # } + /// ``` + fn ends_with_path(&self, child: &Self) -> bool; +} + +/// A trait that represents something bytes-like (e.g. a &[u8] or a &str) +pub trait BytesContainer { + /// Returns a &[u8] representing the receiver + fn container_as_bytes<'a>(&'a self) -> &'a [u8]; + /// Returns the receiver interpreted as a utf-8 string, if possible + #[inline] + fn container_as_str<'a>(&'a self) -> Option<&'a str> { + str::from_utf8(self.container_as_bytes()).ok() + } + /// Returns whether .container_as_str() is guaranteed to not fail + // FIXME (#8888): Remove unused arg once :: works + #[inline] + fn is_str(_: Option<&Self>) -> bool { false } +} + +/// A trait that represents the unsafe operations on GenericPaths +pub trait GenericPathUnsafe { + /// Creates a new Path without checking for null bytes. + /// The resulting Path will always be normalized. + unsafe fn new_unchecked(path: T) -> Self; + + /// Replaces the filename portion of the path without checking for null + /// bytes. + /// See `set_filename` for details. + unsafe fn set_filename_unchecked(&mut self, filename: T); + + /// Pushes a path onto `self` without checking for null bytes. + /// See `push` for details. + unsafe fn push_unchecked(&mut self, path: T); +} + +/// Helper struct for printing paths with format!() +pub struct Display<'a, P:'a> { + path: &'a P, + filename: bool +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl<'a, P: GenericPath> fmt::Debug for Display<'a, P> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.as_cow(), f) + } +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl<'a, P: GenericPath> fmt::Display for Display<'a, P> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_cow().fmt(f) + } +} + +impl<'a, P: GenericPath> Display<'a, P> { + /// Returns the path as a possibly-owned string. + /// + /// If the path is not UTF-8, invalid sequences will be replaced with the + /// Unicode replacement char. This involves allocation. + #[inline] + pub fn as_cow(&self) -> CowString<'a> { + String::from_utf8_lossy(if self.filename { + match self.path.filename() { + None => { + let result: &[u8] = &[]; + result + } + Some(v) => v + } + } else { + self.path.as_vec() + }) + } +} + +impl BytesContainer for str { + #[inline] + fn container_as_bytes(&self) -> &[u8] { + self.as_bytes() + } + #[inline] + fn container_as_str(&self) -> Option<&str> { + Some(self) + } + #[inline] + fn is_str(_: Option<&str>) -> bool { true } +} + +impl BytesContainer for String { + #[inline] + fn container_as_bytes(&self) -> &[u8] { + self.as_bytes() + } + #[inline] + fn container_as_str(&self) -> Option<&str> { + Some(&self[]) + } + #[inline] + fn is_str(_: Option<&String>) -> bool { true } +} + +impl BytesContainer for [u8] { + #[inline] + fn container_as_bytes(&self) -> &[u8] { + self + } +} + +impl BytesContainer for Vec { + #[inline] + fn container_as_bytes(&self) -> &[u8] { + &self[] + } +} + +impl BytesContainer for CString { + #[inline] + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + self.as_bytes() + } +} + +impl<'a, T: ?Sized + BytesContainer> BytesContainer for &'a T { + #[inline] + fn container_as_bytes(&self) -> &[u8] { + (**self).container_as_bytes() + } + #[inline] + fn container_as_str(&self) -> Option<&str> { + (**self).container_as_str() + } + #[inline] + fn is_str(_: Option<& &'a T>) -> bool { BytesContainer::is_str(None::<&T>) } +} + +#[inline(always)] +fn contains_nul(v: &T) -> bool { + v.container_as_bytes().iter().any(|&x| x == 0) +} diff --git a/src/libstd/old_path/posix.rs b/src/libstd/old_path/posix.rs new file mode 100644 index 00000000000..8bcdd89623d --- /dev/null +++ b/src/libstd/old_path/posix.rs @@ -0,0 +1,1348 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! POSIX file path handling + +use clone::Clone; +use cmp::{Ordering, Eq, Ord, PartialEq, PartialOrd}; +use fmt; +use hash; +use old_io::Writer; +use iter::{AdditiveIterator, Extend}; +use iter::{Iterator, IteratorExt, Map}; +use marker::Sized; +use option::Option::{self, Some, None}; +use result::Result::{self, Ok, Err}; +use slice::{AsSlice, Split, SliceExt, SliceConcatExt}; +use str::{self, FromStr, StrExt}; +use vec::Vec; + +use super::{BytesContainer, GenericPath, GenericPathUnsafe}; + +/// Iterator that yields successive components of a Path as &[u8] +pub type Components<'a> = Split<'a, u8, fn(&u8) -> bool>; + +/// Iterator that yields successive components of a Path as Option<&str> +pub type StrComponents<'a> = + Map, fn(&[u8]) -> Option<&str>>; + +/// Represents a POSIX file path +#[derive(Clone)] +pub struct Path { + repr: Vec, // assumed to never be empty or contain NULs + sepidx: Option // index of the final separator in repr +} + +/// The standard path separator character +pub const SEP: char = '/'; + +/// The standard path separator byte +pub const SEP_BYTE: u8 = SEP as u8; + +/// Returns whether the given byte is a path separator +#[inline] +pub fn is_sep_byte(u: &u8) -> bool { + *u as char == SEP +} + +/// Returns whether the given char is a path separator +#[inline] +pub fn is_sep(c: char) -> bool { + c == SEP +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.display(), f) + } +} + +impl PartialEq for Path { + #[inline] + fn eq(&self, other: &Path) -> bool { + self.repr == other.repr + } +} + +impl Eq for Path {} + +impl PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Path { + fn cmp(&self, other: &Path) -> Ordering { + self.repr.cmp(&other.repr) + } +} + +impl FromStr for Path { + type Err = ParsePathError; + fn from_str(s: &str) -> Result { + match Path::new_opt(s) { + Some(p) => Ok(p), + None => Err(ParsePathError), + } + } +} + +/// Valuelue indicating that a path could not be parsed from a string. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct ParsePathError; + +impl hash::Hash for Path { + #[inline] + fn hash(&self, state: &mut S) { + self.repr.hash(state) + } +} + +impl BytesContainer for Path { + #[inline] + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + self.as_vec() + } +} + +impl GenericPathUnsafe for Path { + unsafe fn new_unchecked(path: T) -> Path { + let path = Path::normalize(path.container_as_bytes()); + assert!(!path.is_empty()); + let idx = path.rposition_elem(&SEP_BYTE); + Path{ repr: path, sepidx: idx } + } + + unsafe fn set_filename_unchecked(&mut self, filename: T) { + let filename = filename.container_as_bytes(); + match self.sepidx { + None if b".." == self.repr => { + let mut v = Vec::with_capacity(3 + filename.len()); + v.push_all(dot_dot_static); + v.push(SEP_BYTE); + v.push_all(filename); + // FIXME: this is slow + self.repr = Path::normalize(v.as_slice()); + } + None => { + self.repr = Path::normalize(filename); + } + Some(idx) if &self.repr[idx+1..] == b".." => { + let mut v = Vec::with_capacity(self.repr.len() + 1 + filename.len()); + v.push_all(self.repr.as_slice()); + v.push(SEP_BYTE); + v.push_all(filename); + // FIXME: this is slow + self.repr = Path::normalize(v.as_slice()); + } + Some(idx) => { + let mut v = Vec::with_capacity(idx + 1 + filename.len()); + v.push_all(&self.repr[..idx+1]); + v.push_all(filename); + // FIXME: this is slow + self.repr = Path::normalize(v.as_slice()); + } + } + self.sepidx = self.repr.rposition_elem(&SEP_BYTE); + } + + unsafe fn push_unchecked(&mut self, path: T) { + let path = path.container_as_bytes(); + if !path.is_empty() { + if path[0] == SEP_BYTE { + self.repr = Path::normalize(path); + } else { + let mut v = Vec::with_capacity(self.repr.len() + path.len() + 1); + v.push_all(self.repr.as_slice()); + v.push(SEP_BYTE); + v.push_all(path); + // FIXME: this is slow + self.repr = Path::normalize(v.as_slice()); + } + self.sepidx = self.repr.rposition_elem(&SEP_BYTE); + } + } +} + +impl GenericPath for Path { + #[inline] + fn as_vec<'a>(&'a self) -> &'a [u8] { + self.repr.as_slice() + } + + fn into_vec(self) -> Vec { + self.repr + } + + fn dirname<'a>(&'a self) -> &'a [u8] { + match self.sepidx { + None if b".." == self.repr => self.repr.as_slice(), + None => dot_static, + Some(0) => &self.repr[..1], + Some(idx) if &self.repr[idx+1..] == b".." => self.repr.as_slice(), + Some(idx) => &self.repr[..idx] + } + } + + fn filename<'a>(&'a self) -> Option<&'a [u8]> { + match self.sepidx { + None if b"." == self.repr || + b".." == self.repr => None, + None => Some(self.repr.as_slice()), + Some(idx) if &self.repr[idx+1..] == b".." => None, + Some(0) if self.repr[1..].is_empty() => None, + Some(idx) => Some(&self.repr[idx+1..]) + } + } + + fn pop(&mut self) -> bool { + match self.sepidx { + None if b"." == self.repr => false, + None => { + self.repr = vec![b'.']; + self.sepidx = None; + true + } + Some(0) if b"/" == self.repr => false, + Some(idx) => { + if idx == 0 { + self.repr.truncate(idx+1); + } else { + self.repr.truncate(idx); + } + self.sepidx = self.repr.rposition_elem(&SEP_BYTE); + true + } + } + } + + fn root_path(&self) -> Option { + if self.is_absolute() { + Some(Path::new("/")) + } else { + None + } + } + + #[inline] + fn is_absolute(&self) -> bool { + self.repr[0] == SEP_BYTE + } + + fn is_ancestor_of(&self, other: &Path) -> bool { + if self.is_absolute() != other.is_absolute() { + false + } else { + let mut ita = self.components(); + let mut itb = other.components(); + if b"." == self.repr { + return match itb.next() { + None => true, + Some(b) => b != b".." + }; + } + loop { + match (ita.next(), itb.next()) { + (None, _) => break, + (Some(a), Some(b)) if a == b => { continue }, + (Some(a), _) if a == b".." => { + // if ita contains only .. components, it's an ancestor + return ita.all(|x| x == b".."); + } + _ => return false + } + } + true + } + } + + fn path_relative_from(&self, base: &Path) -> Option { + if self.is_absolute() != base.is_absolute() { + if self.is_absolute() { + Some(self.clone()) + } else { + None + } + } else { + let mut ita = self.components(); + let mut itb = base.components(); + let mut comps = vec![]; + loop { + match (ita.next(), itb.next()) { + (None, None) => break, + (Some(a), None) => { + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + (None, _) => comps.push(dot_dot_static), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(b)) if b == b"." => comps.push(a), + (Some(_), Some(b)) if b == b".." => return None, + (Some(a), Some(_)) => { + comps.push(dot_dot_static); + for _ in itb { + comps.push(dot_dot_static); + } + comps.push(a); + comps.extend(ita.by_ref()); + break; + } + } + } + Some(Path::new(comps.connect(&SEP_BYTE))) + } + } + + fn ends_with_path(&self, child: &Path) -> bool { + if !child.is_relative() { return false; } + let mut selfit = self.components().rev(); + let mut childit = child.components().rev(); + loop { + match (selfit.next(), childit.next()) { + (Some(a), Some(b)) => if a != b { return false; }, + (Some(_), None) => break, + (None, Some(_)) => return false, + (None, None) => break + } + } + true + } +} + +impl Path { + /// Returns a new Path from a byte vector or string + /// + /// # Panics + /// + /// Panics the task if the vector contains a NUL. + #[inline] + pub fn new(path: T) -> Path { + GenericPath::new(path) + } + + /// Returns a new Path from a byte vector or string, if possible + #[inline] + pub fn new_opt(path: T) -> Option { + GenericPath::new_opt(path) + } + + /// Returns a normalized byte vector representation of a path, by removing all empty + /// components, and unnecessary . and .. components. + fn normalize>(v: &V) -> Vec { + // borrowck is being very picky + let val = { + let is_abs = !v.as_slice().is_empty() && v.as_slice()[0] == SEP_BYTE; + let v_ = if is_abs { &v.as_slice()[1..] } else { v.as_slice() }; + let comps = normalize_helper(v_, is_abs); + match comps { + None => None, + Some(comps) => { + if is_abs && comps.is_empty() { + Some(vec![SEP_BYTE]) + } 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.into_iter(); + if !is_abs { + match it.next() { + None => (), + Some(comp) => v.push_all(comp) + } + } + for comp in it { + v.push(SEP_BYTE); + v.push_all(comp); + } + Some(v) + } + } + } + }; + match val { + None => v.as_slice().to_vec(), + 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 components<'a>(&'a self) -> Components<'a> { + let v = if self.repr[0] == SEP_BYTE { + &self.repr[1..] + } else { self.repr.as_slice() }; + let is_sep_byte: fn(&u8) -> bool = is_sep_byte; // coerce to fn ptr + let mut ret = v.split(is_sep_byte); + if v.is_empty() { + // consume the empty "" component + ret.next(); + } + ret + } + + /// Returns an iterator that yields each component of the path as Option<&str>. + /// See components() for details. + pub fn str_components<'a>(&'a self) -> StrComponents<'a> { + fn from_utf8(s: &[u8]) -> Option<&str> { + str::from_utf8(s).ok() + } + let f: fn(&[u8]) -> Option<&str> = from_utf8; // coerce to fn ptr + self.components().map(f) + } +} + +// None result means the byte vector didn't need normalizing +fn normalize_helper<'a>(v: &'a [u8], is_abs: bool) -> Option> { + if is_abs && v.is_empty() { + return None; + } + let mut comps: Vec<&'a [u8]> = vec![]; + let mut n_up = 0u; + let mut changed = false; + for comp in v.split(is_sep_byte) { + if comp.is_empty() { changed = true } + else if comp == b"." { changed = true } + else if comp == b".." { + 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().unwrap(); changed = true } + } else { comps.push(comp) } + } + if changed { + if comps.is_empty() && !is_abs { + if v == b"." { + return None; + } + comps.push(dot_static); + } + Some(comps) + } else { + None + } +} + +#[allow(non_upper_case_globals)] +static dot_static: &'static [u8] = b"."; +#[allow(non_upper_case_globals)] +static dot_dot_static: &'static [u8] = b".."; + +#[cfg(test)] +mod tests { + use super::*; + + use clone::Clone; + use iter::IteratorExt; + use option::Option::{self, Some, None}; + use old_path::GenericPath; + use slice::{AsSlice, SliceExt}; + use str::{self, Str, StrExt}; + use string::ToString; + use vec::Vec; + + 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); + } + ) + } + + #[test] + fn test_paths() { + let empty: &[u8] = &[]; + t!(v: Path::new(empty), 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\xFF"), b"a/b/c\xFF"); + t!(v: Path::new(b"\xFF/../foo\x80"), b"foo\x80"); + let p = Path::new(b"a/b/c\xFF"); + assert!(p.as_str().is_none()); + + t!(s: Path::new(""), "."); + t!(s: Path::new("/"), "/"); + t!(s: Path::new("hi"), "hi"); + t!(s: Path::new("hi/"), "hi"); + t!(s: Path::new("/lib"), "/lib"); + t!(s: Path::new("/lib/"), "/lib"); + t!(s: Path::new("hi/there"), "hi/there"); + t!(s: Path::new("hi/there.txt"), "hi/there.txt"); + + t!(s: Path::new("hi/there/"), "hi/there"); + t!(s: Path::new("hi/../there"), "there"); + t!(s: Path::new("../hi/there"), "../hi/there"); + t!(s: Path::new("/../hi/there"), "/hi/there"); + t!(s: Path::new("foo/.."), "."); + t!(s: Path::new("/foo/.."), "/"); + t!(s: Path::new("/foo/../.."), "/"); + t!(s: Path::new("/foo/../../bar"), "/bar"); + t!(s: Path::new("/./hi/./there/."), "/hi/there"); + t!(s: Path::new("/./hi/./there/./.."), "/hi"); + t!(s: Path::new("foo/../.."), ".."); + t!(s: Path::new("foo/../../.."), "../.."); + t!(s: Path::new("foo/../../bar"), "../bar"); + + assert_eq!(Path::new(b"foo/bar").into_vec(), b"foo/bar"); + assert_eq!(Path::new(b"/foo/../../bar").into_vec(), + b"/bar"); + + let p = Path::new(b"foo/bar\x80"); + assert!(p.as_str().is_none()); + } + + #[test] + fn test_opt_paths() { + assert!(Path::new_opt(b"foo/bar\0").is_none()); + t!(v: Path::new_opt(b"foo/bar").unwrap(), b"foo/bar"); + assert!(Path::new_opt("foo/bar\0").is_none()); + t!(s: Path::new_opt("foo/bar").unwrap(), "foo/bar"); + } + + #[test] + fn test_null_byte() { + use thread::Thread; + let result = Thread::scoped(move|| { + Path::new(b"foo/bar\0") + }).join(); + assert!(result.is_err()); + + let result = Thread::scoped(move|| { + Path::new("test").set_filename(b"f\0o") + }).join(); + assert!(result.is_err()); + + let result = Thread::scoped(move|| { + Path::new("test").push(b"f\0o"); + }).join(); + assert!(result.is_err()); + } + + #[test] + fn test_display_str() { + macro_rules! t { + ($path:expr, $disp:ident, $exp:expr) => ( + { + let path = Path::new($path); + assert_eq!(path.$disp().to_string(), $exp); + } + ) + } + t!("foo", display, "foo"); + t!(b"foo\x80", display, "foo\u{FFFD}"); + t!(b"foo\xFFbar", display, "foo\u{FFFD}bar"); + t!(b"foo\xFF/bar", filename_display, "bar"); + t!(b"foo/\xFFbar", filename_display, "\u{FFFD}bar"); + t!(b"/", filename_display, ""); + + macro_rules! t { + ($path:expr, $exp:expr) => ( + { + let path = Path::new($path); + let mo = path.display().as_cow(); + assert_eq!(mo.as_slice(), $exp); + } + ); + ($path:expr, $exp:expr, filename) => ( + { + let path = Path::new($path); + let mo = path.filename_display().as_cow(); + assert_eq!(mo.as_slice(), $exp); + } + ) + } + + t!("foo", "foo"); + t!(b"foo\x80", "foo\u{FFFD}"); + t!(b"foo\xFFbar", "foo\u{FFFD}bar"); + t!(b"foo\xFF/bar", "bar", filename); + t!(b"foo/\xFFbar", "\u{FFFD}bar", filename); + t!(b"/", "", filename); + } + + #[test] + fn test_display() { + macro_rules! t { + ($path:expr, $exp:expr, $expf:expr) => ( + { + let path = Path::new($path); + let f = format!("{}", path.display()); + assert_eq!(f, $exp); + let f = format!("{}", path.filename_display()); + assert_eq!(f, $expf); + } + ) + } + + t!(b"foo", "foo", "foo"); + t!(b"foo/bar", "foo/bar", "bar"); + t!(b"/", "/", ""); + t!(b"foo\xFF", "foo\u{FFFD}", "foo\u{FFFD}"); + t!(b"foo\xFF/bar", "foo\u{FFFD}/bar", "bar"); + t!(b"foo/\xFFbar", "foo/\u{FFFD}bar", "\u{FFFD}bar"); + t!(b"\xFFfoo/bar\xFF", "\u{FFFD}foo/bar\u{FFFD}", "bar\u{FFFD}"); + } + + #[test] + fn test_components() { + macro_rules! t { + (s: $path:expr, $op:ident, $exp:expr) => ( + { + let path = Path::new($path); + assert_eq!(path.$op(), ($exp).as_bytes()); + } + ); + (s: $path:expr, $op:ident, $exp:expr, opt) => ( + { + let path = Path::new($path); + let left = path.$op().map(|x| str::from_utf8(x).unwrap()); + assert_eq!(left, $exp); + } + ); + (v: $path:expr, $op:ident, $exp:expr) => ( + { + let arg = $path; + let path = Path::new(arg); + assert_eq!(path.$op(), $exp); + } + ); + } + + t!(v: b"a/b/c", filename, Some(b"c")); + t!(v: b"a/b/c\xFF", filename, Some(b"c\xFF")); + t!(v: b"a/b\xFF/c", filename, Some(b"c")); + t!(s: "a/b/c", filename, Some("c"), opt); + t!(s: "/a/b/c", filename, Some("c"), opt); + t!(s: "a", filename, Some("a"), opt); + t!(s: "/a", filename, Some("a"), opt); + t!(s: ".", filename, None, opt); + t!(s: "/", filename, None, opt); + t!(s: "..", filename, None, opt); + t!(s: "../..", filename, None, opt); + + t!(v: b"a/b/c", dirname, b"a/b"); + t!(v: b"a/b/c\xFF", dirname, b"a/b"); + t!(v: b"a/b\xFF/c", dirname, b"a/b\xFF"); + 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, Some(b"there")); + t!(v: b"hi/there\x80.txt", filestem, Some(b"there\x80")); + t!(v: b"hi/there.t\x80xt", filestem, Some(b"there")); + t!(s: "hi/there.txt", filestem, Some("there"), opt); + t!(s: "hi/there", filestem, Some("there"), opt); + t!(s: "there.txt", filestem, Some("there"), opt); + t!(s: "there", filestem, Some("there"), opt); + t!(s: ".", filestem, None, opt); + t!(s: "/", filestem, None, opt); + t!(s: "foo/.bar", filestem, Some(".bar"), opt); + t!(s: ".bar", filestem, Some(".bar"), opt); + t!(s: "..bar", filestem, Some("."), opt); + t!(s: "hi/there..txt", filestem, Some("there."), opt); + t!(s: "..", filestem, None, opt); + t!(s: "../..", filestem, None, opt); + + t!(v: b"hi/there.txt", extension, Some(b"txt")); + t!(v: b"hi/there\x80.txt", extension, Some(b"txt")); + t!(v: b"hi/there.t\x80xt", extension, Some(b"t\x80xt")); + t!(v: b"hi/there", extension, None); + t!(v: b"hi/there\x80", 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::new(path); + let p2 = p1.clone(); + p1.push(join); + assert_eq!(p1, p2.join(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::new($path); + let push = Path::new($push); + p.push(&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_push_many() { + macro_rules! t { + (s: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::new($path); + p.push_many(&$push); + assert_eq!(p.as_str(), Some($exp)); + } + ); + (v: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::new($path); + p.push_many(&$push); + assert_eq!(p.as_vec(), $exp); + } + ) + } + + t!(s: "a/b/c", ["d", "e"], "a/b/c/d/e"); + t!(s: "a/b/c", ["d", "/e"], "/e"); + t!(s: "a/b/c", ["d", "/e", "f"], "/e/f"); + t!(s: "a/b/c", ["d".to_string(), "e".to_string()], "a/b/c/d/e"); + t!(v: b"a/b/c", [b"d", b"e"], b"a/b/c/d/e"); + t!(v: b"a/b/c", [b"d", b"/e", b"f"], b"/e/f"); + t!(v: b"a/b/c", [b"d".to_vec(), b"e".to_vec()], b"a/b/c/d/e"); + } + + #[test] + fn test_pop() { + macro_rules! t { + (s: $path:expr, $left:expr, $right:expr) => ( + { + let mut p = Path::new($path); + let result = p.pop(); + assert_eq!(p.as_str(), Some($left)); + assert_eq!(result, $right); + } + ); + (b: $path:expr, $left:expr, $right:expr) => ( + { + let mut p = Path::new($path); + let result = p.pop(); + assert_eq!(p.as_vec(), $left); + assert_eq!(result, $right); + } + ) + } + + t!(b: b"a/b/c", b"a/b", true); + t!(b: b"a", b".", true); + t!(b: b".", b".", false); + t!(b: b"/a", b"/", true); + t!(b: b"/", b"/", false); + t!(b: b"a/b/c\x80", b"a/b", true); + t!(b: b"a/b\x80/c", b"a/b\x80", true); + t!(b: b"\xFF", b".", true); + t!(b: b"/\xFF", b"/", true); + t!(s: "a/b/c", "a/b", true); + t!(s: "a", ".", true); + t!(s: ".", ".", false); + t!(s: "/a", "/", true); + t!(s: "/", "/", false); + } + + #[test] + fn test_root_path() { + assert_eq!(Path::new(b"a/b/c").root_path(), None); + assert_eq!(Path::new(b"/a/b/c").root_path(), Some(Path::new("/"))); + } + + #[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/\x80/c").join(b"\xFF"), b"a/\x80/c/\xFF"); + t!(s: Path::new("a/b/c").join(".."), "a/b"); + t!(s: Path::new("/a/b/c").join("d"), "/a/b/c/d"); + t!(s: Path::new("a/b").join("c/d"), "a/b/c/d"); + t!(s: Path::new("a/b").join("/c/d"), "/c/d"); + t!(s: Path::new(".").join("a/b"), "a/b"); + t!(s: Path::new("/").join("a/b"), "/a/b"); + } + + #[test] + fn test_join_path() { + macro_rules! t { + (s: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let join = Path::new($join); + let res = path.join(&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_join_many() { + macro_rules! t { + (s: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let res = path.join_many(&$join); + assert_eq!(res.as_str(), Some($exp)); + } + ); + (v: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let res = path.join_many(&$join); + assert_eq!(res.as_vec(), $exp); + } + ) + } + + t!(s: "a/b/c", ["d", "e"], "a/b/c/d/e"); + t!(s: "a/b/c", ["..", "d"], "a/b/d"); + t!(s: "a/b/c", ["d", "/e", "f"], "/e/f"); + t!(s: "a/b/c", ["d".to_string(), "e".to_string()], "a/b/c/d/e"); + t!(v: b"a/b/c", [b"d", b"e"], b"a/b/c/d/e"); + t!(v: b"a/b/c", [b"d".to_vec(), b"e".to_vec()], b"a/b/c/d/e"); + } + + #[test] + fn test_with_helpers() { + let empty: &[u8] = &[]; + + t!(v: Path::new(b"a/b/c").with_filename(b"d"), b"a/b/d"); + t!(v: Path::new(b"a/b/c\xFF").with_filename(b"\x80"), b"a/b/\x80"); + t!(v: Path::new(b"/\xFF/foo").with_filename(b"\xCD"), + b"/\xFF/\xCD"); + t!(s: Path::new("a/b/c").with_filename("d"), "a/b/d"); + t!(s: Path::new(".").with_filename("foo"), "foo"); + t!(s: Path::new("/a/b/c").with_filename("d"), "/a/b/d"); + t!(s: Path::new("/").with_filename("foo"), "/foo"); + t!(s: Path::new("/a").with_filename("foo"), "/foo"); + t!(s: Path::new("foo").with_filename("bar"), "bar"); + t!(s: Path::new("/").with_filename("foo/"), "/foo"); + t!(s: Path::new("/a").with_filename("foo/"), "/foo"); + t!(s: Path::new("a/b/c").with_filename(""), "a/b"); + t!(s: Path::new("a/b/c").with_filename("."), "a/b"); + t!(s: Path::new("a/b/c").with_filename(".."), "a"); + t!(s: Path::new("/a").with_filename(""), "/"); + t!(s: Path::new("foo").with_filename(""), "."); + t!(s: Path::new("a/b/c").with_filename("d/e"), "a/b/d/e"); + t!(s: Path::new("a/b/c").with_filename("/d"), "a/b/d"); + t!(s: Path::new("..").with_filename("foo"), "../foo"); + t!(s: Path::new("../..").with_filename("foo"), "../../foo"); + t!(s: Path::new("..").with_filename(""), ".."); + t!(s: Path::new("../..").with_filename(""), "../.."); + + t!(v: Path::new(b"hi/there\x80.txt").with_extension(b"exe"), + b"hi/there\x80.exe"); + t!(v: Path::new(b"hi/there.txt\x80").with_extension(b"\xFF"), + b"hi/there.\xFF"); + t!(v: Path::new(b"hi/there\x80").with_extension(b"\xFF"), + b"hi/there\x80.\xFF"); + t!(v: Path::new(b"hi/there.\xFF").with_extension(empty), b"hi/there"); + t!(s: Path::new("hi/there.txt").with_extension("exe"), "hi/there.exe"); + t!(s: Path::new("hi/there.txt").with_extension(""), "hi/there"); + t!(s: Path::new("hi/there.txt").with_extension("."), "hi/there.."); + t!(s: Path::new("hi/there.txt").with_extension(".."), "hi/there..."); + t!(s: Path::new("hi/there").with_extension("txt"), "hi/there.txt"); + t!(s: Path::new("hi/there").with_extension("."), "hi/there.."); + t!(s: Path::new("hi/there").with_extension(".."), "hi/there..."); + t!(s: Path::new("hi/there.").with_extension("txt"), "hi/there.txt"); + t!(s: Path::new("hi/.foo").with_extension("txt"), "hi/.foo.txt"); + t!(s: Path::new("hi/there.txt").with_extension(".foo"), "hi/there..foo"); + t!(s: Path::new("/").with_extension("txt"), "/"); + t!(s: Path::new("/").with_extension("."), "/"); + t!(s: Path::new("/").with_extension(".."), "/"); + t!(s: Path::new(".").with_extension("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::new(path); + p1.$set(arg); + let p2 = Path::new(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_filename, with_filename, b"d"); + t!(v: b"/", set_filename, with_filename, b"foo"); + t!(v: b"\x80", set_filename, with_filename, b"\xFF"); + t!(s: "a/b/c", set_filename, with_filename, "d"); + t!(s: "/", set_filename, with_filename, "foo"); + t!(s: ".", set_filename, with_filename, "foo"); + t!(s: "a/b", set_filename, with_filename, ""); + t!(s: "a", set_filename, with_filename, ""); + + t!(v: b"hi/there.txt", set_extension, with_extension, b"exe"); + t!(v: b"hi/there.t\x80xt", set_extension, with_extension, b"exe\xFF"); + t!(s: "hi/there.txt", set_extension, with_extension, "exe"); + t!(s: "hi/there.", set_extension, with_extension, "txt"); + t!(s: "hi/there", set_extension, with_extension, "txt"); + t!(s: "hi/there.txt", set_extension, with_extension, ""); + t!(s: "hi/there", set_extension, with_extension, ""); + t!(s: ".", set_extension, with_extension, "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"), Some(b"c"), b"a/b", Some(b"c"), None); + t!(v: Path::new(b"a/b/\xFF"), Some(b"\xFF"), b"a/b", Some(b"\xFF"), None); + t!(v: Path::new(b"hi/there.\xFF"), Some(b"there.\xFF"), b"hi", + Some(b"there"), Some(b"\xFF")); + t!(s: Path::new("a/b/c"), Some("c"), Some("a/b"), Some("c"), None); + t!(s: Path::new("."), None, Some("."), None, None); + t!(s: Path::new("/"), None, Some("/"), None, None); + t!(s: Path::new(".."), None, Some(".."), None, None); + t!(s: Path::new("../.."), None, Some("../.."), None, None); + t!(s: Path::new("hi/there.txt"), Some("there.txt"), Some("hi"), + Some("there"), Some("txt")); + t!(s: Path::new("hi/there"), Some("there"), Some("hi"), Some("there"), None); + t!(s: Path::new("hi/there."), Some("there."), Some("hi"), + Some("there"), Some("")); + t!(s: Path::new("hi/.there"), Some(".there"), Some("hi"), Some(".there"), None); + t!(s: Path::new("hi/..there"), Some("..there"), Some("hi"), + Some("."), Some("there")); + t!(s: Path::new(b"a/b/\xFF"), None, Some("a/b"), None, None); + t!(s: Path::new(b"a/b/\xFF.txt"), None, Some("a/b"), None, Some("txt")); + t!(s: Path::new(b"a/b/c.\x80"), None, Some("a/b"), Some("c"), None); + t!(s: Path::new(b"\xFF/b"), Some("b"), None, Some("b"), None); + } + + #[test] + fn test_dir_path() { + t!(v: Path::new(b"hi/there\x80").dir_path(), b"hi"); + t!(v: Path::new(b"hi\xFF/there").dir_path(), b"hi\xFF"); + t!(s: Path::new("hi/there").dir_path(), "hi"); + t!(s: Path::new("hi").dir_path(), "."); + t!(s: Path::new("/hi").dir_path(), "/"); + t!(s: Path::new("/").dir_path(), "/"); + t!(s: Path::new("..").dir_path(), ".."); + t!(s: Path::new("../..").dir_path(), "../.."); + } + + #[test] + fn test_is_absolute() { + macro_rules! t { + (s: $path:expr, $abs:expr, $rel:expr) => ( + { + let path = Path::new($path); + assert_eq!(path.is_absolute(), $abs); + assert_eq!(path.is_relative(), $rel); + } + ) + } + t!(s: "a/b/c", false, true); + t!(s: "/a/b/c", true, false); + t!(s: "a", false, true); + t!(s: "/a", true, false); + t!(s: ".", false, true); + t!(s: "/", true, false); + t!(s: "..", false, true); + t!(s: "../..", false, true); + } + + #[test] + fn test_is_ancestor_of() { + macro_rules! t { + (s: $path:expr, $dest:expr, $exp:expr) => ( + { + let path = Path::new($path); + let dest = Path::new($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_ends_with_path() { + macro_rules! t { + (s: $path:expr, $child:expr, $exp:expr) => ( + { + let path = Path::new($path); + let child = Path::new($child); + assert_eq!(path.ends_with_path(&child), $exp); + } + ); + (v: $path:expr, $child:expr, $exp:expr) => ( + { + let path = Path::new($path); + let child = Path::new($child); + assert_eq!(path.ends_with_path(&child), $exp); + } + ) + } + + t!(s: "a/b/c", "c", true); + t!(s: "a/b/c", "d", false); + t!(s: "foo/bar/quux", "bar", false); + t!(s: "foo/bar/quux", "barquux", false); + t!(s: "a/b/c", "b/c", true); + t!(s: "a/b/c", "a/b/c", true); + t!(s: "a/b/c", "foo/a/b/c", false); + t!(s: "/a/b/c", "a/b/c", true); + t!(s: "/a/b/c", "/a/b/c", false); // child must be relative + t!(s: "/a/b/c", "foo/a/b/c", false); + t!(s: "a/b/c", "", false); + t!(s: "", "", true); + t!(s: "/a/b/c", "d/e/f", false); + t!(s: "a/b/c", "a/b", false); + t!(s: "a/b/c", "b", false); + t!(v: b"a/b/c", b"b/c", true); + t!(v: b"a/b/\xFF", b"\xFF", true); + t!(v: b"a/b/\xFF", b"b/\xFF", true); + } + + #[test] + fn test_path_relative_from() { + macro_rules! t { + (s: $path:expr, $other:expr, $exp:expr) => ( + { + let path = Path::new($path); + let other = Path::new($other); + let res = path.path_relative_from(&other); + assert_eq!(res.as_ref().and_then(|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_components_iter() { + macro_rules! t { + (s: $path:expr, $exp:expr) => ( + { + let path = Path::new($path); + let comps = path.components().collect::>(); + let exp: &[&str] = &$exp; + let exps = exp.iter().map(|x| x.as_bytes()).collect::>(); + assert_eq!(comps, exps); + let comps = path.components().rev().collect::>(); + let exps = exps.into_iter().rev().collect::>(); + assert_eq!(comps, exps); + } + ); + (b: $arg:expr, [$($exp:expr),*]) => ( + { + let path = Path::new($arg); + let comps = path.components().collect::>(); + let exp: &[&[u8]] = &[$($exp),*]; + assert_eq!(comps, exp); + let comps = path.components().rev().collect::>(); + let exp = exp.iter().rev().map(|&x|x).collect::>(); + assert_eq!(comps, exp) + } + ) + } + + t!(b: b"a/b/c", [b"a", b"b", b"c"]); + t!(b: b"/\xFF/a/\x80", [b"\xFF", b"a", b"\x80"]); + t!(b: b"../../foo\xCDbar", [b"..", b"..", b"foo\xCDbar"]); + 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"]); + } + + #[test] + fn test_str_components() { + macro_rules! t { + (b: $arg:expr, $exp:expr) => ( + { + let path = Path::new($arg); + let comps = path.str_components().collect::>>(); + let exp: &[Option<&str>] = &$exp; + assert_eq!(comps, exp); + let comps = path.str_components().rev().collect::>>(); + let exp = exp.iter().rev().map(|&x|x).collect::>>(); + assert_eq!(comps, exp); + } + ) + } + + t!(b: b"a/b/c", [Some("a"), Some("b"), Some("c")]); + t!(b: b"/\xFF/a/\x80", [None, Some("a"), None]); + t!(b: b"../../foo\xCDbar", [Some(".."), Some(".."), None]); + // str_components is a wrapper around components, so no need to do + // the full set of tests + } +} + +#[cfg(test)] +mod bench { + extern crate test; + use self::test::Bencher; + use super::*; + use prelude::v1::{Clone, GenericPath}; + + #[bench] + fn join_home_dir(b: &mut Bencher) { + let posix_path = Path::new("/"); + b.iter(|| { + posix_path.join("home"); + }); + } + + #[bench] + fn join_abs_path_home_dir(b: &mut Bencher) { + let posix_path = Path::new("/"); + b.iter(|| { + posix_path.join("/home"); + }); + } + + #[bench] + fn join_many_home_dir(b: &mut Bencher) { + let posix_path = Path::new("/"); + b.iter(|| { + posix_path.join_many(&["home"]); + }); + } + + #[bench] + fn join_many_abs_path_home_dir(b: &mut Bencher) { + let posix_path = Path::new("/"); + b.iter(|| { + posix_path.join_many(&["/home"]); + }); + } + + #[bench] + fn push_home_dir(b: &mut Bencher) { + let mut posix_path = Path::new("/"); + b.iter(|| { + posix_path.push("home"); + }); + } + + #[bench] + fn push_abs_path_home_dir(b: &mut Bencher) { + let mut posix_path = Path::new("/"); + b.iter(|| { + posix_path.push("/home"); + }); + } + + #[bench] + fn push_many_home_dir(b: &mut Bencher) { + let mut posix_path = Path::new("/"); + b.iter(|| { + posix_path.push_many(&["home"]); + }); + } + + #[bench] + fn push_many_abs_path_home_dir(b: &mut Bencher) { + let mut posix_path = Path::new("/"); + b.iter(|| { + posix_path.push_many(&["/home"]); + }); + } + + #[bench] + fn ends_with_path_home_dir(b: &mut Bencher) { + let posix_home_path = Path::new("/home"); + b.iter(|| { + posix_home_path.ends_with_path(&Path::new("home")); + }); + } + + #[bench] + fn ends_with_path_missmatch_jome_home(b: &mut Bencher) { + let posix_home_path = Path::new("/home"); + b.iter(|| { + posix_home_path.ends_with_path(&Path::new("jome")); + }); + } + + #[bench] + fn is_ancestor_of_path_with_10_dirs(b: &mut Bencher) { + let path = Path::new("/home/1/2/3/4/5/6/7/8/9"); + let mut sub = path.clone(); + sub.pop(); + b.iter(|| { + path.is_ancestor_of(&sub); + }); + } + + #[bench] + fn path_relative_from_forward(b: &mut Bencher) { + let path = Path::new("/a/b/c"); + let mut other = path.clone(); + other.pop(); + b.iter(|| { + path.path_relative_from(&other); + }); + } + + #[bench] + fn path_relative_from_same_level(b: &mut Bencher) { + let path = Path::new("/a/b/c"); + let mut other = path.clone(); + other.pop(); + other.push("d"); + b.iter(|| { + path.path_relative_from(&other); + }); + } + + #[bench] + fn path_relative_from_backward(b: &mut Bencher) { + let path = Path::new("/a/b"); + let mut other = path.clone(); + other.push("c"); + b.iter(|| { + path.path_relative_from(&other); + }); + } +} diff --git a/src/libstd/old_path/windows.rs b/src/libstd/old_path/windows.rs new file mode 100644 index 00000000000..2e25403220d --- /dev/null +++ b/src/libstd/old_path/windows.rs @@ -0,0 +1,2329 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +// +// ignore-lexer-test FIXME #15883 + +//! Windows file path handling + +use self::PathPrefix::*; + +use ascii::AsciiExt; +use char::CharExt; +use clone::Clone; +use cmp::{Ordering, Eq, Ord, PartialEq, PartialOrd}; +use fmt; +use hash; +use old_io::Writer; +use iter::{AdditiveIterator, Extend}; +use iter::{Iterator, IteratorExt, Map, repeat}; +use mem; +use option::Option::{self, Some, None}; +use result::Result::{self, Ok, Err}; +use slice::{SliceExt, SliceConcatExt}; +use str::{SplitTerminator, FromStr, StrExt}; +use string::{String, ToString}; +use vec::Vec; + +use super::{contains_nul, BytesContainer, GenericPath, GenericPathUnsafe}; + +/// Iterator that yields successive components of a Path as &str +/// +/// Each component is yielded as Option<&str> for compatibility with PosixPath, but +/// every component in WindowsPath is guaranteed to be Some. +pub type StrComponents<'a> = + Map, fn(&'a str) -> Option<&'a str>>; + +/// Iterator that yields successive components of a Path as &[u8] +pub type Components<'a> = + Map, fn(Option<&str>) -> &[u8]>; + +/// Represents a Windows path +// Notes for Windows path impl: +// The MAX_PATH is 260, but 253 is the practical limit due to some API bugs +// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx for good information +// about windows paths. +// That same page puts a bunch of restrictions on allowed characters in a path. +// `\foo.txt` means "relative to current drive", but will not be considered to be absolute here +// as `∃P | P.join("\foo.txt") != "\foo.txt"`. +// `C:` is interesting, that means "the current directory on drive C". +// Long absolute paths need to have \\?\ prefix (or, for UNC, \\?\UNC\). I think that can be +// ignored for now, though, and only added in a hypothetical .to_pwstr() function. +// However, if a path is parsed that has \\?\, this needs to be preserved as it disables the +// processing of "." and ".." components and / as a separator. +// Experimentally, \\?\foo is not the same thing as \foo. +// Also, \\foo is not valid either (certainly not equivalent to \foo). +// Similarly, C:\\Users is not equivalent to C:\Users, although C:\Users\\foo is equivalent +// to C:\Users\foo. In fact the command prompt treats C:\\foo\bar as UNC path. But it might be +// best to just ignore that and normalize it to C:\foo\bar. +// +// Based on all this, I think the right approach is to do the following: +// * Require valid utf-8 paths. Windows API may use WCHARs, but we don't, and utf-8 is convertible +// to UTF-16 anyway (though does Windows use UTF-16 or UCS-2? Not sure). +// * Parse the prefixes \\?\UNC\, \\?\, and \\.\ explicitly. +// * If \\?\UNC\, treat following two path components as server\share. Don't error for missing +// server\share. +// * If \\?\, parse disk from following component, if present. Don't error for missing disk. +// * If \\.\, treat rest of path as just regular components. I don't know how . and .. are handled +// here, they probably aren't, but I'm not going to worry about that. +// * Else if starts with \\, treat following two components as server\share. Don't error for missing +// server\share. +// * Otherwise, attempt to parse drive from start of path. +// +// The only error condition imposed here is valid utf-8. All other invalid paths are simply +// preserved by the data structure; let the Windows API error out on them. +#[derive(Clone)] +pub struct Path { + repr: String, // assumed to never be empty + prefix: Option, + sepidx: Option // index of the final separator in the non-prefix portion of repr +} + +#[stable(feature = "rust1", since = "1.0.0")] +impl fmt::Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.display(), f) + } +} + +impl PartialEq for Path { + #[inline] + fn eq(&self, other: &Path) -> bool { + self.repr == other.repr + } +} + +impl Eq for Path {} + +impl PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Path { + fn cmp(&self, other: &Path) -> Ordering { + self.repr.cmp(&other.repr) + } +} + +impl FromStr for Path { + type Err = ParsePathError; + fn from_str(s: &str) -> Result { + match Path::new_opt(s) { + Some(p) => Ok(p), + None => Err(ParsePathError), + } + } +} + +/// Value indicating that a path could not be parsed from a string. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct ParsePathError; + +impl hash::Hash for Path { + #[cfg(not(test))] + #[inline] + fn hash(&self, state: &mut S) { + self.repr.hash(state) + } + + #[cfg(test)] + #[inline] + fn hash(&self, _: &mut S) { + // No-op because the `hash` implementation will be wrong. + } +} + +impl BytesContainer for Path { + #[inline] + fn container_as_bytes<'a>(&'a self) -> &'a [u8] { + self.as_vec() + } + #[inline] + fn container_as_str<'a>(&'a self) -> Option<&'a str> { + self.as_str() + } + #[inline] + fn is_str(_: Option<&Path>) -> bool { true } +} + +impl GenericPathUnsafe for Path { + /// See `GenericPathUnsafe::from_vec_unchecked`. + /// + /// # Panics + /// + /// Panics if not valid UTF-8. + #[inline] + unsafe fn new_unchecked(path: T) -> Path { + let (prefix, path) = Path::normalize_(path.container_as_str().unwrap()); + assert!(!path.is_empty()); + let mut ret = Path{ repr: path, prefix: prefix, sepidx: None }; + ret.update_sepidx(); + ret + } + + /// See `GenericPathUnsafe::set_filename_unchecked`. + /// + /// # Panics + /// + /// Panics if not valid UTF-8. + unsafe fn set_filename_unchecked(&mut self, filename: T) { + let filename = filename.container_as_str().unwrap(); + match self.sepidx_or_prefix_len() { + None if ".." == self.repr => { + let mut s = String::with_capacity(3 + filename.len()); + s.push_str(".."); + s.push(SEP); + s.push_str(filename); + self.update_normalized(&s[]); + } + None => { + self.update_normalized(filename); + } + Some((_,idxa,end)) if &self.repr[idxa..end] == ".." => { + let mut s = String::with_capacity(end + 1 + filename.len()); + s.push_str(&self.repr[..end]); + s.push(SEP); + s.push_str(filename); + self.update_normalized(&s[]); + } + Some((idxb,idxa,_)) if self.prefix == Some(DiskPrefix) && idxa == self.prefix_len() => { + let mut s = String::with_capacity(idxb + filename.len()); + s.push_str(&self.repr[..idxb]); + s.push_str(filename); + self.update_normalized(&s[]); + } + Some((idxb,_,_)) => { + let mut s = String::with_capacity(idxb + 1 + filename.len()); + s.push_str(&self.repr[..idxb]); + s.push(SEP); + s.push_str(filename); + self.update_normalized(&s[]); + } + } + } + + /// See `GenericPathUnsafe::push_unchecked`. + /// + /// Concatenating two Windows Paths is rather complicated. + /// For the most part, it will behave as expected, except in the case of + /// pushing a volume-relative path, e.g. `C:foo.txt`. Because we have no + /// concept of per-volume cwds like Windows does, we can't behave exactly + /// like Windows will. Instead, if the receiver is an absolute path on + /// the same volume as the new path, it will be treated as the cwd that + /// the new path is relative to. Otherwise, the new path will be treated + /// as if it were absolute and will replace the receiver outright. + unsafe fn push_unchecked(&mut self, path: T) { + let path = path.container_as_str().unwrap(); + fn is_vol_abs(path: &str, prefix: Option) -> bool { + // assume prefix is Some(DiskPrefix) + let rest = &path[prefix_len(prefix)..]; + !rest.is_empty() && rest.as_bytes()[0].is_ascii() && is_sep(rest.as_bytes()[0] as char) + } + fn shares_volume(me: &Path, path: &str) -> bool { + // path is assumed to have a prefix of Some(DiskPrefix) + let repr = &me.repr[]; + match me.prefix { + Some(DiskPrefix) => { + repr.as_bytes()[0] == path.as_bytes()[0].to_ascii_uppercase() + } + Some(VerbatimDiskPrefix) => { + repr.as_bytes()[4] == path.as_bytes()[0].to_ascii_uppercase() + } + _ => false + } + } + fn is_sep_(prefix: Option, u: u8) -> bool { + if prefix_is_verbatim(prefix) { is_sep_verbatim(u as char) } + else { is_sep(u as char) } + } + + fn replace_path(me: &mut Path, path: &str, prefix: Option) { + let newpath = Path::normalize__(path, prefix); + me.repr = match newpath { + Some(p) => p, + None => String::from_str(path) + }; + me.prefix = prefix; + me.update_sepidx(); + } + fn append_path(me: &mut Path, path: &str) { + // appends a path that has no prefix + // if me is verbatim, we need to pre-normalize the new path + let path_ = if is_verbatim(me) { Path::normalize__(path, None) } + else { None }; + let pathlen = path_.as_ref().map_or(path.len(), |p| p.len()); + let mut s = String::with_capacity(me.repr.len() + 1 + pathlen); + s.push_str(&me.repr[]); + let plen = me.prefix_len(); + // if me is "C:" we don't want to add a path separator + match me.prefix { + Some(DiskPrefix) if me.repr.len() == plen => (), + _ if !(me.repr.len() > plen && me.repr.as_bytes()[me.repr.len()-1] == SEP_BYTE) => { + s.push(SEP); + } + _ => () + } + match path_ { + None => s.push_str(path), + Some(p) => s.push_str(&p[]), + }; + me.update_normalized(&s[]) + } + + if !path.is_empty() { + let prefix = parse_prefix(path); + match prefix { + Some(DiskPrefix) if !is_vol_abs(path, prefix) && shares_volume(self, path) => { + // cwd-relative path, self is on the same volume + append_path(self, &path[prefix_len(prefix)..]); + } + Some(_) => { + // absolute path, or cwd-relative and self is not same volume + replace_path(self, path, prefix); + } + None if !path.is_empty() && is_sep_(self.prefix, path.as_bytes()[0]) => { + // volume-relative path + if self.prefix.is_some() { + // truncate self down to the prefix, then append + let n = self.prefix_len(); + self.repr.truncate(n); + append_path(self, path); + } else { + // we have no prefix, so nothing to be relative to + replace_path(self, path, prefix); + } + } + None => { + // relative path + append_path(self, path); + } + } + } + } +} + +impl GenericPath for Path { + #[inline] + fn new_opt(path: T) -> Option { + match path.container_as_str() { + None => None, + Some(ref s) => { + if contains_nul(s) { + None + } else { + Some(unsafe { GenericPathUnsafe::new_unchecked(*s) }) + } + } + } + } + + /// See `GenericPath::as_str` for info. + /// Always returns a `Some` value. + #[inline] + fn as_str<'a>(&'a self) -> Option<&'a str> { + Some(&self.repr[]) + } + + #[inline] + fn as_vec<'a>(&'a self) -> &'a [u8] { + self.repr.as_bytes() + } + + #[inline] + fn into_vec(self) -> Vec { + self.repr.into_bytes() + } + + #[inline] + fn dirname<'a>(&'a self) -> &'a [u8] { + self.dirname_str().unwrap().as_bytes() + } + + /// See `GenericPath::dirname_str` for info. + /// Always returns a `Some` value. + fn dirname_str<'a>(&'a self) -> Option<&'a str> { + Some(match self.sepidx_or_prefix_len() { + None if ".." == self.repr => &self.repr[], + None => ".", + Some((_,idxa,end)) if &self.repr[idxa..end] == ".." => { + &self.repr[] + } + Some((idxb,_,end)) if &self.repr[idxb..end] == "\\" => { + &self.repr[] + } + Some((0,idxa,_)) => &self.repr[..idxa], + Some((idxb,idxa,_)) => { + match self.prefix { + Some(DiskPrefix) | Some(VerbatimDiskPrefix) if idxb == self.prefix_len() => { + &self.repr[..idxa] + } + _ => &self.repr[..idxb] + } + } + }) + } + + #[inline] + fn filename<'a>(&'a self) -> Option<&'a [u8]> { + self.filename_str().map(|x| x.as_bytes()) + } + + /// See `GenericPath::filename_str` for info. + /// Always returns a `Some` value if `filename` returns a `Some` value. + fn filename_str<'a>(&'a self) -> Option<&'a str> { + let repr = &self.repr[]; + match self.sepidx_or_prefix_len() { + None if "." == repr || ".." == repr => None, + None => Some(repr), + Some((_,idxa,end)) if &repr[idxa..end] == ".." => None, + Some((_,idxa,end)) if idxa == end => None, + Some((_,idxa,end)) => Some(&repr[idxa..end]) + } + } + + /// See `GenericPath::filestem_str` for info. + /// Always returns a `Some` value if `filestem` returns a `Some` value. + #[inline] + fn filestem_str<'a>(&'a self) -> Option<&'a str> { + // filestem() returns a byte vector that's guaranteed valid UTF-8 + self.filestem().map(|t| unsafe { mem::transmute(t) }) + } + + #[inline] + fn extension_str<'a>(&'a self) -> Option<&'a str> { + // extension() returns a byte vector that's guaranteed valid UTF-8 + self.extension().map(|t| unsafe { mem::transmute(t) }) + } + + fn dir_path(&self) -> Path { + unsafe { GenericPathUnsafe::new_unchecked(self.dirname_str().unwrap()) } + } + + #[inline] + fn pop(&mut self) -> bool { + match self.sepidx_or_prefix_len() { + None if "." == self.repr => false, + None => { + self.repr = String::from_str("."); + self.sepidx = None; + true + } + Some((idxb,idxa,end)) if idxb == idxa && idxb == end => false, + Some((idxb,_,end)) if &self.repr[idxb..end] == "\\" => false, + Some((idxb,idxa,_)) => { + let trunc = match self.prefix { + Some(DiskPrefix) | Some(VerbatimDiskPrefix) | None => { + let plen = self.prefix_len(); + if idxb == plen { idxa } else { idxb } + } + _ => idxb + }; + self.repr.truncate(trunc); + self.update_sepidx(); + true + } + } + } + + fn root_path(&self) -> Option { + if self.prefix.is_some() { + Some(Path::new(match self.prefix { + Some(DiskPrefix) if self.is_absolute() => { + &self.repr[..self.prefix_len()+1] + } + Some(VerbatimDiskPrefix) => { + &self.repr[..self.prefix_len()+1] + } + _ => &self.repr[..self.prefix_len()] + })) + } else if is_vol_relative(self) { + Some(Path::new(&self.repr[..1])) + } else { + None + } + } + + /// See `GenericPath::is_absolute` for info. + /// + /// A Windows Path is considered absolute only if it has a non-volume prefix, + /// or if it has a volume prefix and the path starts with '\'. + /// A path of `\foo` is not considered absolute because it's actually + /// relative to the "current volume". A separate method `Path::is_vol_relative` + /// is provided to indicate this case. Similarly a path of `C:foo` is not + /// considered absolute because it's relative to the cwd on volume C:. A + /// separate method `Path::is_cwd_relative` is provided to indicate this case. + #[inline] + fn is_absolute(&self) -> bool { + match self.prefix { + Some(DiskPrefix) => { + let rest = &self.repr[self.prefix_len()..]; + rest.len() > 0 && rest.as_bytes()[0] == SEP_BYTE + } + Some(_) => true, + None => false + } + } + + #[inline] + fn is_relative(&self) -> bool { + self.prefix.is_none() && !is_vol_relative(self) + } + + fn is_ancestor_of(&self, other: &Path) -> bool { + if !self.equiv_prefix(other) { + false + } else if self.is_absolute() != other.is_absolute() || + is_vol_relative(self) != is_vol_relative(other) { + false + } else { + let mut ita = self.str_components().map(|x|x.unwrap()); + let mut itb = other.str_components().map(|x|x.unwrap()); + if "." == self.repr { + return itb.next() != Some(".."); + } + loop { + match (ita.next(), itb.next()) { + (None, _) => break, + (Some(a), Some(b)) if a == b => { continue }, + (Some(a), _) if a == ".." => { + // if ita contains only .. components, it's an ancestor + return ita.all(|x| x == ".."); + } + _ => return false + } + } + true + } + } + + fn path_relative_from(&self, base: &Path) -> Option { + fn comp_requires_verbatim(s: &str) -> bool { + s == "." || s == ".." || s.contains_char(SEP2) + } + + if !self.equiv_prefix(base) { + // prefixes differ + if self.is_absolute() { + Some(self.clone()) + } else if self.prefix == Some(DiskPrefix) && base.prefix == Some(DiskPrefix) { + // both drives, drive letters must differ or they'd be equiv + Some(self.clone()) + } else { + None + } + } else if self.is_absolute() != base.is_absolute() { + if self.is_absolute() { + Some(self.clone()) + } else { + None + } + } else if is_vol_relative(self) != is_vol_relative(base) { + if is_vol_relative(self) { + Some(self.clone()) + } else { + None + } + } else { + let mut ita = self.str_components().map(|x|x.unwrap()); + let mut itb = base.str_components().map(|x|x.unwrap()); + let mut comps = vec![]; + + let a_verb = is_verbatim(self); + let b_verb = is_verbatim(base); + loop { + match (ita.next(), itb.next()) { + (None, None) => break, + (Some(a), None) if a_verb && comp_requires_verbatim(a) => { + return Some(self.clone()) + } + (Some(a), None) => { + comps.push(a); + if !a_verb { + comps.extend(ita.by_ref()); + break; + } + } + (None, _) => comps.push(".."), + (Some(a), Some(b)) if comps.is_empty() && a == b => (), + (Some(a), Some(b)) if !b_verb && b == "." => { + if a_verb && comp_requires_verbatim(a) { + return Some(self.clone()) + } else { comps.push(a) } + } + (Some(_), Some(b)) if !b_verb && b == ".." => return None, + (Some(a), Some(_)) if a_verb && comp_requires_verbatim(a) => { + return Some(self.clone()) + } + (Some(a), Some(_)) => { + comps.push(".."); + for _ in itb.by_ref() { + comps.push(".."); + } + comps.push(a); + if !a_verb { + comps.extend(ita.by_ref()); + break; + } + } + } + } + Some(Path::new(comps.connect("\\"))) + } + } + + fn ends_with_path(&self, child: &Path) -> bool { + if !child.is_relative() { return false; } + let mut selfit = self.str_components().rev(); + let mut childit = child.str_components().rev(); + loop { + match (selfit.next(), childit.next()) { + (Some(a), Some(b)) => if a != b { return false; }, + (Some(_), None) => break, + (None, Some(_)) => return false, + (None, None) => break + } + } + true + } +} + +impl Path { + /// Returns a new `Path` from a `BytesContainer`. + /// + /// # Panics + /// + /// Panics if the vector contains a `NUL`, or if it contains invalid UTF-8. + /// + /// # Example + /// + /// ``` + /// println!("{}", Path::new(r"C:\some\path").display()); + /// ``` + #[inline] + pub fn new(path: T) -> Path { + GenericPath::new(path) + } + + /// Returns a new `Some(Path)` from a `BytesContainer`. + /// + /// Returns `None` if the vector contains a `NUL`, or if it contains invalid UTF-8. + /// + /// # Example + /// + /// ``` + /// let path = Path::new_opt(r"C:\some\path"); + /// + /// match path { + /// Some(path) => println!("{}", path.display()), + /// None => println!("There was a problem with your path."), + /// } + /// ``` + #[inline] + pub fn new_opt(path: T) -> Option { + GenericPath::new_opt(path) + } + + /// Returns an iterator that yields each component of the path in turn as a Option<&str>. + /// Every component is guaranteed to be Some. + /// Does not yield the path prefix (including server/share components in UNC paths). + /// Does not distinguish between volume-relative and relative paths, e.g. + /// \a\b\c and a\b\c. + /// Does not distinguish between absolute and cwd-relative paths, e.g. + /// C:\foo and C:foo. + pub fn str_components<'a>(&'a self) -> StrComponents<'a> { + let repr = &self.repr[]; + let s = match self.prefix { + Some(_) => { + let plen = self.prefix_len(); + if repr.len() > plen && repr.as_bytes()[plen] == SEP_BYTE { + &repr[plen+1..] + } else { &repr[plen..] } + } + None if repr.as_bytes()[0] == SEP_BYTE => &repr[1..], + None => repr + }; + let some: fn(&'a str) -> Option<&'a str> = Some; // coerce to fn ptr + let ret = s.split_terminator(SEP).map(some); + ret + } + + /// Returns an iterator that yields each component of the path in turn as a &[u8]. + /// See str_components() for details. + pub fn components<'a>(&'a self) -> Components<'a> { + fn convert<'a>(x: Option<&'a str>) -> &'a [u8] { + #![inline] + x.unwrap().as_bytes() + } + let convert: for<'b> fn(Option<&'b str>) -> &'b [u8] = convert; // coerce to fn ptr + self.str_components().map(convert) + } + + fn equiv_prefix(&self, other: &Path) -> bool { + let s_repr = &self.repr[]; + let o_repr = &other.repr[]; + match (self.prefix, other.prefix) { + (Some(DiskPrefix), Some(VerbatimDiskPrefix)) => { + self.is_absolute() && + s_repr.as_bytes()[0].to_ascii_lowercase() == + o_repr.as_bytes()[4].to_ascii_lowercase() + } + (Some(VerbatimDiskPrefix), Some(DiskPrefix)) => { + other.is_absolute() && + s_repr.as_bytes()[4].to_ascii_lowercase() == + o_repr.as_bytes()[0].to_ascii_lowercase() + } + (Some(VerbatimDiskPrefix), Some(VerbatimDiskPrefix)) => { + s_repr.as_bytes()[4].to_ascii_lowercase() == + o_repr.as_bytes()[4].to_ascii_lowercase() + } + (Some(UNCPrefix(_,_)), Some(VerbatimUNCPrefix(_,_))) => { + &s_repr[2..self.prefix_len()] == &o_repr[8..other.prefix_len()] + } + (Some(VerbatimUNCPrefix(_,_)), Some(UNCPrefix(_,_))) => { + &s_repr[8..self.prefix_len()] == &o_repr[2..other.prefix_len()] + } + (None, None) => true, + (a, b) if a == b => { + &s_repr[..self.prefix_len()] == &o_repr[..other.prefix_len()] + } + _ => false + } + } + + fn normalize_(s: &str) -> (Option, String) { + // make borrowck happy + let (prefix, val) = { + let prefix = parse_prefix(s); + let path = Path::normalize__(s, prefix); + (prefix, path) + }; + (prefix, match val { + None => s.to_string(), + Some(val) => val + }) + } + + fn normalize__(s: &str, prefix: Option) -> Option { + if prefix_is_verbatim(prefix) { + // don't do any normalization + match prefix { + Some(VerbatimUNCPrefix(x, 0)) if s.len() == 8 + x => { + // the server component has no trailing '\' + let mut s = String::from_str(s); + s.push(SEP); + Some(s) + } + _ => None + } + } else { + let (is_abs, comps) = normalize_helper(s, prefix); + let mut comps = comps; + match (comps.is_some(),prefix) { + (false, Some(DiskPrefix)) => { + if s.as_bytes()[0] >= b'a' && s.as_bytes()[0] <= b'z' { + comps = Some(vec![]); + } + } + (false, Some(VerbatimDiskPrefix)) => { + if s.as_bytes()[4] >= b'a' && s.as_bytes()[0] <= b'z' { + comps = Some(vec![]); + } + } + _ => () + } + match comps { + None => None, + Some(comps) => { + if prefix.is_some() && comps.is_empty() { + match prefix.unwrap() { + DiskPrefix => { + let len = prefix_len(prefix) + is_abs as uint; + let mut s = String::from_str(&s[..len]); + unsafe { + let v = s.as_mut_vec(); + v[0] = (*v)[0].to_ascii_uppercase(); + } + if is_abs { + // normalize C:/ to C:\ + unsafe { + s.as_mut_vec()[2] = SEP_BYTE; + } + } + Some(s) + } + VerbatimDiskPrefix => { + let len = prefix_len(prefix) + is_abs as uint; + let mut s = String::from_str(&s[..len]); + unsafe { + let v = s.as_mut_vec(); + v[4] = (*v)[4].to_ascii_uppercase(); + } + Some(s) + } + _ => { + let plen = prefix_len(prefix); + if s.len() > plen { + Some(String::from_str(&s[..plen])) + } else { None } + } + } + } else if is_abs && comps.is_empty() { + Some(repeat(SEP).take(1).collect()) + } else { + let prefix_ = &s[..prefix_len(prefix)]; + let n = prefix_.len() + + if is_abs { comps.len() } else { comps.len() - 1} + + comps.iter().map(|v| v.len()).sum(); + let mut s = String::with_capacity(n); + match prefix { + Some(DiskPrefix) => { + s.push(prefix_.as_bytes()[0].to_ascii_uppercase() as char); + s.push(':'); + } + Some(VerbatimDiskPrefix) => { + s.push_str(&prefix_[..4]); + s.push(prefix_.as_bytes()[4].to_ascii_uppercase() as char); + s.push_str(&prefix_[5..]); + } + Some(UNCPrefix(a,b)) => { + s.push_str("\\\\"); + s.push_str(&prefix_[2..a+2]); + s.push(SEP); + s.push_str(&prefix_[3+a..3+a+b]); + } + Some(_) => s.push_str(prefix_), + None => () + } + let mut it = comps.into_iter(); + if !is_abs { + match it.next() { + None => (), + Some(comp) => s.push_str(comp) + } + } + for comp in it { + s.push(SEP); + s.push_str(comp); + } + Some(s) + } + } + } + } + } + + fn update_sepidx(&mut self) { + let s = if self.has_nonsemantic_trailing_slash() { + &self.repr[..self.repr.len()-1] + } else { &self.repr[] }; + let sep_test: fn(char) -> bool = if !prefix_is_verbatim(self.prefix) { + is_sep + } else { + is_sep_verbatim + }; + let idx = s.rfind(sep_test); + let prefixlen = self.prefix_len(); + self.sepidx = idx.and_then(|x| if x < prefixlen { None } else { Some(x) }); + } + + fn prefix_len(&self) -> uint { + prefix_len(self.prefix) + } + + // Returns a tuple (before, after, end) where before is the index of the separator + // and after is the index just after the separator. + // end is the length of the string, normally, or the index of the final character if it is + // a non-semantic trailing separator in a verbatim string. + // If the prefix is considered the separator, before and after are the same. + fn sepidx_or_prefix_len(&self) -> Option<(uint,uint,uint)> { + match self.sepidx { + None => match self.prefix_len() { 0 => None, x => Some((x,x,self.repr.len())) }, + Some(x) => { + if self.has_nonsemantic_trailing_slash() { + Some((x,x+1,self.repr.len()-1)) + } else { Some((x,x+1,self.repr.len())) } + } + } + } + + fn has_nonsemantic_trailing_slash(&self) -> bool { + is_verbatim(self) && self.repr.len() > self.prefix_len()+1 && + self.repr.as_bytes()[self.repr.len()-1] == SEP_BYTE + } + + fn update_normalized(&mut self, s: &str) { + let (prefix, path) = Path::normalize_(s); + self.repr = path; + self.prefix = prefix; + self.update_sepidx(); + } +} + +/// Returns whether the path is considered "volume-relative", which means a path +/// that looks like "\foo". Paths of this form are relative to the current volume, +/// but absolute within that volume. +#[inline] +pub fn is_vol_relative(path: &Path) -> bool { + path.prefix.is_none() && is_sep_byte(&path.repr.as_bytes()[0]) +} + +/// Returns whether the path is considered "cwd-relative", which means a path +/// with a volume prefix that is not absolute. This look like "C:foo.txt". Paths +/// of this form are relative to the cwd on the given volume. +#[inline] +pub fn is_cwd_relative(path: &Path) -> bool { + path.prefix == Some(DiskPrefix) && !path.is_absolute() +} + +/// Returns the PathPrefix for this Path +#[inline] +pub fn prefix(path: &Path) -> Option { + path.prefix +} + +/// Returns whether the Path's prefix is a verbatim prefix, i.e. `\\?\` +#[inline] +pub fn is_verbatim(path: &Path) -> bool { + prefix_is_verbatim(path.prefix) +} + +/// Returns the non-verbatim equivalent of the input path, if possible. +/// If the input path is a device namespace path, None is returned. +/// If the input path is not verbatim, it is returned as-is. +/// If the input path is verbatim, but the same path can be expressed as +/// non-verbatim, the non-verbatim version is returned. +/// Otherwise, None is returned. +pub fn make_non_verbatim(path: &Path) -> Option { + let repr = &path.repr[]; + let new_path = match path.prefix { + Some(VerbatimPrefix(_)) | Some(DeviceNSPrefix(_)) => return None, + Some(UNCPrefix(_,_)) | Some(DiskPrefix) | None => return Some(path.clone()), + Some(VerbatimDiskPrefix) => { + // \\?\D:\ + Path::new(&repr[4..]) + } + Some(VerbatimUNCPrefix(_,_)) => { + // \\?\UNC\server\share + Path::new(format!(r"\{}", &repr[7..])) + } + }; + if new_path.prefix.is_none() { + // \\?\UNC\server is a VerbatimUNCPrefix + // but \\server is nothing + return None; + } + // now ensure normalization didn't change anything + if &repr[path.prefix_len()..] == &new_path.repr[new_path.prefix_len()..] { + Some(new_path) + } else { + None + } +} + +/// The standard path separator character +pub const SEP: char = '\\'; +/// The standard path separator byte +pub const SEP_BYTE: u8 = SEP as u8; + +/// The alternative path separator character +pub const SEP2: char = '/'; +/// The alternative path separator character +pub const SEP2_BYTE: u8 = SEP2 as u8; + +/// Returns whether the given char is a path separator. +/// Allows both the primary separator '\' and the alternative separator '/'. +#[inline] +pub fn is_sep(c: char) -> bool { + c == SEP || c == SEP2 +} + +/// Returns whether the given char is a path separator. +/// Only allows the primary separator '\'; use is_sep to allow '/'. +#[inline] +pub fn is_sep_verbatim(c: char) -> bool { + c == SEP +} + +/// Returns whether the given byte is a path separator. +/// Allows both the primary separator '\' and the alternative separator '/'. +#[inline] +pub fn is_sep_byte(u: &u8) -> bool { + *u == SEP_BYTE || *u == SEP2_BYTE +} + +/// Returns whether the given byte is a path separator. +/// Only allows the primary separator '\'; use is_sep_byte to allow '/'. +#[inline] +pub fn is_sep_byte_verbatim(u: &u8) -> bool { + *u == SEP_BYTE +} + +/// Prefix types for Path +#[derive(Copy, PartialEq, Clone, Debug)] +pub enum PathPrefix { + /// Prefix `\\?\`, uint is the length of the following component + VerbatimPrefix(uint), + /// Prefix `\\?\UNC\`, uints are the lengths of the UNC components + VerbatimUNCPrefix(uint, uint), + /// Prefix `\\?\C:\` (for any alphabetic character) + VerbatimDiskPrefix, + /// Prefix `\\.\`, uint is the length of the following component + DeviceNSPrefix(uint), + /// UNC prefix `\\server\share`, uints are the lengths of the server/share + UNCPrefix(uint, uint), + /// Prefix `C:` for any alphabetic character + DiskPrefix +} + +fn parse_prefix<'a>(mut path: &'a str) -> Option { + if path.starts_with("\\\\") { + // \\ + path = &path[2..]; + if path.starts_with("?\\") { + // \\?\ + path = &path[2..]; + if path.starts_with("UNC\\") { + // \\?\UNC\server\share + path = &path[4..]; + let (idx_a, idx_b) = match parse_two_comps(path, is_sep_verbatim) { + Some(x) => x, + None => (path.len(), 0) + }; + return Some(VerbatimUNCPrefix(idx_a, idx_b)); + } else { + // \\?\path + let idx = path.find('\\'); + if idx == Some(2) && path.as_bytes()[1] == b':' { + let c = path.as_bytes()[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + // \\?\C:\ path + return Some(VerbatimDiskPrefix); + } + } + let idx = idx.unwrap_or(path.len()); + return Some(VerbatimPrefix(idx)); + } + } else if path.starts_with(".\\") { + // \\.\path + path = &path[2..]; + let idx = path.find('\\').unwrap_or(path.len()); + return Some(DeviceNSPrefix(idx)); + } + match parse_two_comps(path, is_sep) { + Some((idx_a, idx_b)) if idx_a > 0 && idx_b > 0 => { + // \\server\share + return Some(UNCPrefix(idx_a, idx_b)); + } + _ => () + } + } else if path.len() > 1 && path.as_bytes()[1] == b':' { + // C: + let c = path.as_bytes()[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + return Some(DiskPrefix); + } + } + return None; + + fn parse_two_comps(mut path: &str, f: fn(char) -> bool) -> Option<(uint, uint)> { + let idx_a = match path.find(f) { + None => return None, + Some(x) => x + }; + path = &path[idx_a+1..]; + let idx_b = path.find(f).unwrap_or(path.len()); + Some((idx_a, idx_b)) + } +} + +// None result means the string didn't need normalizing +fn normalize_helper<'a>(s: &'a str, prefix: Option) -> (bool, Option>) { + let f: fn(char) -> bool = if !prefix_is_verbatim(prefix) { + is_sep + } else { + is_sep_verbatim + }; + let is_abs = s.len() > prefix_len(prefix) && f(s.char_at(prefix_len(prefix))); + let s_ = &s[prefix_len(prefix)..]; + let s_ = if is_abs { &s_[1..] } else { s_ }; + + if is_abs && s_.is_empty() { + return (is_abs, match prefix { + Some(DiskPrefix) | None => (if is_sep_verbatim(s.char_at(prefix_len(prefix))) { None } + else { Some(vec![]) }), + Some(_) => Some(vec![]), // need to trim the trailing separator + }); + } + let mut comps: Vec<&'a str> = vec![]; + let mut n_up = 0u; + let mut changed = false; + for comp in s_.split(f) { + if comp.is_empty() { changed = true } + else if comp == "." { changed = true } + else if comp == ".." { + let has_abs_prefix = match prefix { + Some(DiskPrefix) => false, + Some(_) => true, + None => false + }; + if (is_abs || has_abs_prefix) && comps.is_empty() { changed = true } + else if comps.len() == n_up { comps.push(".."); n_up += 1 } + else { comps.pop().unwrap(); changed = true } + } else { comps.push(comp) } + } + if !changed && !prefix_is_verbatim(prefix) { + changed = s.find(is_sep).is_some(); + } + if changed { + if comps.is_empty() && !is_abs && prefix.is_none() { + if s == "." { + return (is_abs, None); + } + comps.push("."); + } + (is_abs, Some(comps)) + } else { + (is_abs, None) + } +} + +fn prefix_is_verbatim(p: Option) -> bool { + match p { + Some(VerbatimPrefix(_)) | Some(VerbatimUNCPrefix(_,_)) | Some(VerbatimDiskPrefix) => true, + Some(DeviceNSPrefix(_)) => true, // not really sure, but I think so + _ => false + } +} + +fn prefix_len(p: Option) -> uint { + match p { + None => 0, + Some(VerbatimPrefix(x)) => 4 + x, + Some(VerbatimUNCPrefix(x,y)) => 8 + x + 1 + y, + Some(VerbatimDiskPrefix) => 6, + Some(UNCPrefix(x,y)) => 2 + x + 1 + y, + Some(DeviceNSPrefix(x)) => 4 + x, + Some(DiskPrefix) => 2 + } +} + +#[cfg(test)] +mod tests { + use super::PathPrefix::*; + use super::parse_prefix; + use super::*; + + use clone::Clone; + use iter::IteratorExt; + use option::Option::{self, Some, None}; + use old_path::GenericPath; + use slice::{AsSlice, SliceExt}; + use str::Str; + use string::ToString; + use vec::Vec; + + 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); + } + ) + } + + #[test] + fn test_parse_prefix() { + macro_rules! t { + ($path:expr, $exp:expr) => ( + { + let path = $path; + let exp = $exp; + let res = parse_prefix(path); + assert_eq!(res, exp); + } + ) + } + + t!("\\\\SERVER\\share\\foo", Some(UNCPrefix(6,5))); + t!("\\\\", None); + t!("\\\\SERVER", None); + t!("\\\\SERVER\\", None); + t!("\\\\SERVER\\\\", None); + t!("\\\\SERVER\\\\foo", None); + t!("\\\\SERVER\\share", Some(UNCPrefix(6,5))); + t!("\\\\SERVER/share/foo", Some(UNCPrefix(6,5))); + t!("\\\\SERVER\\share/foo", Some(UNCPrefix(6,5))); + t!("//SERVER/share/foo", None); + t!("\\\\\\a\\b\\c", None); + t!("\\\\?\\a\\b\\c", Some(VerbatimPrefix(1))); + t!("\\\\?\\a/b/c", Some(VerbatimPrefix(5))); + t!("//?/a/b/c", None); + t!("\\\\.\\a\\b", Some(DeviceNSPrefix(1))); + t!("\\\\.\\a/b", Some(DeviceNSPrefix(3))); + t!("//./a/b", None); + t!("\\\\?\\UNC\\server\\share\\foo", Some(VerbatimUNCPrefix(6,5))); + t!("\\\\?\\UNC\\\\share\\foo", Some(VerbatimUNCPrefix(0,5))); + t!("\\\\?\\UNC\\", Some(VerbatimUNCPrefix(0,0))); + t!("\\\\?\\UNC\\server/share/foo", Some(VerbatimUNCPrefix(16,0))); + t!("\\\\?\\UNC\\server", Some(VerbatimUNCPrefix(6,0))); + t!("\\\\?\\UNC\\server\\", Some(VerbatimUNCPrefix(6,0))); + t!("\\\\?\\UNC/server/share", Some(VerbatimPrefix(16))); + t!("\\\\?\\UNC", Some(VerbatimPrefix(3))); + t!("\\\\?\\C:\\a\\b.txt", Some(VerbatimDiskPrefix)); + t!("\\\\?\\z:\\", Some(VerbatimDiskPrefix)); + t!("\\\\?\\C:", Some(VerbatimPrefix(2))); + t!("\\\\?\\C:a.txt", Some(VerbatimPrefix(7))); + t!("\\\\?\\C:a\\b.txt", Some(VerbatimPrefix(3))); + t!("\\\\?\\C:/a", Some(VerbatimPrefix(4))); + t!("C:\\foo", Some(DiskPrefix)); + t!("z:/foo", Some(DiskPrefix)); + t!("d:", Some(DiskPrefix)); + t!("ab:", None); + t!("ü:\\foo", None); + t!("3:\\foo", None); + t!(" :\\foo", None); + t!("::\\foo", None); + t!("\\\\?\\C:", Some(VerbatimPrefix(2))); + t!("\\\\?\\z:\\", Some(VerbatimDiskPrefix)); + t!("\\\\?\\ab:\\", Some(VerbatimPrefix(3))); + t!("\\\\?\\C:\\a", Some(VerbatimDiskPrefix)); + t!("\\\\?\\C:/a", Some(VerbatimPrefix(4))); + t!("\\\\?\\C:\\a/b", Some(VerbatimDiskPrefix)); + } + + #[test] + fn test_paths() { + let empty: &[u8] = &[]; + t!(v: Path::new(empty), b"."); + t!(v: Path::new(b"\\"), b"\\"); + t!(v: Path::new(b"a\\b\\c"), b"a\\b\\c"); + + t!(s: Path::new(""), "."); + t!(s: Path::new("\\"), "\\"); + t!(s: Path::new("hi"), "hi"); + t!(s: Path::new("hi\\"), "hi"); + t!(s: Path::new("\\lib"), "\\lib"); + t!(s: Path::new("\\lib\\"), "\\lib"); + t!(s: Path::new("hi\\there"), "hi\\there"); + t!(s: Path::new("hi\\there.txt"), "hi\\there.txt"); + t!(s: Path::new("/"), "\\"); + t!(s: Path::new("hi/"), "hi"); + t!(s: Path::new("/lib"), "\\lib"); + t!(s: Path::new("/lib/"), "\\lib"); + t!(s: Path::new("hi/there"), "hi\\there"); + + t!(s: Path::new("hi\\there\\"), "hi\\there"); + t!(s: Path::new("hi\\..\\there"), "there"); + t!(s: Path::new("hi/../there"), "there"); + t!(s: Path::new("..\\hi\\there"), "..\\hi\\there"); + t!(s: Path::new("\\..\\hi\\there"), "\\hi\\there"); + t!(s: Path::new("/../hi/there"), "\\hi\\there"); + t!(s: Path::new("foo\\.."), "."); + t!(s: Path::new("\\foo\\.."), "\\"); + t!(s: Path::new("\\foo\\..\\.."), "\\"); + t!(s: Path::new("\\foo\\..\\..\\bar"), "\\bar"); + t!(s: Path::new("\\.\\hi\\.\\there\\."), "\\hi\\there"); + t!(s: Path::new("\\.\\hi\\.\\there\\.\\.."), "\\hi"); + t!(s: Path::new("foo\\..\\.."), ".."); + t!(s: Path::new("foo\\..\\..\\.."), "..\\.."); + t!(s: Path::new("foo\\..\\..\\bar"), "..\\bar"); + + assert_eq!(Path::new(b"foo\\bar").into_vec(), b"foo\\bar"); + assert_eq!(Path::new(b"\\foo\\..\\..\\bar").into_vec(), b"\\bar"); + + t!(s: Path::new("\\\\a"), "\\a"); + t!(s: Path::new("\\\\a\\"), "\\a"); + t!(s: Path::new("\\\\a\\b"), "\\\\a\\b"); + t!(s: Path::new("\\\\a\\b\\"), "\\\\a\\b"); + t!(s: Path::new("\\\\a\\b/"), "\\\\a\\b"); + t!(s: Path::new("\\\\\\b"), "\\b"); + t!(s: Path::new("\\\\a\\\\b"), "\\a\\b"); + t!(s: Path::new("\\\\a\\b\\c"), "\\\\a\\b\\c"); + t!(s: Path::new("\\\\server\\share/path"), "\\\\server\\share\\path"); + t!(s: Path::new("\\\\server/share/path"), "\\\\server\\share\\path"); + t!(s: Path::new("C:a\\b.txt"), "C:a\\b.txt"); + t!(s: Path::new("C:a/b.txt"), "C:a\\b.txt"); + t!(s: Path::new("z:\\a\\b.txt"), "Z:\\a\\b.txt"); + t!(s: Path::new("z:/a/b.txt"), "Z:\\a\\b.txt"); + t!(s: Path::new("ab:/a/b.txt"), "ab:\\a\\b.txt"); + t!(s: Path::new("C:\\"), "C:\\"); + t!(s: Path::new("C:"), "C:"); + t!(s: Path::new("q:"), "Q:"); + t!(s: Path::new("C:/"), "C:\\"); + t!(s: Path::new("C:\\foo\\.."), "C:\\"); + t!(s: Path::new("C:foo\\.."), "C:"); + t!(s: Path::new("C:\\a\\"), "C:\\a"); + t!(s: Path::new("C:\\a/"), "C:\\a"); + t!(s: Path::new("C:\\a\\b\\"), "C:\\a\\b"); + t!(s: Path::new("C:\\a\\b/"), "C:\\a\\b"); + t!(s: Path::new("C:a\\"), "C:a"); + t!(s: Path::new("C:a/"), "C:a"); + t!(s: Path::new("C:a\\b\\"), "C:a\\b"); + t!(s: Path::new("C:a\\b/"), "C:a\\b"); + t!(s: Path::new("\\\\?\\z:\\a\\b.txt"), "\\\\?\\z:\\a\\b.txt"); + t!(s: Path::new("\\\\?\\C:/a/b.txt"), "\\\\?\\C:/a/b.txt"); + t!(s: Path::new("\\\\?\\C:\\a/b.txt"), "\\\\?\\C:\\a/b.txt"); + t!(s: Path::new("\\\\?\\test\\a\\b.txt"), "\\\\?\\test\\a\\b.txt"); + t!(s: Path::new("\\\\?\\foo\\bar\\"), "\\\\?\\foo\\bar\\"); + t!(s: Path::new("\\\\.\\foo\\bar"), "\\\\.\\foo\\bar"); + t!(s: Path::new("\\\\.\\"), "\\\\.\\"); + t!(s: Path::new("\\\\?\\UNC\\server\\share\\foo"), "\\\\?\\UNC\\server\\share\\foo"); + t!(s: Path::new("\\\\?\\UNC\\server/share"), "\\\\?\\UNC\\server/share\\"); + t!(s: Path::new("\\\\?\\UNC\\server"), "\\\\?\\UNC\\server\\"); + t!(s: Path::new("\\\\?\\UNC\\"), "\\\\?\\UNC\\\\"); + t!(s: Path::new("\\\\?\\UNC"), "\\\\?\\UNC"); + + // I'm not sure whether \\.\foo/bar should normalize to \\.\foo\bar + // as information is sparse and this isn't really googleable. + // I'm going to err on the side of not normalizing it, as this skips the filesystem + t!(s: Path::new("\\\\.\\foo/bar"), "\\\\.\\foo/bar"); + t!(s: Path::new("\\\\.\\foo\\bar"), "\\\\.\\foo\\bar"); + } + + #[test] + fn test_opt_paths() { + assert!(Path::new_opt(b"foo\\bar\0") == None); + assert!(Path::new_opt(b"foo\\bar\x80") == None); + t!(v: Path::new_opt(b"foo\\bar").unwrap(), b"foo\\bar"); + assert!(Path::new_opt("foo\\bar\0") == None); + t!(s: Path::new_opt("foo\\bar").unwrap(), "foo\\bar"); + } + + #[test] + fn test_null_byte() { + use thread::Thread; + let result = Thread::scoped(move|| { + Path::new(b"foo/bar\0") + }).join(); + assert!(result.is_err()); + + let result = Thread::scoped(move|| { + Path::new("test").set_filename(b"f\0o") + }).join(); + assert!(result.is_err()); + + let result = Thread::scoped(move || { + Path::new("test").push(b"f\0o"); + }).join(); + assert!(result.is_err()); + } + + #[test] + #[should_fail] + fn test_not_utf8_panics() { + Path::new(b"hello\x80.txt"); + } + + #[test] + fn test_display_str() { + let path = Path::new("foo"); + assert_eq!(path.display().to_string(), "foo"); + let path = Path::new(b"\\"); + assert_eq!(path.filename_display().to_string(), ""); + + let path = Path::new("foo"); + let mo = path.display().as_cow(); + assert_eq!(mo.as_slice(), "foo"); + let path = Path::new(b"\\"); + let mo = path.filename_display().as_cow(); + assert_eq!(mo.as_slice(), ""); + } + + #[test] + fn test_display() { + macro_rules! t { + ($path:expr, $exp:expr, $expf:expr) => ( + { + let path = Path::new($path); + let f = format!("{}", path.display()); + assert_eq!(f, $exp); + let f = format!("{}", path.filename_display()); + assert_eq!(f, $expf); + } + ) + } + + t!("foo", "foo", "foo"); + t!("foo\\bar", "foo\\bar", "bar"); + t!("\\", "\\", ""); + } + + #[test] + fn test_components() { + macro_rules! t { + (s: $path:expr, $op:ident, $exp:expr) => ( + { + let path = $path; + let path = Path::new(path); + assert_eq!(path.$op(), Some($exp)); + } + ); + (s: $path:expr, $op:ident, $exp:expr, opt) => ( + { + let path = $path; + let path = Path::new(path); + let left = path.$op(); + assert_eq!(left, $exp); + } + ); + (v: $path:expr, $op:ident, $exp:expr) => ( + { + let path = $path; + let path = Path::new(path); + assert_eq!(path.$op(), $exp); + } + ) + } + + t!(v: b"a\\b\\c", filename, Some(b"c")); + t!(s: "a\\b\\c", filename_str, "c"); + t!(s: "\\a\\b\\c", filename_str, "c"); + t!(s: "a", filename_str, "a"); + t!(s: "\\a", filename_str, "a"); + t!(s: ".", filename_str, None, opt); + t!(s: "\\", filename_str, None, opt); + t!(s: "..", filename_str, None, opt); + t!(s: "..\\..", filename_str, None, opt); + t!(s: "c:\\foo.txt", filename_str, "foo.txt"); + t!(s: "C:\\", filename_str, None, opt); + t!(s: "C:", filename_str, None, opt); + t!(s: "\\\\server\\share\\foo.txt", filename_str, "foo.txt"); + t!(s: "\\\\server\\share", filename_str, None, opt); + t!(s: "\\\\server", filename_str, "server"); + t!(s: "\\\\?\\bar\\foo.txt", filename_str, "foo.txt"); + t!(s: "\\\\?\\bar", filename_str, None, opt); + t!(s: "\\\\?\\", filename_str, None, opt); + t!(s: "\\\\?\\UNC\\server\\share\\foo.txt", filename_str, "foo.txt"); + t!(s: "\\\\?\\UNC\\server", filename_str, None, opt); + t!(s: "\\\\?\\UNC\\", filename_str, None, opt); + t!(s: "\\\\?\\C:\\foo.txt", filename_str, "foo.txt"); + t!(s: "\\\\?\\C:\\", filename_str, None, opt); + t!(s: "\\\\?\\C:", filename_str, None, opt); + t!(s: "\\\\?\\foo/bar", filename_str, None, opt); + t!(s: "\\\\?\\C:/foo", filename_str, None, opt); + t!(s: "\\\\.\\foo\\bar", filename_str, "bar"); + t!(s: "\\\\.\\foo", filename_str, None, opt); + t!(s: "\\\\.\\foo/bar", filename_str, None, opt); + t!(s: "\\\\.\\foo\\bar/baz", filename_str, "bar/baz"); + t!(s: "\\\\.\\", filename_str, None, opt); + t!(s: "\\\\?\\a\\b\\", filename_str, "b"); + + t!(v: b"a\\b\\c", dirname, b"a\\b"); + t!(s: "a\\b\\c", dirname_str, "a\\b"); + t!(s: "\\a\\b\\c", dirname_str, "\\a\\b"); + t!(s: "a", dirname_str, "."); + t!(s: "\\a", dirname_str, "\\"); + t!(s: ".", dirname_str, "."); + t!(s: "\\", dirname_str, "\\"); + t!(s: "..", dirname_str, ".."); + t!(s: "..\\..", dirname_str, "..\\.."); + t!(s: "c:\\foo.txt", dirname_str, "C:\\"); + t!(s: "C:\\", dirname_str, "C:\\"); + t!(s: "C:", dirname_str, "C:"); + t!(s: "C:foo.txt", dirname_str, "C:"); + t!(s: "\\\\server\\share\\foo.txt", dirname_str, "\\\\server\\share"); + t!(s: "\\\\server\\share", dirname_str, "\\\\server\\share"); + t!(s: "\\\\server", dirname_str, "\\"); + t!(s: "\\\\?\\bar\\foo.txt", dirname_str, "\\\\?\\bar"); + t!(s: "\\\\?\\bar", dirname_str, "\\\\?\\bar"); + t!(s: "\\\\?\\", dirname_str, "\\\\?\\"); + t!(s: "\\\\?\\UNC\\server\\share\\foo.txt", dirname_str, "\\\\?\\UNC\\server\\share"); + t!(s: "\\\\?\\UNC\\server", dirname_str, "\\\\?\\UNC\\server\\"); + t!(s: "\\\\?\\UNC\\", dirname_str, "\\\\?\\UNC\\\\"); + t!(s: "\\\\?\\C:\\foo.txt", dirname_str, "\\\\?\\C:\\"); + t!(s: "\\\\?\\C:\\", dirname_str, "\\\\?\\C:\\"); + t!(s: "\\\\?\\C:", dirname_str, "\\\\?\\C:"); + t!(s: "\\\\?\\C:/foo/bar", dirname_str, "\\\\?\\C:/foo/bar"); + t!(s: "\\\\?\\foo/bar", dirname_str, "\\\\?\\foo/bar"); + t!(s: "\\\\.\\foo\\bar", dirname_str, "\\\\.\\foo"); + t!(s: "\\\\.\\foo", dirname_str, "\\\\.\\foo"); + t!(s: "\\\\?\\a\\b\\", dirname_str, "\\\\?\\a"); + + t!(v: b"hi\\there.txt", filestem, Some(b"there")); + t!(s: "hi\\there.txt", filestem_str, "there"); + t!(s: "hi\\there", filestem_str, "there"); + t!(s: "there.txt", filestem_str, "there"); + t!(s: "there", filestem_str, "there"); + t!(s: ".", filestem_str, None, opt); + t!(s: "\\", filestem_str, None, opt); + t!(s: "foo\\.bar", filestem_str, ".bar"); + t!(s: ".bar", filestem_str, ".bar"); + t!(s: "..bar", filestem_str, "."); + t!(s: "hi\\there..txt", filestem_str, "there."); + t!(s: "..", filestem_str, None, opt); + t!(s: "..\\..", filestem_str, None, opt); + // filestem is based on filename, so we don't need the full set of prefix tests + + t!(v: b"hi\\there.txt", extension, Some(b"txt")); + t!(v: b"hi\\there", extension, None); + t!(s: "hi\\there.txt", extension_str, Some("txt"), opt); + t!(s: "hi\\there", extension_str, None, opt); + t!(s: "there.txt", extension_str, Some("txt"), opt); + t!(s: "there", extension_str, None, opt); + t!(s: ".", extension_str, None, opt); + t!(s: "\\", extension_str, None, opt); + t!(s: "foo\\.bar", extension_str, None, opt); + t!(s: ".bar", extension_str, None, opt); + t!(s: "..bar", extension_str, Some("bar"), opt); + t!(s: "hi\\there..txt", extension_str, Some("txt"), opt); + t!(s: "..", extension_str, None, opt); + t!(s: "..\\..", extension_str, None, opt); + // extension is based on filename, so we don't need the full set of prefix tests + } + + #[test] + fn test_push() { + macro_rules! t { + (s: $path:expr, $join:expr) => ( + { + let path = $path; + let join = $join; + let mut p1 = Path::new(path); + let p2 = p1.clone(); + p1.push(join); + assert_eq!(p1, p2.join(join)); + } + ) + } + + t!(s: "a\\b\\c", ".."); + t!(s: "\\a\\b\\c", "d"); + t!(s: "a\\b", "c\\d"); + t!(s: "a\\b", "\\c\\d"); + // this is just a sanity-check test. push and join share an implementation, + // so there's no need for the full set of prefix tests + + // we do want to check one odd case though to ensure the prefix is re-parsed + let mut p = Path::new("\\\\?\\C:"); + assert_eq!(prefix(&p), Some(VerbatimPrefix(2))); + p.push("foo"); + assert_eq!(prefix(&p), Some(VerbatimDiskPrefix)); + assert_eq!(p.as_str(), Some("\\\\?\\C:\\foo")); + + // and another with verbatim non-normalized paths + let mut p = Path::new("\\\\?\\C:\\a\\"); + p.push("foo"); + assert_eq!(p.as_str(), Some("\\\\?\\C:\\a\\foo")); + } + + #[test] + fn test_push_path() { + macro_rules! t { + (s: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::new($path); + let push = Path::new($push); + p.push(&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"); + t!(s: "a\\b", "C:a.txt", "C:a.txt"); + t!(s: "a\\b", "..\\..\\..\\c", "..\\c"); + t!(s: "a\\b", "C:\\a.txt", "C:\\a.txt"); + t!(s: "C:\\a", "C:\\b.txt", "C:\\b.txt"); + t!(s: "C:\\a\\b\\c", "C:d", "C:\\a\\b\\c\\d"); + t!(s: "C:a\\b\\c", "C:d", "C:a\\b\\c\\d"); + t!(s: "C:a\\b", "..\\..\\..\\c", "C:..\\c"); + t!(s: "C:\\a\\b", "..\\..\\..\\c", "C:\\c"); + t!(s: "C:", r"a\b\c", r"C:a\b\c"); + t!(s: "C:", r"..\a", r"C:..\a"); + t!(s: "\\\\server\\share\\foo", "bar", "\\\\server\\share\\foo\\bar"); + t!(s: "\\\\server\\share\\foo", "..\\..\\bar", "\\\\server\\share\\bar"); + t!(s: "\\\\server\\share\\foo", "C:baz", "C:baz"); + t!(s: "\\\\?\\C:\\a\\b", "C:c\\d", "\\\\?\\C:\\a\\b\\c\\d"); + t!(s: "\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); + t!(s: "\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); + t!(s: "\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); + t!(s: "\\\\?\\C:\\a\\b", "..\\..\\..\\c", "\\\\?\\C:\\a\\b\\..\\..\\..\\c"); + t!(s: "\\\\?\\foo\\bar", "..\\..\\c", "\\\\?\\foo\\bar\\..\\..\\c"); + t!(s: "\\\\?\\", "foo", "\\\\?\\\\foo"); + t!(s: "\\\\?\\UNC\\server\\share\\foo", "bar", "\\\\?\\UNC\\server\\share\\foo\\bar"); + t!(s: "\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); + t!(s: "\\\\?\\UNC\\server\\share", "C:a", "C:a"); + t!(s: "\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\\\foo"); + t!(s: "C:\\a", "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share"); + t!(s: "\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); + t!(s: "\\\\.\\foo\\bar", "C:a", "C:a"); + // again, not sure about the following, but I'm assuming \\.\ should be verbatim + t!(s: "\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); + + t!(s: "\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one + } + + #[test] + fn test_push_many() { + macro_rules! t { + (s: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::new($path); + p.push_many(&$push); + assert_eq!(p.as_str(), Some($exp)); + } + ); + (v: $path:expr, $push:expr, $exp:expr) => ( + { + let mut p = Path::new($path); + p.push_many(&$push); + assert_eq!(p.as_vec(), $exp); + } + ) + } + + t!(s: "a\\b\\c", ["d", "e"], "a\\b\\c\\d\\e"); + t!(s: "a\\b\\c", ["d", "\\e"], "\\e"); + t!(s: "a\\b\\c", ["d", "\\e", "f"], "\\e\\f"); + t!(s: "a\\b\\c", ["d".to_string(), "e".to_string()], "a\\b\\c\\d\\e"); + t!(v: b"a\\b\\c", [b"d", b"e"], b"a\\b\\c\\d\\e"); + t!(v: b"a\\b\\c", [b"d", b"\\e", b"f"], b"\\e\\f"); + t!(v: b"a\\b\\c", [b"d".to_vec(), b"e".to_vec()], + b"a\\b\\c\\d\\e"); + } + + #[test] + fn test_pop() { + macro_rules! t { + (s: $path:expr, $left:expr, $right:expr) => ( + { + let pstr = $path; + let mut p = Path::new(pstr); + let result = p.pop(); + let left = $left; + assert_eq!(p.as_str(), Some(left)); + assert_eq!(result, $right); + } + ); + (b: $path:expr, $left:expr, $right:expr) => ( + { + let mut p = Path::new($path); + let result = p.pop(); + assert_eq!(p.as_vec(), $left); + assert_eq!(result, $right); + } + ) + } + + t!(s: "a\\b\\c", "a\\b", true); + t!(s: "a", ".", true); + t!(s: ".", ".", false); + t!(s: "\\a", "\\", true); + t!(s: "\\", "\\", false); + t!(b: b"a\\b\\c", b"a\\b", true); + t!(b: b"a", b".", true); + t!(b: b".", b".", false); + t!(b: b"\\a", b"\\", true); + t!(b: b"\\", b"\\", false); + + t!(s: "C:\\a\\b", "C:\\a", true); + t!(s: "C:\\a", "C:\\", true); + t!(s: "C:\\", "C:\\", false); + t!(s: "C:a\\b", "C:a", true); + t!(s: "C:a", "C:", true); + t!(s: "C:", "C:", false); + t!(s: "\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); + t!(s: "\\\\server\\share\\a", "\\\\server\\share", true); + t!(s: "\\\\server\\share", "\\\\server\\share", false); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); + t!(s: "\\\\?\\a\\b", "\\\\?\\a", true); + t!(s: "\\\\?\\a", "\\\\?\\a", false); + t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); + t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\", true); + t!(s: "\\\\?\\C:\\", "\\\\?\\C:\\", false); + t!(s: "\\\\?\\UNC\\server\\share\\a\\b", "\\\\?\\UNC\\server\\share\\a", true); + t!(s: "\\\\?\\UNC\\server\\share\\a", "\\\\?\\UNC\\server\\share", true); + t!(s: "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share", false); + t!(s: "\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); + t!(s: "\\\\.\\a\\b", "\\\\.\\a", true); + t!(s: "\\\\.\\a", "\\\\.\\a", false); + + t!(s: "\\\\?\\a\\b\\", "\\\\?\\a", true); + } + + #[test] + fn test_root_path() { + assert_eq!(Path::new("a\\b\\c").root_path(), None); + assert_eq!(Path::new("\\a\\b\\c").root_path(), Some(Path::new("\\"))); + assert_eq!(Path::new("C:a").root_path(), Some(Path::new("C:"))); + assert_eq!(Path::new("C:\\a").root_path(), Some(Path::new("C:\\"))); + assert_eq!(Path::new("\\\\a\\b\\c").root_path(), Some(Path::new("\\\\a\\b"))); + assert_eq!(Path::new("\\\\?\\a\\b").root_path(), Some(Path::new("\\\\?\\a"))); + assert_eq!(Path::new("\\\\?\\C:\\a").root_path(), Some(Path::new("\\\\?\\C:\\"))); + assert_eq!(Path::new("\\\\?\\UNC\\a\\b\\c").root_path(), + Some(Path::new("\\\\?\\UNC\\a\\b"))); + assert_eq!(Path::new("\\\\.\\a\\b").root_path(), Some(Path::new("\\\\.\\a"))); + } + + #[test] + fn test_join() { + t!(s: Path::new("a\\b\\c").join(".."), "a\\b"); + t!(s: Path::new("\\a\\b\\c").join("d"), "\\a\\b\\c\\d"); + t!(s: Path::new("a\\b").join("c\\d"), "a\\b\\c\\d"); + t!(s: Path::new("a\\b").join("\\c\\d"), "\\c\\d"); + t!(s: Path::new(".").join("a\\b"), "a\\b"); + t!(s: Path::new("\\").join("a\\b"), "\\a\\b"); + 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"); + // full join testing is covered under test_push_path, so no need for + // the full set of prefix tests + } + + #[test] + fn test_join_path() { + macro_rules! t { + (s: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let join = Path::new($join); + let res = path.join(&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"); + // join is implemented using push, so there's no need for + // the full set of prefix tests + } + + #[test] + fn test_join_many() { + macro_rules! t { + (s: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let res = path.join_many(&$join); + assert_eq!(res.as_str(), Some($exp)); + } + ); + (v: $path:expr, $join:expr, $exp:expr) => ( + { + let path = Path::new($path); + let res = path.join_many(&$join); + assert_eq!(res.as_vec(), $exp); + } + ) + } + + t!(s: "a\\b\\c", ["d", "e"], "a\\b\\c\\d\\e"); + t!(s: "a\\b\\c", ["..", "d"], "a\\b\\d"); + t!(s: "a\\b\\c", ["d", "\\e", "f"], "\\e\\f"); + t!(s: "a\\b\\c", ["d".to_string(), "e".to_string()], "a\\b\\c\\d\\e"); + t!(v: b"a\\b\\c", [b"d", b"e"], b"a\\b\\c\\d\\e"); + t!(v: b"a\\b\\c", [b"d".to_vec(), b"e".to_vec()], + b"a\\b\\c\\d\\e"); + } + + #[test] + fn test_with_helpers() { + macro_rules! t { + (s: $path:expr, $op:ident, $arg:expr, $res:expr) => ( + { + let pstr = $path; + let path = Path::new(pstr); + let arg = $arg; + let res = path.$op(arg); + let exp = Path::new($res); + assert_eq!(res, exp); + } + ) + } + + t!(s: "a\\b\\c", with_filename, "d", "a\\b\\d"); + t!(s: ".", with_filename, "foo", "foo"); + t!(s: "\\a\\b\\c", with_filename, "d", "\\a\\b\\d"); + t!(s: "\\", with_filename, "foo", "\\foo"); + t!(s: "\\a", with_filename, "foo", "\\foo"); + t!(s: "foo", with_filename, "bar", "bar"); + t!(s: "\\", with_filename, "foo\\", "\\foo"); + t!(s: "\\a", with_filename, "foo\\", "\\foo"); + t!(s: "a\\b\\c", with_filename, "", "a\\b"); + t!(s: "a\\b\\c", with_filename, ".", "a\\b"); + t!(s: "a\\b\\c", with_filename, "..", "a"); + t!(s: "\\a", with_filename, "", "\\"); + t!(s: "foo", with_filename, "", "."); + t!(s: "a\\b\\c", with_filename, "d\\e", "a\\b\\d\\e"); + t!(s: "a\\b\\c", with_filename, "\\d", "a\\b\\d"); + t!(s: "..", with_filename, "foo", "..\\foo"); + t!(s: "..\\..", with_filename, "foo", "..\\..\\foo"); + t!(s: "..", with_filename, "", ".."); + t!(s: "..\\..", with_filename, "", "..\\.."); + t!(s: "C:\\foo\\bar", with_filename, "baz", "C:\\foo\\baz"); + t!(s: "C:\\foo", with_filename, "bar", "C:\\bar"); + t!(s: "C:\\", with_filename, "foo", "C:\\foo"); + t!(s: "C:foo\\bar", with_filename, "baz", "C:foo\\baz"); + t!(s: "C:foo", with_filename, "bar", "C:bar"); + t!(s: "C:", with_filename, "foo", "C:foo"); + t!(s: "C:\\foo", with_filename, "", "C:\\"); + t!(s: "C:foo", with_filename, "", "C:"); + t!(s: "C:\\foo\\bar", with_filename, "..", "C:\\"); + t!(s: "C:\\foo", with_filename, "..", "C:\\"); + t!(s: "C:\\", with_filename, "..", "C:\\"); + t!(s: "C:foo\\bar", with_filename, "..", "C:"); + t!(s: "C:foo", with_filename, "..", "C:.."); + t!(s: "C:", with_filename, "..", "C:.."); + t!(s: "\\\\server\\share\\foo", with_filename, "bar", "\\\\server\\share\\bar"); + t!(s: "\\\\server\\share", with_filename, "foo", "\\\\server\\share\\foo"); + t!(s: "\\\\server\\share\\foo", with_filename, "", "\\\\server\\share"); + t!(s: "\\\\server\\share", with_filename, "", "\\\\server\\share"); + t!(s: "\\\\server\\share\\foo", with_filename, "..", "\\\\server\\share"); + t!(s: "\\\\server\\share", with_filename, "..", "\\\\server\\share"); + t!(s: "\\\\?\\C:\\foo\\bar", with_filename, "baz", "\\\\?\\C:\\foo\\baz"); + t!(s: "\\\\?\\C:\\foo", with_filename, "bar", "\\\\?\\C:\\bar"); + t!(s: "\\\\?\\C:\\", with_filename, "foo", "\\\\?\\C:\\foo"); + t!(s: "\\\\?\\C:\\foo", with_filename, "..", "\\\\?\\C:\\.."); + t!(s: "\\\\?\\foo\\bar", with_filename, "baz", "\\\\?\\foo\\baz"); + t!(s: "\\\\?\\foo", with_filename, "bar", "\\\\?\\foo\\bar"); + t!(s: "\\\\?\\", with_filename, "foo", "\\\\?\\\\foo"); + t!(s: "\\\\?\\foo\\bar", with_filename, "..", "\\\\?\\foo\\.."); + t!(s: "\\\\.\\foo\\bar", with_filename, "baz", "\\\\.\\foo\\baz"); + t!(s: "\\\\.\\foo", with_filename, "bar", "\\\\.\\foo\\bar"); + t!(s: "\\\\.\\foo\\bar", with_filename, "..", "\\\\.\\foo\\.."); + + t!(s: "hi\\there.txt", with_extension, "exe", "hi\\there.exe"); + t!(s: "hi\\there.txt", with_extension, "", "hi\\there"); + t!(s: "hi\\there.txt", with_extension, ".", "hi\\there.."); + t!(s: "hi\\there.txt", with_extension, "..", "hi\\there..."); + t!(s: "hi\\there", with_extension, "txt", "hi\\there.txt"); + t!(s: "hi\\there", with_extension, ".", "hi\\there.."); + t!(s: "hi\\there", with_extension, "..", "hi\\there..."); + t!(s: "hi\\there.", with_extension, "txt", "hi\\there.txt"); + t!(s: "hi\\.foo", with_extension, "txt", "hi\\.foo.txt"); + t!(s: "hi\\there.txt", with_extension, ".foo", "hi\\there..foo"); + t!(s: "\\", with_extension, "txt", "\\"); + t!(s: "\\", with_extension, ".", "\\"); + t!(s: "\\", with_extension, "..", "\\"); + t!(s: ".", with_extension, "txt", "."); + // extension setter calls filename setter internally, no need for extended tests + } + + #[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::new(path); + p1.$set(arg); + let p2 = Path::new(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_filename, with_filename, b"d"); + t!(v: b"\\", set_filename, with_filename, b"foo"); + t!(s: "a\\b\\c", set_filename, with_filename, "d"); + t!(s: "\\", set_filename, with_filename, "foo"); + t!(s: ".", set_filename, with_filename, "foo"); + t!(s: "a\\b", set_filename, with_filename, ""); + t!(s: "a", set_filename, with_filename, ""); + + t!(v: b"hi\\there.txt", set_extension, with_extension, b"exe"); + t!(s: "hi\\there.txt", set_extension, with_extension, "exe"); + t!(s: "hi\\there.", set_extension, with_extension, "txt"); + t!(s: "hi\\there", set_extension, with_extension, "txt"); + t!(s: "hi\\there.txt", set_extension, with_extension, ""); + t!(s: "hi\\there", set_extension, with_extension, ""); + t!(s: ".", set_extension, with_extension, "txt"); + + // with_ helpers use the setter internally, so the tests for the with_ helpers + // will suffice. No need for the full set of prefix tests. + } + + #[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"), Some(b"c"), b"a\\b", Some(b"c"), None); + t!(s: Path::new("a\\b\\c"), Some("c"), Some("a\\b"), Some("c"), None); + t!(s: Path::new("."), None, Some("."), None, None); + t!(s: Path::new("\\"), None, Some("\\"), None, None); + t!(s: Path::new(".."), None, Some(".."), None, None); + t!(s: Path::new("..\\.."), None, Some("..\\.."), None, None); + t!(s: Path::new("hi\\there.txt"), Some("there.txt"), Some("hi"), + Some("there"), Some("txt")); + t!(s: Path::new("hi\\there"), Some("there"), Some("hi"), Some("there"), None); + t!(s: Path::new("hi\\there."), Some("there."), Some("hi"), + Some("there"), Some("")); + t!(s: Path::new("hi\\.there"), Some(".there"), Some("hi"), Some(".there"), None); + t!(s: Path::new("hi\\..there"), Some("..there"), Some("hi"), + Some("."), Some("there")); + + // these are already tested in test_components, so no need for extended tests + } + + #[test] + fn test_dir_path() { + t!(s: Path::new("hi\\there").dir_path(), "hi"); + t!(s: Path::new("hi").dir_path(), "."); + t!(s: Path::new("\\hi").dir_path(), "\\"); + t!(s: Path::new("\\").dir_path(), "\\"); + t!(s: Path::new("..").dir_path(), ".."); + t!(s: Path::new("..\\..").dir_path(), "..\\.."); + + // dir_path is just dirname interpreted as a path. + // No need for extended tests + } + + #[test] + fn test_is_absolute() { + macro_rules! t { + ($path:expr, $abs:expr, $vol:expr, $cwd:expr, $rel:expr) => ( + { + let path = Path::new($path); + let (abs, vol, cwd, rel) = ($abs, $vol, $cwd, $rel); + assert_eq!(path.is_absolute(), abs); + assert_eq!(is_vol_relative(&path), vol); + assert_eq!(is_cwd_relative(&path), cwd); + assert_eq!(path.is_relative(), rel); + } + ) + } + t!("a\\b\\c", false, false, false, true); + t!("\\a\\b\\c", false, true, false, false); + t!("a", false, false, false, true); + t!("\\a", false, true, false, false); + t!(".", false, false, false, true); + t!("\\", false, true, false, false); + t!("..", false, false, false, true); + t!("..\\..", false, false, false, true); + t!("C:a\\b.txt", false, false, true, false); + t!("C:\\a\\b.txt", true, false, false, false); + t!("\\\\server\\share\\a\\b.txt", true, false, false, false); + t!("\\\\?\\a\\b\\c.txt", true, false, false, false); + t!("\\\\?\\C:\\a\\b.txt", true, false, false, false); + t!("\\\\?\\C:a\\b.txt", true, false, false, false); // NB: not equivalent to C:a\b.txt + t!("\\\\?\\UNC\\server\\share\\a\\b.txt", true, false, false, false); + t!("\\\\.\\a\\b", true, false, false, false); + } + + #[test] + fn test_is_ancestor_of() { + macro_rules! t { + (s: $path:expr, $dest:expr, $exp:expr) => ( + { + let path = Path::new($path); + let dest = Path::new($dest); + let exp = $exp; + let res = path.is_ancestor_of(&dest); + assert_eq!(res, 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); + t!(s: "foo\\bar", "foobar", false); + t!(s: "foobar", "foo\\bar", false); + + t!(s: "foo", "C:foo", false); + t!(s: "C:foo", "foo", false); + t!(s: "C:foo", "C:foo\\bar", true); + t!(s: "C:foo\\bar", "C:foo", false); + t!(s: "C:\\foo", "C:\\foo\\bar", true); + t!(s: "C:", "C:", true); + t!(s: "C:", "C:\\", false); + t!(s: "C:\\", "C:", false); + t!(s: "C:\\", "C:\\", true); + t!(s: "C:\\foo\\bar", "C:\\foo", false); + t!(s: "C:foo\\bar", "C:foo", false); + t!(s: "C:\\foo", "\\foo", false); + t!(s: "\\foo", "C:\\foo", false); + t!(s: "\\\\server\\share\\foo", "\\\\server\\share\\foo\\bar", true); + t!(s: "\\\\server\\share", "\\\\server\\share\\foo", true); + t!(s: "\\\\server\\share\\foo", "\\\\server\\share", false); + t!(s: "C:\\foo", "\\\\server\\share\\foo", false); + t!(s: "\\\\server\\share\\foo", "C:\\foo", false); + t!(s: "\\\\?\\foo\\bar", "\\\\?\\foo\\bar\\baz", true); + t!(s: "\\\\?\\foo\\bar\\baz", "\\\\?\\foo\\bar", false); + t!(s: "\\\\?\\foo\\bar", "\\foo\\bar\\baz", false); + t!(s: "\\foo\\bar", "\\\\?\\foo\\bar\\baz", false); + t!(s: "\\\\?\\C:\\foo\\bar", "\\\\?\\C:\\foo\\bar\\baz", true); + t!(s: "\\\\?\\C:\\foo\\bar\\baz", "\\\\?\\C:\\foo\\bar", false); + t!(s: "\\\\?\\C:\\", "\\\\?\\C:\\foo", true); + t!(s: "\\\\?\\C:", "\\\\?\\C:\\", false); // this is a weird one + t!(s: "\\\\?\\C:\\", "\\\\?\\C:", false); + t!(s: "\\\\?\\C:\\a", "\\\\?\\c:\\a\\b", true); + t!(s: "\\\\?\\c:\\a", "\\\\?\\C:\\a\\b", true); + t!(s: "\\\\?\\C:\\a", "\\\\?\\D:\\a\\b", false); + t!(s: "\\\\?\\foo", "\\\\?\\foobar", false); + t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\c", true); + t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\", true); + t!(s: "\\\\?\\a\\b\\", "\\\\?\\a\\b", true); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", false); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b\\", false); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\b\\c\\d", true); + t!(s: "\\\\?\\UNC\\a\\b\\c\\d", "\\\\?\\UNC\\a\\b\\c", false); + t!(s: "\\\\?\\UNC\\a\\b", "\\\\?\\UNC\\a\\b\\c", true); + t!(s: "\\\\.\\foo\\bar", "\\\\.\\foo\\bar\\baz", true); + t!(s: "\\\\.\\foo\\bar\\baz", "\\\\.\\foo\\bar", false); + t!(s: "\\\\.\\foo", "\\\\.\\foo\\bar", true); + t!(s: "\\\\.\\foo", "\\\\.\\foobar", false); + + t!(s: "\\a\\b", "\\\\?\\a\\b", false); + t!(s: "\\\\?\\a\\b", "\\a\\b", false); + t!(s: "\\a\\b", "\\\\?\\C:\\a\\b", false); + t!(s: "\\\\?\\C:\\a\\b", "\\a\\b", false); + t!(s: "Z:\\a\\b", "\\\\?\\z:\\a\\b", true); + t!(s: "C:\\a\\b", "\\\\?\\D:\\a\\b", false); + t!(s: "a\\b", "\\\\?\\a\\b", false); + t!(s: "\\\\?\\a\\b", "a\\b", false); + t!(s: "C:\\a\\b", "\\\\?\\C:\\a\\b", true); + t!(s: "\\\\?\\C:\\a\\b", "C:\\a\\b", true); + t!(s: "C:a\\b", "\\\\?\\C:\\a\\b", false); + t!(s: "C:a\\b", "\\\\?\\C:a\\b", false); + t!(s: "\\\\?\\C:\\a\\b", "C:a\\b", false); + t!(s: "\\\\?\\C:a\\b", "C:a\\b", false); + t!(s: "C:\\a\\b", "\\\\?\\C:\\a\\b\\", true); + t!(s: "\\\\?\\C:\\a\\b\\", "C:\\a\\b", true); + t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\b\\c", true); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\b\\c", true); + } + + #[test] + fn test_ends_with_path() { + macro_rules! t { + (s: $path:expr, $child:expr, $exp:expr) => ( + { + let path = Path::new($path); + let child = Path::new($child); + assert_eq!(path.ends_with_path(&child), $exp); + } + ); + } + + t!(s: "a\\b\\c", "c", true); + t!(s: "a\\b\\c", "d", false); + t!(s: "foo\\bar\\quux", "bar", false); + t!(s: "foo\\bar\\quux", "barquux", false); + t!(s: "a\\b\\c", "b\\c", true); + t!(s: "a\\b\\c", "a\\b\\c", true); + t!(s: "a\\b\\c", "foo\\a\\b\\c", false); + t!(s: "\\a\\b\\c", "a\\b\\c", true); + t!(s: "\\a\\b\\c", "\\a\\b\\c", false); // child must be relative + t!(s: "\\a\\b\\c", "foo\\a\\b\\c", false); + t!(s: "a\\b\\c", "", false); + t!(s: "", "", true); + t!(s: "\\a\\b\\c", "d\\e\\f", false); + t!(s: "a\\b\\c", "a\\b", false); + t!(s: "a\\b\\c", "b", false); + t!(s: "C:\\a\\b", "b", true); + t!(s: "C:\\a\\b", "C:b", false); + t!(s: "C:\\a\\b", "C:a\\b", false); + } + + #[test] + fn test_path_relative_from() { + macro_rules! t { + (s: $path:expr, $other:expr, $exp:expr) => ( + { + assert_eq!(Path::new($path).path_relative_from(&Path::new($other)) + .as_ref().and_then(|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")); + + t!(s: "C:a\\b\\c", "C:a\\b", Some("c")); + t!(s: "C:a\\b", "C:a\\b\\c", Some("..")); + t!(s: "C:" ,"C:a\\b", Some("..\\..")); + t!(s: "C:a\\b", "C:c\\d", Some("..\\..\\a\\b")); + t!(s: "C:a\\b", "D:c\\d", Some("C:a\\b")); + t!(s: "C:a\\b", "C:..\\c", None); + t!(s: "C:..\\a", "C:b\\c", Some("..\\..\\..\\a")); + t!(s: "C:\\a\\b\\c", "C:\\a\\b", Some("c")); + t!(s: "C:\\a\\b", "C:\\a\\b\\c", Some("..")); + t!(s: "C:\\", "C:\\a\\b", Some("..\\..")); + t!(s: "C:\\a\\b", "C:\\c\\d", Some("..\\..\\a\\b")); + t!(s: "C:\\a\\b", "C:a\\b", Some("C:\\a\\b")); + t!(s: "C:a\\b", "C:\\a\\b", None); + t!(s: "\\a\\b", "C:\\a\\b", None); + t!(s: "\\a\\b", "C:a\\b", None); + t!(s: "a\\b", "C:\\a\\b", None); + t!(s: "a\\b", "C:a\\b", None); + + t!(s: "\\\\a\\b\\c", "\\\\a\\b", Some("c")); + t!(s: "\\\\a\\b", "\\\\a\\b\\c", Some("..")); + t!(s: "\\\\a\\b\\c\\e", "\\\\a\\b\\c\\d", Some("..\\e")); + t!(s: "\\\\a\\c\\d", "\\\\a\\b\\d", Some("\\\\a\\c\\d")); + t!(s: "\\\\b\\c\\d", "\\\\a\\c\\d", Some("\\\\b\\c\\d")); + t!(s: "\\\\a\\b\\c", "\\d\\e", Some("\\\\a\\b\\c")); + t!(s: "\\d\\e", "\\\\a\\b\\c", None); + t!(s: "d\\e", "\\\\a\\b\\c", None); + t!(s: "C:\\a\\b\\c", "\\\\a\\b\\c", Some("C:\\a\\b\\c")); + t!(s: "C:\\c", "\\\\a\\b\\c", Some("C:\\c")); + + t!(s: "\\\\?\\a\\b", "\\a\\b", Some("\\\\?\\a\\b")); + t!(s: "\\\\?\\a\\b", "a\\b", Some("\\\\?\\a\\b")); + t!(s: "\\\\?\\a\\b", "\\b", Some("\\\\?\\a\\b")); + t!(s: "\\\\?\\a\\b", "b", Some("\\\\?\\a\\b")); + t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\c", Some("..")); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", Some("c")); + t!(s: "\\\\?\\a\\b", "\\\\?\\c\\d", Some("\\\\?\\a\\b")); + t!(s: "\\\\?\\a", "\\\\?\\b", Some("\\\\?\\a")); + + t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", Some("b")); + t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\a\\b", Some("..")); + t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\b", Some("..\\a")); + t!(s: "\\\\?\\C:\\a", "\\\\?\\D:\\a", Some("\\\\?\\C:\\a")); + t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\c:\\a", Some("b")); + t!(s: "\\\\?\\C:\\a\\b", "C:\\a", Some("b")); + t!(s: "\\\\?\\C:\\a", "C:\\a\\b", Some("..")); + t!(s: "C:\\a\\b", "\\\\?\\C:\\a", Some("b")); + t!(s: "C:\\a", "\\\\?\\C:\\a\\b", Some("..")); + t!(s: "\\\\?\\C:\\a", "D:\\a", Some("\\\\?\\C:\\a")); + t!(s: "\\\\?\\c:\\a\\b", "C:\\a", Some("b")); + t!(s: "\\\\?\\C:\\a\\b", "C:a\\b", Some("\\\\?\\C:\\a\\b")); + t!(s: "\\\\?\\C:\\a\\.\\b", "C:\\a", Some("\\\\?\\C:\\a\\.\\b")); + t!(s: "\\\\?\\C:\\a\\b/c", "C:\\a", Some("\\\\?\\C:\\a\\b/c")); + t!(s: "\\\\?\\C:\\a\\..\\b", "C:\\a", Some("\\\\?\\C:\\a\\..\\b")); + t!(s: "C:a\\b", "\\\\?\\C:\\a\\b", None); + t!(s: "\\\\?\\C:\\a\\.\\b", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\.\\b")); + t!(s: "\\\\?\\C:\\a\\b/c", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\b/c")); + t!(s: "\\\\?\\C:\\a\\..\\b", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\..\\b")); + t!(s: "\\\\?\\C:\\a\\b\\", "\\\\?\\C:\\a", Some("b")); + t!(s: "\\\\?\\C:\\.\\b", "\\\\?\\C:\\.", Some("b")); + t!(s: "C:\\b", "\\\\?\\C:\\.", Some("..\\b")); + t!(s: "\\\\?\\a\\.\\b\\c", "\\\\?\\a\\.\\b", Some("c")); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\.\\d", Some("..\\..\\b\\c")); + t!(s: "\\\\?\\a\\..\\b", "\\\\?\\a\\..", Some("b")); + t!(s: "\\\\?\\a\\b\\..", "\\\\?\\a\\b", Some("\\\\?\\a\\b\\..")); + t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\..\\b", Some("..\\..\\b\\c")); + + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\b", Some("c")); + t!(s: "\\\\?\\UNC\\a\\b", "\\\\?\\UNC\\a\\b\\c", Some("..")); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\c\\d", Some("\\\\?\\UNC\\a\\b\\c")); + t!(s: "\\\\?\\UNC\\b\\c\\d", "\\\\?\\UNC\\a\\c\\d", Some("\\\\?\\UNC\\b\\c\\d")); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\a\\b\\c", Some("\\\\?\\UNC\\a\\b\\c")); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\C:\\a\\b\\c", Some("\\\\?\\UNC\\a\\b\\c")); + t!(s: "\\\\?\\UNC\\a\\b\\c/d", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\c/d")); + t!(s: "\\\\?\\UNC\\a\\b\\.", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\.")); + t!(s: "\\\\?\\UNC\\a\\b\\..", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\..")); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\b", Some("c")); + t!(s: "\\\\?\\UNC\\a\\b", "\\\\a\\b\\c", Some("..")); + t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\c\\d", Some("\\\\?\\UNC\\a\\b\\c")); + t!(s: "\\\\?\\UNC\\b\\c\\d", "\\\\a\\c\\d", Some("\\\\?\\UNC\\b\\c\\d")); + t!(s: "\\\\?\\UNC\\a\\b\\.", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\.")); + t!(s: "\\\\?\\UNC\\a\\b\\c/d", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\c/d")); + t!(s: "\\\\?\\UNC\\a\\b\\..", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\..")); + t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\b", Some("c")); + t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\c\\d", Some("\\\\a\\b\\c")); + } + + #[test] + fn test_str_components() { + macro_rules! t { + (s: $path:expr, $exp:expr) => ( + { + let path = Path::new($path); + let comps = path.str_components().map(|x|x.unwrap()) + .collect::>(); + let exp: &[&str] = &$exp; + assert_eq!(comps, exp); + let comps = path.str_components().rev().map(|x|x.unwrap()) + .collect::>(); + let exp = exp.iter().rev().map(|&x|x).collect::>(); + assert_eq!(comps, exp); + } + ); + } + + t!(s: b"a\\b\\c", ["a", "b", "c"]); + 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"]); + t!(s: "C:foo\\bar", ["foo", "bar"]); + t!(s: "C:foo", ["foo"]); + t!(s: "C:", []); + t!(s: "C:\\foo\\bar", ["foo", "bar"]); + t!(s: "C:\\foo", ["foo"]); + t!(s: "C:\\", []); + t!(s: "\\\\server\\share\\foo\\bar", ["foo", "bar"]); + t!(s: "\\\\server\\share\\foo", ["foo"]); + t!(s: "\\\\server\\share", []); + t!(s: "\\\\?\\foo\\bar\\baz", ["bar", "baz"]); + t!(s: "\\\\?\\foo\\bar", ["bar"]); + t!(s: "\\\\?\\foo", []); + t!(s: "\\\\?\\", []); + t!(s: "\\\\?\\a\\b", ["b"]); + t!(s: "\\\\?\\a\\b\\", ["b"]); + t!(s: "\\\\?\\foo\\bar\\\\baz", ["bar", "", "baz"]); + t!(s: "\\\\?\\C:\\foo\\bar", ["foo", "bar"]); + t!(s: "\\\\?\\C:\\foo", ["foo"]); + t!(s: "\\\\?\\C:\\", []); + t!(s: "\\\\?\\C:\\foo\\", ["foo"]); + t!(s: "\\\\?\\UNC\\server\\share\\foo\\bar", ["foo", "bar"]); + t!(s: "\\\\?\\UNC\\server\\share\\foo", ["foo"]); + t!(s: "\\\\?\\UNC\\server\\share", []); + t!(s: "\\\\.\\foo\\bar\\baz", ["bar", "baz"]); + t!(s: "\\\\.\\foo\\bar", ["bar"]); + t!(s: "\\\\.\\foo", []); + } + + #[test] + fn test_components_iter() { + macro_rules! t { + (s: $path:expr, $exp:expr) => ( + { + let path = Path::new($path); + let comps = path.components().collect::>(); + let exp: &[&[u8]] = &$exp; + assert_eq!(comps, exp); + let comps = path.components().rev().collect::>(); + let exp = exp.iter().rev().map(|&x|x).collect::>(); + assert_eq!(comps, exp); + } + ) + } + + t!(s: "a\\b\\c", [b"a", b"b", b"c"]); + t!(s: ".", [b"."]); + // since this is really a wrapper around str_components, those tests suffice + } + + #[test] + fn test_make_non_verbatim() { + macro_rules! t { + ($path:expr, $exp:expr) => ( + { + let path = Path::new($path); + let exp: Option<&str> = $exp; + let exp = exp.map(|s| Path::new(s)); + assert_eq!(make_non_verbatim(&path), exp); + } + ) + } + + t!(r"\a\b\c", Some(r"\a\b\c")); + t!(r"a\b\c", Some(r"a\b\c")); + t!(r"C:\a\b\c", Some(r"C:\a\b\c")); + t!(r"C:a\b\c", Some(r"C:a\b\c")); + t!(r"\\server\share\foo", Some(r"\\server\share\foo")); + t!(r"\\.\foo", None); + t!(r"\\?\foo", None); + t!(r"\\?\C:", None); + t!(r"\\?\C:foo", None); + t!(r"\\?\C:\", Some(r"C:\")); + t!(r"\\?\C:\foo", Some(r"C:\foo")); + t!(r"\\?\C:\foo\bar\baz", Some(r"C:\foo\bar\baz")); + t!(r"\\?\C:\foo\.\bar\baz", None); + t!(r"\\?\C:\foo\bar\..\baz", None); + t!(r"\\?\C:\foo\bar\..", None); + t!(r"\\?\UNC\server\share\foo", Some(r"\\server\share\foo")); + t!(r"\\?\UNC\server\share", Some(r"\\server\share")); + t!(r"\\?\UNC\server", None); + t!(r"\\?\UNC\server\", None); + } +} diff --git a/src/libstd/os.rs b/src/libstd/os.rs index d92f361af0b..36122b16ea0 100644 --- a/src/libstd/os.rs +++ b/src/libstd/os.rs @@ -48,7 +48,7 @@ use old_io::{IoResult, IoError}; use ops::{Drop, FnOnce}; use option::Option::{Some, None}; use option::Option; -use path::{Path, GenericPath, BytesContainer}; +use old_path::{Path, GenericPath, BytesContainer}; use ptr::PtrExt; use ptr; use result::Result::{Err, Ok}; @@ -267,7 +267,7 @@ pub fn split_paths(unparsed: T) -> Vec { /// /// ```rust /// use std::os; -/// use std::path::Path; +/// use std::old_path::Path; /// /// let key = "PATH"; /// let mut paths = os::getenv_as_bytes(key).map_or(Vec::new(), os::split_paths); @@ -470,7 +470,7 @@ pub fn tmpdir() -> Path { /// # Example /// ```rust /// use std::os; -/// use std::path::Path; +/// use std::old_path::Path; /// /// // Assume we're in a path like /home/someuser /// let rel_path = Path::new(".."); @@ -500,7 +500,7 @@ pub fn make_absolute(p: &Path) -> IoResult { /// # Example /// ```rust /// use std::os; -/// use std::path::Path; +/// use std::old_path::Path; /// /// let root = Path::new("/"); /// assert!(os::change_dir(&root).is_ok()); diff --git a/src/libstd/path/mod.rs b/src/libstd/path/mod.rs deleted file mode 100644 index 0d80258d7e0..00000000000 --- a/src/libstd/path/mod.rs +++ /dev/null @@ -1,923 +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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Cross-platform path support -//! -//! This module implements support for two flavors of paths. `PosixPath` represents a path on any -//! unix-like system, whereas `WindowsPath` represents a path on Windows. This module also exposes -//! a typedef `Path` which is equal to the appropriate platform-specific path variant. -//! -//! Both `PosixPath` and `WindowsPath` implement a trait `GenericPath`, which contains the set of -//! methods that behave the same for both paths. They each also implement some methods that could -//! not be expressed in `GenericPath`, yet behave identically for both path flavors, such as -//! `.components()`. -//! -//! The three main design goals of this module are 1) to avoid unnecessary allocation, 2) to behave -//! the same regardless of which flavor of path is being used, and 3) to support paths that cannot -//! be represented in UTF-8 (as Linux has no restriction on paths beyond disallowing NUL). -//! -//! ## Usage -//! -//! Usage of this module is fairly straightforward. Unless writing platform-specific code, `Path` -//! should be used to refer to the platform-native path. -//! -//! Creation of a path is typically done with either `Path::new(some_str)` or -//! `Path::new(some_vec)`. This path can be modified with `.push()` and `.pop()` (and other -//! setters). The resulting Path can either be passed to another API that expects a path, or can be -//! turned into a `&[u8]` with `.as_vec()` or a `Option<&str>` with `.as_str()`. Similarly, -//! attributes of the path can be queried with methods such as `.filename()`. There are also -//! methods that return a new path instead of modifying the receiver, such as `.join()` or -//! `.dir_path()`. -//! -//! Paths are always kept in normalized form. This means that creating the path -//! `Path::new("a/b/../c")` will return the path `a/c`. Similarly any attempt to mutate the path -//! will always leave it in normalized form. -//! -//! When rendering a path to some form of output, there is a method `.display()` which is -//! compatible with the `format!()` parameter `{}`. This will render the path as a string, -//! replacing all non-utf8 sequences with the Replacement Character (U+FFFD). As such it is not -//! suitable for passing to any API that actually operates on the path; it is only intended for -//! display. -//! -//! ## Example -//! -//! ```rust -//! use std::old_io::fs::PathExtensions; -//! -//! let mut path = Path::new("/tmp/path"); -//! println!("path: {}", path.display()); -//! path.set_filename("foo"); -//! path.push("bar"); -//! println!("new path: {}", path.display()); -//! println!("path exists: {}", path.exists()); -//! ``` - -#![unstable(feature = "path")] - -use core::marker::Sized; -use ffi::CString; -use clone::Clone; -use fmt; -use iter::IteratorExt; -use option::Option; -use option::Option::{None, Some}; -use str; -use str::StrExt; -use string::{String, CowString}; -use slice::SliceExt; -use vec::Vec; - -/// Typedef for POSIX file paths. -/// See `posix::Path` for more info. -pub use self::posix::Path as PosixPath; - -/// Typedef for Windows file paths. -/// See `windows::Path` for more info. -pub use self::windows::Path as WindowsPath; - -/// Typedef for the platform-native path type -#[cfg(unix)] -pub use self::posix::Path as Path; -/// Typedef for the platform-native path type -#[cfg(windows)] -pub use self::windows::Path as Path; - -/// Typedef for the platform-native component iterator -#[cfg(unix)] -pub use self::posix::Components as Components; -/// Typedef for the platform-native component iterator -#[cfg(windows)] -pub use self::windows::Components as Components; - -/// Typedef for the platform-native str component iterator -#[cfg(unix)] -pub use self::posix::StrComponents as StrComponents; -/// Typedef for the platform-native str component iterator -#[cfg(windows)] -pub use self::windows::StrComponents as StrComponents; - -/// Alias for the platform-native separator character. -#[cfg(unix)] -pub use self::posix::SEP as SEP; -/// Alias for the platform-native separator character. -#[cfg(windows)] -pub use self::windows::SEP as SEP; - -/// Alias for the platform-native separator byte. -#[cfg(unix)] -pub use self::posix::SEP_BYTE as SEP_BYTE; -/// Alias for the platform-native separator byte. -#[cfg(windows)] -pub use self::windows::SEP_BYTE as SEP_BYTE; - -/// Typedef for the platform-native separator char func -#[cfg(unix)] -pub use self::posix::is_sep as is_sep; -/// Typedef for the platform-native separator char func -#[cfg(windows)] -pub use self::windows::is_sep as is_sep; -/// Typedef for the platform-native separator byte func -#[cfg(unix)] -pub use self::posix::is_sep_byte as is_sep_byte; -/// Typedef for the platform-native separator byte func -#[cfg(windows)] -pub use self::windows::is_sep_byte as is_sep_byte; - -pub mod posix; -pub mod windows; - -/// A trait that represents the generic operations available on paths -pub trait GenericPath: Clone + GenericPathUnsafe { - /// Creates a new Path from a byte vector or string. - /// The resulting Path will always be normalized. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let path = Path::new("foo/bar"); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the path contains a NUL. - /// - /// See individual Path impls for additional restrictions. - #[inline] - fn new(path: T) -> Self { - assert!(!contains_nul(&path)); - unsafe { GenericPathUnsafe::new_unchecked(path) } - } - - /// Creates a new Path from a byte vector or string, if possible. - /// The resulting Path will always be normalized. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let x: &[u8] = b"foo\0"; - /// assert!(Path::new_opt(x).is_none()); - /// # } - /// ``` - #[inline] - fn new_opt(path: T) -> Option { - if contains_nul(&path) { - None - } else { - Some(unsafe { GenericPathUnsafe::new_unchecked(path) }) - } - } - - /// Returns the path as a string, if possible. - /// If the path is not representable in utf-8, this returns None. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("/abc/def"); - /// assert_eq!(p.as_str(), Some("/abc/def")); - /// # } - /// ``` - #[inline] - fn as_str<'a>(&'a self) -> Option<&'a str> { - str::from_utf8(self.as_vec()).ok() - } - - /// Returns the path as a byte vector - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def"); - /// assert_eq!(p.as_vec(), b"abc/def"); - /// # } - /// ``` - fn as_vec<'a>(&'a self) -> &'a [u8]; - - /// Converts the Path into an owned byte vector - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def"); - /// assert_eq!(p.into_vec(), b"abc/def".to_vec()); - /// // attempting to use p now results in "error: use of moved value" - /// # } - /// ``` - fn into_vec(self) -> Vec; - - /// Returns an object that implements `Show` for printing paths - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def"); - /// println!("{}", p.display()); // prints "abc/def" - /// # } - /// ``` - fn display<'a>(&'a self) -> Display<'a, Self> { - Display{ path: self, filename: false } - } - - /// Returns an object that implements `Show` for printing filenames - /// - /// If there is no filename, nothing will be printed. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def"); - /// println!("{}", p.filename_display()); // prints "def" - /// # } - /// ``` - fn filename_display<'a>(&'a self) -> Display<'a, Self> { - Display{ path: self, filename: true } - } - - /// Returns the directory component of `self`, as a byte vector (with no trailing separator). - /// If `self` has no directory component, returns ['.']. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def/ghi"); - /// assert_eq!(p.dirname(), b"abc/def"); - /// # } - /// ``` - fn dirname<'a>(&'a self) -> &'a [u8]; - - /// Returns the directory component of `self`, as a string, if possible. - /// See `dirname` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def/ghi"); - /// assert_eq!(p.dirname_str(), Some("abc/def")); - /// # } - /// ``` - #[inline] - fn dirname_str<'a>(&'a self) -> Option<&'a str> { - str::from_utf8(self.dirname()).ok() - } - - /// Returns the file component of `self`, as a byte vector. - /// If `self` represents the root of the file hierarchy, returns None. - /// If `self` is "." or "..", returns None. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def/ghi"); - /// assert_eq!(p.filename(), Some(b"ghi")); - /// # } - /// ``` - fn filename<'a>(&'a self) -> Option<&'a [u8]>; - - /// Returns the file component of `self`, as a string, if possible. - /// See `filename` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def/ghi"); - /// assert_eq!(p.filename_str(), Some("ghi")); - /// # } - /// ``` - #[inline] - fn filename_str<'a>(&'a self) -> Option<&'a str> { - self.filename().and_then(|s| str::from_utf8(s).ok()) - } - - /// 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. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("/abc/def.txt"); - /// assert_eq!(p.filestem(), Some(b"def")); - /// # } - /// ``` - fn filestem<'a>(&'a self) -> Option<&'a [u8]> { - match self.filename() { - None => None, - Some(name) => Some({ - let dot = b'.'; - match name.rposition_elem(&dot) { - None | Some(0) => name, - Some(1) if name == b".." => name, - Some(pos) => &name[..pos] - } - }) - } - } - - /// Returns the stem of the filename of `self`, as a string, if possible. - /// See `filestem` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("/abc/def.txt"); - /// assert_eq!(p.filestem_str(), Some("def")); - /// # } - /// ``` - #[inline] - fn filestem_str<'a>(&'a self) -> Option<&'a str> { - self.filestem().and_then(|s| str::from_utf8(s).ok()) - } - - /// 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. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def.txt"); - /// assert_eq!(p.extension(), Some(b"txt")); - /// # } - /// ``` - fn extension<'a>(&'a self) -> Option<&'a [u8]> { - match self.filename() { - None => None, - Some(name) => { - let dot = b'.'; - match name.rposition_elem(&dot) { - None | Some(0) => None, - Some(1) if name == b".." => None, - Some(pos) => Some(&name[pos+1..]) - } - } - } - } - - /// Returns the extension of the filename of `self`, as a string, if possible. - /// See `extension` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def.txt"); - /// assert_eq!(p.extension_str(), Some("txt")); - /// # } - /// ``` - #[inline] - fn extension_str<'a>(&'a self) -> Option<&'a str> { - self.extension().and_then(|s| str::from_utf8(s).ok()) - } - - /// Replaces the filename portion of the path with the given byte vector or string. - /// If the replacement name is [], this is equivalent to popping the path. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("abc/def.txt"); - /// p.set_filename("foo.dat"); - /// assert!(p == Path::new("abc/foo.dat")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the filename contains a NUL. - #[inline] - fn set_filename(&mut self, filename: T) { - assert!(!contains_nul(&filename)); - unsafe { self.set_filename_unchecked(filename) } - } - - /// Replaces the extension with the given byte vector or string. - /// If there is no extension in `self`, this adds one. - /// If the argument is [] or "", this removes the extension. - /// If `self` has no filename, this is a no-op. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("abc/def.txt"); - /// p.set_extension("csv"); - /// assert_eq!(p, Path::new("abc/def.csv")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the extension contains a NUL. - fn set_extension(&mut self, extension: T) { - assert!(!contains_nul(&extension)); - - let val = self.filename().and_then(|name| { - let dot = b'.'; - let extlen = extension.container_as_bytes().len(); - match (name.rposition_elem(&dot), extlen) { - (None, 0) | (Some(0), 0) => None, - (Some(idx), 0) => Some(name[..idx].to_vec()), - (idx, extlen) => { - let idx = match idx { - None | Some(0) => name.len(), - Some(val) => val - }; - - let mut v; - v = Vec::with_capacity(idx + extlen + 1); - v.push_all(&name[..idx]); - v.push(dot); - v.push_all(extension.container_as_bytes()); - Some(v) - } - } - }); - - match val { - None => (), - Some(v) => unsafe { self.set_filename_unchecked(v) } - } - } - - /// Returns a new Path constructed by replacing the filename with the given - /// byte vector or string. - /// See `set_filename` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("abc/def.txt"); - /// assert_eq!(p.with_filename("foo.dat"), Path::new("abc/foo.dat")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the filename contains a NUL. - #[inline] - fn with_filename(&self, filename: T) -> Self { - let mut p = self.clone(); - p.set_filename(filename); - p - } - - /// Returns a new Path constructed by setting the extension to the given - /// byte vector or string. - /// See `set_extension` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("abc/def.txt"); - /// assert_eq!(p.with_extension("csv"), Path::new("abc/def.csv")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the extension contains a NUL. - #[inline] - fn with_extension(&self, extension: T) -> Self { - let mut p = self.clone(); - p.set_extension(extension); - p - } - - /// Returns the directory component of `self`, as a Path. - /// If `self` represents the root of the filesystem hierarchy, returns `self`. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def/ghi"); - /// assert_eq!(p.dir_path(), Path::new("abc/def")); - /// # } - /// ``` - fn dir_path(&self) -> Self { - // self.dirname() returns a NUL-free vector - unsafe { GenericPathUnsafe::new_unchecked(self.dirname()) } - } - - /// Returns a Path that represents the filesystem root that `self` is rooted in. - /// - /// If `self` is not absolute, or vol/cwd-relative in the case of Windows, this returns None. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// assert_eq!(Path::new("abc/def").root_path(), None); - /// assert_eq!(Path::new("/abc/def").root_path(), Some(Path::new("/"))); - /// # } - /// ``` - fn root_path(&self) -> Option; - - /// Pushes a path (as a byte vector or string) onto `self`. - /// If the argument represents an absolute path, it replaces `self`. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("foo/bar"); - /// p.push("baz.txt"); - /// assert_eq!(p, Path::new("foo/bar/baz.txt")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the path contains a NUL. - #[inline] - fn push(&mut self, path: T) { - assert!(!contains_nul(&path)); - unsafe { self.push_unchecked(path) } - } - - /// Pushes multiple paths (as byte vectors or strings) onto `self`. - /// See `push` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("foo"); - /// p.push_many(&["bar", "baz.txt"]); - /// assert_eq!(p, Path::new("foo/bar/baz.txt")); - /// # } - /// ``` - #[inline] - fn push_many(&mut self, paths: &[T]) { - let t: Option<&T> = None; - if BytesContainer::is_str(t) { - for p in paths { - self.push(p.container_as_str().unwrap()) - } - } else { - for p in paths { - self.push(p.container_as_bytes()) - } - } - } - - /// Removes the last path component from the receiver. - /// Returns `true` if the receiver was modified, or `false` if it already - /// represented the root of the file hierarchy. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let mut p = Path::new("foo/bar/baz.txt"); - /// p.pop(); - /// assert_eq!(p, Path::new("foo/bar")); - /// # } - /// ``` - fn pop(&mut self) -> bool; - - /// Returns a new Path constructed by joining `self` with the given path - /// (as a byte vector or string). - /// If the given path is absolute, the new Path will represent just that. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("/foo"); - /// assert_eq!(p.join("bar.txt"), Path::new("/foo/bar.txt")); - /// # } - /// ``` - /// - /// # Panics - /// - /// Panics the task if the path contains a NUL. - #[inline] - fn join(&self, path: T) -> Self { - let mut p = self.clone(); - p.push(path); - p - } - - /// Returns a new Path constructed by joining `self` with the given paths - /// (as byte vectors or strings). - /// See `join` for details. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("foo"); - /// let fbbq = Path::new("foo/bar/baz/quux.txt"); - /// assert_eq!(p.join_many(&["bar", "baz", "quux.txt"]), fbbq); - /// # } - /// ``` - #[inline] - fn join_many(&self, paths: &[T]) -> Self { - let mut p = self.clone(); - p.push_many(paths); - p - } - - /// Returns whether `self` represents an absolute path. - /// An absolute path is defined as one that, when joined to another path, will - /// yield back the same absolute path. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("/abc/def"); - /// assert!(p.is_absolute()); - /// # } - /// ``` - fn is_absolute(&self) -> bool; - - /// Returns whether `self` represents a relative path. - /// Typically this is the inverse of `is_absolute`. - /// But for Windows paths, it also means the path is not volume-relative or - /// relative to the current working directory. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("abc/def"); - /// assert!(p.is_relative()); - /// # } - /// ``` - fn is_relative(&self) -> bool { - !self.is_absolute() - } - - /// 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. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("foo/bar/baz/quux.txt"); - /// let fb = Path::new("foo/bar"); - /// let bq = Path::new("baz/quux.txt"); - /// assert!(fb.is_ancestor_of(&p)); - /// # } - /// ``` - 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. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("foo/bar/baz/quux.txt"); - /// let fb = Path::new("foo/bar"); - /// let bq = Path::new("baz/quux.txt"); - /// assert_eq!(p.path_relative_from(&fb), Some(bq)); - /// # } - /// ``` - fn path_relative_from(&self, base: &Self) -> Option; - - /// Returns whether the relative path `child` is a suffix of `self`. - /// - /// # Example - /// - /// ``` - /// # foo(); - /// # #[cfg(windows)] fn foo() {} - /// # #[cfg(unix)] fn foo() { - /// let p = Path::new("foo/bar/baz/quux.txt"); - /// let bq = Path::new("baz/quux.txt"); - /// assert!(p.ends_with_path(&bq)); - /// # } - /// ``` - fn ends_with_path(&self, child: &Self) -> bool; -} - -/// A trait that represents something bytes-like (e.g. a &[u8] or a &str) -pub trait BytesContainer { - /// Returns a &[u8] representing the receiver - fn container_as_bytes<'a>(&'a self) -> &'a [u8]; - /// Returns the receiver interpreted as a utf-8 string, if possible - #[inline] - fn container_as_str<'a>(&'a self) -> Option<&'a str> { - str::from_utf8(self.container_as_bytes()).ok() - } - /// Returns whether .container_as_str() is guaranteed to not fail - // FIXME (#8888): Remove unused arg once :: works - #[inline] - fn is_str(_: Option<&Self>) -> bool { false } -} - -/// A trait that represents the unsafe operations on GenericPaths -pub trait GenericPathUnsafe { - /// Creates a new Path without checking for null bytes. - /// The resulting Path will always be normalized. - unsafe fn new_unchecked(path: T) -> Self; - - /// Replaces the filename portion of the path without checking for null - /// bytes. - /// See `set_filename` for details. - unsafe fn set_filename_unchecked(&mut self, filename: T); - - /// Pushes a path onto `self` without checking for null bytes. - /// See `push` for details. - unsafe fn push_unchecked(&mut self, path: T); -} - -/// Helper struct for printing paths with format!() -pub struct Display<'a, P:'a> { - path: &'a P, - filename: bool -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl<'a, P: GenericPath> fmt::Debug for Display<'a, P> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.as_cow(), f) - } -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl<'a, P: GenericPath> fmt::Display for Display<'a, P> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.as_cow().fmt(f) - } -} - -impl<'a, P: GenericPath> Display<'a, P> { - /// Returns the path as a possibly-owned string. - /// - /// If the path is not UTF-8, invalid sequences will be replaced with the - /// Unicode replacement char. This involves allocation. - #[inline] - pub fn as_cow(&self) -> CowString<'a> { - String::from_utf8_lossy(if self.filename { - match self.path.filename() { - None => { - let result: &[u8] = &[]; - result - } - Some(v) => v - } - } else { - self.path.as_vec() - }) - } -} - -impl BytesContainer for str { - #[inline] - fn container_as_bytes(&self) -> &[u8] { - self.as_bytes() - } - #[inline] - fn container_as_str(&self) -> Option<&str> { - Some(self) - } - #[inline] - fn is_str(_: Option<&str>) -> bool { true } -} - -impl BytesContainer for String { - #[inline] - fn container_as_bytes(&self) -> &[u8] { - self.as_bytes() - } - #[inline] - fn container_as_str(&self) -> Option<&str> { - Some(&self[]) - } - #[inline] - fn is_str(_: Option<&String>) -> bool { true } -} - -impl BytesContainer for [u8] { - #[inline] - fn container_as_bytes(&self) -> &[u8] { - self - } -} - -impl BytesContainer for Vec { - #[inline] - fn container_as_bytes(&self) -> &[u8] { - &self[] - } -} - -impl BytesContainer for CString { - #[inline] - fn container_as_bytes<'a>(&'a self) -> &'a [u8] { - self.as_bytes() - } -} - -impl<'a, T: ?Sized + BytesContainer> BytesContainer for &'a T { - #[inline] - fn container_as_bytes(&self) -> &[u8] { - (**self).container_as_bytes() - } - #[inline] - fn container_as_str(&self) -> Option<&str> { - (**self).container_as_str() - } - #[inline] - fn is_str(_: Option<& &'a T>) -> bool { BytesContainer::is_str(None::<&T>) } -} - -#[inline(always)] -fn contains_nul(v: &T) -> bool { - v.container_as_bytes().iter().any(|&x| x == 0) -} diff --git a/src/libstd/path/posix.rs b/src/libstd/path/posix.rs deleted file mode 100644 index 69f815e3f8b..00000000000 --- a/src/libstd/path/posix.rs +++ /dev/null @@ -1,1348 +0,0 @@ -// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! POSIX file path handling - -use clone::Clone; -use cmp::{Ordering, Eq, Ord, PartialEq, PartialOrd}; -use fmt; -use hash; -use old_io::Writer; -use iter::{AdditiveIterator, Extend}; -use iter::{Iterator, IteratorExt, Map}; -use marker::Sized; -use option::Option::{self, Some, None}; -use result::Result::{self, Ok, Err}; -use slice::{AsSlice, Split, SliceExt, SliceConcatExt}; -use str::{self, FromStr, StrExt}; -use vec::Vec; - -use super::{BytesContainer, GenericPath, GenericPathUnsafe}; - -/// Iterator that yields successive components of a Path as &[u8] -pub type Components<'a> = Split<'a, u8, fn(&u8) -> bool>; - -/// Iterator that yields successive components of a Path as Option<&str> -pub type StrComponents<'a> = - Map, fn(&[u8]) -> Option<&str>>; - -/// Represents a POSIX file path -#[derive(Clone)] -pub struct Path { - repr: Vec, // assumed to never be empty or contain NULs - sepidx: Option // index of the final separator in repr -} - -/// The standard path separator character -pub const SEP: char = '/'; - -/// The standard path separator byte -pub const SEP_BYTE: u8 = SEP as u8; - -/// Returns whether the given byte is a path separator -#[inline] -pub fn is_sep_byte(u: &u8) -> bool { - *u as char == SEP -} - -/// Returns whether the given char is a path separator -#[inline] -pub fn is_sep(c: char) -> bool { - c == SEP -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.display(), f) - } -} - -impl PartialEq for Path { - #[inline] - fn eq(&self, other: &Path) -> bool { - self.repr == other.repr - } -} - -impl Eq for Path {} - -impl PartialOrd for Path { - fn partial_cmp(&self, other: &Path) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Path { - fn cmp(&self, other: &Path) -> Ordering { - self.repr.cmp(&other.repr) - } -} - -impl FromStr for Path { - type Err = ParsePathError; - fn from_str(s: &str) -> Result { - match Path::new_opt(s) { - Some(p) => Ok(p), - None => Err(ParsePathError), - } - } -} - -/// Valuelue indicating that a path could not be parsed from a string. -#[derive(Debug, Clone, PartialEq, Copy)] -pub struct ParsePathError; - -impl hash::Hash for Path { - #[inline] - fn hash(&self, state: &mut S) { - self.repr.hash(state) - } -} - -impl BytesContainer for Path { - #[inline] - fn container_as_bytes<'a>(&'a self) -> &'a [u8] { - self.as_vec() - } -} - -impl GenericPathUnsafe for Path { - unsafe fn new_unchecked(path: T) -> Path { - let path = Path::normalize(path.container_as_bytes()); - assert!(!path.is_empty()); - let idx = path.rposition_elem(&SEP_BYTE); - Path{ repr: path, sepidx: idx } - } - - unsafe fn set_filename_unchecked(&mut self, filename: T) { - let filename = filename.container_as_bytes(); - match self.sepidx { - None if b".." == self.repr => { - let mut v = Vec::with_capacity(3 + filename.len()); - v.push_all(dot_dot_static); - v.push(SEP_BYTE); - v.push_all(filename); - // FIXME: this is slow - self.repr = Path::normalize(v.as_slice()); - } - None => { - self.repr = Path::normalize(filename); - } - Some(idx) if &self.repr[idx+1..] == b".." => { - let mut v = Vec::with_capacity(self.repr.len() + 1 + filename.len()); - v.push_all(self.repr.as_slice()); - v.push(SEP_BYTE); - v.push_all(filename); - // FIXME: this is slow - self.repr = Path::normalize(v.as_slice()); - } - Some(idx) => { - let mut v = Vec::with_capacity(idx + 1 + filename.len()); - v.push_all(&self.repr[..idx+1]); - v.push_all(filename); - // FIXME: this is slow - self.repr = Path::normalize(v.as_slice()); - } - } - self.sepidx = self.repr.rposition_elem(&SEP_BYTE); - } - - unsafe fn push_unchecked(&mut self, path: T) { - let path = path.container_as_bytes(); - if !path.is_empty() { - if path[0] == SEP_BYTE { - self.repr = Path::normalize(path); - } else { - let mut v = Vec::with_capacity(self.repr.len() + path.len() + 1); - v.push_all(self.repr.as_slice()); - v.push(SEP_BYTE); - v.push_all(path); - // FIXME: this is slow - self.repr = Path::normalize(v.as_slice()); - } - self.sepidx = self.repr.rposition_elem(&SEP_BYTE); - } - } -} - -impl GenericPath for Path { - #[inline] - fn as_vec<'a>(&'a self) -> &'a [u8] { - self.repr.as_slice() - } - - fn into_vec(self) -> Vec { - self.repr - } - - fn dirname<'a>(&'a self) -> &'a [u8] { - match self.sepidx { - None if b".." == self.repr => self.repr.as_slice(), - None => dot_static, - Some(0) => &self.repr[..1], - Some(idx) if &self.repr[idx+1..] == b".." => self.repr.as_slice(), - Some(idx) => &self.repr[..idx] - } - } - - fn filename<'a>(&'a self) -> Option<&'a [u8]> { - match self.sepidx { - None if b"." == self.repr || - b".." == self.repr => None, - None => Some(self.repr.as_slice()), - Some(idx) if &self.repr[idx+1..] == b".." => None, - Some(0) if self.repr[1..].is_empty() => None, - Some(idx) => Some(&self.repr[idx+1..]) - } - } - - fn pop(&mut self) -> bool { - match self.sepidx { - None if b"." == self.repr => false, - None => { - self.repr = vec![b'.']; - self.sepidx = None; - true - } - Some(0) if b"/" == self.repr => false, - Some(idx) => { - if idx == 0 { - self.repr.truncate(idx+1); - } else { - self.repr.truncate(idx); - } - self.sepidx = self.repr.rposition_elem(&SEP_BYTE); - true - } - } - } - - fn root_path(&self) -> Option { - if self.is_absolute() { - Some(Path::new("/")) - } else { - None - } - } - - #[inline] - fn is_absolute(&self) -> bool { - self.repr[0] == SEP_BYTE - } - - fn is_ancestor_of(&self, other: &Path) -> bool { - if self.is_absolute() != other.is_absolute() { - false - } else { - let mut ita = self.components(); - let mut itb = other.components(); - if b"." == self.repr { - return match itb.next() { - None => true, - Some(b) => b != b".." - }; - } - loop { - match (ita.next(), itb.next()) { - (None, _) => break, - (Some(a), Some(b)) if a == b => { continue }, - (Some(a), _) if a == b".." => { - // if ita contains only .. components, it's an ancestor - return ita.all(|x| x == b".."); - } - _ => return false - } - } - true - } - } - - fn path_relative_from(&self, base: &Path) -> Option { - if self.is_absolute() != base.is_absolute() { - if self.is_absolute() { - Some(self.clone()) - } else { - None - } - } else { - let mut ita = self.components(); - let mut itb = base.components(); - let mut comps = vec![]; - loop { - match (ita.next(), itb.next()) { - (None, None) => break, - (Some(a), None) => { - comps.push(a); - comps.extend(ita.by_ref()); - break; - } - (None, _) => comps.push(dot_dot_static), - (Some(a), Some(b)) if comps.is_empty() && a == b => (), - (Some(a), Some(b)) if b == b"." => comps.push(a), - (Some(_), Some(b)) if b == b".." => return None, - (Some(a), Some(_)) => { - comps.push(dot_dot_static); - for _ in itb { - comps.push(dot_dot_static); - } - comps.push(a); - comps.extend(ita.by_ref()); - break; - } - } - } - Some(Path::new(comps.connect(&SEP_BYTE))) - } - } - - fn ends_with_path(&self, child: &Path) -> bool { - if !child.is_relative() { return false; } - let mut selfit = self.components().rev(); - let mut childit = child.components().rev(); - loop { - match (selfit.next(), childit.next()) { - (Some(a), Some(b)) => if a != b { return false; }, - (Some(_), None) => break, - (None, Some(_)) => return false, - (None, None) => break - } - } - true - } -} - -impl Path { - /// Returns a new Path from a byte vector or string - /// - /// # Panics - /// - /// Panics the task if the vector contains a NUL. - #[inline] - pub fn new(path: T) -> Path { - GenericPath::new(path) - } - - /// Returns a new Path from a byte vector or string, if possible - #[inline] - pub fn new_opt(path: T) -> Option { - GenericPath::new_opt(path) - } - - /// Returns a normalized byte vector representation of a path, by removing all empty - /// components, and unnecessary . and .. components. - fn normalize>(v: &V) -> Vec { - // borrowck is being very picky - let val = { - let is_abs = !v.as_slice().is_empty() && v.as_slice()[0] == SEP_BYTE; - let v_ = if is_abs { &v.as_slice()[1..] } else { v.as_slice() }; - let comps = normalize_helper(v_, is_abs); - match comps { - None => None, - Some(comps) => { - if is_abs && comps.is_empty() { - Some(vec![SEP_BYTE]) - } 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.into_iter(); - if !is_abs { - match it.next() { - None => (), - Some(comp) => v.push_all(comp) - } - } - for comp in it { - v.push(SEP_BYTE); - v.push_all(comp); - } - Some(v) - } - } - } - }; - match val { - None => v.as_slice().to_vec(), - 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 components<'a>(&'a self) -> Components<'a> { - let v = if self.repr[0] == SEP_BYTE { - &self.repr[1..] - } else { self.repr.as_slice() }; - let is_sep_byte: fn(&u8) -> bool = is_sep_byte; // coerce to fn ptr - let mut ret = v.split(is_sep_byte); - if v.is_empty() { - // consume the empty "" component - ret.next(); - } - ret - } - - /// Returns an iterator that yields each component of the path as Option<&str>. - /// See components() for details. - pub fn str_components<'a>(&'a self) -> StrComponents<'a> { - fn from_utf8(s: &[u8]) -> Option<&str> { - str::from_utf8(s).ok() - } - let f: fn(&[u8]) -> Option<&str> = from_utf8; // coerce to fn ptr - self.components().map(f) - } -} - -// None result means the byte vector didn't need normalizing -fn normalize_helper<'a>(v: &'a [u8], is_abs: bool) -> Option> { - if is_abs && v.is_empty() { - return None; - } - let mut comps: Vec<&'a [u8]> = vec![]; - let mut n_up = 0u; - let mut changed = false; - for comp in v.split(is_sep_byte) { - if comp.is_empty() { changed = true } - else if comp == b"." { changed = true } - else if comp == b".." { - 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().unwrap(); changed = true } - } else { comps.push(comp) } - } - if changed { - if comps.is_empty() && !is_abs { - if v == b"." { - return None; - } - comps.push(dot_static); - } - Some(comps) - } else { - None - } -} - -#[allow(non_upper_case_globals)] -static dot_static: &'static [u8] = b"."; -#[allow(non_upper_case_globals)] -static dot_dot_static: &'static [u8] = b".."; - -#[cfg(test)] -mod tests { - use super::*; - - use clone::Clone; - use iter::IteratorExt; - use option::Option::{self, Some, None}; - use path::GenericPath; - use slice::{AsSlice, SliceExt}; - use str::{self, Str, StrExt}; - use string::ToString; - use vec::Vec; - - 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); - } - ) - } - - #[test] - fn test_paths() { - let empty: &[u8] = &[]; - t!(v: Path::new(empty), 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\xFF"), b"a/b/c\xFF"); - t!(v: Path::new(b"\xFF/../foo\x80"), b"foo\x80"); - let p = Path::new(b"a/b/c\xFF"); - assert!(p.as_str().is_none()); - - t!(s: Path::new(""), "."); - t!(s: Path::new("/"), "/"); - t!(s: Path::new("hi"), "hi"); - t!(s: Path::new("hi/"), "hi"); - t!(s: Path::new("/lib"), "/lib"); - t!(s: Path::new("/lib/"), "/lib"); - t!(s: Path::new("hi/there"), "hi/there"); - t!(s: Path::new("hi/there.txt"), "hi/there.txt"); - - t!(s: Path::new("hi/there/"), "hi/there"); - t!(s: Path::new("hi/../there"), "there"); - t!(s: Path::new("../hi/there"), "../hi/there"); - t!(s: Path::new("/../hi/there"), "/hi/there"); - t!(s: Path::new("foo/.."), "."); - t!(s: Path::new("/foo/.."), "/"); - t!(s: Path::new("/foo/../.."), "/"); - t!(s: Path::new("/foo/../../bar"), "/bar"); - t!(s: Path::new("/./hi/./there/."), "/hi/there"); - t!(s: Path::new("/./hi/./there/./.."), "/hi"); - t!(s: Path::new("foo/../.."), ".."); - t!(s: Path::new("foo/../../.."), "../.."); - t!(s: Path::new("foo/../../bar"), "../bar"); - - assert_eq!(Path::new(b"foo/bar").into_vec(), b"foo/bar"); - assert_eq!(Path::new(b"/foo/../../bar").into_vec(), - b"/bar"); - - let p = Path::new(b"foo/bar\x80"); - assert!(p.as_str().is_none()); - } - - #[test] - fn test_opt_paths() { - assert!(Path::new_opt(b"foo/bar\0").is_none()); - t!(v: Path::new_opt(b"foo/bar").unwrap(), b"foo/bar"); - assert!(Path::new_opt("foo/bar\0").is_none()); - t!(s: Path::new_opt("foo/bar").unwrap(), "foo/bar"); - } - - #[test] - fn test_null_byte() { - use thread::Thread; - let result = Thread::scoped(move|| { - Path::new(b"foo/bar\0") - }).join(); - assert!(result.is_err()); - - let result = Thread::scoped(move|| { - Path::new("test").set_filename(b"f\0o") - }).join(); - assert!(result.is_err()); - - let result = Thread::scoped(move|| { - Path::new("test").push(b"f\0o"); - }).join(); - assert!(result.is_err()); - } - - #[test] - fn test_display_str() { - macro_rules! t { - ($path:expr, $disp:ident, $exp:expr) => ( - { - let path = Path::new($path); - assert_eq!(path.$disp().to_string(), $exp); - } - ) - } - t!("foo", display, "foo"); - t!(b"foo\x80", display, "foo\u{FFFD}"); - t!(b"foo\xFFbar", display, "foo\u{FFFD}bar"); - t!(b"foo\xFF/bar", filename_display, "bar"); - t!(b"foo/\xFFbar", filename_display, "\u{FFFD}bar"); - t!(b"/", filename_display, ""); - - macro_rules! t { - ($path:expr, $exp:expr) => ( - { - let path = Path::new($path); - let mo = path.display().as_cow(); - assert_eq!(mo.as_slice(), $exp); - } - ); - ($path:expr, $exp:expr, filename) => ( - { - let path = Path::new($path); - let mo = path.filename_display().as_cow(); - assert_eq!(mo.as_slice(), $exp); - } - ) - } - - t!("foo", "foo"); - t!(b"foo\x80", "foo\u{FFFD}"); - t!(b"foo\xFFbar", "foo\u{FFFD}bar"); - t!(b"foo\xFF/bar", "bar", filename); - t!(b"foo/\xFFbar", "\u{FFFD}bar", filename); - t!(b"/", "", filename); - } - - #[test] - fn test_display() { - macro_rules! t { - ($path:expr, $exp:expr, $expf:expr) => ( - { - let path = Path::new($path); - let f = format!("{}", path.display()); - assert_eq!(f, $exp); - let f = format!("{}", path.filename_display()); - assert_eq!(f, $expf); - } - ) - } - - t!(b"foo", "foo", "foo"); - t!(b"foo/bar", "foo/bar", "bar"); - t!(b"/", "/", ""); - t!(b"foo\xFF", "foo\u{FFFD}", "foo\u{FFFD}"); - t!(b"foo\xFF/bar", "foo\u{FFFD}/bar", "bar"); - t!(b"foo/\xFFbar", "foo/\u{FFFD}bar", "\u{FFFD}bar"); - t!(b"\xFFfoo/bar\xFF", "\u{FFFD}foo/bar\u{FFFD}", "bar\u{FFFD}"); - } - - #[test] - fn test_components() { - macro_rules! t { - (s: $path:expr, $op:ident, $exp:expr) => ( - { - let path = Path::new($path); - assert_eq!(path.$op(), ($exp).as_bytes()); - } - ); - (s: $path:expr, $op:ident, $exp:expr, opt) => ( - { - let path = Path::new($path); - let left = path.$op().map(|x| str::from_utf8(x).unwrap()); - assert_eq!(left, $exp); - } - ); - (v: $path:expr, $op:ident, $exp:expr) => ( - { - let arg = $path; - let path = Path::new(arg); - assert_eq!(path.$op(), $exp); - } - ); - } - - t!(v: b"a/b/c", filename, Some(b"c")); - t!(v: b"a/b/c\xFF", filename, Some(b"c\xFF")); - t!(v: b"a/b\xFF/c", filename, Some(b"c")); - t!(s: "a/b/c", filename, Some("c"), opt); - t!(s: "/a/b/c", filename, Some("c"), opt); - t!(s: "a", filename, Some("a"), opt); - t!(s: "/a", filename, Some("a"), opt); - t!(s: ".", filename, None, opt); - t!(s: "/", filename, None, opt); - t!(s: "..", filename, None, opt); - t!(s: "../..", filename, None, opt); - - t!(v: b"a/b/c", dirname, b"a/b"); - t!(v: b"a/b/c\xFF", dirname, b"a/b"); - t!(v: b"a/b\xFF/c", dirname, b"a/b\xFF"); - 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, Some(b"there")); - t!(v: b"hi/there\x80.txt", filestem, Some(b"there\x80")); - t!(v: b"hi/there.t\x80xt", filestem, Some(b"there")); - t!(s: "hi/there.txt", filestem, Some("there"), opt); - t!(s: "hi/there", filestem, Some("there"), opt); - t!(s: "there.txt", filestem, Some("there"), opt); - t!(s: "there", filestem, Some("there"), opt); - t!(s: ".", filestem, None, opt); - t!(s: "/", filestem, None, opt); - t!(s: "foo/.bar", filestem, Some(".bar"), opt); - t!(s: ".bar", filestem, Some(".bar"), opt); - t!(s: "..bar", filestem, Some("."), opt); - t!(s: "hi/there..txt", filestem, Some("there."), opt); - t!(s: "..", filestem, None, opt); - t!(s: "../..", filestem, None, opt); - - t!(v: b"hi/there.txt", extension, Some(b"txt")); - t!(v: b"hi/there\x80.txt", extension, Some(b"txt")); - t!(v: b"hi/there.t\x80xt", extension, Some(b"t\x80xt")); - t!(v: b"hi/there", extension, None); - t!(v: b"hi/there\x80", 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::new(path); - let p2 = p1.clone(); - p1.push(join); - assert_eq!(p1, p2.join(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::new($path); - let push = Path::new($push); - p.push(&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_push_many() { - macro_rules! t { - (s: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = Path::new($path); - p.push_many(&$push); - assert_eq!(p.as_str(), Some($exp)); - } - ); - (v: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = Path::new($path); - p.push_many(&$push); - assert_eq!(p.as_vec(), $exp); - } - ) - } - - t!(s: "a/b/c", ["d", "e"], "a/b/c/d/e"); - t!(s: "a/b/c", ["d", "/e"], "/e"); - t!(s: "a/b/c", ["d", "/e", "f"], "/e/f"); - t!(s: "a/b/c", ["d".to_string(), "e".to_string()], "a/b/c/d/e"); - t!(v: b"a/b/c", [b"d", b"e"], b"a/b/c/d/e"); - t!(v: b"a/b/c", [b"d", b"/e", b"f"], b"/e/f"); - t!(v: b"a/b/c", [b"d".to_vec(), b"e".to_vec()], b"a/b/c/d/e"); - } - - #[test] - fn test_pop() { - macro_rules! t { - (s: $path:expr, $left:expr, $right:expr) => ( - { - let mut p = Path::new($path); - let result = p.pop(); - assert_eq!(p.as_str(), Some($left)); - assert_eq!(result, $right); - } - ); - (b: $path:expr, $left:expr, $right:expr) => ( - { - let mut p = Path::new($path); - let result = p.pop(); - assert_eq!(p.as_vec(), $left); - assert_eq!(result, $right); - } - ) - } - - t!(b: b"a/b/c", b"a/b", true); - t!(b: b"a", b".", true); - t!(b: b".", b".", false); - t!(b: b"/a", b"/", true); - t!(b: b"/", b"/", false); - t!(b: b"a/b/c\x80", b"a/b", true); - t!(b: b"a/b\x80/c", b"a/b\x80", true); - t!(b: b"\xFF", b".", true); - t!(b: b"/\xFF", b"/", true); - t!(s: "a/b/c", "a/b", true); - t!(s: "a", ".", true); - t!(s: ".", ".", false); - t!(s: "/a", "/", true); - t!(s: "/", "/", false); - } - - #[test] - fn test_root_path() { - assert_eq!(Path::new(b"a/b/c").root_path(), None); - assert_eq!(Path::new(b"/a/b/c").root_path(), Some(Path::new("/"))); - } - - #[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/\x80/c").join(b"\xFF"), b"a/\x80/c/\xFF"); - t!(s: Path::new("a/b/c").join(".."), "a/b"); - t!(s: Path::new("/a/b/c").join("d"), "/a/b/c/d"); - t!(s: Path::new("a/b").join("c/d"), "a/b/c/d"); - t!(s: Path::new("a/b").join("/c/d"), "/c/d"); - t!(s: Path::new(".").join("a/b"), "a/b"); - t!(s: Path::new("/").join("a/b"), "/a/b"); - } - - #[test] - fn test_join_path() { - macro_rules! t { - (s: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let join = Path::new($join); - let res = path.join(&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_join_many() { - macro_rules! t { - (s: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let res = path.join_many(&$join); - assert_eq!(res.as_str(), Some($exp)); - } - ); - (v: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let res = path.join_many(&$join); - assert_eq!(res.as_vec(), $exp); - } - ) - } - - t!(s: "a/b/c", ["d", "e"], "a/b/c/d/e"); - t!(s: "a/b/c", ["..", "d"], "a/b/d"); - t!(s: "a/b/c", ["d", "/e", "f"], "/e/f"); - t!(s: "a/b/c", ["d".to_string(), "e".to_string()], "a/b/c/d/e"); - t!(v: b"a/b/c", [b"d", b"e"], b"a/b/c/d/e"); - t!(v: b"a/b/c", [b"d".to_vec(), b"e".to_vec()], b"a/b/c/d/e"); - } - - #[test] - fn test_with_helpers() { - let empty: &[u8] = &[]; - - t!(v: Path::new(b"a/b/c").with_filename(b"d"), b"a/b/d"); - t!(v: Path::new(b"a/b/c\xFF").with_filename(b"\x80"), b"a/b/\x80"); - t!(v: Path::new(b"/\xFF/foo").with_filename(b"\xCD"), - b"/\xFF/\xCD"); - t!(s: Path::new("a/b/c").with_filename("d"), "a/b/d"); - t!(s: Path::new(".").with_filename("foo"), "foo"); - t!(s: Path::new("/a/b/c").with_filename("d"), "/a/b/d"); - t!(s: Path::new("/").with_filename("foo"), "/foo"); - t!(s: Path::new("/a").with_filename("foo"), "/foo"); - t!(s: Path::new("foo").with_filename("bar"), "bar"); - t!(s: Path::new("/").with_filename("foo/"), "/foo"); - t!(s: Path::new("/a").with_filename("foo/"), "/foo"); - t!(s: Path::new("a/b/c").with_filename(""), "a/b"); - t!(s: Path::new("a/b/c").with_filename("."), "a/b"); - t!(s: Path::new("a/b/c").with_filename(".."), "a"); - t!(s: Path::new("/a").with_filename(""), "/"); - t!(s: Path::new("foo").with_filename(""), "."); - t!(s: Path::new("a/b/c").with_filename("d/e"), "a/b/d/e"); - t!(s: Path::new("a/b/c").with_filename("/d"), "a/b/d"); - t!(s: Path::new("..").with_filename("foo"), "../foo"); - t!(s: Path::new("../..").with_filename("foo"), "../../foo"); - t!(s: Path::new("..").with_filename(""), ".."); - t!(s: Path::new("../..").with_filename(""), "../.."); - - t!(v: Path::new(b"hi/there\x80.txt").with_extension(b"exe"), - b"hi/there\x80.exe"); - t!(v: Path::new(b"hi/there.txt\x80").with_extension(b"\xFF"), - b"hi/there.\xFF"); - t!(v: Path::new(b"hi/there\x80").with_extension(b"\xFF"), - b"hi/there\x80.\xFF"); - t!(v: Path::new(b"hi/there.\xFF").with_extension(empty), b"hi/there"); - t!(s: Path::new("hi/there.txt").with_extension("exe"), "hi/there.exe"); - t!(s: Path::new("hi/there.txt").with_extension(""), "hi/there"); - t!(s: Path::new("hi/there.txt").with_extension("."), "hi/there.."); - t!(s: Path::new("hi/there.txt").with_extension(".."), "hi/there..."); - t!(s: Path::new("hi/there").with_extension("txt"), "hi/there.txt"); - t!(s: Path::new("hi/there").with_extension("."), "hi/there.."); - t!(s: Path::new("hi/there").with_extension(".."), "hi/there..."); - t!(s: Path::new("hi/there.").with_extension("txt"), "hi/there.txt"); - t!(s: Path::new("hi/.foo").with_extension("txt"), "hi/.foo.txt"); - t!(s: Path::new("hi/there.txt").with_extension(".foo"), "hi/there..foo"); - t!(s: Path::new("/").with_extension("txt"), "/"); - t!(s: Path::new("/").with_extension("."), "/"); - t!(s: Path::new("/").with_extension(".."), "/"); - t!(s: Path::new(".").with_extension("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::new(path); - p1.$set(arg); - let p2 = Path::new(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_filename, with_filename, b"d"); - t!(v: b"/", set_filename, with_filename, b"foo"); - t!(v: b"\x80", set_filename, with_filename, b"\xFF"); - t!(s: "a/b/c", set_filename, with_filename, "d"); - t!(s: "/", set_filename, with_filename, "foo"); - t!(s: ".", set_filename, with_filename, "foo"); - t!(s: "a/b", set_filename, with_filename, ""); - t!(s: "a", set_filename, with_filename, ""); - - t!(v: b"hi/there.txt", set_extension, with_extension, b"exe"); - t!(v: b"hi/there.t\x80xt", set_extension, with_extension, b"exe\xFF"); - t!(s: "hi/there.txt", set_extension, with_extension, "exe"); - t!(s: "hi/there.", set_extension, with_extension, "txt"); - t!(s: "hi/there", set_extension, with_extension, "txt"); - t!(s: "hi/there.txt", set_extension, with_extension, ""); - t!(s: "hi/there", set_extension, with_extension, ""); - t!(s: ".", set_extension, with_extension, "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"), Some(b"c"), b"a/b", Some(b"c"), None); - t!(v: Path::new(b"a/b/\xFF"), Some(b"\xFF"), b"a/b", Some(b"\xFF"), None); - t!(v: Path::new(b"hi/there.\xFF"), Some(b"there.\xFF"), b"hi", - Some(b"there"), Some(b"\xFF")); - t!(s: Path::new("a/b/c"), Some("c"), Some("a/b"), Some("c"), None); - t!(s: Path::new("."), None, Some("."), None, None); - t!(s: Path::new("/"), None, Some("/"), None, None); - t!(s: Path::new(".."), None, Some(".."), None, None); - t!(s: Path::new("../.."), None, Some("../.."), None, None); - t!(s: Path::new("hi/there.txt"), Some("there.txt"), Some("hi"), - Some("there"), Some("txt")); - t!(s: Path::new("hi/there"), Some("there"), Some("hi"), Some("there"), None); - t!(s: Path::new("hi/there."), Some("there."), Some("hi"), - Some("there"), Some("")); - t!(s: Path::new("hi/.there"), Some(".there"), Some("hi"), Some(".there"), None); - t!(s: Path::new("hi/..there"), Some("..there"), Some("hi"), - Some("."), Some("there")); - t!(s: Path::new(b"a/b/\xFF"), None, Some("a/b"), None, None); - t!(s: Path::new(b"a/b/\xFF.txt"), None, Some("a/b"), None, Some("txt")); - t!(s: Path::new(b"a/b/c.\x80"), None, Some("a/b"), Some("c"), None); - t!(s: Path::new(b"\xFF/b"), Some("b"), None, Some("b"), None); - } - - #[test] - fn test_dir_path() { - t!(v: Path::new(b"hi/there\x80").dir_path(), b"hi"); - t!(v: Path::new(b"hi\xFF/there").dir_path(), b"hi\xFF"); - t!(s: Path::new("hi/there").dir_path(), "hi"); - t!(s: Path::new("hi").dir_path(), "."); - t!(s: Path::new("/hi").dir_path(), "/"); - t!(s: Path::new("/").dir_path(), "/"); - t!(s: Path::new("..").dir_path(), ".."); - t!(s: Path::new("../..").dir_path(), "../.."); - } - - #[test] - fn test_is_absolute() { - macro_rules! t { - (s: $path:expr, $abs:expr, $rel:expr) => ( - { - let path = Path::new($path); - assert_eq!(path.is_absolute(), $abs); - assert_eq!(path.is_relative(), $rel); - } - ) - } - t!(s: "a/b/c", false, true); - t!(s: "/a/b/c", true, false); - t!(s: "a", false, true); - t!(s: "/a", true, false); - t!(s: ".", false, true); - t!(s: "/", true, false); - t!(s: "..", false, true); - t!(s: "../..", false, true); - } - - #[test] - fn test_is_ancestor_of() { - macro_rules! t { - (s: $path:expr, $dest:expr, $exp:expr) => ( - { - let path = Path::new($path); - let dest = Path::new($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_ends_with_path() { - macro_rules! t { - (s: $path:expr, $child:expr, $exp:expr) => ( - { - let path = Path::new($path); - let child = Path::new($child); - assert_eq!(path.ends_with_path(&child), $exp); - } - ); - (v: $path:expr, $child:expr, $exp:expr) => ( - { - let path = Path::new($path); - let child = Path::new($child); - assert_eq!(path.ends_with_path(&child), $exp); - } - ) - } - - t!(s: "a/b/c", "c", true); - t!(s: "a/b/c", "d", false); - t!(s: "foo/bar/quux", "bar", false); - t!(s: "foo/bar/quux", "barquux", false); - t!(s: "a/b/c", "b/c", true); - t!(s: "a/b/c", "a/b/c", true); - t!(s: "a/b/c", "foo/a/b/c", false); - t!(s: "/a/b/c", "a/b/c", true); - t!(s: "/a/b/c", "/a/b/c", false); // child must be relative - t!(s: "/a/b/c", "foo/a/b/c", false); - t!(s: "a/b/c", "", false); - t!(s: "", "", true); - t!(s: "/a/b/c", "d/e/f", false); - t!(s: "a/b/c", "a/b", false); - t!(s: "a/b/c", "b", false); - t!(v: b"a/b/c", b"b/c", true); - t!(v: b"a/b/\xFF", b"\xFF", true); - t!(v: b"a/b/\xFF", b"b/\xFF", true); - } - - #[test] - fn test_path_relative_from() { - macro_rules! t { - (s: $path:expr, $other:expr, $exp:expr) => ( - { - let path = Path::new($path); - let other = Path::new($other); - let res = path.path_relative_from(&other); - assert_eq!(res.as_ref().and_then(|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_components_iter() { - macro_rules! t { - (s: $path:expr, $exp:expr) => ( - { - let path = Path::new($path); - let comps = path.components().collect::>(); - let exp: &[&str] = &$exp; - let exps = exp.iter().map(|x| x.as_bytes()).collect::>(); - assert_eq!(comps, exps); - let comps = path.components().rev().collect::>(); - let exps = exps.into_iter().rev().collect::>(); - assert_eq!(comps, exps); - } - ); - (b: $arg:expr, [$($exp:expr),*]) => ( - { - let path = Path::new($arg); - let comps = path.components().collect::>(); - let exp: &[&[u8]] = &[$($exp),*]; - assert_eq!(comps, exp); - let comps = path.components().rev().collect::>(); - let exp = exp.iter().rev().map(|&x|x).collect::>(); - assert_eq!(comps, exp) - } - ) - } - - t!(b: b"a/b/c", [b"a", b"b", b"c"]); - t!(b: b"/\xFF/a/\x80", [b"\xFF", b"a", b"\x80"]); - t!(b: b"../../foo\xCDbar", [b"..", b"..", b"foo\xCDbar"]); - 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"]); - } - - #[test] - fn test_str_components() { - macro_rules! t { - (b: $arg:expr, $exp:expr) => ( - { - let path = Path::new($arg); - let comps = path.str_components().collect::>>(); - let exp: &[Option<&str>] = &$exp; - assert_eq!(comps, exp); - let comps = path.str_components().rev().collect::>>(); - let exp = exp.iter().rev().map(|&x|x).collect::>>(); - assert_eq!(comps, exp); - } - ) - } - - t!(b: b"a/b/c", [Some("a"), Some("b"), Some("c")]); - t!(b: b"/\xFF/a/\x80", [None, Some("a"), None]); - t!(b: b"../../foo\xCDbar", [Some(".."), Some(".."), None]); - // str_components is a wrapper around components, so no need to do - // the full set of tests - } -} - -#[cfg(test)] -mod bench { - extern crate test; - use self::test::Bencher; - use super::*; - use prelude::v1::{Clone, GenericPath}; - - #[bench] - fn join_home_dir(b: &mut Bencher) { - let posix_path = Path::new("/"); - b.iter(|| { - posix_path.join("home"); - }); - } - - #[bench] - fn join_abs_path_home_dir(b: &mut Bencher) { - let posix_path = Path::new("/"); - b.iter(|| { - posix_path.join("/home"); - }); - } - - #[bench] - fn join_many_home_dir(b: &mut Bencher) { - let posix_path = Path::new("/"); - b.iter(|| { - posix_path.join_many(&["home"]); - }); - } - - #[bench] - fn join_many_abs_path_home_dir(b: &mut Bencher) { - let posix_path = Path::new("/"); - b.iter(|| { - posix_path.join_many(&["/home"]); - }); - } - - #[bench] - fn push_home_dir(b: &mut Bencher) { - let mut posix_path = Path::new("/"); - b.iter(|| { - posix_path.push("home"); - }); - } - - #[bench] - fn push_abs_path_home_dir(b: &mut Bencher) { - let mut posix_path = Path::new("/"); - b.iter(|| { - posix_path.push("/home"); - }); - } - - #[bench] - fn push_many_home_dir(b: &mut Bencher) { - let mut posix_path = Path::new("/"); - b.iter(|| { - posix_path.push_many(&["home"]); - }); - } - - #[bench] - fn push_many_abs_path_home_dir(b: &mut Bencher) { - let mut posix_path = Path::new("/"); - b.iter(|| { - posix_path.push_many(&["/home"]); - }); - } - - #[bench] - fn ends_with_path_home_dir(b: &mut Bencher) { - let posix_home_path = Path::new("/home"); - b.iter(|| { - posix_home_path.ends_with_path(&Path::new("home")); - }); - } - - #[bench] - fn ends_with_path_missmatch_jome_home(b: &mut Bencher) { - let posix_home_path = Path::new("/home"); - b.iter(|| { - posix_home_path.ends_with_path(&Path::new("jome")); - }); - } - - #[bench] - fn is_ancestor_of_path_with_10_dirs(b: &mut Bencher) { - let path = Path::new("/home/1/2/3/4/5/6/7/8/9"); - let mut sub = path.clone(); - sub.pop(); - b.iter(|| { - path.is_ancestor_of(&sub); - }); - } - - #[bench] - fn path_relative_from_forward(b: &mut Bencher) { - let path = Path::new("/a/b/c"); - let mut other = path.clone(); - other.pop(); - b.iter(|| { - path.path_relative_from(&other); - }); - } - - #[bench] - fn path_relative_from_same_level(b: &mut Bencher) { - let path = Path::new("/a/b/c"); - let mut other = path.clone(); - other.pop(); - other.push("d"); - b.iter(|| { - path.path_relative_from(&other); - }); - } - - #[bench] - fn path_relative_from_backward(b: &mut Bencher) { - let path = Path::new("/a/b"); - let mut other = path.clone(); - other.push("c"); - b.iter(|| { - path.path_relative_from(&other); - }); - } -} diff --git a/src/libstd/path/windows.rs b/src/libstd/path/windows.rs deleted file mode 100644 index fcdebaf2cd3..00000000000 --- a/src/libstd/path/windows.rs +++ /dev/null @@ -1,2329 +0,0 @@ -// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -// -// ignore-lexer-test FIXME #15883 - -//! Windows file path handling - -use self::PathPrefix::*; - -use ascii::AsciiExt; -use char::CharExt; -use clone::Clone; -use cmp::{Ordering, Eq, Ord, PartialEq, PartialOrd}; -use fmt; -use hash; -use old_io::Writer; -use iter::{AdditiveIterator, Extend}; -use iter::{Iterator, IteratorExt, Map, repeat}; -use mem; -use option::Option::{self, Some, None}; -use result::Result::{self, Ok, Err}; -use slice::{SliceExt, SliceConcatExt}; -use str::{SplitTerminator, FromStr, StrExt}; -use string::{String, ToString}; -use vec::Vec; - -use super::{contains_nul, BytesContainer, GenericPath, GenericPathUnsafe}; - -/// Iterator that yields successive components of a Path as &str -/// -/// Each component is yielded as Option<&str> for compatibility with PosixPath, but -/// every component in WindowsPath is guaranteed to be Some. -pub type StrComponents<'a> = - Map, fn(&'a str) -> Option<&'a str>>; - -/// Iterator that yields successive components of a Path as &[u8] -pub type Components<'a> = - Map, fn(Option<&str>) -> &[u8]>; - -/// Represents a Windows path -// Notes for Windows path impl: -// The MAX_PATH is 260, but 253 is the practical limit due to some API bugs -// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx for good information -// about windows paths. -// That same page puts a bunch of restrictions on allowed characters in a path. -// `\foo.txt` means "relative to current drive", but will not be considered to be absolute here -// as `∃P | P.join("\foo.txt") != "\foo.txt"`. -// `C:` is interesting, that means "the current directory on drive C". -// Long absolute paths need to have \\?\ prefix (or, for UNC, \\?\UNC\). I think that can be -// ignored for now, though, and only added in a hypothetical .to_pwstr() function. -// However, if a path is parsed that has \\?\, this needs to be preserved as it disables the -// processing of "." and ".." components and / as a separator. -// Experimentally, \\?\foo is not the same thing as \foo. -// Also, \\foo is not valid either (certainly not equivalent to \foo). -// Similarly, C:\\Users is not equivalent to C:\Users, although C:\Users\\foo is equivalent -// to C:\Users\foo. In fact the command prompt treats C:\\foo\bar as UNC path. But it might be -// best to just ignore that and normalize it to C:\foo\bar. -// -// Based on all this, I think the right approach is to do the following: -// * Require valid utf-8 paths. Windows API may use WCHARs, but we don't, and utf-8 is convertible -// to UTF-16 anyway (though does Windows use UTF-16 or UCS-2? Not sure). -// * Parse the prefixes \\?\UNC\, \\?\, and \\.\ explicitly. -// * If \\?\UNC\, treat following two path components as server\share. Don't error for missing -// server\share. -// * If \\?\, parse disk from following component, if present. Don't error for missing disk. -// * If \\.\, treat rest of path as just regular components. I don't know how . and .. are handled -// here, they probably aren't, but I'm not going to worry about that. -// * Else if starts with \\, treat following two components as server\share. Don't error for missing -// server\share. -// * Otherwise, attempt to parse drive from start of path. -// -// The only error condition imposed here is valid utf-8. All other invalid paths are simply -// preserved by the data structure; let the Windows API error out on them. -#[derive(Clone)] -pub struct Path { - repr: String, // assumed to never be empty - prefix: Option, - sepidx: Option // index of the final separator in the non-prefix portion of repr -} - -#[stable(feature = "rust1", since = "1.0.0")] -impl fmt::Debug for Path { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.display(), f) - } -} - -impl PartialEq for Path { - #[inline] - fn eq(&self, other: &Path) -> bool { - self.repr == other.repr - } -} - -impl Eq for Path {} - -impl PartialOrd for Path { - fn partial_cmp(&self, other: &Path) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Path { - fn cmp(&self, other: &Path) -> Ordering { - self.repr.cmp(&other.repr) - } -} - -impl FromStr for Path { - type Err = ParsePathError; - fn from_str(s: &str) -> Result { - match Path::new_opt(s) { - Some(p) => Ok(p), - None => Err(ParsePathError), - } - } -} - -/// Value indicating that a path could not be parsed from a string. -#[derive(Debug, Clone, PartialEq, Copy)] -pub struct ParsePathError; - -impl hash::Hash for Path { - #[cfg(not(test))] - #[inline] - fn hash(&self, state: &mut S) { - self.repr.hash(state) - } - - #[cfg(test)] - #[inline] - fn hash(&self, _: &mut S) { - // No-op because the `hash` implementation will be wrong. - } -} - -impl BytesContainer for Path { - #[inline] - fn container_as_bytes<'a>(&'a self) -> &'a [u8] { - self.as_vec() - } - #[inline] - fn container_as_str<'a>(&'a self) -> Option<&'a str> { - self.as_str() - } - #[inline] - fn is_str(_: Option<&Path>) -> bool { true } -} - -impl GenericPathUnsafe for Path { - /// See `GenericPathUnsafe::from_vec_unchecked`. - /// - /// # Panics - /// - /// Panics if not valid UTF-8. - #[inline] - unsafe fn new_unchecked(path: T) -> Path { - let (prefix, path) = Path::normalize_(path.container_as_str().unwrap()); - assert!(!path.is_empty()); - let mut ret = Path{ repr: path, prefix: prefix, sepidx: None }; - ret.update_sepidx(); - ret - } - - /// See `GenericPathUnsafe::set_filename_unchecked`. - /// - /// # Panics - /// - /// Panics if not valid UTF-8. - unsafe fn set_filename_unchecked(&mut self, filename: T) { - let filename = filename.container_as_str().unwrap(); - match self.sepidx_or_prefix_len() { - None if ".." == self.repr => { - let mut s = String::with_capacity(3 + filename.len()); - s.push_str(".."); - s.push(SEP); - s.push_str(filename); - self.update_normalized(&s[]); - } - None => { - self.update_normalized(filename); - } - Some((_,idxa,end)) if &self.repr[idxa..end] == ".." => { - let mut s = String::with_capacity(end + 1 + filename.len()); - s.push_str(&self.repr[..end]); - s.push(SEP); - s.push_str(filename); - self.update_normalized(&s[]); - } - Some((idxb,idxa,_)) if self.prefix == Some(DiskPrefix) && idxa == self.prefix_len() => { - let mut s = String::with_capacity(idxb + filename.len()); - s.push_str(&self.repr[..idxb]); - s.push_str(filename); - self.update_normalized(&s[]); - } - Some((idxb,_,_)) => { - let mut s = String::with_capacity(idxb + 1 + filename.len()); - s.push_str(&self.repr[..idxb]); - s.push(SEP); - s.push_str(filename); - self.update_normalized(&s[]); - } - } - } - - /// See `GenericPathUnsafe::push_unchecked`. - /// - /// Concatenating two Windows Paths is rather complicated. - /// For the most part, it will behave as expected, except in the case of - /// pushing a volume-relative path, e.g. `C:foo.txt`. Because we have no - /// concept of per-volume cwds like Windows does, we can't behave exactly - /// like Windows will. Instead, if the receiver is an absolute path on - /// the same volume as the new path, it will be treated as the cwd that - /// the new path is relative to. Otherwise, the new path will be treated - /// as if it were absolute and will replace the receiver outright. - unsafe fn push_unchecked(&mut self, path: T) { - let path = path.container_as_str().unwrap(); - fn is_vol_abs(path: &str, prefix: Option) -> bool { - // assume prefix is Some(DiskPrefix) - let rest = &path[prefix_len(prefix)..]; - !rest.is_empty() && rest.as_bytes()[0].is_ascii() && is_sep(rest.as_bytes()[0] as char) - } - fn shares_volume(me: &Path, path: &str) -> bool { - // path is assumed to have a prefix of Some(DiskPrefix) - let repr = &me.repr[]; - match me.prefix { - Some(DiskPrefix) => { - repr.as_bytes()[0] == path.as_bytes()[0].to_ascii_uppercase() - } - Some(VerbatimDiskPrefix) => { - repr.as_bytes()[4] == path.as_bytes()[0].to_ascii_uppercase() - } - _ => false - } - } - fn is_sep_(prefix: Option, u: u8) -> bool { - if prefix_is_verbatim(prefix) { is_sep_verbatim(u as char) } - else { is_sep(u as char) } - } - - fn replace_path(me: &mut Path, path: &str, prefix: Option) { - let newpath = Path::normalize__(path, prefix); - me.repr = match newpath { - Some(p) => p, - None => String::from_str(path) - }; - me.prefix = prefix; - me.update_sepidx(); - } - fn append_path(me: &mut Path, path: &str) { - // appends a path that has no prefix - // if me is verbatim, we need to pre-normalize the new path - let path_ = if is_verbatim(me) { Path::normalize__(path, None) } - else { None }; - let pathlen = path_.as_ref().map_or(path.len(), |p| p.len()); - let mut s = String::with_capacity(me.repr.len() + 1 + pathlen); - s.push_str(&me.repr[]); - let plen = me.prefix_len(); - // if me is "C:" we don't want to add a path separator - match me.prefix { - Some(DiskPrefix) if me.repr.len() == plen => (), - _ if !(me.repr.len() > plen && me.repr.as_bytes()[me.repr.len()-1] == SEP_BYTE) => { - s.push(SEP); - } - _ => () - } - match path_ { - None => s.push_str(path), - Some(p) => s.push_str(&p[]), - }; - me.update_normalized(&s[]) - } - - if !path.is_empty() { - let prefix = parse_prefix(path); - match prefix { - Some(DiskPrefix) if !is_vol_abs(path, prefix) && shares_volume(self, path) => { - // cwd-relative path, self is on the same volume - append_path(self, &path[prefix_len(prefix)..]); - } - Some(_) => { - // absolute path, or cwd-relative and self is not same volume - replace_path(self, path, prefix); - } - None if !path.is_empty() && is_sep_(self.prefix, path.as_bytes()[0]) => { - // volume-relative path - if self.prefix.is_some() { - // truncate self down to the prefix, then append - let n = self.prefix_len(); - self.repr.truncate(n); - append_path(self, path); - } else { - // we have no prefix, so nothing to be relative to - replace_path(self, path, prefix); - } - } - None => { - // relative path - append_path(self, path); - } - } - } - } -} - -impl GenericPath for Path { - #[inline] - fn new_opt(path: T) -> Option { - match path.container_as_str() { - None => None, - Some(ref s) => { - if contains_nul(s) { - None - } else { - Some(unsafe { GenericPathUnsafe::new_unchecked(*s) }) - } - } - } - } - - /// See `GenericPath::as_str` for info. - /// Always returns a `Some` value. - #[inline] - fn as_str<'a>(&'a self) -> Option<&'a str> { - Some(&self.repr[]) - } - - #[inline] - fn as_vec<'a>(&'a self) -> &'a [u8] { - self.repr.as_bytes() - } - - #[inline] - fn into_vec(self) -> Vec { - self.repr.into_bytes() - } - - #[inline] - fn dirname<'a>(&'a self) -> &'a [u8] { - self.dirname_str().unwrap().as_bytes() - } - - /// See `GenericPath::dirname_str` for info. - /// Always returns a `Some` value. - fn dirname_str<'a>(&'a self) -> Option<&'a str> { - Some(match self.sepidx_or_prefix_len() { - None if ".." == self.repr => &self.repr[], - None => ".", - Some((_,idxa,end)) if &self.repr[idxa..end] == ".." => { - &self.repr[] - } - Some((idxb,_,end)) if &self.repr[idxb..end] == "\\" => { - &self.repr[] - } - Some((0,idxa,_)) => &self.repr[..idxa], - Some((idxb,idxa,_)) => { - match self.prefix { - Some(DiskPrefix) | Some(VerbatimDiskPrefix) if idxb == self.prefix_len() => { - &self.repr[..idxa] - } - _ => &self.repr[..idxb] - } - } - }) - } - - #[inline] - fn filename<'a>(&'a self) -> Option<&'a [u8]> { - self.filename_str().map(|x| x.as_bytes()) - } - - /// See `GenericPath::filename_str` for info. - /// Always returns a `Some` value if `filename` returns a `Some` value. - fn filename_str<'a>(&'a self) -> Option<&'a str> { - let repr = &self.repr[]; - match self.sepidx_or_prefix_len() { - None if "." == repr || ".." == repr => None, - None => Some(repr), - Some((_,idxa,end)) if &repr[idxa..end] == ".." => None, - Some((_,idxa,end)) if idxa == end => None, - Some((_,idxa,end)) => Some(&repr[idxa..end]) - } - } - - /// See `GenericPath::filestem_str` for info. - /// Always returns a `Some` value if `filestem` returns a `Some` value. - #[inline] - fn filestem_str<'a>(&'a self) -> Option<&'a str> { - // filestem() returns a byte vector that's guaranteed valid UTF-8 - self.filestem().map(|t| unsafe { mem::transmute(t) }) - } - - #[inline] - fn extension_str<'a>(&'a self) -> Option<&'a str> { - // extension() returns a byte vector that's guaranteed valid UTF-8 - self.extension().map(|t| unsafe { mem::transmute(t) }) - } - - fn dir_path(&self) -> Path { - unsafe { GenericPathUnsafe::new_unchecked(self.dirname_str().unwrap()) } - } - - #[inline] - fn pop(&mut self) -> bool { - match self.sepidx_or_prefix_len() { - None if "." == self.repr => false, - None => { - self.repr = String::from_str("."); - self.sepidx = None; - true - } - Some((idxb,idxa,end)) if idxb == idxa && idxb == end => false, - Some((idxb,_,end)) if &self.repr[idxb..end] == "\\" => false, - Some((idxb,idxa,_)) => { - let trunc = match self.prefix { - Some(DiskPrefix) | Some(VerbatimDiskPrefix) | None => { - let plen = self.prefix_len(); - if idxb == plen { idxa } else { idxb } - } - _ => idxb - }; - self.repr.truncate(trunc); - self.update_sepidx(); - true - } - } - } - - fn root_path(&self) -> Option { - if self.prefix.is_some() { - Some(Path::new(match self.prefix { - Some(DiskPrefix) if self.is_absolute() => { - &self.repr[..self.prefix_len()+1] - } - Some(VerbatimDiskPrefix) => { - &self.repr[..self.prefix_len()+1] - } - _ => &self.repr[..self.prefix_len()] - })) - } else if is_vol_relative(self) { - Some(Path::new(&self.repr[..1])) - } else { - None - } - } - - /// See `GenericPath::is_absolute` for info. - /// - /// A Windows Path is considered absolute only if it has a non-volume prefix, - /// or if it has a volume prefix and the path starts with '\'. - /// A path of `\foo` is not considered absolute because it's actually - /// relative to the "current volume". A separate method `Path::is_vol_relative` - /// is provided to indicate this case. Similarly a path of `C:foo` is not - /// considered absolute because it's relative to the cwd on volume C:. A - /// separate method `Path::is_cwd_relative` is provided to indicate this case. - #[inline] - fn is_absolute(&self) -> bool { - match self.prefix { - Some(DiskPrefix) => { - let rest = &self.repr[self.prefix_len()..]; - rest.len() > 0 && rest.as_bytes()[0] == SEP_BYTE - } - Some(_) => true, - None => false - } - } - - #[inline] - fn is_relative(&self) -> bool { - self.prefix.is_none() && !is_vol_relative(self) - } - - fn is_ancestor_of(&self, other: &Path) -> bool { - if !self.equiv_prefix(other) { - false - } else if self.is_absolute() != other.is_absolute() || - is_vol_relative(self) != is_vol_relative(other) { - false - } else { - let mut ita = self.str_components().map(|x|x.unwrap()); - let mut itb = other.str_components().map(|x|x.unwrap()); - if "." == self.repr { - return itb.next() != Some(".."); - } - loop { - match (ita.next(), itb.next()) { - (None, _) => break, - (Some(a), Some(b)) if a == b => { continue }, - (Some(a), _) if a == ".." => { - // if ita contains only .. components, it's an ancestor - return ita.all(|x| x == ".."); - } - _ => return false - } - } - true - } - } - - fn path_relative_from(&self, base: &Path) -> Option { - fn comp_requires_verbatim(s: &str) -> bool { - s == "." || s == ".." || s.contains_char(SEP2) - } - - if !self.equiv_prefix(base) { - // prefixes differ - if self.is_absolute() { - Some(self.clone()) - } else if self.prefix == Some(DiskPrefix) && base.prefix == Some(DiskPrefix) { - // both drives, drive letters must differ or they'd be equiv - Some(self.clone()) - } else { - None - } - } else if self.is_absolute() != base.is_absolute() { - if self.is_absolute() { - Some(self.clone()) - } else { - None - } - } else if is_vol_relative(self) != is_vol_relative(base) { - if is_vol_relative(self) { - Some(self.clone()) - } else { - None - } - } else { - let mut ita = self.str_components().map(|x|x.unwrap()); - let mut itb = base.str_components().map(|x|x.unwrap()); - let mut comps = vec![]; - - let a_verb = is_verbatim(self); - let b_verb = is_verbatim(base); - loop { - match (ita.next(), itb.next()) { - (None, None) => break, - (Some(a), None) if a_verb && comp_requires_verbatim(a) => { - return Some(self.clone()) - } - (Some(a), None) => { - comps.push(a); - if !a_verb { - comps.extend(ita.by_ref()); - break; - } - } - (None, _) => comps.push(".."), - (Some(a), Some(b)) if comps.is_empty() && a == b => (), - (Some(a), Some(b)) if !b_verb && b == "." => { - if a_verb && comp_requires_verbatim(a) { - return Some(self.clone()) - } else { comps.push(a) } - } - (Some(_), Some(b)) if !b_verb && b == ".." => return None, - (Some(a), Some(_)) if a_verb && comp_requires_verbatim(a) => { - return Some(self.clone()) - } - (Some(a), Some(_)) => { - comps.push(".."); - for _ in itb.by_ref() { - comps.push(".."); - } - comps.push(a); - if !a_verb { - comps.extend(ita.by_ref()); - break; - } - } - } - } - Some(Path::new(comps.connect("\\"))) - } - } - - fn ends_with_path(&self, child: &Path) -> bool { - if !child.is_relative() { return false; } - let mut selfit = self.str_components().rev(); - let mut childit = child.str_components().rev(); - loop { - match (selfit.next(), childit.next()) { - (Some(a), Some(b)) => if a != b { return false; }, - (Some(_), None) => break, - (None, Some(_)) => return false, - (None, None) => break - } - } - true - } -} - -impl Path { - /// Returns a new `Path` from a `BytesContainer`. - /// - /// # Panics - /// - /// Panics if the vector contains a `NUL`, or if it contains invalid UTF-8. - /// - /// # Example - /// - /// ``` - /// println!("{}", Path::new(r"C:\some\path").display()); - /// ``` - #[inline] - pub fn new(path: T) -> Path { - GenericPath::new(path) - } - - /// Returns a new `Some(Path)` from a `BytesContainer`. - /// - /// Returns `None` if the vector contains a `NUL`, or if it contains invalid UTF-8. - /// - /// # Example - /// - /// ``` - /// let path = Path::new_opt(r"C:\some\path"); - /// - /// match path { - /// Some(path) => println!("{}", path.display()), - /// None => println!("There was a problem with your path."), - /// } - /// ``` - #[inline] - pub fn new_opt(path: T) -> Option { - GenericPath::new_opt(path) - } - - /// Returns an iterator that yields each component of the path in turn as a Option<&str>. - /// Every component is guaranteed to be Some. - /// Does not yield the path prefix (including server/share components in UNC paths). - /// Does not distinguish between volume-relative and relative paths, e.g. - /// \a\b\c and a\b\c. - /// Does not distinguish between absolute and cwd-relative paths, e.g. - /// C:\foo and C:foo. - pub fn str_components<'a>(&'a self) -> StrComponents<'a> { - let repr = &self.repr[]; - let s = match self.prefix { - Some(_) => { - let plen = self.prefix_len(); - if repr.len() > plen && repr.as_bytes()[plen] == SEP_BYTE { - &repr[plen+1..] - } else { &repr[plen..] } - } - None if repr.as_bytes()[0] == SEP_BYTE => &repr[1..], - None => repr - }; - let some: fn(&'a str) -> Option<&'a str> = Some; // coerce to fn ptr - let ret = s.split_terminator(SEP).map(some); - ret - } - - /// Returns an iterator that yields each component of the path in turn as a &[u8]. - /// See str_components() for details. - pub fn components<'a>(&'a self) -> Components<'a> { - fn convert<'a>(x: Option<&'a str>) -> &'a [u8] { - #![inline] - x.unwrap().as_bytes() - } - let convert: for<'b> fn(Option<&'b str>) -> &'b [u8] = convert; // coerce to fn ptr - self.str_components().map(convert) - } - - fn equiv_prefix(&self, other: &Path) -> bool { - let s_repr = &self.repr[]; - let o_repr = &other.repr[]; - match (self.prefix, other.prefix) { - (Some(DiskPrefix), Some(VerbatimDiskPrefix)) => { - self.is_absolute() && - s_repr.as_bytes()[0].to_ascii_lowercase() == - o_repr.as_bytes()[4].to_ascii_lowercase() - } - (Some(VerbatimDiskPrefix), Some(DiskPrefix)) => { - other.is_absolute() && - s_repr.as_bytes()[4].to_ascii_lowercase() == - o_repr.as_bytes()[0].to_ascii_lowercase() - } - (Some(VerbatimDiskPrefix), Some(VerbatimDiskPrefix)) => { - s_repr.as_bytes()[4].to_ascii_lowercase() == - o_repr.as_bytes()[4].to_ascii_lowercase() - } - (Some(UNCPrefix(_,_)), Some(VerbatimUNCPrefix(_,_))) => { - &s_repr[2..self.prefix_len()] == &o_repr[8..other.prefix_len()] - } - (Some(VerbatimUNCPrefix(_,_)), Some(UNCPrefix(_,_))) => { - &s_repr[8..self.prefix_len()] == &o_repr[2..other.prefix_len()] - } - (None, None) => true, - (a, b) if a == b => { - &s_repr[..self.prefix_len()] == &o_repr[..other.prefix_len()] - } - _ => false - } - } - - fn normalize_(s: &str) -> (Option, String) { - // make borrowck happy - let (prefix, val) = { - let prefix = parse_prefix(s); - let path = Path::normalize__(s, prefix); - (prefix, path) - }; - (prefix, match val { - None => s.to_string(), - Some(val) => val - }) - } - - fn normalize__(s: &str, prefix: Option) -> Option { - if prefix_is_verbatim(prefix) { - // don't do any normalization - match prefix { - Some(VerbatimUNCPrefix(x, 0)) if s.len() == 8 + x => { - // the server component has no trailing '\' - let mut s = String::from_str(s); - s.push(SEP); - Some(s) - } - _ => None - } - } else { - let (is_abs, comps) = normalize_helper(s, prefix); - let mut comps = comps; - match (comps.is_some(),prefix) { - (false, Some(DiskPrefix)) => { - if s.as_bytes()[0] >= b'a' && s.as_bytes()[0] <= b'z' { - comps = Some(vec![]); - } - } - (false, Some(VerbatimDiskPrefix)) => { - if s.as_bytes()[4] >= b'a' && s.as_bytes()[0] <= b'z' { - comps = Some(vec![]); - } - } - _ => () - } - match comps { - None => None, - Some(comps) => { - if prefix.is_some() && comps.is_empty() { - match prefix.unwrap() { - DiskPrefix => { - let len = prefix_len(prefix) + is_abs as uint; - let mut s = String::from_str(&s[..len]); - unsafe { - let v = s.as_mut_vec(); - v[0] = (*v)[0].to_ascii_uppercase(); - } - if is_abs { - // normalize C:/ to C:\ - unsafe { - s.as_mut_vec()[2] = SEP_BYTE; - } - } - Some(s) - } - VerbatimDiskPrefix => { - let len = prefix_len(prefix) + is_abs as uint; - let mut s = String::from_str(&s[..len]); - unsafe { - let v = s.as_mut_vec(); - v[4] = (*v)[4].to_ascii_uppercase(); - } - Some(s) - } - _ => { - let plen = prefix_len(prefix); - if s.len() > plen { - Some(String::from_str(&s[..plen])) - } else { None } - } - } - } else if is_abs && comps.is_empty() { - Some(repeat(SEP).take(1).collect()) - } else { - let prefix_ = &s[..prefix_len(prefix)]; - let n = prefix_.len() + - if is_abs { comps.len() } else { comps.len() - 1} + - comps.iter().map(|v| v.len()).sum(); - let mut s = String::with_capacity(n); - match prefix { - Some(DiskPrefix) => { - s.push(prefix_.as_bytes()[0].to_ascii_uppercase() as char); - s.push(':'); - } - Some(VerbatimDiskPrefix) => { - s.push_str(&prefix_[..4]); - s.push(prefix_.as_bytes()[4].to_ascii_uppercase() as char); - s.push_str(&prefix_[5..]); - } - Some(UNCPrefix(a,b)) => { - s.push_str("\\\\"); - s.push_str(&prefix_[2..a+2]); - s.push(SEP); - s.push_str(&prefix_[3+a..3+a+b]); - } - Some(_) => s.push_str(prefix_), - None => () - } - let mut it = comps.into_iter(); - if !is_abs { - match it.next() { - None => (), - Some(comp) => s.push_str(comp) - } - } - for comp in it { - s.push(SEP); - s.push_str(comp); - } - Some(s) - } - } - } - } - } - - fn update_sepidx(&mut self) { - let s = if self.has_nonsemantic_trailing_slash() { - &self.repr[..self.repr.len()-1] - } else { &self.repr[] }; - let sep_test: fn(char) -> bool = if !prefix_is_verbatim(self.prefix) { - is_sep - } else { - is_sep_verbatim - }; - let idx = s.rfind(sep_test); - let prefixlen = self.prefix_len(); - self.sepidx = idx.and_then(|x| if x < prefixlen { None } else { Some(x) }); - } - - fn prefix_len(&self) -> uint { - prefix_len(self.prefix) - } - - // Returns a tuple (before, after, end) where before is the index of the separator - // and after is the index just after the separator. - // end is the length of the string, normally, or the index of the final character if it is - // a non-semantic trailing separator in a verbatim string. - // If the prefix is considered the separator, before and after are the same. - fn sepidx_or_prefix_len(&self) -> Option<(uint,uint,uint)> { - match self.sepidx { - None => match self.prefix_len() { 0 => None, x => Some((x,x,self.repr.len())) }, - Some(x) => { - if self.has_nonsemantic_trailing_slash() { - Some((x,x+1,self.repr.len()-1)) - } else { Some((x,x+1,self.repr.len())) } - } - } - } - - fn has_nonsemantic_trailing_slash(&self) -> bool { - is_verbatim(self) && self.repr.len() > self.prefix_len()+1 && - self.repr.as_bytes()[self.repr.len()-1] == SEP_BYTE - } - - fn update_normalized(&mut self, s: &str) { - let (prefix, path) = Path::normalize_(s); - self.repr = path; - self.prefix = prefix; - self.update_sepidx(); - } -} - -/// Returns whether the path is considered "volume-relative", which means a path -/// that looks like "\foo". Paths of this form are relative to the current volume, -/// but absolute within that volume. -#[inline] -pub fn is_vol_relative(path: &Path) -> bool { - path.prefix.is_none() && is_sep_byte(&path.repr.as_bytes()[0]) -} - -/// Returns whether the path is considered "cwd-relative", which means a path -/// with a volume prefix that is not absolute. This look like "C:foo.txt". Paths -/// of this form are relative to the cwd on the given volume. -#[inline] -pub fn is_cwd_relative(path: &Path) -> bool { - path.prefix == Some(DiskPrefix) && !path.is_absolute() -} - -/// Returns the PathPrefix for this Path -#[inline] -pub fn prefix(path: &Path) -> Option { - path.prefix -} - -/// Returns whether the Path's prefix is a verbatim prefix, i.e. `\\?\` -#[inline] -pub fn is_verbatim(path: &Path) -> bool { - prefix_is_verbatim(path.prefix) -} - -/// Returns the non-verbatim equivalent of the input path, if possible. -/// If the input path is a device namespace path, None is returned. -/// If the input path is not verbatim, it is returned as-is. -/// If the input path is verbatim, but the same path can be expressed as -/// non-verbatim, the non-verbatim version is returned. -/// Otherwise, None is returned. -pub fn make_non_verbatim(path: &Path) -> Option { - let repr = &path.repr[]; - let new_path = match path.prefix { - Some(VerbatimPrefix(_)) | Some(DeviceNSPrefix(_)) => return None, - Some(UNCPrefix(_,_)) | Some(DiskPrefix) | None => return Some(path.clone()), - Some(VerbatimDiskPrefix) => { - // \\?\D:\ - Path::new(&repr[4..]) - } - Some(VerbatimUNCPrefix(_,_)) => { - // \\?\UNC\server\share - Path::new(format!(r"\{}", &repr[7..])) - } - }; - if new_path.prefix.is_none() { - // \\?\UNC\server is a VerbatimUNCPrefix - // but \\server is nothing - return None; - } - // now ensure normalization didn't change anything - if &repr[path.prefix_len()..] == &new_path.repr[new_path.prefix_len()..] { - Some(new_path) - } else { - None - } -} - -/// The standard path separator character -pub const SEP: char = '\\'; -/// The standard path separator byte -pub const SEP_BYTE: u8 = SEP as u8; - -/// The alternative path separator character -pub const SEP2: char = '/'; -/// The alternative path separator character -pub const SEP2_BYTE: u8 = SEP2 as u8; - -/// Returns whether the given char is a path separator. -/// Allows both the primary separator '\' and the alternative separator '/'. -#[inline] -pub fn is_sep(c: char) -> bool { - c == SEP || c == SEP2 -} - -/// Returns whether the given char is a path separator. -/// Only allows the primary separator '\'; use is_sep to allow '/'. -#[inline] -pub fn is_sep_verbatim(c: char) -> bool { - c == SEP -} - -/// Returns whether the given byte is a path separator. -/// Allows both the primary separator '\' and the alternative separator '/'. -#[inline] -pub fn is_sep_byte(u: &u8) -> bool { - *u == SEP_BYTE || *u == SEP2_BYTE -} - -/// Returns whether the given byte is a path separator. -/// Only allows the primary separator '\'; use is_sep_byte to allow '/'. -#[inline] -pub fn is_sep_byte_verbatim(u: &u8) -> bool { - *u == SEP_BYTE -} - -/// Prefix types for Path -#[derive(Copy, PartialEq, Clone, Debug)] -pub enum PathPrefix { - /// Prefix `\\?\`, uint is the length of the following component - VerbatimPrefix(uint), - /// Prefix `\\?\UNC\`, uints are the lengths of the UNC components - VerbatimUNCPrefix(uint, uint), - /// Prefix `\\?\C:\` (for any alphabetic character) - VerbatimDiskPrefix, - /// Prefix `\\.\`, uint is the length of the following component - DeviceNSPrefix(uint), - /// UNC prefix `\\server\share`, uints are the lengths of the server/share - UNCPrefix(uint, uint), - /// Prefix `C:` for any alphabetic character - DiskPrefix -} - -fn parse_prefix<'a>(mut path: &'a str) -> Option { - if path.starts_with("\\\\") { - // \\ - path = &path[2..]; - if path.starts_with("?\\") { - // \\?\ - path = &path[2..]; - if path.starts_with("UNC\\") { - // \\?\UNC\server\share - path = &path[4..]; - let (idx_a, idx_b) = match parse_two_comps(path, is_sep_verbatim) { - Some(x) => x, - None => (path.len(), 0) - }; - return Some(VerbatimUNCPrefix(idx_a, idx_b)); - } else { - // \\?\path - let idx = path.find('\\'); - if idx == Some(2) && path.as_bytes()[1] == b':' { - let c = path.as_bytes()[0]; - if c.is_ascii() && (c as char).is_alphabetic() { - // \\?\C:\ path - return Some(VerbatimDiskPrefix); - } - } - let idx = idx.unwrap_or(path.len()); - return Some(VerbatimPrefix(idx)); - } - } else if path.starts_with(".\\") { - // \\.\path - path = &path[2..]; - let idx = path.find('\\').unwrap_or(path.len()); - return Some(DeviceNSPrefix(idx)); - } - match parse_two_comps(path, is_sep) { - Some((idx_a, idx_b)) if idx_a > 0 && idx_b > 0 => { - // \\server\share - return Some(UNCPrefix(idx_a, idx_b)); - } - _ => () - } - } else if path.len() > 1 && path.as_bytes()[1] == b':' { - // C: - let c = path.as_bytes()[0]; - if c.is_ascii() && (c as char).is_alphabetic() { - return Some(DiskPrefix); - } - } - return None; - - fn parse_two_comps(mut path: &str, f: fn(char) -> bool) -> Option<(uint, uint)> { - let idx_a = match path.find(f) { - None => return None, - Some(x) => x - }; - path = &path[idx_a+1..]; - let idx_b = path.find(f).unwrap_or(path.len()); - Some((idx_a, idx_b)) - } -} - -// None result means the string didn't need normalizing -fn normalize_helper<'a>(s: &'a str, prefix: Option) -> (bool, Option>) { - let f: fn(char) -> bool = if !prefix_is_verbatim(prefix) { - is_sep - } else { - is_sep_verbatim - }; - let is_abs = s.len() > prefix_len(prefix) && f(s.char_at(prefix_len(prefix))); - let s_ = &s[prefix_len(prefix)..]; - let s_ = if is_abs { &s_[1..] } else { s_ }; - - if is_abs && s_.is_empty() { - return (is_abs, match prefix { - Some(DiskPrefix) | None => (if is_sep_verbatim(s.char_at(prefix_len(prefix))) { None } - else { Some(vec![]) }), - Some(_) => Some(vec![]), // need to trim the trailing separator - }); - } - let mut comps: Vec<&'a str> = vec![]; - let mut n_up = 0u; - let mut changed = false; - for comp in s_.split(f) { - if comp.is_empty() { changed = true } - else if comp == "." { changed = true } - else if comp == ".." { - let has_abs_prefix = match prefix { - Some(DiskPrefix) => false, - Some(_) => true, - None => false - }; - if (is_abs || has_abs_prefix) && comps.is_empty() { changed = true } - else if comps.len() == n_up { comps.push(".."); n_up += 1 } - else { comps.pop().unwrap(); changed = true } - } else { comps.push(comp) } - } - if !changed && !prefix_is_verbatim(prefix) { - changed = s.find(is_sep).is_some(); - } - if changed { - if comps.is_empty() && !is_abs && prefix.is_none() { - if s == "." { - return (is_abs, None); - } - comps.push("."); - } - (is_abs, Some(comps)) - } else { - (is_abs, None) - } -} - -fn prefix_is_verbatim(p: Option) -> bool { - match p { - Some(VerbatimPrefix(_)) | Some(VerbatimUNCPrefix(_,_)) | Some(VerbatimDiskPrefix) => true, - Some(DeviceNSPrefix(_)) => true, // not really sure, but I think so - _ => false - } -} - -fn prefix_len(p: Option) -> uint { - match p { - None => 0, - Some(VerbatimPrefix(x)) => 4 + x, - Some(VerbatimUNCPrefix(x,y)) => 8 + x + 1 + y, - Some(VerbatimDiskPrefix) => 6, - Some(UNCPrefix(x,y)) => 2 + x + 1 + y, - Some(DeviceNSPrefix(x)) => 4 + x, - Some(DiskPrefix) => 2 - } -} - -#[cfg(test)] -mod tests { - use super::PathPrefix::*; - use super::parse_prefix; - use super::*; - - use clone::Clone; - use iter::IteratorExt; - use option::Option::{self, Some, None}; - use path::GenericPath; - use slice::{AsSlice, SliceExt}; - use str::Str; - use string::ToString; - use vec::Vec; - - 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); - } - ) - } - - #[test] - fn test_parse_prefix() { - macro_rules! t { - ($path:expr, $exp:expr) => ( - { - let path = $path; - let exp = $exp; - let res = parse_prefix(path); - assert_eq!(res, exp); - } - ) - } - - t!("\\\\SERVER\\share\\foo", Some(UNCPrefix(6,5))); - t!("\\\\", None); - t!("\\\\SERVER", None); - t!("\\\\SERVER\\", None); - t!("\\\\SERVER\\\\", None); - t!("\\\\SERVER\\\\foo", None); - t!("\\\\SERVER\\share", Some(UNCPrefix(6,5))); - t!("\\\\SERVER/share/foo", Some(UNCPrefix(6,5))); - t!("\\\\SERVER\\share/foo", Some(UNCPrefix(6,5))); - t!("//SERVER/share/foo", None); - t!("\\\\\\a\\b\\c", None); - t!("\\\\?\\a\\b\\c", Some(VerbatimPrefix(1))); - t!("\\\\?\\a/b/c", Some(VerbatimPrefix(5))); - t!("//?/a/b/c", None); - t!("\\\\.\\a\\b", Some(DeviceNSPrefix(1))); - t!("\\\\.\\a/b", Some(DeviceNSPrefix(3))); - t!("//./a/b", None); - t!("\\\\?\\UNC\\server\\share\\foo", Some(VerbatimUNCPrefix(6,5))); - t!("\\\\?\\UNC\\\\share\\foo", Some(VerbatimUNCPrefix(0,5))); - t!("\\\\?\\UNC\\", Some(VerbatimUNCPrefix(0,0))); - t!("\\\\?\\UNC\\server/share/foo", Some(VerbatimUNCPrefix(16,0))); - t!("\\\\?\\UNC\\server", Some(VerbatimUNCPrefix(6,0))); - t!("\\\\?\\UNC\\server\\", Some(VerbatimUNCPrefix(6,0))); - t!("\\\\?\\UNC/server/share", Some(VerbatimPrefix(16))); - t!("\\\\?\\UNC", Some(VerbatimPrefix(3))); - t!("\\\\?\\C:\\a\\b.txt", Some(VerbatimDiskPrefix)); - t!("\\\\?\\z:\\", Some(VerbatimDiskPrefix)); - t!("\\\\?\\C:", Some(VerbatimPrefix(2))); - t!("\\\\?\\C:a.txt", Some(VerbatimPrefix(7))); - t!("\\\\?\\C:a\\b.txt", Some(VerbatimPrefix(3))); - t!("\\\\?\\C:/a", Some(VerbatimPrefix(4))); - t!("C:\\foo", Some(DiskPrefix)); - t!("z:/foo", Some(DiskPrefix)); - t!("d:", Some(DiskPrefix)); - t!("ab:", None); - t!("ü:\\foo", None); - t!("3:\\foo", None); - t!(" :\\foo", None); - t!("::\\foo", None); - t!("\\\\?\\C:", Some(VerbatimPrefix(2))); - t!("\\\\?\\z:\\", Some(VerbatimDiskPrefix)); - t!("\\\\?\\ab:\\", Some(VerbatimPrefix(3))); - t!("\\\\?\\C:\\a", Some(VerbatimDiskPrefix)); - t!("\\\\?\\C:/a", Some(VerbatimPrefix(4))); - t!("\\\\?\\C:\\a/b", Some(VerbatimDiskPrefix)); - } - - #[test] - fn test_paths() { - let empty: &[u8] = &[]; - t!(v: Path::new(empty), b"."); - t!(v: Path::new(b"\\"), b"\\"); - t!(v: Path::new(b"a\\b\\c"), b"a\\b\\c"); - - t!(s: Path::new(""), "."); - t!(s: Path::new("\\"), "\\"); - t!(s: Path::new("hi"), "hi"); - t!(s: Path::new("hi\\"), "hi"); - t!(s: Path::new("\\lib"), "\\lib"); - t!(s: Path::new("\\lib\\"), "\\lib"); - t!(s: Path::new("hi\\there"), "hi\\there"); - t!(s: Path::new("hi\\there.txt"), "hi\\there.txt"); - t!(s: Path::new("/"), "\\"); - t!(s: Path::new("hi/"), "hi"); - t!(s: Path::new("/lib"), "\\lib"); - t!(s: Path::new("/lib/"), "\\lib"); - t!(s: Path::new("hi/there"), "hi\\there"); - - t!(s: Path::new("hi\\there\\"), "hi\\there"); - t!(s: Path::new("hi\\..\\there"), "there"); - t!(s: Path::new("hi/../there"), "there"); - t!(s: Path::new("..\\hi\\there"), "..\\hi\\there"); - t!(s: Path::new("\\..\\hi\\there"), "\\hi\\there"); - t!(s: Path::new("/../hi/there"), "\\hi\\there"); - t!(s: Path::new("foo\\.."), "."); - t!(s: Path::new("\\foo\\.."), "\\"); - t!(s: Path::new("\\foo\\..\\.."), "\\"); - t!(s: Path::new("\\foo\\..\\..\\bar"), "\\bar"); - t!(s: Path::new("\\.\\hi\\.\\there\\."), "\\hi\\there"); - t!(s: Path::new("\\.\\hi\\.\\there\\.\\.."), "\\hi"); - t!(s: Path::new("foo\\..\\.."), ".."); - t!(s: Path::new("foo\\..\\..\\.."), "..\\.."); - t!(s: Path::new("foo\\..\\..\\bar"), "..\\bar"); - - assert_eq!(Path::new(b"foo\\bar").into_vec(), b"foo\\bar"); - assert_eq!(Path::new(b"\\foo\\..\\..\\bar").into_vec(), b"\\bar"); - - t!(s: Path::new("\\\\a"), "\\a"); - t!(s: Path::new("\\\\a\\"), "\\a"); - t!(s: Path::new("\\\\a\\b"), "\\\\a\\b"); - t!(s: Path::new("\\\\a\\b\\"), "\\\\a\\b"); - t!(s: Path::new("\\\\a\\b/"), "\\\\a\\b"); - t!(s: Path::new("\\\\\\b"), "\\b"); - t!(s: Path::new("\\\\a\\\\b"), "\\a\\b"); - t!(s: Path::new("\\\\a\\b\\c"), "\\\\a\\b\\c"); - t!(s: Path::new("\\\\server\\share/path"), "\\\\server\\share\\path"); - t!(s: Path::new("\\\\server/share/path"), "\\\\server\\share\\path"); - t!(s: Path::new("C:a\\b.txt"), "C:a\\b.txt"); - t!(s: Path::new("C:a/b.txt"), "C:a\\b.txt"); - t!(s: Path::new("z:\\a\\b.txt"), "Z:\\a\\b.txt"); - t!(s: Path::new("z:/a/b.txt"), "Z:\\a\\b.txt"); - t!(s: Path::new("ab:/a/b.txt"), "ab:\\a\\b.txt"); - t!(s: Path::new("C:\\"), "C:\\"); - t!(s: Path::new("C:"), "C:"); - t!(s: Path::new("q:"), "Q:"); - t!(s: Path::new("C:/"), "C:\\"); - t!(s: Path::new("C:\\foo\\.."), "C:\\"); - t!(s: Path::new("C:foo\\.."), "C:"); - t!(s: Path::new("C:\\a\\"), "C:\\a"); - t!(s: Path::new("C:\\a/"), "C:\\a"); - t!(s: Path::new("C:\\a\\b\\"), "C:\\a\\b"); - t!(s: Path::new("C:\\a\\b/"), "C:\\a\\b"); - t!(s: Path::new("C:a\\"), "C:a"); - t!(s: Path::new("C:a/"), "C:a"); - t!(s: Path::new("C:a\\b\\"), "C:a\\b"); - t!(s: Path::new("C:a\\b/"), "C:a\\b"); - t!(s: Path::new("\\\\?\\z:\\a\\b.txt"), "\\\\?\\z:\\a\\b.txt"); - t!(s: Path::new("\\\\?\\C:/a/b.txt"), "\\\\?\\C:/a/b.txt"); - t!(s: Path::new("\\\\?\\C:\\a/b.txt"), "\\\\?\\C:\\a/b.txt"); - t!(s: Path::new("\\\\?\\test\\a\\b.txt"), "\\\\?\\test\\a\\b.txt"); - t!(s: Path::new("\\\\?\\foo\\bar\\"), "\\\\?\\foo\\bar\\"); - t!(s: Path::new("\\\\.\\foo\\bar"), "\\\\.\\foo\\bar"); - t!(s: Path::new("\\\\.\\"), "\\\\.\\"); - t!(s: Path::new("\\\\?\\UNC\\server\\share\\foo"), "\\\\?\\UNC\\server\\share\\foo"); - t!(s: Path::new("\\\\?\\UNC\\server/share"), "\\\\?\\UNC\\server/share\\"); - t!(s: Path::new("\\\\?\\UNC\\server"), "\\\\?\\UNC\\server\\"); - t!(s: Path::new("\\\\?\\UNC\\"), "\\\\?\\UNC\\\\"); - t!(s: Path::new("\\\\?\\UNC"), "\\\\?\\UNC"); - - // I'm not sure whether \\.\foo/bar should normalize to \\.\foo\bar - // as information is sparse and this isn't really googleable. - // I'm going to err on the side of not normalizing it, as this skips the filesystem - t!(s: Path::new("\\\\.\\foo/bar"), "\\\\.\\foo/bar"); - t!(s: Path::new("\\\\.\\foo\\bar"), "\\\\.\\foo\\bar"); - } - - #[test] - fn test_opt_paths() { - assert!(Path::new_opt(b"foo\\bar\0") == None); - assert!(Path::new_opt(b"foo\\bar\x80") == None); - t!(v: Path::new_opt(b"foo\\bar").unwrap(), b"foo\\bar"); - assert!(Path::new_opt("foo\\bar\0") == None); - t!(s: Path::new_opt("foo\\bar").unwrap(), "foo\\bar"); - } - - #[test] - fn test_null_byte() { - use thread::Thread; - let result = Thread::scoped(move|| { - Path::new(b"foo/bar\0") - }).join(); - assert!(result.is_err()); - - let result = Thread::scoped(move|| { - Path::new("test").set_filename(b"f\0o") - }).join(); - assert!(result.is_err()); - - let result = Thread::scoped(move || { - Path::new("test").push(b"f\0o"); - }).join(); - assert!(result.is_err()); - } - - #[test] - #[should_fail] - fn test_not_utf8_panics() { - Path::new(b"hello\x80.txt"); - } - - #[test] - fn test_display_str() { - let path = Path::new("foo"); - assert_eq!(path.display().to_string(), "foo"); - let path = Path::new(b"\\"); - assert_eq!(path.filename_display().to_string(), ""); - - let path = Path::new("foo"); - let mo = path.display().as_cow(); - assert_eq!(mo.as_slice(), "foo"); - let path = Path::new(b"\\"); - let mo = path.filename_display().as_cow(); - assert_eq!(mo.as_slice(), ""); - } - - #[test] - fn test_display() { - macro_rules! t { - ($path:expr, $exp:expr, $expf:expr) => ( - { - let path = Path::new($path); - let f = format!("{}", path.display()); - assert_eq!(f, $exp); - let f = format!("{}", path.filename_display()); - assert_eq!(f, $expf); - } - ) - } - - t!("foo", "foo", "foo"); - t!("foo\\bar", "foo\\bar", "bar"); - t!("\\", "\\", ""); - } - - #[test] - fn test_components() { - macro_rules! t { - (s: $path:expr, $op:ident, $exp:expr) => ( - { - let path = $path; - let path = Path::new(path); - assert_eq!(path.$op(), Some($exp)); - } - ); - (s: $path:expr, $op:ident, $exp:expr, opt) => ( - { - let path = $path; - let path = Path::new(path); - let left = path.$op(); - assert_eq!(left, $exp); - } - ); - (v: $path:expr, $op:ident, $exp:expr) => ( - { - let path = $path; - let path = Path::new(path); - assert_eq!(path.$op(), $exp); - } - ) - } - - t!(v: b"a\\b\\c", filename, Some(b"c")); - t!(s: "a\\b\\c", filename_str, "c"); - t!(s: "\\a\\b\\c", filename_str, "c"); - t!(s: "a", filename_str, "a"); - t!(s: "\\a", filename_str, "a"); - t!(s: ".", filename_str, None, opt); - t!(s: "\\", filename_str, None, opt); - t!(s: "..", filename_str, None, opt); - t!(s: "..\\..", filename_str, None, opt); - t!(s: "c:\\foo.txt", filename_str, "foo.txt"); - t!(s: "C:\\", filename_str, None, opt); - t!(s: "C:", filename_str, None, opt); - t!(s: "\\\\server\\share\\foo.txt", filename_str, "foo.txt"); - t!(s: "\\\\server\\share", filename_str, None, opt); - t!(s: "\\\\server", filename_str, "server"); - t!(s: "\\\\?\\bar\\foo.txt", filename_str, "foo.txt"); - t!(s: "\\\\?\\bar", filename_str, None, opt); - t!(s: "\\\\?\\", filename_str, None, opt); - t!(s: "\\\\?\\UNC\\server\\share\\foo.txt", filename_str, "foo.txt"); - t!(s: "\\\\?\\UNC\\server", filename_str, None, opt); - t!(s: "\\\\?\\UNC\\", filename_str, None, opt); - t!(s: "\\\\?\\C:\\foo.txt", filename_str, "foo.txt"); - t!(s: "\\\\?\\C:\\", filename_str, None, opt); - t!(s: "\\\\?\\C:", filename_str, None, opt); - t!(s: "\\\\?\\foo/bar", filename_str, None, opt); - t!(s: "\\\\?\\C:/foo", filename_str, None, opt); - t!(s: "\\\\.\\foo\\bar", filename_str, "bar"); - t!(s: "\\\\.\\foo", filename_str, None, opt); - t!(s: "\\\\.\\foo/bar", filename_str, None, opt); - t!(s: "\\\\.\\foo\\bar/baz", filename_str, "bar/baz"); - t!(s: "\\\\.\\", filename_str, None, opt); - t!(s: "\\\\?\\a\\b\\", filename_str, "b"); - - t!(v: b"a\\b\\c", dirname, b"a\\b"); - t!(s: "a\\b\\c", dirname_str, "a\\b"); - t!(s: "\\a\\b\\c", dirname_str, "\\a\\b"); - t!(s: "a", dirname_str, "."); - t!(s: "\\a", dirname_str, "\\"); - t!(s: ".", dirname_str, "."); - t!(s: "\\", dirname_str, "\\"); - t!(s: "..", dirname_str, ".."); - t!(s: "..\\..", dirname_str, "..\\.."); - t!(s: "c:\\foo.txt", dirname_str, "C:\\"); - t!(s: "C:\\", dirname_str, "C:\\"); - t!(s: "C:", dirname_str, "C:"); - t!(s: "C:foo.txt", dirname_str, "C:"); - t!(s: "\\\\server\\share\\foo.txt", dirname_str, "\\\\server\\share"); - t!(s: "\\\\server\\share", dirname_str, "\\\\server\\share"); - t!(s: "\\\\server", dirname_str, "\\"); - t!(s: "\\\\?\\bar\\foo.txt", dirname_str, "\\\\?\\bar"); - t!(s: "\\\\?\\bar", dirname_str, "\\\\?\\bar"); - t!(s: "\\\\?\\", dirname_str, "\\\\?\\"); - t!(s: "\\\\?\\UNC\\server\\share\\foo.txt", dirname_str, "\\\\?\\UNC\\server\\share"); - t!(s: "\\\\?\\UNC\\server", dirname_str, "\\\\?\\UNC\\server\\"); - t!(s: "\\\\?\\UNC\\", dirname_str, "\\\\?\\UNC\\\\"); - t!(s: "\\\\?\\C:\\foo.txt", dirname_str, "\\\\?\\C:\\"); - t!(s: "\\\\?\\C:\\", dirname_str, "\\\\?\\C:\\"); - t!(s: "\\\\?\\C:", dirname_str, "\\\\?\\C:"); - t!(s: "\\\\?\\C:/foo/bar", dirname_str, "\\\\?\\C:/foo/bar"); - t!(s: "\\\\?\\foo/bar", dirname_str, "\\\\?\\foo/bar"); - t!(s: "\\\\.\\foo\\bar", dirname_str, "\\\\.\\foo"); - t!(s: "\\\\.\\foo", dirname_str, "\\\\.\\foo"); - t!(s: "\\\\?\\a\\b\\", dirname_str, "\\\\?\\a"); - - t!(v: b"hi\\there.txt", filestem, Some(b"there")); - t!(s: "hi\\there.txt", filestem_str, "there"); - t!(s: "hi\\there", filestem_str, "there"); - t!(s: "there.txt", filestem_str, "there"); - t!(s: "there", filestem_str, "there"); - t!(s: ".", filestem_str, None, opt); - t!(s: "\\", filestem_str, None, opt); - t!(s: "foo\\.bar", filestem_str, ".bar"); - t!(s: ".bar", filestem_str, ".bar"); - t!(s: "..bar", filestem_str, "."); - t!(s: "hi\\there..txt", filestem_str, "there."); - t!(s: "..", filestem_str, None, opt); - t!(s: "..\\..", filestem_str, None, opt); - // filestem is based on filename, so we don't need the full set of prefix tests - - t!(v: b"hi\\there.txt", extension, Some(b"txt")); - t!(v: b"hi\\there", extension, None); - t!(s: "hi\\there.txt", extension_str, Some("txt"), opt); - t!(s: "hi\\there", extension_str, None, opt); - t!(s: "there.txt", extension_str, Some("txt"), opt); - t!(s: "there", extension_str, None, opt); - t!(s: ".", extension_str, None, opt); - t!(s: "\\", extension_str, None, opt); - t!(s: "foo\\.bar", extension_str, None, opt); - t!(s: ".bar", extension_str, None, opt); - t!(s: "..bar", extension_str, Some("bar"), opt); - t!(s: "hi\\there..txt", extension_str, Some("txt"), opt); - t!(s: "..", extension_str, None, opt); - t!(s: "..\\..", extension_str, None, opt); - // extension is based on filename, so we don't need the full set of prefix tests - } - - #[test] - fn test_push() { - macro_rules! t { - (s: $path:expr, $join:expr) => ( - { - let path = $path; - let join = $join; - let mut p1 = Path::new(path); - let p2 = p1.clone(); - p1.push(join); - assert_eq!(p1, p2.join(join)); - } - ) - } - - t!(s: "a\\b\\c", ".."); - t!(s: "\\a\\b\\c", "d"); - t!(s: "a\\b", "c\\d"); - t!(s: "a\\b", "\\c\\d"); - // this is just a sanity-check test. push and join share an implementation, - // so there's no need for the full set of prefix tests - - // we do want to check one odd case though to ensure the prefix is re-parsed - let mut p = Path::new("\\\\?\\C:"); - assert_eq!(prefix(&p), Some(VerbatimPrefix(2))); - p.push("foo"); - assert_eq!(prefix(&p), Some(VerbatimDiskPrefix)); - assert_eq!(p.as_str(), Some("\\\\?\\C:\\foo")); - - // and another with verbatim non-normalized paths - let mut p = Path::new("\\\\?\\C:\\a\\"); - p.push("foo"); - assert_eq!(p.as_str(), Some("\\\\?\\C:\\a\\foo")); - } - - #[test] - fn test_push_path() { - macro_rules! t { - (s: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = Path::new($path); - let push = Path::new($push); - p.push(&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"); - t!(s: "a\\b", "C:a.txt", "C:a.txt"); - t!(s: "a\\b", "..\\..\\..\\c", "..\\c"); - t!(s: "a\\b", "C:\\a.txt", "C:\\a.txt"); - t!(s: "C:\\a", "C:\\b.txt", "C:\\b.txt"); - t!(s: "C:\\a\\b\\c", "C:d", "C:\\a\\b\\c\\d"); - t!(s: "C:a\\b\\c", "C:d", "C:a\\b\\c\\d"); - t!(s: "C:a\\b", "..\\..\\..\\c", "C:..\\c"); - t!(s: "C:\\a\\b", "..\\..\\..\\c", "C:\\c"); - t!(s: "C:", r"a\b\c", r"C:a\b\c"); - t!(s: "C:", r"..\a", r"C:..\a"); - t!(s: "\\\\server\\share\\foo", "bar", "\\\\server\\share\\foo\\bar"); - t!(s: "\\\\server\\share\\foo", "..\\..\\bar", "\\\\server\\share\\bar"); - t!(s: "\\\\server\\share\\foo", "C:baz", "C:baz"); - t!(s: "\\\\?\\C:\\a\\b", "C:c\\d", "\\\\?\\C:\\a\\b\\c\\d"); - t!(s: "\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); - t!(s: "\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); - t!(s: "\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); - t!(s: "\\\\?\\C:\\a\\b", "..\\..\\..\\c", "\\\\?\\C:\\a\\b\\..\\..\\..\\c"); - t!(s: "\\\\?\\foo\\bar", "..\\..\\c", "\\\\?\\foo\\bar\\..\\..\\c"); - t!(s: "\\\\?\\", "foo", "\\\\?\\\\foo"); - t!(s: "\\\\?\\UNC\\server\\share\\foo", "bar", "\\\\?\\UNC\\server\\share\\foo\\bar"); - t!(s: "\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); - t!(s: "\\\\?\\UNC\\server\\share", "C:a", "C:a"); - t!(s: "\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\\\foo"); - t!(s: "C:\\a", "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share"); - t!(s: "\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); - t!(s: "\\\\.\\foo\\bar", "C:a", "C:a"); - // again, not sure about the following, but I'm assuming \\.\ should be verbatim - t!(s: "\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); - - t!(s: "\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one - } - - #[test] - fn test_push_many() { - macro_rules! t { - (s: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = Path::new($path); - p.push_many(&$push); - assert_eq!(p.as_str(), Some($exp)); - } - ); - (v: $path:expr, $push:expr, $exp:expr) => ( - { - let mut p = Path::new($path); - p.push_many(&$push); - assert_eq!(p.as_vec(), $exp); - } - ) - } - - t!(s: "a\\b\\c", ["d", "e"], "a\\b\\c\\d\\e"); - t!(s: "a\\b\\c", ["d", "\\e"], "\\e"); - t!(s: "a\\b\\c", ["d", "\\e", "f"], "\\e\\f"); - t!(s: "a\\b\\c", ["d".to_string(), "e".to_string()], "a\\b\\c\\d\\e"); - t!(v: b"a\\b\\c", [b"d", b"e"], b"a\\b\\c\\d\\e"); - t!(v: b"a\\b\\c", [b"d", b"\\e", b"f"], b"\\e\\f"); - t!(v: b"a\\b\\c", [b"d".to_vec(), b"e".to_vec()], - b"a\\b\\c\\d\\e"); - } - - #[test] - fn test_pop() { - macro_rules! t { - (s: $path:expr, $left:expr, $right:expr) => ( - { - let pstr = $path; - let mut p = Path::new(pstr); - let result = p.pop(); - let left = $left; - assert_eq!(p.as_str(), Some(left)); - assert_eq!(result, $right); - } - ); - (b: $path:expr, $left:expr, $right:expr) => ( - { - let mut p = Path::new($path); - let result = p.pop(); - assert_eq!(p.as_vec(), $left); - assert_eq!(result, $right); - } - ) - } - - t!(s: "a\\b\\c", "a\\b", true); - t!(s: "a", ".", true); - t!(s: ".", ".", false); - t!(s: "\\a", "\\", true); - t!(s: "\\", "\\", false); - t!(b: b"a\\b\\c", b"a\\b", true); - t!(b: b"a", b".", true); - t!(b: b".", b".", false); - t!(b: b"\\a", b"\\", true); - t!(b: b"\\", b"\\", false); - - t!(s: "C:\\a\\b", "C:\\a", true); - t!(s: "C:\\a", "C:\\", true); - t!(s: "C:\\", "C:\\", false); - t!(s: "C:a\\b", "C:a", true); - t!(s: "C:a", "C:", true); - t!(s: "C:", "C:", false); - t!(s: "\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); - t!(s: "\\\\server\\share\\a", "\\\\server\\share", true); - t!(s: "\\\\server\\share", "\\\\server\\share", false); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); - t!(s: "\\\\?\\a\\b", "\\\\?\\a", true); - t!(s: "\\\\?\\a", "\\\\?\\a", false); - t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); - t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\", true); - t!(s: "\\\\?\\C:\\", "\\\\?\\C:\\", false); - t!(s: "\\\\?\\UNC\\server\\share\\a\\b", "\\\\?\\UNC\\server\\share\\a", true); - t!(s: "\\\\?\\UNC\\server\\share\\a", "\\\\?\\UNC\\server\\share", true); - t!(s: "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share", false); - t!(s: "\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); - t!(s: "\\\\.\\a\\b", "\\\\.\\a", true); - t!(s: "\\\\.\\a", "\\\\.\\a", false); - - t!(s: "\\\\?\\a\\b\\", "\\\\?\\a", true); - } - - #[test] - fn test_root_path() { - assert_eq!(Path::new("a\\b\\c").root_path(), None); - assert_eq!(Path::new("\\a\\b\\c").root_path(), Some(Path::new("\\"))); - assert_eq!(Path::new("C:a").root_path(), Some(Path::new("C:"))); - assert_eq!(Path::new("C:\\a").root_path(), Some(Path::new("C:\\"))); - assert_eq!(Path::new("\\\\a\\b\\c").root_path(), Some(Path::new("\\\\a\\b"))); - assert_eq!(Path::new("\\\\?\\a\\b").root_path(), Some(Path::new("\\\\?\\a"))); - assert_eq!(Path::new("\\\\?\\C:\\a").root_path(), Some(Path::new("\\\\?\\C:\\"))); - assert_eq!(Path::new("\\\\?\\UNC\\a\\b\\c").root_path(), - Some(Path::new("\\\\?\\UNC\\a\\b"))); - assert_eq!(Path::new("\\\\.\\a\\b").root_path(), Some(Path::new("\\\\.\\a"))); - } - - #[test] - fn test_join() { - t!(s: Path::new("a\\b\\c").join(".."), "a\\b"); - t!(s: Path::new("\\a\\b\\c").join("d"), "\\a\\b\\c\\d"); - t!(s: Path::new("a\\b").join("c\\d"), "a\\b\\c\\d"); - t!(s: Path::new("a\\b").join("\\c\\d"), "\\c\\d"); - t!(s: Path::new(".").join("a\\b"), "a\\b"); - t!(s: Path::new("\\").join("a\\b"), "\\a\\b"); - 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"); - // full join testing is covered under test_push_path, so no need for - // the full set of prefix tests - } - - #[test] - fn test_join_path() { - macro_rules! t { - (s: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let join = Path::new($join); - let res = path.join(&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"); - // join is implemented using push, so there's no need for - // the full set of prefix tests - } - - #[test] - fn test_join_many() { - macro_rules! t { - (s: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let res = path.join_many(&$join); - assert_eq!(res.as_str(), Some($exp)); - } - ); - (v: $path:expr, $join:expr, $exp:expr) => ( - { - let path = Path::new($path); - let res = path.join_many(&$join); - assert_eq!(res.as_vec(), $exp); - } - ) - } - - t!(s: "a\\b\\c", ["d", "e"], "a\\b\\c\\d\\e"); - t!(s: "a\\b\\c", ["..", "d"], "a\\b\\d"); - t!(s: "a\\b\\c", ["d", "\\e", "f"], "\\e\\f"); - t!(s: "a\\b\\c", ["d".to_string(), "e".to_string()], "a\\b\\c\\d\\e"); - t!(v: b"a\\b\\c", [b"d", b"e"], b"a\\b\\c\\d\\e"); - t!(v: b"a\\b\\c", [b"d".to_vec(), b"e".to_vec()], - b"a\\b\\c\\d\\e"); - } - - #[test] - fn test_with_helpers() { - macro_rules! t { - (s: $path:expr, $op:ident, $arg:expr, $res:expr) => ( - { - let pstr = $path; - let path = Path::new(pstr); - let arg = $arg; - let res = path.$op(arg); - let exp = Path::new($res); - assert_eq!(res, exp); - } - ) - } - - t!(s: "a\\b\\c", with_filename, "d", "a\\b\\d"); - t!(s: ".", with_filename, "foo", "foo"); - t!(s: "\\a\\b\\c", with_filename, "d", "\\a\\b\\d"); - t!(s: "\\", with_filename, "foo", "\\foo"); - t!(s: "\\a", with_filename, "foo", "\\foo"); - t!(s: "foo", with_filename, "bar", "bar"); - t!(s: "\\", with_filename, "foo\\", "\\foo"); - t!(s: "\\a", with_filename, "foo\\", "\\foo"); - t!(s: "a\\b\\c", with_filename, "", "a\\b"); - t!(s: "a\\b\\c", with_filename, ".", "a\\b"); - t!(s: "a\\b\\c", with_filename, "..", "a"); - t!(s: "\\a", with_filename, "", "\\"); - t!(s: "foo", with_filename, "", "."); - t!(s: "a\\b\\c", with_filename, "d\\e", "a\\b\\d\\e"); - t!(s: "a\\b\\c", with_filename, "\\d", "a\\b\\d"); - t!(s: "..", with_filename, "foo", "..\\foo"); - t!(s: "..\\..", with_filename, "foo", "..\\..\\foo"); - t!(s: "..", with_filename, "", ".."); - t!(s: "..\\..", with_filename, "", "..\\.."); - t!(s: "C:\\foo\\bar", with_filename, "baz", "C:\\foo\\baz"); - t!(s: "C:\\foo", with_filename, "bar", "C:\\bar"); - t!(s: "C:\\", with_filename, "foo", "C:\\foo"); - t!(s: "C:foo\\bar", with_filename, "baz", "C:foo\\baz"); - t!(s: "C:foo", with_filename, "bar", "C:bar"); - t!(s: "C:", with_filename, "foo", "C:foo"); - t!(s: "C:\\foo", with_filename, "", "C:\\"); - t!(s: "C:foo", with_filename, "", "C:"); - t!(s: "C:\\foo\\bar", with_filename, "..", "C:\\"); - t!(s: "C:\\foo", with_filename, "..", "C:\\"); - t!(s: "C:\\", with_filename, "..", "C:\\"); - t!(s: "C:foo\\bar", with_filename, "..", "C:"); - t!(s: "C:foo", with_filename, "..", "C:.."); - t!(s: "C:", with_filename, "..", "C:.."); - t!(s: "\\\\server\\share\\foo", with_filename, "bar", "\\\\server\\share\\bar"); - t!(s: "\\\\server\\share", with_filename, "foo", "\\\\server\\share\\foo"); - t!(s: "\\\\server\\share\\foo", with_filename, "", "\\\\server\\share"); - t!(s: "\\\\server\\share", with_filename, "", "\\\\server\\share"); - t!(s: "\\\\server\\share\\foo", with_filename, "..", "\\\\server\\share"); - t!(s: "\\\\server\\share", with_filename, "..", "\\\\server\\share"); - t!(s: "\\\\?\\C:\\foo\\bar", with_filename, "baz", "\\\\?\\C:\\foo\\baz"); - t!(s: "\\\\?\\C:\\foo", with_filename, "bar", "\\\\?\\C:\\bar"); - t!(s: "\\\\?\\C:\\", with_filename, "foo", "\\\\?\\C:\\foo"); - t!(s: "\\\\?\\C:\\foo", with_filename, "..", "\\\\?\\C:\\.."); - t!(s: "\\\\?\\foo\\bar", with_filename, "baz", "\\\\?\\foo\\baz"); - t!(s: "\\\\?\\foo", with_filename, "bar", "\\\\?\\foo\\bar"); - t!(s: "\\\\?\\", with_filename, "foo", "\\\\?\\\\foo"); - t!(s: "\\\\?\\foo\\bar", with_filename, "..", "\\\\?\\foo\\.."); - t!(s: "\\\\.\\foo\\bar", with_filename, "baz", "\\\\.\\foo\\baz"); - t!(s: "\\\\.\\foo", with_filename, "bar", "\\\\.\\foo\\bar"); - t!(s: "\\\\.\\foo\\bar", with_filename, "..", "\\\\.\\foo\\.."); - - t!(s: "hi\\there.txt", with_extension, "exe", "hi\\there.exe"); - t!(s: "hi\\there.txt", with_extension, "", "hi\\there"); - t!(s: "hi\\there.txt", with_extension, ".", "hi\\there.."); - t!(s: "hi\\there.txt", with_extension, "..", "hi\\there..."); - t!(s: "hi\\there", with_extension, "txt", "hi\\there.txt"); - t!(s: "hi\\there", with_extension, ".", "hi\\there.."); - t!(s: "hi\\there", with_extension, "..", "hi\\there..."); - t!(s: "hi\\there.", with_extension, "txt", "hi\\there.txt"); - t!(s: "hi\\.foo", with_extension, "txt", "hi\\.foo.txt"); - t!(s: "hi\\there.txt", with_extension, ".foo", "hi\\there..foo"); - t!(s: "\\", with_extension, "txt", "\\"); - t!(s: "\\", with_extension, ".", "\\"); - t!(s: "\\", with_extension, "..", "\\"); - t!(s: ".", with_extension, "txt", "."); - // extension setter calls filename setter internally, no need for extended tests - } - - #[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::new(path); - p1.$set(arg); - let p2 = Path::new(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_filename, with_filename, b"d"); - t!(v: b"\\", set_filename, with_filename, b"foo"); - t!(s: "a\\b\\c", set_filename, with_filename, "d"); - t!(s: "\\", set_filename, with_filename, "foo"); - t!(s: ".", set_filename, with_filename, "foo"); - t!(s: "a\\b", set_filename, with_filename, ""); - t!(s: "a", set_filename, with_filename, ""); - - t!(v: b"hi\\there.txt", set_extension, with_extension, b"exe"); - t!(s: "hi\\there.txt", set_extension, with_extension, "exe"); - t!(s: "hi\\there.", set_extension, with_extension, "txt"); - t!(s: "hi\\there", set_extension, with_extension, "txt"); - t!(s: "hi\\there.txt", set_extension, with_extension, ""); - t!(s: "hi\\there", set_extension, with_extension, ""); - t!(s: ".", set_extension, with_extension, "txt"); - - // with_ helpers use the setter internally, so the tests for the with_ helpers - // will suffice. No need for the full set of prefix tests. - } - - #[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"), Some(b"c"), b"a\\b", Some(b"c"), None); - t!(s: Path::new("a\\b\\c"), Some("c"), Some("a\\b"), Some("c"), None); - t!(s: Path::new("."), None, Some("."), None, None); - t!(s: Path::new("\\"), None, Some("\\"), None, None); - t!(s: Path::new(".."), None, Some(".."), None, None); - t!(s: Path::new("..\\.."), None, Some("..\\.."), None, None); - t!(s: Path::new("hi\\there.txt"), Some("there.txt"), Some("hi"), - Some("there"), Some("txt")); - t!(s: Path::new("hi\\there"), Some("there"), Some("hi"), Some("there"), None); - t!(s: Path::new("hi\\there."), Some("there."), Some("hi"), - Some("there"), Some("")); - t!(s: Path::new("hi\\.there"), Some(".there"), Some("hi"), Some(".there"), None); - t!(s: Path::new("hi\\..there"), Some("..there"), Some("hi"), - Some("."), Some("there")); - - // these are already tested in test_components, so no need for extended tests - } - - #[test] - fn test_dir_path() { - t!(s: Path::new("hi\\there").dir_path(), "hi"); - t!(s: Path::new("hi").dir_path(), "."); - t!(s: Path::new("\\hi").dir_path(), "\\"); - t!(s: Path::new("\\").dir_path(), "\\"); - t!(s: Path::new("..").dir_path(), ".."); - t!(s: Path::new("..\\..").dir_path(), "..\\.."); - - // dir_path is just dirname interpreted as a path. - // No need for extended tests - } - - #[test] - fn test_is_absolute() { - macro_rules! t { - ($path:expr, $abs:expr, $vol:expr, $cwd:expr, $rel:expr) => ( - { - let path = Path::new($path); - let (abs, vol, cwd, rel) = ($abs, $vol, $cwd, $rel); - assert_eq!(path.is_absolute(), abs); - assert_eq!(is_vol_relative(&path), vol); - assert_eq!(is_cwd_relative(&path), cwd); - assert_eq!(path.is_relative(), rel); - } - ) - } - t!("a\\b\\c", false, false, false, true); - t!("\\a\\b\\c", false, true, false, false); - t!("a", false, false, false, true); - t!("\\a", false, true, false, false); - t!(".", false, false, false, true); - t!("\\", false, true, false, false); - t!("..", false, false, false, true); - t!("..\\..", false, false, false, true); - t!("C:a\\b.txt", false, false, true, false); - t!("C:\\a\\b.txt", true, false, false, false); - t!("\\\\server\\share\\a\\b.txt", true, false, false, false); - t!("\\\\?\\a\\b\\c.txt", true, false, false, false); - t!("\\\\?\\C:\\a\\b.txt", true, false, false, false); - t!("\\\\?\\C:a\\b.txt", true, false, false, false); // NB: not equivalent to C:a\b.txt - t!("\\\\?\\UNC\\server\\share\\a\\b.txt", true, false, false, false); - t!("\\\\.\\a\\b", true, false, false, false); - } - - #[test] - fn test_is_ancestor_of() { - macro_rules! t { - (s: $path:expr, $dest:expr, $exp:expr) => ( - { - let path = Path::new($path); - let dest = Path::new($dest); - let exp = $exp; - let res = path.is_ancestor_of(&dest); - assert_eq!(res, 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); - t!(s: "foo\\bar", "foobar", false); - t!(s: "foobar", "foo\\bar", false); - - t!(s: "foo", "C:foo", false); - t!(s: "C:foo", "foo", false); - t!(s: "C:foo", "C:foo\\bar", true); - t!(s: "C:foo\\bar", "C:foo", false); - t!(s: "C:\\foo", "C:\\foo\\bar", true); - t!(s: "C:", "C:", true); - t!(s: "C:", "C:\\", false); - t!(s: "C:\\", "C:", false); - t!(s: "C:\\", "C:\\", true); - t!(s: "C:\\foo\\bar", "C:\\foo", false); - t!(s: "C:foo\\bar", "C:foo", false); - t!(s: "C:\\foo", "\\foo", false); - t!(s: "\\foo", "C:\\foo", false); - t!(s: "\\\\server\\share\\foo", "\\\\server\\share\\foo\\bar", true); - t!(s: "\\\\server\\share", "\\\\server\\share\\foo", true); - t!(s: "\\\\server\\share\\foo", "\\\\server\\share", false); - t!(s: "C:\\foo", "\\\\server\\share\\foo", false); - t!(s: "\\\\server\\share\\foo", "C:\\foo", false); - t!(s: "\\\\?\\foo\\bar", "\\\\?\\foo\\bar\\baz", true); - t!(s: "\\\\?\\foo\\bar\\baz", "\\\\?\\foo\\bar", false); - t!(s: "\\\\?\\foo\\bar", "\\foo\\bar\\baz", false); - t!(s: "\\foo\\bar", "\\\\?\\foo\\bar\\baz", false); - t!(s: "\\\\?\\C:\\foo\\bar", "\\\\?\\C:\\foo\\bar\\baz", true); - t!(s: "\\\\?\\C:\\foo\\bar\\baz", "\\\\?\\C:\\foo\\bar", false); - t!(s: "\\\\?\\C:\\", "\\\\?\\C:\\foo", true); - t!(s: "\\\\?\\C:", "\\\\?\\C:\\", false); // this is a weird one - t!(s: "\\\\?\\C:\\", "\\\\?\\C:", false); - t!(s: "\\\\?\\C:\\a", "\\\\?\\c:\\a\\b", true); - t!(s: "\\\\?\\c:\\a", "\\\\?\\C:\\a\\b", true); - t!(s: "\\\\?\\C:\\a", "\\\\?\\D:\\a\\b", false); - t!(s: "\\\\?\\foo", "\\\\?\\foobar", false); - t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\c", true); - t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\", true); - t!(s: "\\\\?\\a\\b\\", "\\\\?\\a\\b", true); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", false); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b\\", false); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\b\\c\\d", true); - t!(s: "\\\\?\\UNC\\a\\b\\c\\d", "\\\\?\\UNC\\a\\b\\c", false); - t!(s: "\\\\?\\UNC\\a\\b", "\\\\?\\UNC\\a\\b\\c", true); - t!(s: "\\\\.\\foo\\bar", "\\\\.\\foo\\bar\\baz", true); - t!(s: "\\\\.\\foo\\bar\\baz", "\\\\.\\foo\\bar", false); - t!(s: "\\\\.\\foo", "\\\\.\\foo\\bar", true); - t!(s: "\\\\.\\foo", "\\\\.\\foobar", false); - - t!(s: "\\a\\b", "\\\\?\\a\\b", false); - t!(s: "\\\\?\\a\\b", "\\a\\b", false); - t!(s: "\\a\\b", "\\\\?\\C:\\a\\b", false); - t!(s: "\\\\?\\C:\\a\\b", "\\a\\b", false); - t!(s: "Z:\\a\\b", "\\\\?\\z:\\a\\b", true); - t!(s: "C:\\a\\b", "\\\\?\\D:\\a\\b", false); - t!(s: "a\\b", "\\\\?\\a\\b", false); - t!(s: "\\\\?\\a\\b", "a\\b", false); - t!(s: "C:\\a\\b", "\\\\?\\C:\\a\\b", true); - t!(s: "\\\\?\\C:\\a\\b", "C:\\a\\b", true); - t!(s: "C:a\\b", "\\\\?\\C:\\a\\b", false); - t!(s: "C:a\\b", "\\\\?\\C:a\\b", false); - t!(s: "\\\\?\\C:\\a\\b", "C:a\\b", false); - t!(s: "\\\\?\\C:a\\b", "C:a\\b", false); - t!(s: "C:\\a\\b", "\\\\?\\C:\\a\\b\\", true); - t!(s: "\\\\?\\C:\\a\\b\\", "C:\\a\\b", true); - t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\b\\c", true); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\b\\c", true); - } - - #[test] - fn test_ends_with_path() { - macro_rules! t { - (s: $path:expr, $child:expr, $exp:expr) => ( - { - let path = Path::new($path); - let child = Path::new($child); - assert_eq!(path.ends_with_path(&child), $exp); - } - ); - } - - t!(s: "a\\b\\c", "c", true); - t!(s: "a\\b\\c", "d", false); - t!(s: "foo\\bar\\quux", "bar", false); - t!(s: "foo\\bar\\quux", "barquux", false); - t!(s: "a\\b\\c", "b\\c", true); - t!(s: "a\\b\\c", "a\\b\\c", true); - t!(s: "a\\b\\c", "foo\\a\\b\\c", false); - t!(s: "\\a\\b\\c", "a\\b\\c", true); - t!(s: "\\a\\b\\c", "\\a\\b\\c", false); // child must be relative - t!(s: "\\a\\b\\c", "foo\\a\\b\\c", false); - t!(s: "a\\b\\c", "", false); - t!(s: "", "", true); - t!(s: "\\a\\b\\c", "d\\e\\f", false); - t!(s: "a\\b\\c", "a\\b", false); - t!(s: "a\\b\\c", "b", false); - t!(s: "C:\\a\\b", "b", true); - t!(s: "C:\\a\\b", "C:b", false); - t!(s: "C:\\a\\b", "C:a\\b", false); - } - - #[test] - fn test_path_relative_from() { - macro_rules! t { - (s: $path:expr, $other:expr, $exp:expr) => ( - { - assert_eq!(Path::new($path).path_relative_from(&Path::new($other)) - .as_ref().and_then(|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")); - - t!(s: "C:a\\b\\c", "C:a\\b", Some("c")); - t!(s: "C:a\\b", "C:a\\b\\c", Some("..")); - t!(s: "C:" ,"C:a\\b", Some("..\\..")); - t!(s: "C:a\\b", "C:c\\d", Some("..\\..\\a\\b")); - t!(s: "C:a\\b", "D:c\\d", Some("C:a\\b")); - t!(s: "C:a\\b", "C:..\\c", None); - t!(s: "C:..\\a", "C:b\\c", Some("..\\..\\..\\a")); - t!(s: "C:\\a\\b\\c", "C:\\a\\b", Some("c")); - t!(s: "C:\\a\\b", "C:\\a\\b\\c", Some("..")); - t!(s: "C:\\", "C:\\a\\b", Some("..\\..")); - t!(s: "C:\\a\\b", "C:\\c\\d", Some("..\\..\\a\\b")); - t!(s: "C:\\a\\b", "C:a\\b", Some("C:\\a\\b")); - t!(s: "C:a\\b", "C:\\a\\b", None); - t!(s: "\\a\\b", "C:\\a\\b", None); - t!(s: "\\a\\b", "C:a\\b", None); - t!(s: "a\\b", "C:\\a\\b", None); - t!(s: "a\\b", "C:a\\b", None); - - t!(s: "\\\\a\\b\\c", "\\\\a\\b", Some("c")); - t!(s: "\\\\a\\b", "\\\\a\\b\\c", Some("..")); - t!(s: "\\\\a\\b\\c\\e", "\\\\a\\b\\c\\d", Some("..\\e")); - t!(s: "\\\\a\\c\\d", "\\\\a\\b\\d", Some("\\\\a\\c\\d")); - t!(s: "\\\\b\\c\\d", "\\\\a\\c\\d", Some("\\\\b\\c\\d")); - t!(s: "\\\\a\\b\\c", "\\d\\e", Some("\\\\a\\b\\c")); - t!(s: "\\d\\e", "\\\\a\\b\\c", None); - t!(s: "d\\e", "\\\\a\\b\\c", None); - t!(s: "C:\\a\\b\\c", "\\\\a\\b\\c", Some("C:\\a\\b\\c")); - t!(s: "C:\\c", "\\\\a\\b\\c", Some("C:\\c")); - - t!(s: "\\\\?\\a\\b", "\\a\\b", Some("\\\\?\\a\\b")); - t!(s: "\\\\?\\a\\b", "a\\b", Some("\\\\?\\a\\b")); - t!(s: "\\\\?\\a\\b", "\\b", Some("\\\\?\\a\\b")); - t!(s: "\\\\?\\a\\b", "b", Some("\\\\?\\a\\b")); - t!(s: "\\\\?\\a\\b", "\\\\?\\a\\b\\c", Some("..")); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\b", Some("c")); - t!(s: "\\\\?\\a\\b", "\\\\?\\c\\d", Some("\\\\?\\a\\b")); - t!(s: "\\\\?\\a", "\\\\?\\b", Some("\\\\?\\a")); - - t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", Some("b")); - t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\a\\b", Some("..")); - t!(s: "\\\\?\\C:\\a", "\\\\?\\C:\\b", Some("..\\a")); - t!(s: "\\\\?\\C:\\a", "\\\\?\\D:\\a", Some("\\\\?\\C:\\a")); - t!(s: "\\\\?\\C:\\a\\b", "\\\\?\\c:\\a", Some("b")); - t!(s: "\\\\?\\C:\\a\\b", "C:\\a", Some("b")); - t!(s: "\\\\?\\C:\\a", "C:\\a\\b", Some("..")); - t!(s: "C:\\a\\b", "\\\\?\\C:\\a", Some("b")); - t!(s: "C:\\a", "\\\\?\\C:\\a\\b", Some("..")); - t!(s: "\\\\?\\C:\\a", "D:\\a", Some("\\\\?\\C:\\a")); - t!(s: "\\\\?\\c:\\a\\b", "C:\\a", Some("b")); - t!(s: "\\\\?\\C:\\a\\b", "C:a\\b", Some("\\\\?\\C:\\a\\b")); - t!(s: "\\\\?\\C:\\a\\.\\b", "C:\\a", Some("\\\\?\\C:\\a\\.\\b")); - t!(s: "\\\\?\\C:\\a\\b/c", "C:\\a", Some("\\\\?\\C:\\a\\b/c")); - t!(s: "\\\\?\\C:\\a\\..\\b", "C:\\a", Some("\\\\?\\C:\\a\\..\\b")); - t!(s: "C:a\\b", "\\\\?\\C:\\a\\b", None); - t!(s: "\\\\?\\C:\\a\\.\\b", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\.\\b")); - t!(s: "\\\\?\\C:\\a\\b/c", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\b/c")); - t!(s: "\\\\?\\C:\\a\\..\\b", "\\\\?\\C:\\a", Some("\\\\?\\C:\\a\\..\\b")); - t!(s: "\\\\?\\C:\\a\\b\\", "\\\\?\\C:\\a", Some("b")); - t!(s: "\\\\?\\C:\\.\\b", "\\\\?\\C:\\.", Some("b")); - t!(s: "C:\\b", "\\\\?\\C:\\.", Some("..\\b")); - t!(s: "\\\\?\\a\\.\\b\\c", "\\\\?\\a\\.\\b", Some("c")); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\.\\d", Some("..\\..\\b\\c")); - t!(s: "\\\\?\\a\\..\\b", "\\\\?\\a\\..", Some("b")); - t!(s: "\\\\?\\a\\b\\..", "\\\\?\\a\\b", Some("\\\\?\\a\\b\\..")); - t!(s: "\\\\?\\a\\b\\c", "\\\\?\\a\\..\\b", Some("..\\..\\b\\c")); - - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\b", Some("c")); - t!(s: "\\\\?\\UNC\\a\\b", "\\\\?\\UNC\\a\\b\\c", Some("..")); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\UNC\\a\\c\\d", Some("\\\\?\\UNC\\a\\b\\c")); - t!(s: "\\\\?\\UNC\\b\\c\\d", "\\\\?\\UNC\\a\\c\\d", Some("\\\\?\\UNC\\b\\c\\d")); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\a\\b\\c", Some("\\\\?\\UNC\\a\\b\\c")); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\?\\C:\\a\\b\\c", Some("\\\\?\\UNC\\a\\b\\c")); - t!(s: "\\\\?\\UNC\\a\\b\\c/d", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\c/d")); - t!(s: "\\\\?\\UNC\\a\\b\\.", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\.")); - t!(s: "\\\\?\\UNC\\a\\b\\..", "\\\\?\\UNC\\a\\b", Some("\\\\?\\UNC\\a\\b\\..")); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\b", Some("c")); - t!(s: "\\\\?\\UNC\\a\\b", "\\\\a\\b\\c", Some("..")); - t!(s: "\\\\?\\UNC\\a\\b\\c", "\\\\a\\c\\d", Some("\\\\?\\UNC\\a\\b\\c")); - t!(s: "\\\\?\\UNC\\b\\c\\d", "\\\\a\\c\\d", Some("\\\\?\\UNC\\b\\c\\d")); - t!(s: "\\\\?\\UNC\\a\\b\\.", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\.")); - t!(s: "\\\\?\\UNC\\a\\b\\c/d", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\c/d")); - t!(s: "\\\\?\\UNC\\a\\b\\..", "\\\\a\\b", Some("\\\\?\\UNC\\a\\b\\..")); - t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\b", Some("c")); - t!(s: "\\\\a\\b\\c", "\\\\?\\UNC\\a\\c\\d", Some("\\\\a\\b\\c")); - } - - #[test] - fn test_str_components() { - macro_rules! t { - (s: $path:expr, $exp:expr) => ( - { - let path = Path::new($path); - let comps = path.str_components().map(|x|x.unwrap()) - .collect::>(); - let exp: &[&str] = &$exp; - assert_eq!(comps, exp); - let comps = path.str_components().rev().map(|x|x.unwrap()) - .collect::>(); - let exp = exp.iter().rev().map(|&x|x).collect::>(); - assert_eq!(comps, exp); - } - ); - } - - t!(s: b"a\\b\\c", ["a", "b", "c"]); - 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"]); - t!(s: "C:foo\\bar", ["foo", "bar"]); - t!(s: "C:foo", ["foo"]); - t!(s: "C:", []); - t!(s: "C:\\foo\\bar", ["foo", "bar"]); - t!(s: "C:\\foo", ["foo"]); - t!(s: "C:\\", []); - t!(s: "\\\\server\\share\\foo\\bar", ["foo", "bar"]); - t!(s: "\\\\server\\share\\foo", ["foo"]); - t!(s: "\\\\server\\share", []); - t!(s: "\\\\?\\foo\\bar\\baz", ["bar", "baz"]); - t!(s: "\\\\?\\foo\\bar", ["bar"]); - t!(s: "\\\\?\\foo", []); - t!(s: "\\\\?\\", []); - t!(s: "\\\\?\\a\\b", ["b"]); - t!(s: "\\\\?\\a\\b\\", ["b"]); - t!(s: "\\\\?\\foo\\bar\\\\baz", ["bar", "", "baz"]); - t!(s: "\\\\?\\C:\\foo\\bar", ["foo", "bar"]); - t!(s: "\\\\?\\C:\\foo", ["foo"]); - t!(s: "\\\\?\\C:\\", []); - t!(s: "\\\\?\\C:\\foo\\", ["foo"]); - t!(s: "\\\\?\\UNC\\server\\share\\foo\\bar", ["foo", "bar"]); - t!(s: "\\\\?\\UNC\\server\\share\\foo", ["foo"]); - t!(s: "\\\\?\\UNC\\server\\share", []); - t!(s: "\\\\.\\foo\\bar\\baz", ["bar", "baz"]); - t!(s: "\\\\.\\foo\\bar", ["bar"]); - t!(s: "\\\\.\\foo", []); - } - - #[test] - fn test_components_iter() { - macro_rules! t { - (s: $path:expr, $exp:expr) => ( - { - let path = Path::new($path); - let comps = path.components().collect::>(); - let exp: &[&[u8]] = &$exp; - assert_eq!(comps, exp); - let comps = path.components().rev().collect::>(); - let exp = exp.iter().rev().map(|&x|x).collect::>(); - assert_eq!(comps, exp); - } - ) - } - - t!(s: "a\\b\\c", [b"a", b"b", b"c"]); - t!(s: ".", [b"."]); - // since this is really a wrapper around str_components, those tests suffice - } - - #[test] - fn test_make_non_verbatim() { - macro_rules! t { - ($path:expr, $exp:expr) => ( - { - let path = Path::new($path); - let exp: Option<&str> = $exp; - let exp = exp.map(|s| Path::new(s)); - assert_eq!(make_non_verbatim(&path), exp); - } - ) - } - - t!(r"\a\b\c", Some(r"\a\b\c")); - t!(r"a\b\c", Some(r"a\b\c")); - t!(r"C:\a\b\c", Some(r"C:\a\b\c")); - t!(r"C:a\b\c", Some(r"C:a\b\c")); - t!(r"\\server\share\foo", Some(r"\\server\share\foo")); - t!(r"\\.\foo", None); - t!(r"\\?\foo", None); - t!(r"\\?\C:", None); - t!(r"\\?\C:foo", None); - t!(r"\\?\C:\", Some(r"C:\")); - t!(r"\\?\C:\foo", Some(r"C:\foo")); - t!(r"\\?\C:\foo\bar\baz", Some(r"C:\foo\bar\baz")); - t!(r"\\?\C:\foo\.\bar\baz", None); - t!(r"\\?\C:\foo\bar\..\baz", None); - t!(r"\\?\C:\foo\bar\..", None); - t!(r"\\?\UNC\server\share\foo", Some(r"\\server\share\foo")); - t!(r"\\?\UNC\server\share", Some(r"\\server\share")); - t!(r"\\?\UNC\server", None); - t!(r"\\?\UNC\server\", None); - } -} diff --git a/src/libstd/prelude/v1.rs b/src/libstd/prelude/v1.rs index 2398485afef..d2dc3345120 100644 --- a/src/libstd/prelude/v1.rs +++ b/src/libstd/prelude/v1.rs @@ -56,7 +56,7 @@ #[doc(no_inline)] pub use vec::Vec; // NB: remove when path reform lands -#[doc(no_inline)] pub use path::{Path, GenericPath}; +#[doc(no_inline)] pub use old_path::{Path, GenericPath}; // NB: remove when I/O reform lands #[doc(no_inline)] pub use old_io::{Buffer, Writer, Reader, Seek, BufferPrelude}; // NB: remove when range syntax lands diff --git a/src/libstd/rand/os.rs b/src/libstd/rand/os.rs index 4b45d5501c2..797b9332f17 100644 --- a/src/libstd/rand/os.rs +++ b/src/libstd/rand/os.rs @@ -20,7 +20,7 @@ mod imp { use self::OsRngInner::*; use old_io::{IoResult, File}; - use path::Path; + use old_path::Path; use rand::Rng; use rand::reader::ReaderRng; use result::Result::Ok; diff --git a/src/libstd/sys/common/mod.rs b/src/libstd/sys/common/mod.rs index ae01586c703..6f6b4c58717 100644 --- a/src/libstd/sys/common/mod.rs +++ b/src/libstd/sys/common/mod.rs @@ -16,7 +16,7 @@ use prelude::v1::*; use sys::{last_error, retry}; use ffi::CString; use num::Int; -use path::BytesContainer; +use old_path::BytesContainer; use collections; pub mod backtrace; diff --git a/src/libstd/sys/unix/process.rs b/src/libstd/sys/unix/process.rs index 7e117b10a34..20f86227e8e 100644 --- a/src/libstd/sys/unix/process.rs +++ b/src/libstd/sys/unix/process.rs @@ -20,7 +20,7 @@ use old_io::{self, IoResult, IoError, EndOfFile}; use libc::{self, pid_t, c_void, c_int}; use mem; use os; -use path::BytesContainer; +use old_path::BytesContainer; use ptr; use sync::mpsc::{channel, Sender, Receiver}; use sys::fs::FileDesc; diff --git a/src/libstd/sys/windows/backtrace.rs b/src/libstd/sys/windows/backtrace.rs index 66712b9e3a1..92e309da34b 100644 --- a/src/libstd/sys/windows/backtrace.rs +++ b/src/libstd/sys/windows/backtrace.rs @@ -32,7 +32,7 @@ use libc; use mem; use ops::Drop; use option::Option::{Some}; -use path::Path; +use old_path::Path; use ptr; use result::Result::{Ok, Err}; use slice::SliceExt; diff --git a/src/libstd/sys/windows/process.rs b/src/libstd/sys/windows/process.rs index 3ca735f7fdf..315c41e779a 100644 --- a/src/libstd/sys/windows/process.rs +++ b/src/libstd/sys/windows/process.rs @@ -23,7 +23,7 @@ use old_io::process::{ProcessExit, ExitStatus}; use old_io::{IoResult, IoError}; use old_io; use os; -use path::BytesContainer; +use old_path::BytesContainer; use ptr; use str; use sync::{StaticMutex, MUTEX_INIT}; diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs index 5c3892e49c0..129c1d20bc0 100644 --- a/src/libsyntax/parse/token.rs +++ b/src/libsyntax/parse/token.rs @@ -25,7 +25,7 @@ use serialize::{Decodable, Decoder, Encodable, Encoder}; use std::fmt; use std::mem; use std::ops::Deref; -use std::path::BytesContainer; +use std::old_path::BytesContainer; use std::rc::Rc; #[allow(non_camel_case_types)] diff --git a/src/test/compile-fail/range-3.rs b/src/test/compile-fail/range-3.rs index fe79165236f..78c575d33ba 100644 --- a/src/test/compile-fail/range-3.rs +++ b/src/test/compile-fail/range-3.rs @@ -13,4 +13,4 @@ pub fn main() { let r = 1..2..3; //~^ ERROR expected one of `.`, `;`, or an operator, found `..` -} \ No newline at end of file +} diff --git a/src/test/compile-fail/range-4.rs b/src/test/compile-fail/range-4.rs index bbd6ae289cc..a3e27fbbe9a 100644 --- a/src/test/compile-fail/range-4.rs +++ b/src/test/compile-fail/range-4.rs @@ -13,4 +13,4 @@ pub fn main() { let r = ..1..2; //~^ ERROR expected one of `.`, `;`, or an operator, found `..` -} \ No newline at end of file +} diff --git a/src/test/debuginfo/associated-types.rs b/src/test/debuginfo/associated-types.rs index 6a624e39e32..0f6f0ac6ae7 100644 --- a/src/test/debuginfo/associated-types.rs +++ b/src/test/debuginfo/associated-types.rs @@ -149,4 +149,4 @@ fn main() { assoc_enum(Enum::Variant2(8i64, 9i32)); } -fn zzz() { () } \ No newline at end of file +fn zzz() { () } -- cgit 1.4.1-3-g733a5 From 45ddf50cebd8f3353383e473327911c1f8b34cd6 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Thu, 29 Jan 2015 14:33:11 -0800 Subject: Add new path module Implements [RFC 474](https://github.com/rust-lang/rfcs/pull/474); see that RFC for details/motivation for this change. This initial commit does not include additional normalization or platform-specific path extensions. These will be done in follow up commits or PRs. --- src/libstd/ffi/mod.rs | 1 + src/libstd/ffi/os_str.rs | 2 +- src/libstd/path.rs | 2567 ++++++++++++++++++++ src/libstd/sys/unix/os_str.rs | 2 +- src/libstd/sys/windows/os_str.rs | 2 +- src/test/run-pass/issue-15149.rs | 2 +- src/test/run-pass/issue-3424.rs | 7 +- .../run-pass/process-spawn-with-unicode-params.rs | 2 +- 8 files changed, 2576 insertions(+), 9 deletions(-) create mode 100755 src/libstd/path.rs (limited to 'src/libstd') diff --git a/src/libstd/ffi/mod.rs b/src/libstd/ffi/mod.rs index 76f925a23f1..07a4f17796c 100644 --- a/src/libstd/ffi/mod.rs +++ b/src/libstd/ffi/mod.rs @@ -24,6 +24,7 @@ pub use self::os_str::OsStr; mod c_str; mod os_str; +// FIXME (#21670): these should be defined in the os_str module /// Freely convertible to an `&OsStr` slice. pub trait AsOsStr { /// Convert to an `&OsStr` slice. diff --git a/src/libstd/ffi/os_str.rs b/src/libstd/ffi/os_str.rs index b8d770e6ad6..4d7292b6eb4 100644 --- a/src/libstd/ffi/os_str.rs +++ b/src/libstd/ffi/os_str.rs @@ -41,7 +41,7 @@ use string::{String, CowString}; use ops; use cmp; use hash::{Hash, Hasher, Writer}; -use path::{Path, GenericPath}; +use old_path::{Path, GenericPath}; use sys::os_str::{Buf, Slice}; use sys_common::{AsInner, IntoInner, FromInner}; diff --git a/src/libstd/path.rs b/src/libstd/path.rs new file mode 100755 index 00000000000..3fd45ea6a7b --- /dev/null +++ b/src/libstd/path.rs @@ -0,0 +1,2567 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Cross-platform path manipulation. +//! +//! This module provides two types, `PathBuf` and `Path` (akin to `String` and +//! `str`), for working with paths abstractly. These types are thin wrappers +//! around `OsString` and `OsStr` respectively, meaning that they work directly +//! on strings according to the local platform's path syntax. +//! +//! ## Simple usage +//! +//! Path manipulation involves both parsing components from slices and building +//! new owned paths. +//! +//! To parse a path, you can create a `Path` slice from a `str` +//! slice and start asking questions: +//! +//! ```rust +//! use std::path::Path; +//! +//! let path = Path::new("/tmp/foo/bar.txt"); +//! let file = path.file_name(); +//! let extension = path.extension(); +//! let parent_dir = path.parent(); +//! ``` +//! +//! To build or modify paths, use `PathBuf`: +//! +//! ```rust +//! use std::path::PathBuf; +//! +//! let mut path = PathBuf::new("c:\\"); +//! path.push("windows"); +//! path.push("system32"); +//! path.set_extension("dll"); +//! ``` +//! +//! ## Path components and normalization +//! +//! The path APIs are built around the notion of "components", which roughly +//! correspond to the substrings between path separators (`/` and, on Windows, +//! `\`). The APIs for path parsing are largely specified in terms of the path's +//! components, so it's important to clearly understand how those are determined. +//! +//! A path can always be reconstructed into an equivalent path by putting +//! together its components via `push`. Syntactically, the paths may differ by +//! the normalization described below. +//! +//! ### Component types +//! +//! Components come in several types: +//! +//! * Normal components are the default: standard references to files or +//! directories. The path `a/b` has two normal components, `a` and `b`. +//! +//! * Current directory components represent the `.` character. For example, +//! `a/.` has a normal component `a` and a current directory component. +//! +//! * The root directory component represents a separator that designates +//! starting from root. For example, `/a/b` has a root directory component +//! followed by normal components `a` and `b`. +//! +//! On Windows, two additional component types come into play: +//! +//! * Prefix components, of which there is a large variety. For example, `C:` +//! and `\\server\share` are prefixes. The path `C:windows` has a prefix +//! component `C:` and a normal component `windows`; the path `C:\windows` has a +//! prefix component `C:`, a root directory component, and a normal component +//! `windows`. +//! +//! * Empty components, a special case for so-called "verbatim" paths where very +//! little normalization is allowed. For example, `\\?\C:\` has a "verbatim" +//! prefix `\\?\C:`, a root component, and an empty component (as a way of +//! representing the trailing `\`. Such a trailing `\` is in fact the only +//! situation in which an empty component is produced. +//! +//! ### Normalization +//! +//! Aside from splitting on the separator(s), there is a small amount of +//! "normalization": +//! +//! * Repeated separators are ignored: `a/b` and `a//b` both have components `a` +//! and `b`. +//! +//! * Paths ending in a separator are treated as if they has a current directory +//! component at the end (or, in verbatim paths, an empty component). For +//! example, while `a/b` has components `a` and `b`, the paths `a/b/` and +//! `a/b/.` both have components `a`, `b`, and `.` (current directory). The +//! reason for this normalization is that `a/b` and `a/b/` are treated +//! differently in some contexts, but `a/b/` and `a/b/.` are always treated +//! the same. +//! +//! No other normalization takes place by default. In particular, `a/./b/` and +//! `a/b` are treated distinctly in terms of components, as are `a/c` and +//! `a/b/../c`. Further normalization is possible to build on top of the +//! components APIs, and will be included in this library very soon. + +#![unstable(feature = "path")] + +use core::prelude::*; + +use borrow::BorrowFrom; +use cmp; +use iter; +use mem; +use ops::{self, Deref}; +use string::CowString; +use vec::Vec; +use fmt; + +use ffi::{OsStr, OsString, AsOsStr}; + +use self::platform::{is_sep, is_verbatim_sep, MAIN_SEP_STR, parse_prefix, Prefix}; + +//////////////////////////////////////////////////////////////////////////////// +// GENERAL NOTES +//////////////////////////////////////////////////////////////////////////////// +// +// Parsing in this module is done by directly transmuting OsStr to [u8] slices, +// taking advantage of the fact that OsStr always encodes ASCII characters +// as-is. Eventually, this transmutation should be replaced by direct uses of +// OsStr APIs for parsing, but it will take a while for those to become +// available. + +//////////////////////////////////////////////////////////////////////////////// +// Platform-specific definitions +//////////////////////////////////////////////////////////////////////////////// + +// The following modules give the most basic tools for parsing paths on various +// platforms. The bulk of the code is devoted to parsing prefixes on Windows. + +#[cfg(unix)] +mod platform { + use core::prelude::*; + use ffi::OsStr; + + #[inline] + pub fn is_sep(b: u8) -> bool { + b == b'/' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'/' + } + + pub fn parse_prefix(_: &OsStr) -> Option { + None + } + + #[derive(Copy, Clone, Show, Hash, PartialEq, Eq)] + pub struct Prefix<'a>; + + impl<'a> Prefix<'a> { + #[inline] + pub fn len(&self) -> usize { 0 } + #[inline] + pub fn is_verbatim(&self) -> bool { false } + #[inline] + pub fn is_drive(&self) -> bool { false } + #[inline] + pub fn has_implicit_root(&self) -> bool { false } + } + + pub const MAIN_SEP_STR: &'static str = "/"; +} + +#[cfg(windows)] +mod platform { + use core::prelude::*; + + use super::{Path, os_str_as_u8_slice, u8_slice_as_os_str}; + use ffi::OsStr; + use ascii::*; + + #[inline] + pub fn is_sep(b: u8) -> bool { + b == b'/' || b == b'\\' + } + + #[inline] + pub fn is_verbatim_sep(b: u8) -> bool { + b == b'\\' + } + + pub fn parse_prefix<'a>(path: &'a OsStr) -> Option { + use self::Prefix::*; + unsafe { + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + let mut path = os_str_as_u8_slice(path); + + if path.starts_with(br"\\") { + // \\ + path = &path[2..]; + if path.starts_with(br"?\") { + // \\?\ + path = &path[2..]; + if path.starts_with(br"UNC\") { + // \\?\UNC\server\share + path = &path[4..]; + let (server, share) = match parse_two_comps(path, is_verbatim_sep) { + Some((server, share)) => (u8_slice_as_os_str(server), + u8_slice_as_os_str(share)), + None => (u8_slice_as_os_str(path), + u8_slice_as_os_str(&[])), + }; + return Some(VerbatimUNC(server, share)); + } else { + // \\?\path + let idx = path.position_elem(&b'\\'); + if idx == Some(2) && path[1] == b':' { + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + // \\?\C:\ path + let slice = u8_slice_as_os_str(&path[0..1]); + return Some(VerbatimDisk(slice)); + } + } + let slice = &path[.. idx.unwrap_or(path.len())]; + return Some(Verbatim(u8_slice_as_os_str(slice))); + } + } else if path.starts_with(b".\\") { + // \\.\path + path = &path[2..]; + let slice = &path[.. path.position_elem(&b'\\').unwrap_or(path.len())]; + return Some(DeviceNS(u8_slice_as_os_str(slice))); + } + match parse_two_comps(path, is_sep) { + Some((server, share)) if server.len() > 0 && share.len() > 0 => { + // \\server\share + return Some(UNC(u8_slice_as_os_str(server), + u8_slice_as_os_str(share))); + } + _ => () + } + } else if path.len() > 1 && path[1] == b':' { + // C: + let c = path[0]; + if c.is_ascii() && (c as char).is_alphabetic() { + return Some(Disk(u8_slice_as_os_str(&path[0..1]))); + } + } + return None; + } + + fn parse_two_comps(mut path: &[u8], f: fn(u8) -> bool) -> Option<(&[u8], &[u8])> { + let first = match path.iter().position(|x| f(*x)) { + None => return None, + Some(x) => &path[.. x] + }; + path = &path[(first.len()+1)..]; + let idx = path.iter().position(|x| f(*x)); + let second = &path[.. idx.unwrap_or(path.len())]; + Some((first, second)) + } + } + + /// Windows path prefixes. + /// + /// Windows uses a variety of path styles, including references to drive + /// volumes (like `C:`), network shared (like `\\server\share`) and + /// others. In addition, some path prefixes are "verbatim", in which case + /// `/` is *not* treated as a separator and essentially no normalization is + /// performed. + #[derive(Copy, Clone, Debug, Hash, Eq)] + pub enum Prefix<'a> { + /// Prefix `\\?\`, together with the given component immediately following it. + Verbatim(&'a OsStr), + + /// Prefix `\\?\UNC\`, with the "server" and "share" components following it. + VerbatimUNC(&'a OsStr, &'a OsStr), + + /// Prefix like `\\?\C:\`, for the given drive letter + VerbatimDisk(&'a OsStr), + + /// Prefix `\\.\`, together with the given component immediately following it. + DeviceNS(&'a OsStr), + + /// Prefix `\\server\share`, with the given "server" and "share" components. + UNC(&'a OsStr, &'a OsStr), + + /// Prefix `C:` for the given disk drive. + Disk(&'a OsStr), + } + + impl<'a> Prefix<'a> { + #[inline] + pub fn len(&self) -> usize { + use self::Prefix::*; + fn os_str_len(s: &OsStr) -> usize { + unsafe { os_str_as_u8_slice(s).len() } + } + match *self { + Verbatim(x) => 4 + os_str_len(x), + VerbatimUNC(x,y) => 8 + os_str_len(x) + + if os_str_len(y) > 0 { 1 + os_str_len(y) } + else { 0 }, + VerbatimDisk(_) => 6, + UNC(x,y) => 2 + os_str_len(x) + + if os_str_len(y) > 0 { 1 + os_str_len(y) } + else { 0 }, + DeviceNS(x) => 4 + os_str_len(x), + Disk(_) => 2 + } + + } + + #[inline] + pub fn is_verbatim(&self) -> bool { + use self::Prefix::*; + match *self { + Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(_, _) => true, + _ => false + } + } + + #[inline] + pub fn is_drive(&self) -> bool { + match *self { + Prefix::Disk(_) => true, + _ => false, + } + } + + #[inline] + pub fn has_implicit_root(&self) -> bool { + !self.is_drive() + } + } + + impl<'a> ops::PartialEq for Prefix<'a> { + fn eq(&self, other: &Prefix<'a>) -> bool { + use self::Prefix::*; + match (*self, *other) { + (Verbatim(x), Verbatim(y)) => x == y, + (VerbatimUNC(x1, x2), Verbatim(y1, y2)) => x1 == y1 && x2 == y2, + (VerbatimDisk(x), VerbatimDisk(y)) => + os_str_as_u8_slice(x).eq_ignore_ascii_case(os_str_as_u8_slice(y)), + (DeviceNS(x), DeviceNS(y)) => x == y, + (UNC(x1, x2), UNC(y1, y2)) => x1 == y1 && x2 == y2, + (Disk(x), Disk(y)) => + os_str_as_u8_slice(x).eq_ignore_ascii_case(os_str_as_u8_slice(y)), + _ => false, + } + } + } + + pub const MAIN_SEP_STR: &'static str = "\\"; +} + +//////////////////////////////////////////////////////////////////////////////// +// Misc helpers +//////////////////////////////////////////////////////////////////////////////// + +// Iterate through `iter` while it matches `prefix`; return `None` if `prefix` +// is not a prefix of `iter`, otherwise return `Some(iter_after_prefix)` giving +// `iter` after having exhausted `prefix`. +fn iter_after(mut iter: I, mut prefix: J) -> Option where + I: Iterator + Clone, J: Iterator, A: PartialEq +{ + loop { + let mut iter_next = iter.clone(); + match (iter_next.next(), prefix.next()) { + (Some(x), Some(y)) => { + if x != y { return None } + } + (Some(_), None) => return Some(iter), + (None, None) => return Some(iter), + (None, Some(_)) => return None, + } + iter = iter_next; + } +} + +// See note at the top of this module to understand why these are used: +fn os_str_as_u8_slice(s: &OsStr) -> &[u8] { + unsafe { mem::transmute(s) } +} +unsafe fn u8_slice_as_os_str(s: &[u8]) -> &OsStr { + mem::transmute(s) +} + +//////////////////////////////////////////////////////////////////////////////// +// Cross-platform parsing +//////////////////////////////////////////////////////////////////////////////// + +/// Says whether the path ends in a separator character and therefore needs to +/// be treated as if it ended with an additional `.` +fn has_suffix(s: &[u8], prefix: Option) -> bool { + let (prefix_len, verbatim) = if let Some(p) = prefix { + (p.len(), p.is_verbatim()) + } else { (0, false) }; + if prefix_len > 0 && prefix_len == s.len() && !verbatim { return true; } + let mut splits = s[prefix_len..].split(|b| is_sep(*b)); + let last = splits.next_back().unwrap(); + let more = splits.next_back().is_some(); + more && last == b"" +} + +/// Says whether the first byte after the prefix is a separator. +fn has_physical_root(s: &[u8], prefix: Option) -> bool { + let path = if let Some(p) = prefix { &s[p.len()..] } else { s }; + path.len() > 0 && is_sep(path[0]) +} + +fn parse_single_component(comp: &[u8]) -> Option { + match comp { + b"." => Some(Component::CurDir), + b".." => Some(Component::ParentDir), + b"" => None, + _ => Some(Component::Normal(unsafe { u8_slice_as_os_str(comp) })) + } +} + +// basic workhorse for splitting stem and extension +#[allow(unused_unsafe)] // FIXME +fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { + unsafe { + if os_str_as_u8_slice(file) == b".." { return (Some(file), None) } + + // The unsafety here stems from converting between &OsStr and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &OsStr values are produced + // only from ASCII-bounded slices of existing &OsStr values. + + let mut iter = os_str_as_u8_slice(file).rsplitn(1, |b| *b == b'.'); + let after = iter.next(); + let before = iter.next(); + if before == Some(b"") { + (Some(file), None) + } else { + (before.map(|s| u8_slice_as_os_str(s)), + after.map(|s| u8_slice_as_os_str(s))) + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The core iterators +//////////////////////////////////////////////////////////////////////////////// + +/// Component parsing works by a double-ended state machine; the cursors at the +/// front and back of the path each keep track of what parts of the path have +/// been consumed so far. +/// +/// Going front to back, a path is made up of a prefix, a root component, a body +/// (of normal components), and a suffix/emptycomponent (normalized `.` or `` +/// for a path ending with the separator) +#[derive(Copy, Clone, PartialEq, PartialOrd, Show)] +enum State { + Prefix = 0, // c: + Root = 1, // / + Body = 2, // foo/bar/baz + Suffix = 3, // . + Done = 4, +} + +/// A single component of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Show)] +pub enum Component<'a> { + /// A Windows path prefix, e.g. `C:` or `\server\share` + Prefix(&'a OsStr), + + /// An empty component. Only used on Windows for the last component of + /// verbatim paths ending with a separator (e.g. the last component of + /// `\\?\C:\windows\` but not `\\?\C:\windows` or `C:\windows`). + Empty, + + /// The root directory component, appears after any prefix and before anything else + RootDir, + + /// A reference to the current directory, i.e. `.` + CurDir, + + /// A reference to the parent directory, i.e. `..` + ParentDir, + + /// A normal component, i.e. `a` and `b` in `a/b` + Normal(&'a OsStr), +} + +impl<'a> Component<'a> { + /// Extract the underlying `OsStr` slice + pub fn as_os_str(self) -> &'a OsStr { + match self { + Component::Prefix(path) => path, + Component::Empty => OsStr::from_str(""), + Component::RootDir => OsStr::from_str(MAIN_SEP_STR), + Component::CurDir => OsStr::from_str("."), + Component::ParentDir => OsStr::from_str(".."), + Component::Normal(path) => path, + } + } +} + +/// The core iterator giving the components of a path. +/// +/// See the module documentation for an in-depth explanation of components and +/// their role in the API. +#[derive(Clone)] +pub struct Components<'a> { + // The path left to parse components from + path: &'a [u8], + + // The prefix as it was originally parsed, if any + prefix: Option>, + + // true if path *physically* has a root separator; for most Windows + // prefixes, it may have a "logical" rootseparator for the purposes of + // normalization, e.g. \\server\share == \\server\share\. + has_physical_root: bool, + + // The iterator is double-ended, and these two states keep track of what has + // been produced from either end + front: State, + back: State, +} + +/// An iterator over the components of a path, as `OsStr` slices. +#[derive(Clone)] +pub struct Iter<'a> { + inner: Components<'a> +} + +impl<'a> Components<'a> { + // how long is the prefix, if any? + #[inline] + fn prefix_len(&self) -> usize { + self.prefix.as_ref().map(Prefix::len).unwrap_or(0) + } + + #[inline] + fn prefix_verbatim(&self) -> bool { + self.prefix.as_ref().map(Prefix::is_verbatim).unwrap_or(false) + } + + /// how much of the prefix is left from the point of view of iteration? + #[inline] + fn prefix_remaining(&self) -> usize { + if self.front == State::Prefix { self.prefix_len() } + else { 0 } + } + + fn prefix_and_root(&self) -> usize { + let root = if self.front <= State::Root && self.has_physical_root { 1 } else { 0 }; + self.prefix_remaining() + root + } + + // is the iteration complete? + #[inline] + fn finished(&self) -> bool { + self.front == State::Done || self.back == State::Done || self.front > self.back + } + + #[inline] + fn is_sep(&self, b: u8) -> bool { + if self.prefix_verbatim() { + is_verbatim_sep(b) + } else { + is_sep(b) + } + } + + /// Extract a slice corresponding to the portion of the path remaining for iteration. + pub fn as_path(&self) -> &'a Path { + let mut comps = self.clone(); + if comps.front == State::Body { comps.trim_left(); } + if comps.back == State::Body { comps.trim_right(); } + if comps.path.is_empty() && comps.front < comps.back && comps.back == State::Suffix { + Path::new(".") + } else { + unsafe { Path::from_u8_slice(comps.path) } + } + } + + /// Is the *original* path rooted? + fn has_root(&self) -> bool { + if self.has_physical_root { return true } + if let Some(p) = self.prefix { + if p.has_implicit_root() { return true } + } + false + } + + // parse a component from the left, saying how many bytes to consume to + // remove the component + fn parse_next_component(&self) -> (usize, Option>) { + debug_assert!(self.front == State::Body); + let (extra, comp) = match self.path.iter().position(|b| self.is_sep(*b)) { + None => (0, self.path), + Some(i) => (1, &self.path[.. i]), + }; + (comp.len() + extra, parse_single_component(comp)) + } + + // parse a component from the right, saying how many bytes to consume to + // remove the component + fn parse_next_component_back(&self) -> (usize, Option>) { + debug_assert!(self.back == State::Body); + let start = self.prefix_and_root(); + let (extra, comp) = match self.path[start..].iter().rposition(|b| self.is_sep(*b)) { + None => (0, &self.path[start ..]), + Some(i) => (1, &self.path[start + i + 1 ..]), + }; + (comp.len() + extra, parse_single_component(comp)) + } + + // trim away repeated separators (i.e. emtpy components) on the left + fn trim_left(&mut self) { + while !self.path.is_empty() { + let (size, comp) = self.parse_next_component(); + if comp.is_some() { + return; + } else { + self.path = &self.path[size ..]; + } + } + } + + // trim away repeated separators (i.e. emtpy components) on the right + fn trim_right(&mut self) { + while self.path.len() > self.prefix_and_root() { + let (size, comp) = self.parse_next_component_back(); + if comp.is_some() { + return; + } else { + self.path = &self.path[.. self.path.len() - size]; + } + } + } + + /// Examine the next component without consuming it. + pub fn peek(&self) -> Option> { + self.clone().next() + } +} + +impl<'a> Iter<'a> { + /// Extract a slice corresponding to the portion of the path remaining for iteration. + pub fn as_path(&self) -> &'a Path { + self.inner.as_path() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { + self.inner.next().map(Component::as_os_str) + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a OsStr> { + self.inner.next_back().map(Component::as_os_str) + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + while !self.finished() { + match self.front { + State::Prefix if self.prefix_len() > 0 => { + self.front = State::Root; + debug_assert!(self.prefix_len() <= self.path.len()); + let prefix = &self.path[.. self.prefix_len()]; + self.path = &self.path[self.prefix_len() .. ]; + return Some(Component::Prefix(unsafe { u8_slice_as_os_str(prefix) })) + } + State::Prefix => { + self.front = State::Root; + } + State::Root => { + self.front = State::Body; + if self.has_physical_root { + debug_assert!(self.path.len() > 0); + self.path = &self.path[1..]; + return Some(Component::RootDir) + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir) + } + } + } + State::Body if !self.path.is_empty() => { + let (size, comp) = self.parse_next_component(); + self.path = &self.path[size ..]; + if comp.is_some() { return comp } + } + State::Body => { + self.front = State::Suffix; + } + State::Suffix => { + self.front = State::Done; + if self.prefix_verbatim() { + return Some(Component::Empty) + } else { + return Some(Component::CurDir) + } + } + State::Done => unreachable!() + } + } + None + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + while !self.finished() { + match self.back { + State::Suffix => { + self.back = State::Body; + if self.prefix_verbatim() { + return Some(Component::Empty) + } else { + return Some(Component::CurDir) + } + } + State::Body if self.path.len() > self.prefix_and_root() => { + let (size, comp) = self.parse_next_component_back(); + self.path = &self.path[.. self.path.len() - size]; + if comp.is_some() { return comp } + } + State::Body => { + self.back = State::Root; + } + State::Root => { + self.back = State::Prefix; + if self.has_physical_root { + self.path = &self.path[.. self.path.len() - 1]; + return Some(Component::RootDir) + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir) + } + } + } + State::Prefix if self.prefix_len() > 0 => { + self.back = State::Done; + return Some(Component::Prefix(unsafe { + u8_slice_as_os_str(self.path) + })) + } + State::Prefix => { + self.back = State::Done; + return None + } + State::Done => unreachable!() + } + } + None + } +} + +fn optional_path(path: &Path) -> Option<&Path> { + if path.as_u8_slice().is_empty() { None } else { Some(path) } +} + +impl<'a> cmp::PartialEq for Components<'a> { + fn eq(&self, other: &Components<'a>) -> bool { + iter::order::eq(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Eq for Components<'a> {} + +impl<'a> cmp::PartialOrd for Components<'a> { + fn partial_cmp(&self, other: &Components<'a>) -> Option { + iter::order::partial_cmp(self.clone(), other.clone()) + } +} + +impl<'a> cmp::Ord for Components<'a> { + fn cmp(&self, other: &Components<'a>) -> cmp::Ordering { + iter::order::cmp(self.clone(), other.clone()) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Basic types and traits +//////////////////////////////////////////////////////////////////////////////// + +/// An owned, mutable path (akin to `String`). +/// +/// This type provides methods like `push` and `set_extension` that mutate the +/// path in place. It also implements `Deref` to `Path`, meaning that all +/// methods on `Path` slices are available on `PathBuf` values as well. +/// +/// More details about the overall approach can be found in +/// the module documentation. +/// +/// # Example +/// +/// ```rust +/// use std::path::PathBuf; +/// +/// let mut path = PathBuf::new("c:\\"); +/// path.push("windows"); +/// path.push("system32"); +/// path.set_extension("dll"); +/// ``` +#[derive(Clone, Hash)] +pub struct PathBuf { + inner: OsString +} + +impl PathBuf { + fn as_mut_vec(&mut self) -> &mut Vec { + unsafe { mem::transmute(self) } + } + + /// Allocate a `PathBuf` with initial contents given by the + /// argument. + pub fn new(s: &S) -> PathBuf { + PathBuf { inner: s.as_os_str().to_os_string() } + } + + /// Extend `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g. `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self. + pub fn push(&mut self, path: &P) where P: AsPath { + // in general, a separator is needed if the rightmost byte is not a separator + let mut need_sep = self.as_mut_vec().last().map(|c| !is_sep(*c)).unwrap_or(false); + + // in the special case of `C:` on Windows, do *not* add a separator + { + let comps = self.components(); + if comps.prefix_len() > 0 && + comps.prefix_len() == comps.path.len() && + comps.prefix.unwrap().is_drive() + { + need_sep = false + } + } + + let path = path.as_path(); + + // absolute `path` replaces `self` + if path.is_absolute() || path.prefix().is_some() { + self.as_mut_vec().truncate(0); + + // `path` has a root but no prefix, e.g. `\windows` (Windows only) + } else if path.has_root() { + let prefix_len = self.components().prefix_remaining(); + self.as_mut_vec().truncate(prefix_len); + + // `path` is a pure relative path + } else if need_sep { + self.inner.push_os_str(OsStr::from_str(MAIN_SEP_STR)); + } + + self.inner.push_os_str(path.as_os_str()); + } + + /// Truncate `self` to `self.parent()`. + /// + /// Returns `None` and does nothing if `self.parent()` is `None`. + pub fn pop(&mut self) -> bool { + match self.parent().map(|p| p.as_u8_slice().len()) { + Some(len) => { + self.as_mut_vec().truncate(len); + true + } + None => false + } + } + + /// Updates `self.file_name()` to `file_name`. + /// + /// If `self.file_name()` was `None`, this is equivalent to pushing + /// `file_name`. + /// + /// # Examples + /// + /// ```rust + /// use std::path::{Path, PathBuf}; + /// + /// let mut buf = PathBuf::new("/foo/"); + /// assert!(buf.file_name() == None); + /// buf.set_file_name("bar"); + /// assert!(buf == PathBuf::new("/foo/bar")); + /// assert!(buf.file_name().is_some()); + /// buf.set_file_name("baz.txt"); + /// assert!(buf == PathBuf::new("/foo/baz.txt")); + /// ``` + pub fn set_file_name(&mut self, file_name: &S) where S: AsOsStr { + if self.file_name().is_some() && !self.pop() { + // Given that there is a file name, this is reachable only for + // Windows paths like c:file or paths like `foo`, but not `c:\` or + // `/`. + let prefix_len = self.components().prefix_remaining(); + self.as_mut_vec().truncate(prefix_len); + } + self.push(file_name.as_os_str()); + } + + /// Updates `self.extension()` to `extension`. + /// + /// If `self.file_name()` is `None`, does nothing and returns `false`. + /// + /// Otherwise, returns `tru`; if `self.exension()` is `None`, the extension + /// is added; otherwise it is replaced. + pub fn set_extension(&mut self, extension: &S) -> bool { + if self.file_name().is_none() { return false; } + + let mut stem = match self.file_stem() { + Some(stem) => stem.to_os_string(), + None => OsString::from_str(""), + }; + + let extension = extension.as_os_str(); + if os_str_as_u8_slice(extension).len() > 0 { + stem.push_os_str(OsStr::from_str(".")); + stem.push_os_str(extension.as_os_str()); + } + self.set_file_name(&stem); + + true + } +} + +impl<'a, P: ?Sized + 'a> iter::FromIterator<&'a P> for PathBuf where P: AsPath { + fn from_iter>(iter: I) -> PathBuf { + let mut buf = PathBuf::new(""); + buf.extend(iter); + buf + } +} + +impl<'a, P: ?Sized + 'a> iter::Extend<&'a P> for PathBuf where P: AsPath { + fn extend>(&mut self, iter: I) { + for p in iter { + self.push(p) + } + } +} + +impl fmt::Debug for PathBuf { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + unsafe { mem::transmute(&self.inner[]) } + } +} + +impl BorrowFrom for Path { + fn borrow_from(owned: &PathBuf) -> &Path { + owned.deref() + } +} + +impl cmp::PartialEq for PathBuf { + fn eq(&self, other: &PathBuf) -> bool { + self.components() == other.components() + } +} + +impl cmp::Eq for PathBuf {} + +impl cmp::PartialOrd for PathBuf { + fn partial_cmp(&self, other: &PathBuf) -> Option { + self.components().partial_cmp(&other.components()) + } +} + +impl cmp::Ord for PathBuf { + fn cmp(&self, other: &PathBuf) -> cmp::Ordering { + self.components().cmp(&other.components()) + } +} + +/// A slice of a path (akin to `str`). +/// +/// This type supports a number of operations for inspecting a path, including +/// breaking the path into its components (separated by `/` or `\`, depending on +/// the platform), extracting the file name, determining whether the path is +/// absolute, and so on. More details about the overall approach can be found in +/// the module documentation. +/// +/// This is an *unsized* type, meaning that it must always be used with behind a +/// pointer like `&` or `Box`. +/// +/// # Example +/// +/// ```rust +/// use std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// let file = path.file_name(); +/// let extension = path.extension(); +/// let parent_dir = path.parent(); +/// ``` +/// +pub struct Path { + inner: OsStr +} + +impl Path { + // The following (private!) function allows construction of a path from a u8 + // slice, which is only safe when it is known to follow the OsStr encoding. + unsafe fn from_u8_slice(s: &[u8]) -> &Path { + mem::transmute(s) + } + // The following (private!) function reveals the byte encoding used for OsStr. + fn as_u8_slice(&self) -> &[u8] { + unsafe { mem::transmute(self) } + } + + /// Directly wrap a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + pub fn new(s: &S) -> &Path { + unsafe { mem::transmute(s.as_os_str()) } + } + + /// Yield a `&str` slice if the `Path` is valid unicode. + /// + /// This conversion may entail doing a check for UTF-8 validity. + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() + } + + /// Convert a `Path` to a `CowString`. + /// + /// Any non-Unicode sequences are replaced with U+FFFD REPLACEMENT CHARACTER. + pub fn to_string_lossy(&self) -> CowString { + self.inner.to_string_lossy() + } + + /// Convert a `Path` to an owned `PathBuf`. + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::new(self) + } + + /// A path is *absolute* if it is indepedent of the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and `has_root` are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. In + /// other words, `path.is_absolute() == path.prefix().is_some() && path.has_root()`. + pub fn is_absolute(&self) -> bool { + self.has_root() && + (cfg!(unix) || self.prefix().is_some()) + } + + /// A path is *relative* if it is not absolute. + pub fn is_relative(&self) -> bool { + !self.is_absolute() + } + + /// Returns the *prefix* of a path, if any. + /// + /// Prefixes are relevant only for Windows paths, and consist of volumes + /// like `C:`, UNC prefixes like `\\server`, and others described in more + /// detail in `std::os::windows::PathExt`. + pub fn prefix(&self) -> Option<&Path> { + let iter = self.components(); + optional_path(unsafe { + Path::from_u8_slice( + &self.as_u8_slice()[.. iter.prefix_remaining()]) + }) + } + + /// A path has a root if the body of the path begins with the directory separator. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g. `\\windows` + /// * has a prefix followed by a separator, e.g. `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g. `\\server\share` + pub fn has_root(&self) -> bool { + self.components().has_root() + } + + /// The path without its final component. + /// + /// Does nothing, returning `None` if the path consists of just a prefix + /// and/or root directory reference. + /// + /// # Examples + /// + /// ```rust + /// use std::path::Path; + /// + /// let path = Path::new("/foo/bar"); + /// let foo = path.parent().unwrap(); + /// assert!(foo == Path::new("/foo")); + /// let root = foo.parent().unwrap(); + /// assert!(root == Path::new("/")); + /// assert!(root.parent() == None); + /// ``` + pub fn parent(&self) -> Option<&Path> { + let mut comps = self.components(); + let comp = comps.next_back(); + let rest = optional_path(comps.as_path()); + + match (comp, comps.next_back()) { + (Some(Component::CurDir), Some(Component::RootDir)) => None, + (Some(Component::CurDir), Some(Component::Prefix(_))) => None, + (Some(Component::Empty), Some(Component::RootDir)) => None, + (Some(Component::Empty), Some(Component::Prefix(_))) => None, + (Some(Component::Prefix(_)), None) => None, + (Some(Component::RootDir), Some(Component::Prefix(_))) => None, + _ => rest + } + } + + /// The final component of the path, if it is a normal file. + /// + /// If the path terminates in `.`, `..`, or consists solely or a root of + /// prefix, `file` will return `None`. + pub fn file_name(&self) -> Option<&OsStr> { + self.components().next_back().and_then(|p| match p { + Component::Normal(p) => Some(p.as_os_str()), + _ => None + }) + } + + /// Returns a path that, when joined onto `base`, yields `self`. + pub fn relative_from<'a, P: ?Sized>(&'a self, base: &'a P) -> Option<&Path> where + P: AsPath + { + iter_after(self.components(), base.as_path().components()).map(|c| c.as_path()) + } + + /// Determines whether `base` is a prefix of `self`. + pub fn starts_with(&self, base: &P) -> bool where P: AsPath { + iter_after(self.components(), base.as_path().components()).is_some() + } + + /// Determines whether `base` is a suffix of `self`. + pub fn ends_with(&self, child: &P) -> bool where P: AsPath { + iter_after(self.components().rev(), child.as_path().components().rev()).is_some() + } + + /// Extract the stem (non-extension) portion of `self.file()`. + /// + /// The stem is: + /// + /// * None, if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + pub fn file_stem(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.or(after)) + } + + /// Extract the extension of `self.file()`, if possible. + /// + /// The extension is: + /// + /// * None, if there is no file name; + /// * None, if there is no embedded `.`; + /// * None, if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + pub fn extension(&self) -> Option<&OsStr> { + self.file_name().map(split_file_at_dot).and_then(|(before, after)| before.and(after)) + } + + /// Creates an owned `PathBuf` with `path` adjoined to `self`. + /// + /// See `PathBuf::push` for more details on what it means to adjoin a path. + pub fn join(&self, path: &P) -> PathBuf where P: AsPath { + let mut buf = self.to_path_buf(); + buf.push(path); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given file name. + /// + /// See `PathBuf::set_file_name` for more details. + pub fn with_file_name(&self, file_name: &S) -> PathBuf where S: AsOsStr { + let mut buf = self.to_path_buf(); + buf.set_file_name(file_name); + buf + } + + /// Creates an owned `PathBuf` like `self` but with the given extension. + /// + /// See `PathBuf::set_extension` for more details. + pub fn with_extension(&self, extension: &S) -> PathBuf where S: AsOsStr { + let mut buf = self.to_path_buf(); + buf.set_extension(extension); + buf + } + + /// Produce an iterator over the components of the path. + pub fn components(&self) -> Components { + let prefix = parse_prefix(self.as_os_str()); + Components { + path: self.as_u8_slice(), + prefix: prefix, + has_physical_root: has_physical_root(self.as_u8_slice(), prefix), + front: State::Prefix, + back: if has_suffix(self.as_u8_slice(), prefix) { State::Suffix } + else { State::Body }, + } + } + + /// Produce an iterator over the path's components viewed as `OsStr` slices. + pub fn iter(&self) -> Iter { + Iter { inner: self.components() } + } + + /// Returns an object that implements `Display` for safely printing paths + /// that may contain non-Unicode data. + pub fn display(&self) -> Display { + Display { path: self } + } +} + +impl AsOsStr for Path { + fn as_os_str(&self) -> &OsStr { + &self.inner + } +} + +impl fmt::Debug for Path { + fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.inner.fmt(formatter) + } +} + +/// Helper struct for safely printing paths with `format!()` and `{}` +pub struct Display<'a> { + path: &'a Path +} + +impl<'a> fmt::Debug for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.path.to_string_lossy(), f) + } +} + +impl<'a> fmt::Display for Display<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.path.to_string_lossy(), f) + } +} + +impl cmp::PartialEq for Path { + fn eq(&self, other: &Path) -> bool { + iter::order::eq(self.components(), other.components()) + } +} + +impl cmp::Eq for Path {} + +impl cmp::PartialOrd for Path { + fn partial_cmp(&self, other: &Path) -> Option { + self.components().partial_cmp(&other.components()) + } +} + +impl cmp::Ord for Path { + fn cmp(&self, other: &Path) -> cmp::Ordering { + self.components().cmp(&other.components()) + } +} + +/// Freely convertible to a `Path`. +pub trait AsPath { + /// Convert to a `Path`. + fn as_path(&self) -> &Path; +} + +impl AsPath for T { + fn as_path(&self) -> &Path { Path::new(self.as_os_str()) } +} + +#[cfg(test)] +mod tests { + use super::*; + use ffi::OsStr; + use core::prelude::*; + use string::{ToString, String}; + use vec::Vec; + + macro_rules! t( + ($path:expr, iter: $iter:expr) => ( + { + let path = Path::new($path); + + // Forward iteration + let comps = path.iter() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exp: &[&str] = &$iter; + let exps = exp.iter().map(|s| s.to_string()).collect::>(); + assert!(comps == exps, "iter: Expected {:?}, found {:?}", + exps, comps); + + // Reverse iteration + let comps = Path::new($path).iter().rev() + .map(|p| p.to_string_lossy().into_owned()) + .collect::>(); + let exps = exps.into_iter().rev().collect::>(); + assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}", + exps, comps); + } + ); + + ($path:expr, has_root: $has_root:expr, is_absolute: $is_absolute:expr) => ( + { + let path = Path::new($path); + + let act_root = path.has_root(); + assert!(act_root == $has_root, "has_root: Expected {:?}, found {:?}", + $has_root, act_root); + + let act_abs = path.is_absolute(); + assert!(act_abs == $is_absolute, "is_absolute: Expected {:?}, found {:?}", + $is_absolute, act_abs); + } + ); + + ($path:expr, parent: $parent:expr, file_name: $file:expr) => ( + { + let path = Path::new($path); + + let parent = path.parent().map(|p| p.to_str().unwrap()); + let exp_parent: Option<&str> = $parent; + assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}", + exp_parent, parent); + + let file = path.file_name().map(|p| p.to_str().unwrap()); + let exp_file: Option<&str> = $file; + assert!(file == exp_file, "file_name: Expected {:?}, found {:?}", + exp_file, file); + } + ); + + ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + let path = Path::new($path); + + let stem = path.file_stem().map(|p| p.to_str().unwrap()); + let exp_stem: Option<&str> = $file_stem; + assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}", + exp_stem, stem); + + let ext = path.extension().map(|p| p.to_str().unwrap()); + let exp_ext: Option<&str> = $extension; + assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", + exp_ext, ext); + } + ); + + ($path:expr, iter: $iter:expr, + has_root: $has_root:expr, is_absolute: $is_absolute:expr, + parent: $parent:expr, file_name: $file:expr, + file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + t!($path, iter: $iter); + t!($path, has_root: $has_root, is_absolute: $is_absolute); + t!($path, parent: $parent, file_name: $file); + t!($path, file_stem: $file_stem, extension: $extension); + } + ); + ); + + #[test] + #[cfg(unix)] + pub fn test_decompositions_unix() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: None, + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["/", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo", "."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo/", + iter: ["/", "foo", "."], + has_root: true, + is_absolute: true, + parent: Some("/foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["/", "foo", "."], + has_root: true, + is_absolute: true, + parent: Some("///foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("///foo///bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./.", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["/", ".."], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: ["..", "."], + has_root: false, + is_absolute: false, + parent: Some(".."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo", "."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo", ".", "."], + has_root: false, + is_absolute: false, + parent: Some("foo/."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./bar", + iter: ["foo", ".", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", "..", "."], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", ".", "b"], + has_root: false, + is_absolute: false, + parent: Some("a/."), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + } + + #[test] + #[cfg(windows)] + pub fn test_decompositions_windows() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: None, + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("/", + iter: ["\\", "."], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\", + iter: ["\\", "."], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:", + iter: ["c:", "."], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:\\", + iter: ["c:", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:\\", + iter: ["c:", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("c:/", + iter: ["c:", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None + ); + + t!("foo/", + iter: ["foo", "."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/foo/", + iter: ["\\", "foo", "."], + has_root: true, + is_absolute: false, + parent: Some("/foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("/foo/bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("///foo///", + iter: ["\\", "foo", "."], + has_root: true, + is_absolute: false, + parent: Some("///foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("///foo///bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./.", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("./.", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("/..", + iter: ["\\", ".."], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("../", + iter: ["..", "."], + has_root: false, + is_absolute: false, + parent: Some(".."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/.", + iter: ["foo", "."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./", + iter: ["foo", ".", "."], + has_root: false, + is_absolute: false, + parent: Some("foo/."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/./bar", + iter: ["foo", ".", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("foo/../", + iter: ["foo", "..", "."], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("./", + iter: [".", "."], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: None, + file_stem: None, + extension: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/./b", + iter: ["a", ".", "b"], + has_root: false, + is_absolute: false, + parent: Some("a/."), + file_name: Some("b"), + file_stem: Some("b"), + extension: None + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None); + + t!("a\\b\\c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a\\b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None + ); + + t!("\\a", + iter: ["\\", "a"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("a"), + file_stem: Some("a"), + extension: None + ); + + t!("c:\\foo.txt", + iter: ["c:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("c:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share\\foo.txt", + iter: ["\\\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\server\\share", + iter: ["\\\\server\\share", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\server", + iter: ["\\", "server"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("server"), + file_stem: Some("server"), + extension: None + ); + + t!("\\\\?\\bar\\foo.txt", + iter: ["\\\\?\\bar", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\bar\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\bar", + iter: ["\\\\?\\bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\", + iter: ["\\\\?\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\server\\share\\foo.txt", + iter: ["\\\\?\\UNC\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\UNC\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("\\\\?\\UNC\\server", + iter: ["\\\\?\\UNC\\server"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\UNC\\", + iter: ["\\\\?\\UNC\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\C:\\foo.txt", + iter: ["\\\\?\\C:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt") + ); + + + t!("\\\\?\\C:\\", + iter: ["\\\\?\\C:", "\\", ""], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:", + iter: ["\\\\?\\C:"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\foo/bar", + iter: ["\\\\?\\foo/bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\?\\C:/foo", + iter: ["\\\\?\\C:/foo"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar", + iter: ["\\\\.\\foo", "\\", "bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None + ); + + + t!("\\\\.\\foo", + iter: ["\\\\.\\foo", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo/bar", + iter: ["\\\\.\\foo/bar", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + + t!("\\\\.\\foo\\bar/baz", + iter: ["\\\\.\\foo", "\\", "bar", "baz"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\bar"), + file_name: Some("baz"), + file_stem: Some("baz"), + extension: None + ); + + + t!("\\\\.\\", + iter: ["\\\\.\\", "\\", "."], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None + ); + + t!("\\\\?\\a\\b\\", + iter: ["\\\\?\\a", "\\", "b", ""], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\a\\b"), + file_name: None, + file_stem: None, + extension: None + ); + } + + #[test] + pub fn test_stem_ext() { + t!("foo", + file_stem: Some("foo"), + extension: None + ); + + t!("foo.", + file_stem: Some("foo"), + extension: Some("") + ); + + t!(".foo", + file_stem: Some(".foo"), + extension: None + ); + + t!("foo.txt", + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.txt", + file_stem: Some("foo.bar"), + extension: Some("txt") + ); + + t!("foo.bar.", + file_stem: Some("foo.bar"), + extension: Some("") + ); + + t!(".", + file_stem: None, + extension: None + ); + + t!("..", + file_stem: None, + extension: None + ); + + t!("", + file_stem: None, + extension: None + ); + } + + #[test] + pub fn test_push() { + macro_rules! tp( + ($path:expr, $push:expr, $expected:expr) => ( { + let mut actual = PathBuf::new($path); + actual.push($push); + assert!(actual.to_str() == Some($expected), + "pushing {:?} onto {:?}: Expected {:?}, got {:?}", + $push, $path, $expected, actual.to_str().unwrap()); + }); + ); + + if cfg!(unix) { + tp!("", "foo", "foo"); + tp!("foo", "bar", "foo/bar"); + tp!("foo/", "bar", "foo/bar"); + tp!("foo//", "bar", "foo//bar"); + tp!("foo/.", "bar", "foo/./bar"); + tp!("foo./.", "bar", "foo././bar"); + tp!("foo", "", "foo/"); + tp!("foo", ".", "foo/."); + tp!("foo", "..", "foo/.."); + tp!("foo", "/", "/"); + tp!("/foo/bar", "/", "/"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", "./baz", "/foo/bar/./baz"); + } else { + tp!("", "foo", "foo"); + tp!("foo", "bar", r"foo\bar"); + tp!("foo/", "bar", r"foo/bar"); + tp!(r"foo\", "bar", r"foo\bar"); + tp!("foo//", "bar", r"foo//bar"); + tp!(r"foo\\", "bar", r"foo\\bar"); + tp!("foo/.", "bar", r"foo/.\bar"); + tp!("foo./.", "bar", r"foo./.\bar"); + tp!(r"foo\.", "bar", r"foo\.\bar"); + tp!(r"foo.\.", "bar", r"foo.\.\bar"); + tp!("foo", "", "foo\\"); + tp!("foo", ".", r"foo\."); + tp!("foo", "..", r"foo\.."); + tp!("foo", "/", "/"); + tp!("foo", r"\", r"\"); + tp!("/foo/bar", "/", "/"); + tp!(r"\foo\bar", r"\", r"\"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", r"\baz", r"\baz"); + tp!("/foo/bar", "./baz", r"/foo/bar\./baz"); + tp!("/foo/bar", r".\baz", r"/foo/bar\.\baz"); + + tp!("c:\\", "windows", "c:\\windows"); + tp!("c:", "windows", "c:windows"); + + tp!("a\\b\\c", "d", "a\\b\\c\\d"); + tp!("\\a\\b\\c", "d", "\\a\\b\\c\\d"); + tp!("a\\b", "c\\d", "a\\b\\c\\d"); + tp!("a\\b", "\\c\\d", "\\c\\d"); + tp!("a\\b", ".", "a\\b\\."); + tp!("a\\b", "..\\c", "a\\b\\..\\c"); + tp!("a\\b", "C:a.txt", "C:a.txt"); + tp!("a\\b", "C:\\a.txt", "C:\\a.txt"); + tp!("C:\\a", "C:\\b.txt", "C:\\b.txt"); + tp!("C:\\a\\b\\c", "C:d", "C:d"); + tp!("C:a\\b\\c", "C:d", "C:d"); + tp!("C:", r"a\b\c", r"C:a\b\c"); + tp!("C:", r"..\a", r"C:..\a"); + tp!("\\\\server\\share\\foo", "bar", "\\\\server\\share\\foo\\bar"); + tp!("\\\\server\\share\\foo", "C:baz", "C:baz"); + tp!("\\\\?\\C:\\a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); + tp!("\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); + tp!("\\\\?\\UNC\\server\\share\\foo", "bar", "\\\\?\\UNC\\server\\share\\foo\\bar"); + tp!("\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); + tp!("\\\\?\\UNC\\server\\share", "C:a", "C:a"); + + // Note: modified from old path API + tp!("\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\foo"); + + tp!("C:\\a", "\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share"); + tp!("\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); + tp!("\\\\.\\foo\\bar", "C:a", "C:a"); + // again, not sure about the following, but I'm assuming \\.\ should be verbatim + tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); + + tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one + } + } + + #[test] + pub fn test_pop() { + macro_rules! tp( + ($path:expr, $expected:expr, $output:expr) => ( { + let mut actual = PathBuf::new($path); + let output = actual.pop(); + assert!(actual.to_str() == Some($expected) && output == $output, + "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $expected, $output, + actual.to_str().unwrap(), output); + }); + ); + + tp!("", "", false); + tp!("/", "/", false); + tp!("foo", "foo", false); + tp!(".", ".", false); + tp!("/foo", "/", true); + tp!("/foo/bar", "/foo", true); + tp!("foo/bar", "foo", true); + tp!("foo/.", "foo", true); + tp!("foo//bar", "foo", true); + + if cfg!(windows) { + tp!("a\\b\\c", "a\\b", true); + tp!("\\a", "\\", true); + tp!("\\", "\\", false); + + tp!("C:\\a\\b", "C:\\a", true); + tp!("C:\\a", "C:\\", true); + tp!("C:\\", "C:\\", false); + tp!("C:a\\b", "C:a", true); + tp!("C:a", "C:", true); + tp!("C:", "C:", false); + tp!("\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); + tp!("\\\\server\\share\\a", "\\\\server\\share\\", true); + tp!("\\\\server\\share", "\\\\server\\share", false); + tp!("\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); + tp!("\\\\?\\a\\b", "\\\\?\\a\\", true); + tp!("\\\\?\\a", "\\\\?\\a", false); + tp!("\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); + tp!("\\\\?\\C:\\a", "\\\\?\\C:\\", true); + tp!("\\\\?\\C:\\", "\\\\?\\C:\\", false); + tp!("\\\\?\\UNC\\server\\share\\a\\b", "\\\\?\\UNC\\server\\share\\a", true); + tp!("\\\\?\\UNC\\server\\share\\a", "\\\\?\\UNC\\server\\share\\", true); + tp!("\\\\?\\UNC\\server\\share", "\\\\?\\UNC\\server\\share", false); + tp!("\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); + tp!("\\\\.\\a\\b", "\\\\.\\a\\", true); + tp!("\\\\.\\a", "\\\\.\\a", false); + + tp!("\\\\?\\a\\b\\", "\\\\?\\a\\b", true); + } + } + + #[test] + pub fn test_set_file_name() { + macro_rules! tfn( + ($path:expr, $file:expr, $expected:expr) => ( { + let mut p = PathBuf::new($path); + p.set_file_name($file); + assert!(p.to_str() == Some($expected), + "setting file name of {:?} to {:?}: Expected {:?}, got {:?}", + $path, $file, $expected, + p.to_str().unwrap()); + }); + ); + + tfn!("foo", "foo", "foo"); + tfn!("foo", "bar", "bar"); + tfn!("foo", "", ""); + tfn!("", "foo", "foo"); + tfn!(".", "foo", "./foo"); + tfn!("foo/", "bar", "foo/bar"); + tfn!("foo/.", "bar", "foo/./bar"); + tfn!("..", "foo", "../foo"); + tfn!("foo/..", "bar", "foo/../bar"); + tfn!("/", "foo", "/foo"); + } + + #[test] + pub fn test_set_extension() { + macro_rules! tfe( + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ( { + let mut p = PathBuf::new($path); + let output = p.set_extension($ext); + assert!(p.to_str() == Some($expected) && output == $output, + "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $ext, $expected, $output, + p.to_str().unwrap(), output); + }); + ); + + tfe!("foo", "txt", "foo.txt", true); + tfe!("foo.bar", "txt", "foo.txt", true); + tfe!("foo.bar.baz", "txt", "foo.bar.txt", true); + tfe!(".test", "txt", ".test.txt", true); + tfe!("foo.txt", "", "foo", true); + tfe!("foo", "", "foo", true); + tfe!("", "foo", "", false); + tfe!(".", "foo", ".", false); + tfe!("foo/", "bar", "foo/", false); + tfe!("foo/.", "bar", "foo/.", false); + tfe!("..", "foo", "..", false); + tfe!("foo/..", "bar", "foo/..", false); + tfe!("/", "foo", "/", false); + } + + #[test] + pub fn test_compare() { + macro_rules! tc( + ($path1:expr, $path2:expr, eq: $eq:expr, + starts_with: $starts_with:expr, ends_with: $ends_with:expr, + relative_from: $relative_from:expr) => ({ + let path1 = Path::new($path1); + let path2 = Path::new($path2); + + let eq = path1 == path2; + assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", + $path1, $path2, $eq, eq); + + let starts_with = path1.starts_with(path2); + assert!(starts_with == $starts_with, + "{:?}.starts_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $starts_with, starts_with); + + let ends_with = path1.ends_with(path2); + assert!(ends_with == $ends_with, + "{:?}.ends_with({:?}), expected {:?}, got {:?}", $path1, $path2, + $ends_with, ends_with); + + let relative_from = path1.relative_from(path2).map(|p| p.to_str().unwrap()); + let exp: Option<&str> = $relative_from; + assert!(relative_from == exp, + "{:?}.relative_from({:?}), expected {:?}, got {:?}", $path1, $path2, + exp, relative_from); + }); + ); + + tc!("", "", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo", "", + eq: false, + starts_with: true, + ends_with: true, + relative_from: Some("foo") + ); + + tc!("", "foo", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("foo", "foo", + eq: true, + starts_with: true, + ends_with: true, + relative_from: Some("") + ); + + tc!("foo/", "foo", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some(".") + ); + + tc!("foo/bar", "foo", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("bar") + ); + + tc!("foo/bar/baz", "foo/bar", + eq: false, + starts_with: true, + ends_with: false, + relative_from: Some("baz") + ); + + tc!("foo/bar", "foo/bar/baz", + eq: false, + starts_with: false, + ends_with: false, + relative_from: None + ); + + tc!("./foo/bar/", ".", + eq: false, + starts_with: true, + ends_with: true, + relative_from: Some("foo/bar/") + ); + } +} diff --git a/src/libstd/sys/unix/os_str.rs b/src/libstd/sys/unix/os_str.rs index 3dd89ad0759..a2c93dea6a4 100644 --- a/src/libstd/sys/unix/os_str.rs +++ b/src/libstd/sys/unix/os_str.rs @@ -20,7 +20,7 @@ use str; use string::{String, CowString}; use mem; -#[derive(Clone)] +#[derive(Clone, Hash)] pub struct Buf { pub inner: Vec } diff --git a/src/libstd/sys/windows/os_str.rs b/src/libstd/sys/windows/os_str.rs index aab2406cef9..af94b56bf1f 100644 --- a/src/libstd/sys/windows/os_str.rs +++ b/src/libstd/sys/windows/os_str.rs @@ -18,7 +18,7 @@ use result::Result; use option::Option; use mem; -#[derive(Clone)] +#[derive(Clone, Hash)] pub struct Buf { pub inner: Wtf8Buf } diff --git a/src/test/run-pass/issue-15149.rs b/src/test/run-pass/issue-15149.rs index 1d18f33fd18..b37c71bc326 100644 --- a/src/test/run-pass/issue-15149.rs +++ b/src/test/run-pass/issue-15149.rs @@ -11,7 +11,7 @@ use std::slice::SliceExt; use std::old_io::{Command, fs, USER_RWX}; use std::os; -use std::path::BytesContainer; +use std::old_path::BytesContainer; use std::rand::random; fn main() { diff --git a/src/test/run-pass/issue-3424.rs b/src/test/run-pass/issue-3424.rs index 6647fbe2238..0d85f61e513 100644 --- a/src/test/run-pass/issue-3424.rs +++ b/src/test/run-pass/issue-3424.rs @@ -1,4 +1,3 @@ - // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. @@ -15,8 +14,8 @@ #![feature(box_syntax)] #![feature(unboxed_closures)] -use std::path::{Path}; -use std::path; +use std::old_path::{Path}; +use std::old_path; use std::result; use std::thunk::Thunk; @@ -28,7 +27,7 @@ fn tester() result::Result::Ok("more blah".to_string()) }; - let path = path::Path::new("blah"); + let path = old_path::Path::new("blah"); assert!(loader(&path).is_ok()); } diff --git a/src/test/run-pass/process-spawn-with-unicode-params.rs b/src/test/run-pass/process-spawn-with-unicode-params.rs index 5dcaa885e38..c6fd5527261 100644 --- a/src/test/run-pass/process-spawn-with-unicode-params.rs +++ b/src/test/run-pass/process-spawn-with-unicode-params.rs @@ -20,7 +20,7 @@ use std::old_io; use std::old_io::fs; use std::old_io::Command; use std::os; -use std::path::Path; +use std::old_path::Path; fn main() { let my_args = os::args(); -- cgit 1.4.1-3-g733a5 From 70ecd8ed38d5bedbeb281d78c3da44477764236a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 3 Feb 2015 16:39:27 -0800 Subject: Test fixes and rebase conflicts --- src/libstd/path.rs | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) (limited to 'src/libstd') diff --git a/src/libstd/path.rs b/src/libstd/path.rs index 3fd45ea6a7b..3f4f1ec4c0d 100755 --- a/src/libstd/path.rs +++ b/src/libstd/path.rs @@ -156,7 +156,7 @@ mod platform { None } - #[derive(Copy, Clone, Show, Hash, PartialEq, Eq)] + #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct Prefix<'a>; impl<'a> Prefix<'a> { @@ -177,9 +177,10 @@ mod platform { mod platform { use core::prelude::*; - use super::{Path, os_str_as_u8_slice, u8_slice_as_os_str}; - use ffi::OsStr; + use char::CharExt as UnicodeCharExt; + use super::{os_str_as_u8_slice, u8_slice_as_os_str}; use ascii::*; + use ffi::OsStr; #[inline] pub fn is_sep(b: u8) -> bool { @@ -299,7 +300,7 @@ mod platform { pub fn len(&self) -> usize { use self::Prefix::*; fn os_str_len(s: &OsStr) -> usize { - unsafe { os_str_as_u8_slice(s).len() } + os_str_as_u8_slice(s).len() } match *self { Verbatim(x) => 4 + os_str_len(x), @@ -339,12 +340,12 @@ mod platform { } } - impl<'a> ops::PartialEq for Prefix<'a> { + impl<'a> PartialEq for Prefix<'a> { fn eq(&self, other: &Prefix<'a>) -> bool { use self::Prefix::*; match (*self, *other) { (Verbatim(x), Verbatim(y)) => x == y, - (VerbatimUNC(x1, x2), Verbatim(y1, y2)) => x1 == y1 && x2 == y2, + (VerbatimUNC(x1, x2), VerbatimUNC(y1, y2)) => x1 == y1 && x2 == y2, (VerbatimDisk(x), VerbatimDisk(y)) => os_str_as_u8_slice(x).eq_ignore_ascii_case(os_str_as_u8_slice(y)), (DeviceNS(x), DeviceNS(y)) => x == y, @@ -457,7 +458,7 @@ fn split_file_at_dot(file: &OsStr) -> (Option<&OsStr>, Option<&OsStr>) { /// Going front to back, a path is made up of a prefix, a root component, a body /// (of normal components), and a suffix/emptycomponent (normalized `.` or `` /// for a path ending with the separator) -#[derive(Copy, Clone, PartialEq, PartialOrd, Show)] +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] enum State { Prefix = 0, // c: Root = 1, // / @@ -470,7 +471,7 @@ enum State { /// /// See the module documentation for an in-depth explanation of components and /// their role in the API. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Show)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Component<'a> { /// A Windows path prefix, e.g. `C:` or `\server\share` Prefix(&'a OsStr), @@ -2434,12 +2435,21 @@ mod tests { tfn!("foo", "bar", "bar"); tfn!("foo", "", ""); tfn!("", "foo", "foo"); - tfn!(".", "foo", "./foo"); - tfn!("foo/", "bar", "foo/bar"); - tfn!("foo/.", "bar", "foo/./bar"); - tfn!("..", "foo", "../foo"); - tfn!("foo/..", "bar", "foo/../bar"); - tfn!("/", "foo", "/foo"); + if cfg!(unix) { + tfn!(".", "foo", "./foo"); + tfn!("foo/", "bar", "foo/bar"); + tfn!("foo/.", "bar", "foo/./bar"); + tfn!("..", "foo", "../foo"); + tfn!("foo/..", "bar", "foo/../bar"); + tfn!("/", "foo", "/foo"); + } else { + tfn!(".", "foo", r".\foo"); + tfn!(r"foo\", "bar", r"foo\bar"); + tfn!(r"foo\.", "bar", r"foo\.\bar"); + tfn!("..", "foo", r"..\foo"); + tfn!(r"foo\..", "bar", r"foo\..\bar"); + tfn!(r"\", "foo", r"\foo"); + } } #[test] -- cgit 1.4.1-3-g733a5