Repository: vitiral/path_abs (language: rust)

Issue:
PathAbs constructor cleanup
Currently, there are a bunch of different ways to construct a `PathAbs`:

- `PathArc::absolute()` constructs a `PathAbs` using lexical canonicalization
- `PathAbs::new()` has a docstring that implies it uses `std::fs::canonicalize()` but actually calls `PathArc::absolute()`.
- `PathAbs::mock()` constructs a `PathAbs` but doesn't actually validate its absoluteness

I think these could be cleaned up/rationalized. For example:

- `PathAbs::new()` should take a path and validate that it conforms to the rules for absolute paths, returning an error if something's wrong
    - Normally `new()` is for the most commonly-used constructor, but I'm not sure any of them are obviously the best
    - "validate in `new()`" follows standard library types like `std::ptr::NonNull`
- `PathAbs::new_with_canonicalization()` should take a path and call `std::fs::canonicalize()` on it
    - Not currently a supported constructor, but if it's in the standard library people will be surprised if this crate doesn't support it at all
- `PathAbs::new_with_parents_removed()` should resolve `..` components by removing the previous component, like `PathArc::absolute()` does today
- Delete `PathAbs::mock()`
    - somebody who wants a path for testing can use [the tempfile crate](https://crates.io/crates/tempfile)
    - if this method is really needed, we can rename it to `PathAbs::new_unchecked()` after the model of `std::ptr::NonNull`
- Delete `PathArc::absolute()`
    - `PathArc` is kind of a behind-the-scenes type (see #18) and doesn't need any more attention drawn to it
    - people who want to turn a `PathArc` into a `PathAbs` can use one of the direct constructors

This would also provide a natural place for `PathAbs::new_with_parents_resolved()` to slot in, from #20.




Discussion / hints:
I agree with a lot of this. Do note that releasing this will require a major version bump since it is breaking backwards compatibility.

Some comments:
- Delete `PathArc::absolute()`: the reason this exists is because `Path::absolute` exists and this overrides it. If we remove `Deref` then it might not be an issue anymore. I need to think more about the part which `PathArc` and `Deref` plays.
- `PathAbs::new()`: the docstring should be fixed for sure. I'm not sure if throwing an error if it isn't already an absolute path is the best thing here. I need to think about it more, but I'm leaning towards just fixing the docsftring.
- `PathAbs::new_with_canonicalization()`: do you think this can be named `PathAbs::canonicalized`? This would be similar verbiage to the `sorted` std function for python. I'm not sure if there is equivalents in rust.
- `PathAbs::new_with_parents_removed()`: I agree this constructor should exist but I'm not sure about the name. Some ideas: `PathAbs::walked`, `PathAbs::resolved`. `resolved` would kinda-sorta follow the [python function](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve), although that also resolves symlinks (but has `strict=False` behavior which allows non-existent paths... it's weird). The idea is that it differs from `PathAbs::new` by "resolving" `..` elements instead of removing them syntactically. Maybe calling it `PathAbs::parents_resolved` would be okay as well.
- `PathAbs::mock()`: I'm okay naming this `new_unchecked()`. I think providing a way to unit test without filesystem calls is important to this library, and `new_unchecked` would also let us get some performance gains for internal methods.

Also I use `PathArc` quite a bit for it's serialization abilities, especially when communicating with webapps. I'm not sure what the right thing to do here is. Note that the webapp use case does NOT want to actually resolve any paths, so keeping everything as `PathArc` is better.

It's possible that serialization in general is an overloaded feature of this library and should be split into it's own library (`path_ser` maybe?). I'm not totally sure but it's at least worth considering.
> Do note that releasing this will require a major version bump since it is breaking backwards compatibility.

Of course! I understand this is a big change, and compatibility must be considered too.

> `PathAbs::new()` ... I'm not sure if throwing an error if it isn't already an absolute path is the best thing here.

I wanted to rename `mock()` to `new_unchecked()`, which implies that `new()` does some kind of checking, so I figured "validate this is an absolute path" would be the least surprising thing to check. That said, `String::new()` allocates an empty string rather than taking a `&[u8]` and checking for UTF-8 encoding... I thought allocating an empty path would be weird, but that's exactly what `PathBuf::new()` does, so there's precedent.

Of course, an empty *absolute* path doesn't make much sense, but the absolute version of an empty path is probably `std::env::current_dir()` which would actually be a somewhat useful default.

> `PathAbs::new_with_canonicalization()`: do you think this can be named `PathAbs::canonicalized`?

Possibly, but constructors in Rust tend to be named `with_foo()` or `from_bar()` or [similar](https://rust-lang-nursery.github.io/api-guidelines/predictability.html#constructors-are-static-inherent-methods-c-ctor), and I was trying to come up with a name that fit that scheme.

> `PathAbs::new_with_parents_removed()`: I agree this constructor should exist but I'm not sure about the name.

To be clear, I wanted to use this name for the current behaviour of `PathAbs::new()`, lexically resolving parent references. The other algorithm I worked on (resolving symlinks followed by `..`) would be `PathAbs::new_with_parents_resolved()`. Hopefully, somebody looking at "parents removed" and "parents resolved" next to each other in the documentation would get a big hint about how those two similar methods are different, and why somebody might choose one over the other.

> It's possible that serialization in general is an overloaded feature of this library and should be split into it's own library (path_ser maybe?).

I'm 100% on board with having serialisation support in `path_abs`. For one thing, it's [a recommendation in the Rust API guidelines](https://rust-lang-nursery.github.io/api-guidelines/interoperability.html#data-structures-implement-serdes-serialize-deserialize-c-serde), and for another I've used crates where serialisation is a feature in a separate crate (specifically `url` and `url_serde`) and it was a pain. 

Fix patch:
diff --git a/src/abs.rs b/src/abs.rs
index d8ff1f7..f000c42 100644
--- a/src/abs.rs
+++ b/src/abs.rs
@@ -156,51 +156,21 @@ impl PathAbs {
         Ok(PathAbs(Arc::new(res)))
     }
 
-    /// Resolve the `PathAbs` as a `PathFile`. Return an error if it is not a file.
-    pub fn into_file(self) -> Result<PathFile> {
-        PathFile::from_abs(self)
-    }
-
-    /// Resolve the `PathAbs` as a `PathDir`. Return an error if it is not a directory.
-    pub fn into_dir(self) -> Result<PathDir> {
-        PathDir::from_abs(self)
+    /// Create a PathAbs unchecked.
+    ///
+    /// This is mostly used for constructing during tests, or if the path was previously validated.
+    /// This is effectively the same as a `Arc<PathBuf>`.
+    ///
+    /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause
+    /// > panics in some methods if the path was not properly validated.
+    pub fn new_unchecked<P: Into<Arc<PathBuf>>>(path: P) -> PathAbs {
+        PathAbs(path.into())
     }
 
     /// Return a reference to a basic `std::path::Path`
     pub fn as_path(&self) -> &Path {
         self.as_ref()
     }
-
-    /// For constructing mocked paths during tests. This is effectively the same as a `PathBuf`.
-    ///
-    /// This is NOT checked for validity so the file may or may not actually exist and will
-    /// NOT be, in any way, an absolute or canonicalized path.
-    ///
-    /// # Examples
-    /// ```rust
-    /// # extern crate path_abs;
-    /// use path_abs::{PathAbs, PathInfo};
-    ///
-    /// # fn try_main() -> ::std::io::Result<()> {
-    /// // this file exist
-    /// let lib = PathAbs::new("src/lib.rs")?;
-    ///
-    /// let lib_mocked = PathAbs::mock("src/lib.rs");
-    ///
-    /// // in this case, the mocked file exists
-    /// assert!(lib_mocked.exists());
-    ///
-    /// // However, it is NOT equivalent to `lib`
-    /// assert_ne!(lib, lib_mocked);
-    ///
-    /// // this file doesn't exist at all
-    /// let dne = PathAbs::mock("src/dne.rs");
-    /// assert!(!dne.exists());
-    /// # Ok(()) } fn main() { try_main().unwrap() }
-    /// ```
-    pub fn mock<P: AsRef<Path>>(fake_path: P) -> PathAbs {
-        PathAbs(Arc::new(fake_path.as_ref().to_path_buf()))
-    }
 }
 
 impl fmt::Debug for PathAbs {
@@ -213,8 +183,8 @@ impl PathMut for PathAbs {
     fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
         self.0.append(path)
     }
-    fn truncate_to_parent(&mut self) -> Result<()> {
-        self.0.truncate_to_parent()
+    fn pop_up(&mut self) -> Result<()> {
+        self.0.pop_up()
     }
     fn truncate_to_root(&mut self) {
         self.0.truncate_to_root()
@@ -243,6 +213,12 @@ impl PathOps for PathAbs {
     }
 }
 
+impl AsRef<ffi::OsStr> for PathAbs {
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        self.0.as_ref().as_ref()
+    }
+}
+
 impl AsRef<Path> for PathAbs {
     fn as_ref(&self) -> &Path {
         self.0.as_ref()
diff --git a/src/dir.rs b/src/dir.rs
index 44a4e40..1bbcb2b 100644
--- a/src/dir.rs
+++ b/src/dir.rs
@@ -35,7 +35,18 @@ impl PathDir {
     /// ```
     pub fn new<P: AsRef<Path>>(path: P) -> Result<PathDir> {
         let abs = PathAbs::new(path)?;
-        PathDir::from_abs(abs)
+        PathDir::try_from(abs)
+    }
+
+    /// Create a `PathDir` unchecked.
+    ///
+    /// This is mostly used for constructing during tests, or if the path was previously validated.
+    /// This is effectively the same as a `Arc<PathBuf>`.
+    ///
+    /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause
+    /// > panics in some methods if the path was not properly validated.
+    pub fn new_unchecked<P: Into<Arc<PathBuf>>>(path: P) -> PathDir {
+        PathDir(PathAbs::new_unchecked(path))
     }
 
     /// Returns the current working directory from the `env` as a `PathDir`.
@@ -74,12 +85,13 @@ impl PathDir {
     ///
     /// # fn try_main() -> ::std::io::Result<()> {
     /// let src_abs = PathAbs::new("src")?;
-    /// let src_dir = PathDir::from_abs(src_abs)?;
+    /// let src_dir = PathDir::try_from(src_abs)?;
     /// # Ok(()) } fn main() { try_main().unwrap() }
     /// ```
-    pub fn from_abs(abs: PathAbs) -> Result<PathDir> {
+    pub fn try_from<P: Into<PathAbs>>(path: P) -> Result<PathDir> {
+        let abs = path.into();
         if abs.is_dir() {
-            Ok(PathDir::from_abs_unchecked(abs))
+            Ok(PathDir::new_unchecked(abs))
         } else {
             Err(Error::new(
                 io::Error::new(io::ErrorKind::InvalidInput, "path is not a dir"),
@@ -89,15 +101,6 @@ impl PathDir {
         }
     }
 
-    #[inline(always)]
-    /// Do the conversion _without checking_.
-    ///
-    /// This is typically used by external libraries when the type is already known
-    /// through some other means (to avoid a syscall).
-    pub fn from_abs_unchecked(abs: PathAbs) -> PathDir {
-        PathDir(abs)
-    }
-
     /// Instantiate a new `PathDir` to a directory, creating the directory if it doesn't exist.
     ///
     /// # Examples
@@ -349,13 +352,6 @@ impl PathDir {
             Err(_) => None,
         }
     }
-
-    /// Create a mock dir type. *For use in tests only*.
-    ///
-    /// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
-    pub fn mock<P: AsRef<Path>>(path: P) -> PathDir {
-        PathDir(PathAbs::mock(path))
-    }
 }
 
 /// An iterator over `PathType` objects, returned by `PathDir::list`.
@@ -392,6 +388,12 @@ impl fmt::Debug for PathDir {
     }
 }
 
+impl AsRef<ffi::OsStr> for PathDir {
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        self.0.as_ref()
+    }
+}
+
 impl AsRef<PathAbs> for PathDir {
     fn as_ref(&self) -> &PathAbs {
         &self.0
diff --git a/src/file.rs b/src/file.rs
index 9b015c1..7197c7a 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -34,36 +34,24 @@ impl PathFile {
     /// ```
     pub fn new<P: AsRef<Path>>(path: P) -> Result<PathFile> {
         let abs = PathAbs::new(path)?;
-        PathFile::from_abs(abs)
+        PathFile::try_from(abs)
     }
 
-    /// Get the parent directory of this file as a `PathDir`.
+    /// Create a `PathFile` unchecked.
     ///
-    /// > This does not make aditional syscalls, as the parent by definition must be a directory
-    /// > and exist.
+    /// This is mostly used for constructing during tests, or if the path was previously validated.
+    /// This is effectively the same as a `Arc<PathBuf>`.
     ///
-    /// # Examples
-    /// ```rust
-    /// # extern crate path_abs;
-    /// use path_abs::{PathDir, PathFile};
-    ///
-    /// # fn try_main() -> ::std::io::Result<()> {
-    /// let lib = PathFile::new("src/lib.rs")?;
-    /// let src = lib.parent_dir().unwrap();
-    /// assert_eq!(PathDir::new("src")?, src);
-    /// # Ok(()) } fn main() { try_main().unwrap() }
-    /// ```
-    pub fn parent_dir(&self) -> Option<PathDir> {
-        match self.parent() {
-            Ok(path) => Some(PathDir(PathAbs(Arc::new(path.to_path_buf())))),
-            Err(_) => None,
-        }
+    /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause
+    /// > panics in some methods if the path was not properly validated.
+    pub fn new_unchecked<P: Into<Arc<PathBuf>>>(path: P) -> PathFile {
+        PathFile(PathAbs::new_unchecked(path))
     }
 
-    /// Consume the `PathAbs` validating that the path is a file and returning `PathFile`. The file
-    /// must exist or `io::Error` will be returned.
+    /// Convert a `PathAbs` into a `PathFile`, first validating that the path is a file.
     ///
-    /// If the path is actually a dir returns `io::ErrorKind::InvalidInput`.
+    /// # Error
+    /// If the path is not a file.
     ///
     /// # Examples
     /// ```rust
@@ -72,12 +60,13 @@ impl PathFile {
     ///
     /// # fn try_main() -> ::std::io::Result<()> {
     /// let lib_abs = PathAbs::new("src/lib.rs")?;
-    /// let lib_file = PathFile::from_abs(lib_abs)?;
+    /// let lib_file = PathFile::try_from(lib_abs)?;
     /// # Ok(()) } fn main() { try_main().unwrap() }
     /// ```
-    pub fn from_abs(abs: PathAbs) -> Result<PathFile> {
+    pub fn try_from<P: Into<PathAbs>>(path: P) -> Result<PathFile> {
+        let abs = path.into();
         if abs.is_file() {
-            Ok(PathFile::from_abs_unchecked(abs))
+            Ok(PathFile::new_unchecked(abs))
         } else {
             Err(Error::new(
                 io::Error::new(io::ErrorKind::InvalidInput, "path is not a file"),
@@ -87,13 +76,29 @@ impl PathFile {
         }
     }
 
-    #[inline(always)]
-    /// Do the conversion _without checking_.
+    /// Get the parent directory of this file as a `PathDir`.
+    ///
+    /// > This does not make aditional syscalls, as the parent by definition must be a directory
+    /// > and exist.
     ///
-    /// This is typically used by external libraries when the type is already known
-    /// through some other means (to avoid a syscall).
-    pub fn from_abs_unchecked(abs: PathAbs) -> PathFile {
-        PathFile(abs)
+    /// # Panics
+    /// Panics if there is no parent. The only way this could happen is if
+    /// it was constructed with `new_unchecked` using a relative path.
+    ///
+    /// # Examples
+    /// ```rust
+    /// # extern crate path_abs;
+    /// use path_abs::{PathDir, PathFile};
+    ///
+    /// # fn try_main() -> ::std::io::Result<()> {
+    /// let lib = PathFile::new("src/lib.rs")?;
+    /// let src = lib.parent_dir();
+    /// assert_eq!(PathDir::new("src")?, src);
+    /// # Ok(()) } fn main() { try_main().unwrap() }
+    /// ```
+    pub fn parent_dir(&self) -> PathDir {
+        let path = self.parent().expect("PathFile did not have a parent.");
+        PathDir::new_unchecked(PathAbs::new_unchecked(path.to_path_buf()))
     }
 
     /// Instantiate a new `PathFile`, creating an empty file if it doesn't exist.
@@ -456,13 +461,6 @@ impl PathFile {
     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)
-    pub fn mock<P: AsRef<Path>>(path: P) -> PathFile {
-        PathFile(PathAbs::mock(path))
-    }
 }
 
 impl fmt::Debug for PathFile {
@@ -471,6 +469,12 @@ impl fmt::Debug for PathFile {
     }
 }
 
+impl AsRef<ffi::OsStr> for PathFile {
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        self.0.as_ref()
+    }
+}
+
 impl AsRef<PathAbs> for PathFile {
     fn as_ref(&self) -> &PathAbs {
         &self.0
diff --git a/src/lib.rs b/src/lib.rs
index 110a92f..c13d620 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,10 +13,11 @@
 //! This includes:
 //!
 //! - Cleaner _absolute_ paths (which is distinct from canonicalized paths).
-//! - Improved error messages, see the **Better Errors** section.
-//! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a certain
-//!   type. Obviously a file/dir can be deleted/changed by another process.
-//! - More stringent mutability requirements. See the **Differing Method Signatures** section.
+//! - Improved error messages, see the [Better Errors](#better-errors) section.
+//! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a
+//!   certain type. Obviously a file/dir can be deleted/changed by another process.
+//! - More stringent mutability requirements. See the
+//!   [Differing Method Signatures](#differing-method-signatures) section.
 //! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem
 //!   operations and allows more flexibility and ergonomics in the library for relatively low cost.
 //!
@@ -180,12 +181,8 @@
 //!     ?
 //!     .unwrap_file();
 //!
-//! // Or use `PathAbs::into_file`
-//! let file2 = abs.clone().into_file()?;
-//!
 //! assert!(abs.is_file());
 //! assert!(file.is_file());
-//! assert!(file2.is_file());
 //!
 //! // ----------------------------------
 //! // Opening a File
@@ -225,7 +222,7 @@ use std::ffi;
 use std::fmt;
 use std::fs;
 use std::io;
-use std::path;
+use std::path::{self, Component, Components};
 use std_prelude::*;
 
 mod abs;
@@ -411,7 +408,11 @@ pub trait PathInfo {
         Path::extension(self.as_path())
     }
 
-    fn components(&self) -> path::Components<'_> {
+    fn join<P: AsRef<Path>>(&self, path: P) -> PathBuf {
+        Path::join(self.as_path(), path)
+    }
+
+    fn components(&self) -> Components<'_> {
         Path::components(self.as_path())
     }
 
@@ -507,6 +508,18 @@ pub trait PathInfo {
     }
 }
 
+// impl<T> PathInfo for T
+// where
+//     T: AsRef<Path>
+// {
+//     fn as_path(&self) -> &Path {
+//         PathBuf::as_path(self.borrow())
+//     }
+//     fn to_arc_pathbuf(&self) -> Arc<PathBuf> {
+//         self.clone().into()
+//     }
+// }
+
 impl<T> PathInfo for T
 where
     T: Clone + Borrow<PathBuf> + Into<Arc<PathBuf>>,
@@ -532,7 +545,7 @@ impl PathInfo for Path {
 ///
 /// These methods are not implemented for all `path_abs` types because they
 /// may break the type's invariant. For example, if you could call
-/// `truncate_to_parent()` on a `PathFile`, it would no longer be the path to
+/// `pop_up()` on a `PathFile`, it would no longer be the path to
 /// a file, but the path to a directory.
 ///
 /// As a general rule, methods that can return an error will return a rich
@@ -545,33 +558,40 @@ pub trait PathMut: PathInfo {
     /// like [`PathBuf::push`], so absolute paths won't wholly replace the
     /// current path.
     ///
+    /// `..` components are resolved using [`pop_up`], which can consume components
+    /// on `self`
+    ///
     /// # Errors
     ///
-    /// This method returns an error if the `path` parameter contains a prefix
-    /// component (on Windows, like `C:`), or enough `..` components to consume
-    /// this path.
+    /// This method returns an error if the result would try to go outside a filesystem root,
+    /// like `/` on Unix or `C:\` on Windows.
     ///
     /// # Example
     ///
     /// ```rust
-    /// use std::path::Path;
+    /// use std::path::PathBuf;
     /// use path_abs::PathMut;
     ///
-    /// let mut somepath = Path::new("foo").to_path_buf();
+    /// let mut somepath = PathBuf::from("foo");
     /// somepath.append("bar");
     ///
-    /// assert_eq!(somepath.as_path(), Path::new("foo/bar"));
+    /// assert_eq!(somepath, PathBuf::from("foo/bar"));
     /// ```
     ///
+    /// [`pop_up`]: trait.PathMut.html#method.pop_up
     /// [`PathBuf::push`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.push
     fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()>;
 
-    /// Removes the last component of this path.
+    /// Go "up" one directory.
+    ///
+    /// This removes the last component of this path. It also resolves any `..` that exist at the
+    /// _end_ of the path until a real item can be truncated. If the path is relative, and no
+    /// items remain then a `..` is appended to the path.
     ///
     /// # Errors
     ///
-    /// This method returns an error if this path has no parent, i.e. it
-    /// represents a filesystem root, like `/` on Unix or `C:\` on Windows.
+    /// This method returns an error if the result would try to go outside a filesystem root,
+    /// like `/` on Unix or `C:\` on Windows.
     ///
     /// # Example
     ///
@@ -582,12 +602,41 @@ pub trait PathMut: PathInfo {
     ///
     /// let executable = Path::new("/usr/loca/bin/myapp");
     /// let mut install_path = executable.to_path_buf();
-    /// install_path.truncate_to_parent()?;
+    /// install_path.pop_up()?;
     ///
     /// assert_eq!(install_path.as_path(), Path::new("/usr/local/bin"));
     /// # Ok(()) }
     /// ```
-    fn truncate_to_parent(&mut self) -> Result<()>;
+    ///
+    /// Example handling weird relative paths
+    ///
+    /// ```rust
+    /// # fn example() -> Result<(), path_abs::Error> {
+    /// use std::path::Path;
+    /// use path_abs::PathMut;
+    ///
+    /// let executable = Path::new("../../weird/../relative/path/../../");
+    /// let mut install_path = executable.to_path_buf();
+    /// install_path.pop_up()?;
+    ///
+    /// assert_eq!(install_path.as_path(), Path::new("../../../"));
+    /// # Ok(()) }
+    /// ```
+    ///
+    /// Error use case
+    ///
+    /// ```rust
+    /// # fn example() -> Result<(), path_abs::Error> {
+    /// use std::path::Path;
+    /// use path_abs::PathMut;
+    ///
+    /// let tmp = Path::new("/tmp");
+    /// let mut relative = tmp.to_path_buf();
+    /// relative.pop_up()?;
+    /// assert!(relative.pop_up().is_err());
+    /// # Ok(()) }
+    /// ```
+    fn pop_up(&mut self) -> Result<()>;
 
     /// Removes all components after the root, if any.
     ///
@@ -597,13 +646,13 @@ pub trait PathMut: PathInfo {
     /// # Example
     ///
     /// ```no_run
-    /// use std::path::Path;
+    /// use std::path::PathBuf;
     /// use path_abs::PathMut;
     ///
-    /// let mut somepath = Path::new(r"C:\foo\bar").to_path_buf();
+    /// let mut somepath = PathBuf::from(r"C:\foo\bar");
     /// somepath.truncate_to_root();
     ///
-    /// assert_eq!(somepath.as_path(), Path::new(r"C:\"));
+    /// assert_eq!(somepath, PathBuf::from(r"C:\"));
     /// ```
     fn truncate_to_root(&mut self);
 
@@ -616,33 +665,75 @@ impl PathMut for PathBuf {
     fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
         for each in path.as_ref().components() {
             match each {
-                path::Component::Normal(c) => self.push(c),
-                path::Component::CurDir => (), // "." does nothing
-                path::Component::Prefix(_) => {
+                Component::Normal(c) => self.push(c),
+                Component::CurDir => (), // "." does nothing
+                Component::Prefix(_) => {
                     return Err(Error::new(
                         io::Error::new(io::ErrorKind::Other, "appended path has a prefix"),
                         "appending path",
                         path.as_ref().to_path_buf().into(),
                     ));
                 }
-                path::Component::RootDir => (), // leading "/" does nothing
-                path::Component::ParentDir => self.truncate_to_parent()?,
+                Component::RootDir => (), // leading "/" does nothing
+                Component::ParentDir => self.pop_up()?,
             }
         }
 
         Ok(())
     }
 
-    fn truncate_to_parent(&mut self) -> Result<()> {
+    fn pop_up(&mut self) -> Result<()> {
+        /// Pop off the parent components and return how
+        /// many were removed.
+        fn pop_parent_components(p: &mut PathBuf) -> usize {
+            let mut cur_dirs: usize = 0;
+            let mut parents: usize = 0;
+            let mut components = p.components();
+            while let Some(c) = components.next_back() {
+                match c {
+                    Component::CurDir => cur_dirs += 1,
+                    Component::ParentDir => parents += 1,
+                    _ => break,
+                }
+            }
+            for _ in 0..(cur_dirs+parents) {
+                p.pop();
+            }
+            parents
+        }
+
+        let mut ending_parents = 0;
+        loop {
+            ending_parents += pop_parent_components(self);
+            if ending_parents == 0 || self.file_name().is_none() {
+                break;
+            } else {
+                // we have at least one "parent" to consume
+                self.pop();
+                ending_parents -= 1;
+            }
+        }
+
         if self.pop() {
-            Ok(())
-        } else {
-            Err(Error::new(
-                io::Error::new(io::ErrorKind::NotFound, "path has no parent"),
+            // do nothing, success
+        } else if self.has_root() {
+            // We tried to pop off the root
+            return Err(Error::new(
+                io::Error::new(io::ErrorKind::NotFound, "cannot get parent of root path"),
                 "truncating to parent",
                 self.clone().into(),
-            ))
+            ));
+        } else {
+            // we are creating a relative path, `"../"`
+            self.push("..")
+        }
+
+        // Put all unhandled parents back, creating a relative path.
+        for _ in 0..ending_parents {
+            self.push("..")
         }
+
+        Ok(())
     }
 
     fn truncate_to_root(&mut self) {
@@ -650,7 +741,7 @@ impl PathMut for PathBuf {
         for component in self.components().take(2) {
             match component {
                 // We want to keep prefix and RootDir components of this path
-                path::Component::Prefix(_) | path::Component::RootDir => res.push(component),
+                Component::Prefix(_) | Component::RootDir => res.push(component),
                 // We want to discard all other components.
                 _ => break,
             }
@@ -673,8 +764,8 @@ impl PathMut for Arc<PathBuf> {
     fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
         Arc::make_mut(self).append(path)
     }
-    fn truncate_to_parent(&mut self) -> Result<()> {
-        Arc::make_mut(self).truncate_to_parent()
+    fn pop_up(&mut self) -> Result<()> {
+        Arc::make_mut(self).pop_up()
     }
     fn truncate_to_root(&mut self) {
         Arc::make_mut(self).truncate_to_root()
@@ -709,13 +800,12 @@ pub trait PathOps: PathInfo {
     ///
     /// Note that this method represents pure concatenation, not "adjoinment"
     /// like [`PathBuf::join`], so absolute paths won't wholly replace the
-    /// current path.
+    /// current path. See [`append`] for more information about how it works.
     ///
     /// # Errors
     ///
-    /// This method returns an error if the `path` parameter contains a prefix
-    /// component (on Windows, like `C:`), or enough `..` components to consume
-    /// this path.
+    /// This method returns an error if the result would try to go outside a filesystem root,
+    /// like `/` on Unix or `C:\` on Windows.
     ///
     /// # Example
     ///
@@ -736,6 +826,7 @@ pub trait PathOps: PathInfo {
     /// }
     /// ```
     ///
+    /// [`append`]: trait.PathMut.html#method.append
     /// [`PathBuf::join`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.join
     fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output>;
 
@@ -756,28 +847,70 @@ pub trait PathOps: PathInfo {
     fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output;
 }
 
-impl PathOps for PathBuf {
+// impl<T> PathOps for T
+// where
+//     T: PathInfo
+//
+// {
+//     type Output = PathBuf;
+//
+//     fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
+//         let mut res = self.as_ref().to_owned();
+//         res.append(path)?;
+//         Ok(res)
+//     }
+//
+//     fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
+//         let mut res = self.as_ref().to_owned();
+//         res.set_file_name(file_name);
+//         res
+//     }
+//
+//     fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
+//         let mut res = self.as_ref().to_owned();
+//         res.set_extension(extension);
+//         res
+//     }
+// }
+
+impl PathOps for Path {
     type Output = PathBuf;
 
     fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
-        let mut res = self.clone();
+        let mut res = self.to_owned();
         res.append(path)?;
         Ok(res)
     }
 
     fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
-        let mut res = self.clone();
+        let mut res = self.to_owned();
         res.set_file_name(file_name);
         res
     }
 
     fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
-        let mut res = self.clone();
+        let mut res = self.to_owned();
         res.set_extension(extension);
         res
     }
 }
 
+impl PathOps for PathBuf {
+    type Output = PathBuf;
+
+    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
+        self.as_path().concat(path)
+    }
+
+    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
+        self.as_path().with_file_name(file_name)
+    }
+
+    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
+        self.as_path().with_extension(extension)
+    }
+}
+
 impl PathOps for Arc<PathBuf> {
     type Output = Arc<PathBuf>;
 
@@ -855,17 +988,14 @@ mod tests {
 
         #[test]
         fn test_pathinfo_parent() {
-            let p = Path::new(r"C:\foo\bar").to_path_buf();
+            let p = PathBuf::from(r"C:\foo\bar");
 
             let actual = <PathBuf as PathInfo>::parent(&p).expect("could not find parent?");
-            let expected = Path::new(r"C:\foo").to_path_buf();
-
+            let expected = PathBuf::from(r"C:\foo");
             assert_eq!(actual, expected);
 
-            let p = Path::new(r"C:\").to_path_buf();
-
+            let p = PathBuf::from(r"C:\");
             let actual = <PathBuf as PathInfo>::parent(&p).expect_err("root has a parent?");
-
             assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
             assert_eq!(actual.action(), "truncating to parent");
             assert_eq!(actual.path(), Path::new(r"C:\"));
@@ -873,7 +1003,7 @@ mod tests {
 
         #[test]
         fn test_pathinfo_starts_with() {
-            let p = Path::new(r"foo\bar").to_path_buf();
+            let p = PathBuf::from(r"foo\bar");
 
             assert_eq!(
                 <PathBuf as PathInfo>::starts_with(&p, Path::new("foo")),
@@ -887,7 +1017,7 @@ mod tests {
 
         #[test]
         fn test_pathinfo_ends_with() {
-            let p = Path::new(r"foo\bar").to_path_buf();
+            let p = PathBuf::from(r"foo\bar");
 
             assert_eq!(
                 <PathBuf as PathInfo>::ends_with(&p, Path::new("foo")),
@@ -899,60 +1029,45 @@ mod tests {
         #[test]
         fn test_pathops_concat() {
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new("bar"))
                 .expect("Could not concat paths?");
-            let expected = Path::new(r"foo\bar").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\bar");
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new(r"bar\..\baz"))
                 .expect("Could not concat path with ..?");
-            let expected = Path::new(r"foo\baz").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\baz");
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat("..")
                 .expect("Could not cancel path with ..?");
-            let expected = Path::new(r"").to_path_buf();
-
+            let expected = PathBuf::from(r"");
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(r"..\..")
-                .expect_err("Could escape prefix with ..?");
-
-            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
-            assert_eq!(actual.action(), "truncating to parent");
-            assert_eq!(actual.path(), Path::new(""));
+                .expect("Could not escape prefix with ..?");
+            let expected = PathBuf::from("../");
+            assert_eq!(actual, expected);
 
             let actual = Path::new(r"C:\foo")
-                .to_path_buf()
                 .concat(r"..\..")
                 .expect_err("Could escape root with ..?");
-
             assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
             assert_eq!(actual.action(), "truncating to parent");
             assert_eq!(actual.path(), Path::new(r"C:\"));
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new(r"\windows\system32"))
                 .expect("Could not concat path with RootDir?");
-            let expected = Path::new(r"foo\windows\system32").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\windows\system32");
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new(r"C:bar"))
                 .expect_err("Could concat path with prefix?");
-
             assert_eq!(actual.io_error().kind(), io::ErrorKind::Other);
             assert_eq!(actual.action(), "appending path");
             assert_eq!(actual.path(), Path::new(r"C:bar"));
@@ -960,39 +1075,33 @@ mod tests {
 
         #[test]
         fn test_pathmut_append() {
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new("bar"))
                 .expect("Could not append paths?");
-            let expected = Path::new(r"foo\bar").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\bar");
             assert_eq!(actual, expected);
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new(r"bar\..\baz"))
                 .expect("Could not append path with ..?");
-            let expected = Path::new(r"foo\baz").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\baz");
             assert_eq!(actual, expected);
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual.append("..").expect("Could not cancel path with ..?");
-            let expected = Path::new(r"").to_path_buf();
-
+            let expected = PathBuf::from(r"");
             assert_eq!(actual, expected);
 
-            let actual = Path::new("foo")
-                .to_path_buf()
+            let mut actual = PathBuf::from("foo");
+            actual
                 .append(r"..\..")
-                .expect_err("Could escape prefix with ..?");
-
-            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
-            assert_eq!(actual.action(), "truncating to parent");
-            assert_eq!(actual.path(), Path::new(""));
+                .expect("Could not escape prefix with ..?");
+            let expected = PathBuf::from("../");
+            assert_eq!(actual, expected);
 
-            let actual = Path::new(r"C:\foo")
-                .to_path_buf()
+            let actual = PathBuf::from(r"C:\foo")
                 .append(r"..\..")
                 .expect_err("Could escape root with ..?");
 
@@ -1000,34 +1109,29 @@ mod tests {
             assert_eq!(actual.action(), "truncating to parent");
             assert_eq!(actual.path(), Path::new(r"C:\"));
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new(r"\windows\system32"))
                 .expect("Could not append RootDir to path?");
-            let expected = Path::new(r"foo\windows\system32").to_path_buf();
-
+            let expected = PathBuf::from(r"foo\windows\system32");
             assert_eq!(actual, expected);
 
-            let actual = Path::new("foo")
-                .to_path_buf()
+            let actual = PathBuf::from("foo")
                 .append(Path::new(r"C:bar"))
                 .expect_err("Could append prefix to path?");
-
             assert_eq!(actual.io_error().kind(), io::ErrorKind::Other);
             assert_eq!(actual.action(), "appending path");
             assert_eq!(actual.path(), Path::new(r"C:bar"));
         }
 
         #[test]
-        fn test_pathmut_truncate_to_parent() {
-            let mut p = Path::new(r"C:\foo\bar").to_path_buf();
-            p.truncate_to_parent().expect("could not find parent?");
-
+        fn test_pathmut_pop_up() {
+            let mut p = PathBuf::from(r"C:\foo\bar");
+            p.pop_up().expect("could not find parent?");
             assert_eq!(p.as_path(), Path::new(r"C:\foo"));
 
-            let mut p = Path::new(r"C:\").to_path_buf();
-            let actual = p.truncate_to_parent().expect_err("root has a parent?");
-
+            let mut p = PathBuf::from(r"C:\");
+            let actual = p.pop_up().expect_err("root has a parent?");
             assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
             assert_eq!(actual.action(), "truncating to parent");
             assert_eq!(actual.path(), Path::new(r"C:\"));
@@ -1035,19 +1139,19 @@ mod tests {
 
         #[test]
         fn test_pathmut_truncate_to_root() {
-            let mut p = Path::new(r"C:\foo\bar").to_path_buf();
+            let mut p = PathBuf::from(r"C:\foo\bar");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new(r"C:\"));
 
-            let mut p = Path::new(r"C:foo").to_path_buf();
+            let mut p = PathBuf::from(r"C:foo");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new(r"C:"));
 
-            let mut p = Path::new(r"\foo").to_path_buf();
+            let mut p = PathBuf::from(r"\foo");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new(r"\"));
 
-            let mut p = Path::new(r"foo").to_path_buf();
+            let mut p = PathBuf::from(r"foo");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new(r""));
         }
@@ -1055,25 +1159,31 @@ mod tests {
 
     #[cfg(not(windows))]
     mod unix {
+        // FIXME: is anything really unix specific?
+    }
+
+    mod any {
         use super::*;
 
         #[test]
         fn test_pathinfo_is_absolute() {
-            let p = Path::new("/foo/bar").to_path_buf();
+            let p = PathBuf::from("/foo/bar");
+
 
-            assert_eq!(<PathBuf as PathInfo>::is_absolute(&p), true,);
+            let expected = !cfg!(windows);
+            assert_eq!(<PathBuf as PathInfo>::is_absolute(&p), expected);
         }
 
         #[test]
         fn test_pathinfo_parent() {
-            let p = Path::new("/foo/bar").to_path_buf();
+            let p = PathBuf::from("/foo/bar");
 
             let actual = <PathBuf as PathInfo>::parent(&p).expect("could not find parent?");
-            let expected = Path::new("/foo").to_path_buf();
+            let expected = PathBuf::from("/foo");
 
             assert_eq!(actual, expected);
 
-            let p = Path::new("/").to_path_buf();
+            let p = PathBuf::from("/");
 
             let actual = <PathBuf as PathInfo>::parent(&p).expect_err("root has a parent?");
 
@@ -1084,7 +1194,7 @@ mod tests {
 
         #[test]
         fn test_pathinfo_starts_with() {
-            let p = Path::new("foo/bar").to_path_buf();
+            let p = PathBuf::from("foo/bar");
 
             assert_eq!(
                 <PathBuf as PathInfo>::starts_with(&p, Path::new("foo")),
@@ -1098,7 +1208,7 @@ mod tests {
 
         #[test]
         fn test_pathinfo_ends_with() {
-            let p = Path::new("foo/bar").to_path_buf();
+            let p = PathBuf::from("foo/bar");
 
             assert_eq!(
                 <PathBuf as PathInfo>::ends_with(&p, Path::new("foo")),
@@ -1110,40 +1220,33 @@ mod tests {
         #[test]
         fn test_pathops_concat() {
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new("bar"))
                 .expect("Could not concat paths?");
-            let expected = Path::new("foo/bar").to_path_buf();
+            let expected = PathBuf::from("foo/bar");
 
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new("bar/../baz"))
                 .expect("Could not concat path with ..?");
-            let expected = Path::new("foo/baz").to_path_buf();
+            let expected = PathBuf::from("foo/baz");
 
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat("..")
                 .expect("Could not cancel path with ..?");
-            let expected = Path::new(r"").to_path_buf();
+            let expected = PathBuf::from(r"");
 
             assert_eq!(actual, expected);
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat("../..")
-                .expect_err("Could escape prefix with ..?");
-
-            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
-            assert_eq!(actual.action(), "truncating to parent");
-            assert_eq!(actual.path(), Path::new(""));
+                .expect("Could not prefix with ..?");
+            let expected = PathBuf::from(r"../");
+            assert_eq!(actual, expected);
 
             let actual = Path::new("/foo")
-                .to_path_buf()
                 .concat("../..")
                 .expect_err("Could escape root with ..?");
 
@@ -1152,74 +1255,128 @@ mod tests {
             assert_eq!(actual.path(), Path::new("/"));
 
             let actual = Path::new("foo")
-                .to_path_buf()
                 .concat(Path::new("/etc/passwd"))
                 .expect("Could not concat RootDir to path?");
-            let expected: PathBuf = Path::new("foo/etc/passwd").to_path_buf();
+            let expected: PathBuf = PathBuf::from("foo/etc/passwd");
 
             assert_eq!(actual, expected);
         }
 
+        #[test]
+        fn test_pathops_concat_relative() {
+            let actual = Path::new("../foo")
+                .concat("bar")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../foo/bar");
+            assert_eq!(actual, expected);
+
+            let actual = Path::new("../foo")
+                .concat("..")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"..");
+            assert_eq!(actual, expected);
+
+            let actual = Path::new("../foo")
+                .concat("../..")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../..");
+            assert_eq!(actual, expected);
+
+            let actual = Path::new("../foo/../bar")
+                .concat("../..")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../..");
+            assert_eq!(actual, expected);
+
+            let actual = Path::new("../foo/../bar/..")
+                .concat("../..")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../../..");
+            assert_eq!(actual, expected);
+
+            let actual = PathBuf::from("../foo/..")
+                .concat("../../baz")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../../../baz");
+            assert_eq!(actual, expected);
+        }
+
+        #[test]
+        fn test_pathops_concat_cur() {
+            // just check that pahts don't normalize...
+            let actual = Path::new("foo/././..").as_os_str();
+            let expected = ffi::OsStr::new("foo/././..");
+            assert_eq!(actual, expected);
+
+            let actual = PathBuf::from("././foo/././..")
+                .concat("../bar")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../bar");
+            assert_eq!(actual, expected);
+        }
+
+        #[test]
+        fn test_pathops_concat_consume() {
+            let actual = Path::new("foo")
+                .concat("../../bar")
+                .expect("Could not create relative path with concat");
+            let expected = PathBuf::from(r"../bar");
+            assert_eq!(actual, expected);
+        }
+
         #[test]
         fn test_pathmut_append() {
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new("bar"))
                 .expect("Could not append paths?");
-            let expected = Path::new("foo/bar").to_path_buf();
-
+            let expected = PathBuf::from("foo/bar");
             assert_eq!(actual, expected);
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new("bar/../baz"))
                 .expect("Could not append path with ..?");
-            let expected = Path::new("foo/baz").to_path_buf();
-
+            let expected = PathBuf::from("foo/baz");
             assert_eq!(actual, expected);
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual.append("..").expect("Could not cancel path with ..?");
-            let expected = Path::new(r"").to_path_buf();
-
+            let expected = PathBuf::from(r"");
             assert_eq!(actual, expected);
 
-            let actual = Path::new("foo")
-                .to_path_buf()
+            let mut actual = PathBuf::from("foo");
+            actual
                 .append("../..")
-                .expect_err("Could escape prefix with ..?");
-
-            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
-            assert_eq!(actual.action(), "truncating to parent");
-            assert_eq!(actual.path(), Path::new(""));
+                .expect("Could not escape prefix with ..?");
+            let expected = PathBuf::from("../");
+            assert_eq!(actual, expected);
 
-            let actual = Path::new("/foo")
-                .to_path_buf()
+            let actual = PathBuf::from("/foo")
                 .append("../..")
                 .expect_err("Could escape root with ..?");
-
             assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
             assert_eq!(actual.action(), "truncating to parent");
             assert_eq!(actual.path(), Path::new("/"));
 
-            let mut actual = Path::new("foo").to_path_buf();
+            let mut actual = PathBuf::from("foo");
             actual
                 .append(Path::new("/etc/passwd"))
                 .expect("Could not append RootDir to path?");
-            let expected: PathBuf = Path::new("foo/etc/passwd").to_path_buf();
+            let expected: PathBuf = PathBuf::from("foo/etc/passwd");
 
             assert_eq!(actual, expected);
         }
 
         #[test]
-        fn test_pathmut_truncate_to_parent() {
-            let mut p = Path::new("/foo/bar").to_path_buf();
-            p.truncate_to_parent().expect("could not find parent?");
+        fn test_pathmut_pop_up() {
+            let mut p = PathBuf::from("/foo/bar");
+            p.pop_up().expect("could not find parent?");
 
             assert_eq!(p.as_path(), Path::new("/foo"));
 
-            let mut p = Path::new("/").to_path_buf();
-            let actual = p.truncate_to_parent().expect_err("root has a parent?");
+            let mut p = PathBuf::from("/");
+            let actual = p.pop_up().expect_err("root has a parent?");
 
             assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
             assert_eq!(actual.action(), "truncating to parent");
@@ -1228,11 +1385,11 @@ mod tests {
 
         #[test]
         fn test_pathmut_truncate_to_root() {
-            let mut p = Path::new("/foo/bar").to_path_buf();
+            let mut p = PathBuf::from("/foo/bar");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new("/"));
 
-            let mut p = Path::new("foo/bar").to_path_buf();
+            let mut p = PathBuf::from("foo/bar");
             p.truncate_to_root();
             assert_eq!(p.as_path(), Path::new(""));
         }
diff --git a/src/ser.rs b/src/ser.rs
index ad83f46..d64f7cd 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -99,7 +99,7 @@ impl<'de> Deserialize<'de> for PathFile {
         D: Deserializer<'de>,
     {
         let abs = PathAbs::deserialize(deserializer)?;
-        PathFile::from_abs(abs).map_err(|err| serde::de::Error::custom(&err.to_string()))
+        PathFile::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string()))
     }
 }
 
@@ -109,7 +109,7 @@ impl<'de> Deserialize<'de> for PathDir {
         D: Deserializer<'de>,
     {
         let abs = PathAbs::deserialize(deserializer)?;
-        PathDir::from_abs(abs).map_err(|err| serde::de::Error::custom(&err.to_string()))
+        PathDir::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string()))
     }
 }
 
diff --git a/src/ty.rs b/src/ty.rs
index d60f487..8a731ae 100644
--- a/src/ty.rs
+++ b/src/ty.rs
@@ -45,11 +45,12 @@ impl PathType {
     /// # Ok(()) } fn main() { try_main().unwrap() }
     pub fn new<P: AsRef<Path>>(path: P) -> Result<PathType> {
         let abs = PathAbs::new(&path)?;
-        PathType::from_abs(abs)
+        PathType::try_from(abs)
     }
 
     /// Consume the `PathAbs` returning the `PathType`.
-    pub fn from_abs(abs: PathAbs) -> Result<PathType> {
+    pub fn try_from<P: Into<PathAbs>>(path: P) -> Result<PathType> {
+        let abs = path.into();
         let ty = abs.metadata()?.file_type();
         if ty.is_file() {
             Ok(PathType::File(PathFile(abs)))
@@ -116,19 +117,11 @@ impl PathType {
             false
         }
     }
+}
 
-    /// Create a mock file type. *For use in tests only*.
-    ///
-    /// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
-    pub fn mock_file<P: AsRef<Path>>(path: P) -> PathType {
-        PathType::File(PathFile::mock(path))
-    }
-
-    /// Create a mock dir type. *For use in tests only*.
-    ///
-    /// See the docs for [`PathAbs::mock`](struct.PathAbs.html#method.mock)
-    pub fn mock_dir<P: AsRef<Path>>(path: P) -> PathType {
-        PathType::Dir(PathDir::mock(path))
+impl AsRef<ffi::OsStr> for PathType {
+    fn as_ref(&self) -> &std::ffi::OsStr {
+        self.0.as_ref().as_ref()
     }
 }
 


Test patch: