Skip to content

Commit

Permalink
Merge #11
Browse files Browse the repository at this point in the history
11: WIP #6: add absolute() method on PathArc, still need tests r=vitiral a=vitiral

This is a first attempt at implementing #6.
  • Loading branch information
bors[bot] committed Mar 22, 2018
2 parents dcd6606 + 365fda4 commit 4b0d8de
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 52 deletions.
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ branches:
# channels to enable unstable features when building for nightly. Or you could add additional
# matrix entries to test different combinations of features.
environment:
RUST_BACKTRACE: 1
matrix:

### MSVC Toolchains ###
Expand Down
4 changes: 2 additions & 2 deletions src/abs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std_prelude::*;
use super::{PathArc, PathDir, PathFile, Result};

#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
/// An absolute ([canonicalized][1]) path that is guaranteed (when created) to exist.
/// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist.
///
/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
pub struct PathAbs(pub(crate) PathArc);
Expand All @@ -31,7 +31,7 @@ impl PathAbs {
/// ```
pub fn new<P: AsRef<Path>>(path: P) -> Result<PathAbs> {
let arc = PathArc::new(path);
arc.canonicalize()
arc.absolute()
}

/// Resolve the `PathAbs` as a `PathFile`. Return an error if it is not a file.
Expand Down
168 changes: 166 additions & 2 deletions src/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

use std::fmt;
use std::fs;
use std::io;
use std::env;
use std::ffi::OsStr;
use std::path::{Component, Prefix};
use std_prelude::*;

use super::{Error, Result};
Expand Down Expand Up @@ -101,8 +104,6 @@ impl PathArc {
/// Returns the canonical form of the path with all intermediate components normalized and
/// symbolic links resolved.
///
/// > This is identical to `PathAbs::new(path)`.
///
/// This function is identical to [std::path::Path::canonicalize][0] except:
/// - It returns a `PathAbs` object
/// - It has error messages which include the action and the path
Expand Down Expand Up @@ -146,6 +147,148 @@ impl PathArc {
pub fn as_path(&self) -> &Path {
self.as_ref()
}

/// Convert the path to an absolute one, this is different from [`canonicalize`] in that it
/// _preserves_ symlinks. The destination may or may not exist.
///
/// This function will:
/// - Use [`env::current_dir()`] to resolve relative paths.
/// - Strip any `.` components (`/a/./c` -> `/a/c`)
/// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will
/// _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use
/// `canonicalize()` instead.
///
/// > On windows, this will always call `canonicalize()` on the first component to guarantee
/// > it is the correct canonicalized prefix.
///
/// > In linux, the only syscall this can make is to get the current_dir for relative paths.
///
/// [`canonicalize`]: struct.PathAbs.html#method.ca
/// [`env::current_dir()`]: https://doc.rust-lang.org/std/env/fn.current_dir.html
pub fn absolute(&self) -> Result<PathAbs> {
let mut components = self.components();
let mut stack: Vec<OsString> = Vec::new();

fn to_os(c: Component) -> OsString {
c.as_os_str().to_os_string()
}

macro_rules! pop_stack { [] => {{
if let None = stack.pop() {
return Err(Error::new(
io::Error::new(io::ErrorKind::NotFound, ".. consumed root"),
"resolving absolute",
self.clone(),
));
}
}}}

// Get a canonicalized path from "root"
{
loop {
let component = match components.next() {
None => break,
Some(c) => c,
};

match component {
Component::CurDir => {
// ignore
continue;
}
Component::Prefix(prefix) => {
assert!(!cfg!(unix), "Component::Prefix in unix");
match prefix.kind() {
Prefix::Disk(_) | Prefix::UNC(_, _) => {
// Make the prefix a more "standard" form
let c = PathArc::new(component.as_os_str()).canonicalize()?;
stack.extend(c.components().map(to_os));
}
_ => {
// Already in the "most standardized" form
// TODO: some more testing to make sure that canoninicalize()
// cannot be called on these forms would be good
//
// Maybe it can be called when _both_ prefix and root are included?
// i.e. `\\?\C:` is not enough, need
// `\\?\C:\` (??)
//
stack.push(to_os(component));
}
}
}
Component::RootDir => {
if cfg!(windows) {
// In windows we have to make sure the prefix is the correct form.
//
// DAMNIT WINDOWS:
// The filename, directory name, or volume label syntax is
// incorrect. (os error 123) when canonicalizing \
let c = PathArc::new(component.as_os_str()).canonicalize()?;
stack.extend(c.components().map(to_os));
} else {
stack.push(to_os(component));
}

}
Component::ParentDir | Component::Normal(_) => {
// First item is either a ParentDir or Normal, in either
// case we need to get current_dir
let cwd = env::current_dir().map_err(|e| {
Error::new(
e,
"getting current_dir while resolving absolute",
self.clone(),
)
})?;
let mut cwd_components = cwd.components();
if cfg!(windows) {
// For windows, canonicalize the first entry in current_dir
let root = PathArc::new(
cwd_components.next().expect("current_dir was empty").as_os_str(),
).canonicalize()?;
stack.extend(root.components().map(to_os));
}
stack.extend(cwd_components.map(to_os));

match component {
Component::ParentDir => pop_stack!(),
Component::Normal(_) => stack.push(to_os(component)),
_ => unreachable!(),
}
}
}
break;
}
}

for component in components {
match component {
Component::CurDir => { /* ignore, probably impossible */ }
Component::Prefix(_) => unreachable!(),
Component::RootDir => {
if cfg!(unix) {
unreachable!("root is already handled on unix");
}
// This is actually possible on windows because root is distinct
// from prefix (?)
stack.push(to_os(component));
}
Component::ParentDir => pop_stack!(),
Component::Normal(_) => stack.push(to_os(component)),
}
}

if stack.is_empty() {
return Err(Error::new(
io::Error::new(io::ErrorKind::NotFound, "resolving resulted in empty path"),
"resolving absolute",
self.clone(),
));
}

Ok(PathAbs(PathArc(Arc::new(PathBuf::from_iter(stack)))))
}
}

impl fmt::Debug for PathArc {
Expand Down Expand Up @@ -230,3 +373,24 @@ impl Into<PathBuf> for PathArc {
}
}
}

#[test]
fn test_prefix_windows() {
fn f<P: AsRef<Path>>(p: P) -> Result<PathAbs> {
PathArc::new(p).absolute()
}
assert!(f(r"\\?\C:\blah\blah").is_ok());
assert!(f(r"\blah\blah").is_ok());
assert!(f(r"C:\blah\blah").is_ok());

// TODO: this is how to get the hostname, but getting the "share name"
// seems to be more difficult.
// let hostname = ::std::process::Command::new("hostname")
// .output()
// .expect("could not get hostname")
// .stdout;
// let hostname = ::std::str::from_utf8(&hostname).unwrap().trim();

// assert!(f(format!(r"\\{}\share", hostname)).is_ok());
// assert!(f(format!(r"\\?\UNC\{}\share", hostname)).is_ok());
}
38 changes: 22 additions & 16 deletions src/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ impl PathDir {
///
/// If the path is actually a file returns `io::ErrorKind::InvalidInput`.
///
/// > This does not call [`Path::cannonicalize()`][1], instead trusting that the input is
/// > already a fully qualified path.
///
/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
Expand Down Expand Up @@ -172,10 +167,6 @@ impl PathDir {

/// List the contents of the directory, returning an iterator of `PathType`s.
///
/// > **Warning**: because `PathAbs` is the canonicalized path, symlinks are always resolved.
/// > This means that if the directory contains a symlink you may get a path from a completely
/// > _different directory_.
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
Expand Down Expand Up @@ -287,28 +278,43 @@ impl PathDir {
/// let dir = PathDir::create(example)?;
/// let file = PathFile::create(dir.join("example.txt"))?;
///
/// dir.symlink(example_sym)?;
/// let dir_sym = PathDir::new(example_sym)?;
/// let dir_sym = dir.symlink(example_sym)?;
///
/// // They have a different "absolute path"
/// assert_ne!(dir, dir_sym);
///
/// // But they can be canonicalized to the same file.
/// let dir_can = dir_sym.canonicalize()?;
/// assert_eq!(dir, dir_can);
///
/// // They are canonicalized to the same file.
/// assert_eq!(dir, dir_sym);
/// # Ok(()) } fn main() { try_main().unwrap() }
/// ```
pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<()> {
pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<PathDir> {
symlink_dir(&self, &dst).map_err(|err| {
Error::new(
err,
&format!("linking to {} from", dst.as_ref().display()),
&format!("linking from {} to", dst.as_ref().display()),
self.clone().into(),
)
})
})?;
PathDir::new(dst)
}

/// Return a reference to a basic `std::path::Path`
pub fn as_path(&self) -> &Path {
self.as_ref()
}

/// Returns the canonical form of the path with all intermediate components normalized and
/// symbolic links resolved.
///
/// See [`PathAbs::canonicalize`]
///
/// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize
pub fn canonicalize(&self) -> Result<PathDir> {
Ok(PathDir(self.0.canonicalize()?))
}

/// Create a mock dir type. *For use in tests only*.
///
/// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
Expand Down
2 changes: 1 addition & 1 deletion src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl FileEdit {
Ok(FileEdit(FileOpen::open(path, options)?))
}

/// Shortcut to open the file if the path is already canonicalized.
/// Shortcut to open the file if the path is already absolute.
pub(crate) fn open_path(path: PathFile, mut options: fs::OpenOptions) -> Result<FileEdit> {
options.write(true);
options.read(true);
Expand Down
33 changes: 21 additions & 12 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ impl PathFile {
///
/// If the path is actually a dir returns `io::ErrorKind::InvalidInput`.
///
/// > This does not call [`Path::cannonicalize()`][1], instead trusting that the input is
/// > already a fully qualified path.
///
/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
Expand Down Expand Up @@ -375,21 +370,25 @@ impl PathFile {
///
/// let contents = "This is some contents";
/// file.write_str(contents);
/// file.symlink(example_sym)?;
/// let file_sym = PathFile::new(example_sym)?;
/// let file_sym = file.symlink(example_sym)?;
///
/// // They are canonicalized to the same file.
/// assert_eq!(file, file_sym);
/// // They have a different "absolute path"
/// assert_ne!(file, file_sym);
///
/// // But they can be canonicalized to the same file.
/// let file_can = file_sym.canonicalize()?;
/// assert_eq!(file, file_can);
/// # Ok(()) } fn main() { try_main().unwrap() }
/// ```
pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<()> {
pub fn symlink<P: AsRef<Path>>(&self, dst: P) -> Result<PathFile> {
symlink_file(&self, &dst).map_err(|err| {
Error::new(
err,
&format!("linking to {} from", dst.as_ref().display()),
&format!("linking from {} to", dst.as_ref().display()),
self.clone().into(),
)
})
})?;
PathFile::new(dst)
}

/// Remove (delete) the file from the filesystem, consuming self.
Expand Down Expand Up @@ -424,6 +423,16 @@ impl PathFile {
self.as_ref()
}

/// Returns the canonical form of the path with all intermediate components normalized and
/// symbolic links resolved.
///
/// See [`PathAbs::canonicalize`]
///
/// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize
pub fn canonicalize(&self) -> Result<PathFile> {
Ok(PathFile(self.0.canonicalize()?))
}

/// Create a mock file type. *For use in tests only*.
///
/// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
Expand Down
13 changes: 6 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@
//! - [`PathArc`](struct.PathArc.html): a reference counted `PathBuf` with methods reimplemented
//! with better error messages. Use this for a generic serializable path that may or may
//! not exist.
//! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (canonicalized) path that is
//! guaranteed (on initialization) to exist.
//! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed to be a file, with
//! associated methods.
//! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed to be a directory, with
//! associated methods.
//! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_
//! canonicalized) path that is not necessarily guaranteed to exist.
//! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to
//! exist and be a file, with associated methods.
//! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist
//! and be a directory, with associated methods.
//! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir.
//! Returned by [`PathDir::list`][dir_list]
//!
Expand Down Expand Up @@ -372,7 +372,6 @@ mod tests {
}

#[test]

/// Tests to make sure the error messages look like we expect.
fn sanity_errors() {
let tmp_dir = TempDir::new("example").expect("create temp dir");
Expand Down
Loading

0 comments on commit 4b0d8de

Please sign in to comment.