Skip to content

Commit

Permalink
WIP #6: add absolute() method on PathArc, still need tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vitiral committed Mar 22, 2018
1 parent dcd6606 commit 081e130
Showing 1 changed file with 102 additions and 0 deletions.
102 changes: 102 additions & 0 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, PrefixComponent};
use std_prelude::*;

use super::{Error, Result};
Expand Down Expand Up @@ -146,6 +149,105 @@ 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
/// must exist.
///
/// This function will strip any `.` components (`/a/./c` -> `/a/c`)
/// as well as resolve leading `..` using [`env::current_dir()`]. However,
/// any `..` in the middle of the path will cause `io::ErrorKind::InvalidInput`
/// to be returned.
///
/// > On windows, this will always call `canonicalize()` on the first component
/// > to guarantee it is the canonicalized prefix.
///
/// [`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 got_nonparent = false;
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(_) | Component::RootDir => {
if cfg!(windows) {
// In windows we have to make sure the prefix is added to the root
// path.
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 cwd = PathArc::new(cwd).canonicalize()?;
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(_) | Component::RootDir => unreachable!(),
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

0 comments on commit 081e130

Please sign in to comment.