path_abs/
lib.rs

1/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com
2 *
3 * Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4 * http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5 * http://opensource.org/licenses/MIT>, at your option. This file may not be
6 * copied, modified, or distributed except according to those terms.
7 */
8//! Ergonomic paths and files in rust.
9//!
10//! This library aims to provide ergonomic path and file operations to rust with reasonable
11//! performance.
12//!
13//! This includes:
14//!
15//! - Improved methods for the `std` path types using [`PathInfo`] [`PathMut`] and [`PathOps`]
16//! - Cleaner _absolute_ paths (which is distinct from canonicalized paths).
17//! - Improved error messages, see the [Better Errors](#better-errors) section.
18//! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a
19//!   certain type. Obviously a file/dir can be deleted/changed by another process.
20//! - More stringent mutability requirements. See the
21//!   [Differing Method Signatures](#differing-method-signatures) section.
22//! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem
23//!   operations and allows more flexibility and ergonomics in the library for relatively low cost.
24//!
25//! ## Better Errors
26//!
27//! All errors include the **path** and **action** which caused the error, as well as the unaltered
28//! `std::io::Error` message. Errors are convertable into `std::io::Error`, giving almost complete
29//! compatibility with existing code.
30//!
31//! ### `set_len` (i.e. truncate a file):
32//!
33//! - [`/* */ std::fs::File::set_len(0)`][file_set_len]: `Invalid argument (os error 22)`
34//! - [`path_abs::FileWrite::set_len(0)`][path_set_len]: `Invalid argument (os error 22) when setting
35//!   len for /path/to/example/foo.txt`
36//!
37//! > The above error is actually impossible because `FileWrite` is always writeable, and
38//! > `FileRead` does not implement `set_len`. However, it is kept for demonstration.
39//!
40//! ### `open_read` (open file for reading):
41//!
42//! - [`/**/ std::fs::File::read(path)`][file_read]: `No such file or directory (os error 2)`
43//! - [`path_abs::FileRead::open(path)`][path_read]: `No such file or directory (os error 2) when
44//!   opening example/foo.txt`
45//!
46//! And every other method has similarily improved errors. If a method does not have pretty error
47//! messages please open a ticket.
48//!
49//! [file_set_len]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len
50//! [file_read]: https://doc.rust-lang.org/std/fs/struct.File.html#method.read
51//! [path_set_len]: struct.FileWrite.html#method.set_len
52//! [path_read]: struct.FileRead.html#method.open
53//!
54//!
55//! ## Exported Path Types
56//!
57//! These are the exported Path types. All of them are absolute.
58//!
59//! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_
60//!   canonicalized) path that is not necessarily guaranteed to exist.
61//! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to
62//!   exist and be a file, with associated methods.
63//! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist
64//!   and be a directory, with associated methods.
65//! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir.
66//!   Returned by [`PathDir::list`][dir_list]
67//!
68//! In addition, all paths are serializable through serde (even on windows!) by using the crate
69//! [`stfu8`](https://crates.io/crates/stfu8) to encode/decode, allowing ill-formed UTF-16. See
70//! that crate for more details on how the resulting encoding can be edited (by hand) even in the
71//! case of what *would be* ill-formed UTF-16.
72//!
73//! [dir_list]: struct.PathDir.html#method.list
74//!
75//!
76//! ## Exported File Types
77//!
78//! All File types provide _type safe_ access to their relevant traits. For instance, you can't
79//! `read` with a `FileWrite` and you can't `write` with a `FileRead`.
80//!
81//! - [`FileRead`](struct.FileRead.html): a read-only file handle with `path()` attached and
82//!   improved error messages. Contains only the methods and trait implementations which are
83//!   allowed by a read-only file.
84//! - [`FileWrite`](struct.FileWrite.html): a write-only file handle with `path()` attached and
85//!   improved error messages. Contains only the methods and trait implementations which are
86//!   allowed by a write-only file.
87//! - [`FileEdit`](struct.FileEdit.html): a read/write file handle with `path()` attached and
88//!   improved error messages. Contains methods and trait implements for both readable _and_
89//!   writeable files.
90//!
91//! ### Differing Method Signatures
92//!
93//! The type signatures of the `File*` types regarding `read`, `write` and other methods is
94//! slightly different than `std::fs::File` -- they all take `&mut` instead of `&`. This is to
95//! avoid a [common possible footgun](https://github.com/rust-lang/rust/issues/47708).
96//!
97//! To demonstrate, imagine the following scenario:
98//!
99//! - You pass your open `&File` to a method, which puts it in a thread. This thread constantly
100//!   calls `seek(SeekFrom::Start(10))`
101//! - You periodically read from a file expecting new data, but are always getting the same data.
102//!
103//! Yes, this is actually allowed by the rust compiler since `seek` is implemented for
104//! [`&File`](https://doc.rust-lang.org/std/fs/struct.File.html#impl-Seek-1). Technically this is
105//! still _memory safe_ since the operating system will handle any contention, however many would
106//! argue that it isn't _expected_ that an immutable reference passed to another
107//! function can affect the seek position of a file.
108//!
109//!
110//! # Examples
111//! Recreating `Cargo.init` in `example/`
112//!
113//! ```rust
114//! # extern crate path_abs;
115//! # extern crate tempdir;
116//! use std::path::Path;
117//! use std::collections::HashSet;
118//! use path_abs::{
119//!     PathAbs,   // absolute path
120//!     PathDir,   // absolute path to a directory
121//!     PathFile,  // absolute path to a file
122//!     PathType,  // enum of Dir or File
123//!     PathInfo,  // trait for query methods
124//!     PathOps,   // trait for methods that make new paths
125//!     FileRead,  // Open read-only file handler
126//!     FileWrite, // Open write-only file handler
127//!     FileEdit,  // Open read/write file handler
128//! };
129//!
130//! # fn try_main() -> ::std::io::Result<()> {
131//! let example = Path::new("example");
132//! # let tmp = tempdir::TempDir::new("ex")?;
133//! # let example = &tmp.path().join(example);
134//!
135//! // Create your paths
136//! let project = PathDir::create_all(example)?;
137//! let src = PathDir::create(project.concat("src")?)?;
138//! let lib = PathFile::create(src.concat("lib.rs")?)?;
139//! let cargo = PathFile::create(project.concat("Cargo.toml")?)?;
140//!
141//! // Write the templates
142//! lib.write_str(r#"
143//! #[cfg(test)]
144//! mod tests {
145//!     #[test]
146//!     fn it_works() {
147//!         assert_eq!(2 + 2, 4);
148//!     }
149//! }"#)?;
150//!
151//! cargo.write_str(r#"
152//! [package]
153//! name = "example"
154//! version = "0.1.0"
155//! authors = ["Garrett Berg <vitiral@gmail.com>"]
156//!
157//! [dependencies]
158//! "#)?;
159//!
160//! // Put our result into a HashMap so we can assert it
161//! let mut result = HashSet::new();
162//! for p in project.list()? {
163//!     result.insert(p?);
164//! }
165//!
166//! // Create our expected value
167//! let mut expected = HashSet::new();
168//! expected.insert(PathType::Dir(src));
169//! expected.insert(PathType::File(cargo));
170//!
171//! assert_eq!(expected, result);
172//!
173//! // ----------------------------------
174//! // Creating types from existing paths
175//!
176//! // Creating a generic path
177//! let lib_path = example.join("src").join("lib.rs");
178//! let abs = PathAbs::new(&lib_path)?;
179//!
180//! // Or a path with a known type
181//! let file = PathType::new(&lib_path)
182//!     ?
183//!     .unwrap_file();
184//!
185//! assert!(abs.is_file());
186//! assert!(file.is_file());
187//!
188//! // ----------------------------------
189//! // Opening a File
190//!
191//! // open read-only using the PathFile method
192//! let read = file.open_read()?;
193//!
194//! // Or use the type directly: open for appending
195//! let write = FileWrite::open_append(&file)?;
196//!
197//! // Open for read/write editing.
198//! let edit = file.open_edit()?;
199//! # Ok(()) } fn main() { try_main().unwrap() }
200//! ```
201//!
202//! [`PathInfo`]: trait.PathInfo.html
203//! [`PathOps`]: trait.PathOps.html
204//! [`PathMut`]: trait.PathMut.html
205
206#![cfg_attr(target_os = "wasi",
207            feature(wasi_ext))]
208
209#[cfg(feature = "serialize")]
210extern crate serde;
211#[macro_use]
212#[cfg(feature = "serialize")]
213extern crate serde_derive;
214
215#[cfg(feature = "serialize")]
216extern crate stfu8;
217
218#[macro_use]
219#[cfg(test)]
220extern crate pretty_assertions;
221#[cfg(test)]
222extern crate regex;
223#[cfg(test)]
224extern crate serde_json;
225#[cfg(test)]
226extern crate tempdir;
227
228use std::error;
229use std::ffi;
230use std::fmt;
231use std::fs;
232use std::io;
233use std::path::{self, Component, Components};
234use std_prelude::*;
235
236mod abs;
237mod dir;
238mod edit;
239mod file;
240pub mod open;
241mod read;
242#[cfg(feature = "serialize")]
243pub mod ser;
244mod ty;
245mod write;
246
247pub use crate::abs::PathAbs;
248pub use crate::dir::{ListDir, PathDir};
249pub use crate::file::PathFile;
250#[cfg(feature = "serialize")]
251pub use crate::ser::PathSer;
252pub use crate::ty::PathType;
253
254pub use crate::edit::FileEdit;
255pub use crate::read::FileRead;
256pub use crate::write::FileWrite;
257
258pub type Result<T> = ::std::result::Result<T, Error>;
259
260/// An error produced by performing an filesystem operation on a `Path`.
261///
262/// This error type is a light wrapper around [`std::io::Error`]. In particular, it adds the
263/// following information:
264///
265/// - The action being performed when the error occured
266/// - The path associated with the IO error.
267///
268/// To maintain good ergonomics, this type has a `impl From<Error> for std::io::Error` defined so
269/// that you may use an [`io::Result`] with methods in this crate if you don't care about accessing
270/// the underlying error data in a structured form (the pretty format will be preserved however).
271///
272/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
273/// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html
274///
275/// # Examples
276/// ```rust
277/// use path_abs::Error as PathError;
278/// use path_abs::PathFile;
279///
280/// /// main function, note that you can use `io::Error`
281/// fn try_main() -> Result<(), ::std::io::Error> {
282///     let lib = PathFile::new("src/lib.rs")?;
283///     Ok(())
284/// }
285///
286/// ```
287pub struct Error {
288    io_err: io::Error,
289    action: String,
290    path: Arc<PathBuf>,
291}
292
293impl Error {
294    /// Create a new error when the path and action are known.
295    pub fn new(io_err: io::Error, action: &str, path: Arc<PathBuf>) -> Error {
296        Error {
297            io_err,
298            action: action.into(),
299            path,
300        }
301    }
302}
303
304impl fmt::Debug for Error {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        write!(f, "Error<{}>", self)
307    }
308}
309
310impl fmt::Display for Error {
311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
312        write!(
313            f,
314            "{} when {} {}",
315            self.io_err,
316            self.action,
317            self.path.display()
318        )
319    }
320}
321
322impl Error {
323    /// Returns the path associated with this error.
324    pub fn path(&self) -> &Path {
325        self.path.as_ref()
326    }
327
328    /// Returns the `std::io::Error` associated with this errors.
329    pub fn io_error(&self) -> &io::Error {
330        &self.io_err
331    }
332
333    /// Returns the action being performed when this error occured.
334    pub fn action(&self) -> &str {
335        &self.action
336    }
337}
338
339impl error::Error for Error {
340    fn description(&self) -> &str {
341        self.io_err.description()
342    }
343
344    fn cause(&self) -> Option<&dyn error::Error> {
345        Some(&self.io_err)
346    }
347}
348
349impl From<Error> for io::Error {
350    fn from(err: Error) -> io::Error {
351        io::Error::new(err.io_err.kind(), err)
352    }
353}
354
355/// Methods that return information about a path.
356///
357/// This trait provides the familiar methods from `std::path::Path`
358/// for the `Path*` types. These methods take the same parameters and return
359/// the same types as the originals in the standard library, except where
360/// noted.
361///
362/// As a general rule, methods that can return an error will return a rich
363/// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will
364/// automatically convert into a `std::io::Error` with `?` if needed).
365///
366/// [`path_abs::Error`]: struct.Error.html
367/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
368pub trait PathInfo {
369    fn as_path(&self) -> &Path;
370
371    fn to_arc_pathbuf(&self) -> Arc<PathBuf>;
372
373    fn as_os_str(&self) -> &ffi::OsStr {
374        Path::as_os_str(self.as_path())
375    }
376
377    fn to_str(&self) -> Option<&str> {
378        Path::to_str(self.as_path())
379    }
380
381    fn to_string_lossy(&self) -> Cow<'_, str> {
382        Path::to_string_lossy(self.as_path())
383    }
384
385    fn is_absolute(&self) -> bool {
386        Path::is_absolute(self.as_path())
387    }
388
389    fn is_relative(&self) -> bool {
390        Path::is_relative(self.as_path())
391    }
392
393    fn has_root(&self) -> bool {
394        Path::has_root(self.as_path())
395    }
396
397    fn ancestors(&self) -> path::Ancestors<'_> {
398        Path::ancestors(self.as_path())
399    }
400
401    fn file_name(&self) -> Option<&ffi::OsStr> {
402        Path::file_name(self.as_path())
403    }
404
405    fn strip_prefix<P>(&self, base: P) -> std::result::Result<&Path, path::StripPrefixError>
406    where
407        P: AsRef<Path>,
408    {
409        Path::strip_prefix(self.as_path(), base)
410    }
411
412    fn starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
413        Path::starts_with(self.as_path(), base)
414    }
415
416    fn ends_with<P: AsRef<Path>>(&self, base: P) -> bool {
417        Path::ends_with(self.as_path(), base)
418    }
419
420    fn file_stem(&self) -> Option<&ffi::OsStr> {
421        Path::file_stem(self.as_path())
422    }
423
424    fn extension(&self) -> Option<&ffi::OsStr> {
425        Path::extension(self.as_path())
426    }
427
428    fn components(&self) -> Components<'_> {
429        Path::components(self.as_path())
430    }
431
432    fn iter(&self) -> path::Iter<'_> {
433        Path::iter(self.as_path())
434    }
435
436    fn display(&self) -> path::Display<'_> {
437        Path::display(self.as_path())
438    }
439
440    /// Queries the file system to get information about a file, directory, etc.
441    ///
442    /// The same as [`std::path::Path::metadata()`], except that it returns a
443    /// rich [`path_abs::Error`] when a problem is encountered.
444    ///
445    /// [`path_abs::Error`]: struct.Error.html
446    /// [`std::path::Path::metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.metadata
447    fn metadata(&self) -> Result<fs::Metadata> {
448        Path::metadata(self.as_path())
449            .map_err(|err| Error::new(err, "getting metadata of", self.to_arc_pathbuf()))
450    }
451
452    /// Queries the metadata about a file without following symlinks.
453    ///
454    /// The same as [`std::path::Path::symlink_metadata()`], except that it
455    /// returns a rich [`path_abs::Error`] when a problem is encountered.
456    ///
457    /// [`path_abs::Error`]: struct.Error.html
458    /// [`std::path::Path::symlink_metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.symlink_metadata
459    fn symlink_metadata(&self) -> Result<fs::Metadata> {
460        Path::symlink_metadata(self.as_path())
461            .map_err(|err| Error::new(err, "getting symlink metadata of", self.to_arc_pathbuf()))
462    }
463
464    fn exists(&self) -> bool {
465        Path::exists(self.as_path())
466    }
467
468    fn is_file(&self) -> bool {
469        Path::is_file(self.as_path())
470    }
471
472    fn is_dir(&self) -> bool {
473        Path::is_dir(self.as_path())
474    }
475
476    /// Reads a symbolic link, returning the path that the link points to.
477    ///
478    /// The same as [`std::path::Path::read_link()`], except that it returns a
479    /// rich [`path_abs::Error`] when a problem is encountered.
480    ///
481    /// [`path_abs::Error`]: struct.Error.html
482    /// [`std::path::Pathdoc.rust-lang.org/stable/std/path/struct.Path.html#method.read_link
483    fn read_link(&self) -> Result<PathBuf> {
484        Path::read_link(self.as_path())
485            .map_err(|err| Error::new(err, "reading link target of", self.to_arc_pathbuf()))
486    }
487
488    /// Returns the canonical, absolute form of the path with all intermediate
489    /// components normalized and symbolic links resolved.
490    ///
491    /// The same as [`std::path::Path::canonicalize()`],
492    ///   - On success, returns a `path_abs::PathAbs` instead of a `PathBuf`
493    ///   - returns a rich [`path_abs::Error`] when a problem is encountered
494    ///
495    /// [`path_abs::Error`]: struct.Error.html
496    /// [`std::path::Path::canonicalize()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.canonicalize
497    fn canonicalize(&self) -> Result<PathAbs> {
498        Path::canonicalize(self.as_path())
499            .map(|path| PathAbs(path.into()))
500            .map_err(|err| Error::new(err, "canonicalizing", self.to_arc_pathbuf()))
501    }
502
503    /// Returns the path without its final component, if there is one.
504    ///
505    /// The same as [`std::path::Path::parent()`], except that it returns a
506    /// `Result` with a rich [`path_abs::Error`] when a problem is encountered.
507    ///
508    /// [`path_abs::Error`]: struct.Error.html
509    /// [`std::path::Path::parent()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.parent
510    fn parent(&self) -> Result<&Path> {
511        let parent_path = Path::parent(self.as_path());
512        if let Some(p) = parent_path {
513            Ok(p)
514        } else {
515            Err(Error::new(
516                io::Error::new(io::ErrorKind::NotFound, "path has no parent"),
517                "truncating to parent",
518                self.to_arc_pathbuf(),
519            ))
520        }
521    }
522}
523
524// TODO: I would like to be able to do this.
525// impl<T> PathInfo for T
526// where
527//     T: AsRef<Path>
528// {
529//     fn as_path(&self) -> &Path {
530//         PathBuf::as_path(self.borrow())
531//     }
532//     fn to_arc_pathbuf(&self) -> Arc<PathBuf> {
533//         self.clone().into()
534//     }
535// }
536
537impl<T> PathInfo for T
538where
539    T: Clone + Borrow<PathBuf> + Into<Arc<PathBuf>>,
540{
541    fn as_path(&self) -> &Path {
542        PathBuf::as_path(self.borrow())
543    }
544    fn to_arc_pathbuf(&self) -> Arc<PathBuf> {
545        self.clone().into()
546    }
547}
548
549impl PathInfo for Path {
550    fn as_path(&self) -> &Path {
551        &self
552    }
553    fn to_arc_pathbuf(&self) -> Arc<PathBuf> {
554        self.to_path_buf().into()
555    }
556}
557
558/// Methods that modify a path.
559///
560/// These methods are not implemented for all `path_abs` types because they
561/// may break the type's invariant. For example, if you could call
562/// `pop_up()` on a `PathFile`, it would no longer be the path to
563/// a file, but the path to a directory.
564///
565/// As a general rule, methods that can return an error will return a rich
566/// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will
567/// automatically convert into a `std::io::Error` with `?` if needed).
568pub trait PathMut: PathInfo {
569    /// Appends `path` to this path.
570    ///
571    /// Note that this method represents pure concatenation, not "adjoinment"
572    /// like [`PathBuf::push`], so absolute paths won't wholly replace the
573    /// current path.
574    ///
575    /// `..` components are resolved using [`pop_up`], which can consume components
576    /// on `self`
577    ///
578    /// # Errors
579    ///
580    /// This method returns an error if the result would try to go outside a filesystem root,
581    /// like `/` on Unix or `C:\` on Windows.
582    ///
583    /// # Example
584    ///
585    /// ```rust
586    /// use std::path::PathBuf;
587    /// use path_abs::PathMut;
588    ///
589    /// let mut somepath = PathBuf::from("foo");
590    /// somepath.append("bar");
591    ///
592    /// assert_eq!(somepath, PathBuf::from("foo/bar"));
593    /// ```
594    ///
595    /// [`pop_up`]: trait.PathMut.html#method.pop_up
596    /// [`PathBuf::push`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.push
597    fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()>;
598
599    /// Go "up" one directory.
600    ///
601    /// This removes the last component of this path. It also resolves any `..` that exist at the
602    /// _end_ of the path until a real item can be truncated. If the path is relative, and no
603    /// items remain then a `..` is appended to the path.
604    ///
605    /// # Errors
606    ///
607    /// This method returns an error if the result would try to go outside a filesystem root,
608    /// like `/` on Unix or `C:\` on Windows.
609    ///
610    /// # Example
611    ///
612    /// ```rust
613    /// # fn example() -> Result<(), path_abs::Error> {
614    /// use std::path::Path;
615    /// use path_abs::PathMut;
616    ///
617    /// let executable = Path::new("/usr/loca/bin/myapp");
618    /// let mut install_path = executable.to_path_buf();
619    /// install_path.pop_up()?;
620    ///
621    /// assert_eq!(install_path.as_path(), Path::new("/usr/local/bin"));
622    /// # Ok(()) }
623    /// ```
624    ///
625    /// Example handling weird relative paths
626    ///
627    /// ```rust
628    /// # fn example() -> Result<(), path_abs::Error> {
629    /// use std::path::Path;
630    /// use path_abs::PathMut;
631    ///
632    /// let executable = Path::new("../../weird/../relative/path/../../");
633    /// let mut install_path = executable.to_path_buf();
634    /// install_path.pop_up()?;
635    ///
636    /// assert_eq!(install_path.as_path(), Path::new("../../../"));
637    /// # Ok(()) }
638    /// ```
639    ///
640    /// Error use case
641    ///
642    /// ```rust
643    /// # fn example() -> Result<(), path_abs::Error> {
644    /// use std::path::Path;
645    /// use path_abs::PathMut;
646    ///
647    /// let tmp = Path::new("/tmp");
648    /// let mut relative = tmp.to_path_buf();
649    /// relative.pop_up()?;
650    /// assert!(relative.pop_up().is_err());
651    /// # Ok(()) }
652    /// ```
653    fn pop_up(&mut self) -> Result<()>;
654
655    /// Removes all components after the root, if any.
656    ///
657    /// This is mostly useful on Windows, since it preserves the prefix before
658    /// the root.
659    ///
660    /// # Example
661    ///
662    /// ```no_run
663    /// use std::path::PathBuf;
664    /// use path_abs::PathMut;
665    ///
666    /// let mut somepath = PathBuf::from(r"C:\foo\bar");
667    /// somepath.truncate_to_root();
668    ///
669    /// assert_eq!(somepath, PathBuf::from(r"C:\"));
670    /// ```
671    fn truncate_to_root(&mut self);
672
673    fn set_file_name<S: AsRef<ffi::OsStr>>(&mut self, file_name: S);
674
675    fn set_extension<S: AsRef<ffi::OsStr>>(&mut self, extension: S) -> bool;
676}
677
678impl PathMut for PathBuf {
679    fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
680        for each in path.as_ref().components() {
681            match each {
682                Component::Normal(c) => self.push(c),
683                Component::CurDir => (), // "." does nothing
684                Component::Prefix(_) => {
685                    return Err(Error::new(
686                        io::Error::new(io::ErrorKind::Other, "appended path has a prefix"),
687                        "appending path",
688                        path.as_ref().to_path_buf().into(),
689                    ));
690                }
691                Component::RootDir => (), // leading "/" does nothing
692                Component::ParentDir => self.pop_up()?,
693            }
694        }
695
696        Ok(())
697    }
698
699    fn pop_up(&mut self) -> Result<()> {
700        /// Pop off the parent components and return how
701        /// many were removed.
702        fn pop_parent_components(p: &mut PathBuf) -> usize {
703            let mut cur_dirs: usize = 0;
704            let mut parents: usize = 0;
705            let mut components = p.components();
706            while let Some(c) = components.next_back() {
707                match c {
708                    Component::CurDir => cur_dirs += 1,
709                    Component::ParentDir => parents += 1,
710                    _ => break,
711                }
712            }
713            for _ in 0..(cur_dirs + parents) {
714                p.pop();
715            }
716            parents
717        }
718
719        let mut ending_parents = 0;
720        loop {
721            ending_parents += pop_parent_components(self);
722            if ending_parents == 0 || self.file_name().is_none() {
723                break;
724            } else {
725                // we have at least one "parent" to consume
726                self.pop();
727                ending_parents -= 1;
728            }
729        }
730
731        if self.pop() {
732            // do nothing, success
733        } else if self.has_root() {
734            // We tried to pop off the root
735            return Err(Error::new(
736                io::Error::new(io::ErrorKind::NotFound, "cannot get parent of root path"),
737                "truncating to parent",
738                self.clone().into(),
739            ));
740        } else {
741            // we are creating a relative path, `"../"`
742            self.push("..")
743        }
744
745        // Put all unhandled parents back, creating a relative path.
746        for _ in 0..ending_parents {
747            self.push("..")
748        }
749
750        Ok(())
751    }
752
753    fn truncate_to_root(&mut self) {
754        let mut res = PathBuf::new();
755        for component in self.components().take(2) {
756            match component {
757                // We want to keep prefix and RootDir components of this path
758                Component::Prefix(_) | Component::RootDir => res.push(component),
759                // We want to discard all other components.
760                _ => break,
761            }
762        }
763
764        // Clobber ourselves with the new value.
765        *self = res;
766    }
767
768    fn set_file_name<S: AsRef<ffi::OsStr>>(&mut self, file_name: S) {
769        self.set_file_name(file_name)
770    }
771
772    fn set_extension<S: AsRef<ffi::OsStr>>(&mut self, extension: S) -> bool {
773        self.set_extension(extension)
774    }
775}
776
777impl PathMut for Arc<PathBuf> {
778    fn append<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
779        Arc::make_mut(self).append(path)
780    }
781    fn pop_up(&mut self) -> Result<()> {
782        Arc::make_mut(self).pop_up()
783    }
784    fn truncate_to_root(&mut self) {
785        Arc::make_mut(self).truncate_to_root()
786    }
787    fn set_file_name<S: AsRef<ffi::OsStr>>(&mut self, file_name: S) {
788        Arc::make_mut(self).set_file_name(file_name)
789    }
790    fn set_extension<S: AsRef<ffi::OsStr>>(&mut self, extension: S) -> bool {
791        Arc::make_mut(self).set_extension(extension)
792    }
793}
794
795/// Methods that return new path-like objects.
796///
797/// Like the methods of [`PathInfo`] and [`PathMut`], these methods are similar
798/// to ones from the standard library's [`PathBuf`] but may return a rich
799/// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will
800/// automatically convert into a `std::io::Error` with `?` if needed).
801///
802/// Unlike the methods of [`PathInfo`] and [`PathMut`], different types that
803/// implement this trait may have different return types.
804///
805/// [`PathInfo`]: trait.PathInfo.html
806/// [`PathMut`]: trait.PathMut.html
807/// [`PathBuf`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html
808/// [`path_abs::Error`]: struct.Error.html
809/// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html
810pub trait PathOps: PathInfo {
811    type Output: PathOps;
812
813    /// Returns a new value representing the concatenation of two paths.
814    ///
815    /// Note that this method represents pure concatenation, not "adjoinment"
816    /// like [`PathBuf::join`], so absolute paths won't wholly replace the
817    /// current path. See [`append`] for more information about how it works.
818    ///
819    /// # Errors
820    ///
821    /// This method returns an error if the result would try to go outside a filesystem root,
822    /// like `/` on Unix or `C:\` on Windows.
823    ///
824    /// # Example
825    ///
826    /// ```rust
827    /// use path_abs::{PathInfo, PathOps, Result};
828    ///
829    /// fn find_config_file<P: PathOps>(
830    ///     search_path: &[P],
831    ///     file_name: &str,
832    /// ) -> Option<<P as PathOps>::Output> {
833    ///     for each in search_path.iter() {
834    ///         if let Ok(maybe_config) = each.concat(file_name) {
835    ///             if maybe_config.is_file() { return Some(maybe_config); }
836    ///         }
837    ///     }
838    ///
839    ///     None
840    /// }
841    /// ```
842    ///
843    /// [`append`]: trait.PathMut.html#method.append
844    /// [`PathBuf::join`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.join
845    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output>;
846
847    /// An exact replica of `std::path::Path::join` with all of its gotchas and pitfalls,, except
848    /// returns a more relevant type.
849    ///
850    /// In general, prefer [`concat`]
851    ///
852    /// [`concat`]: trait.PathOps.html#method.concat
853    fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output;
854
855    /// Creates a new path object like `self` but with the given file name.
856    ///
857    /// The same as [`std::path::Path::with_file_name()`], except that the
858    /// return type depends on the trait implementation.
859    ///
860    /// [`std::path::Path::with_file_name()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_file_name
861    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output;
862
863    /// Creates a new path object like `self` but with the given extension.
864    ///
865    /// The same as [`std::path::Path::with_extension()`], except that the
866    /// return type depends on the trait implementation.
867    ///
868    /// [`std::path::Path::with_extension()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_extension
869    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output;
870}
871
872// impl<T> PathOps for T
873// where
874//     T: PathInfo
875//
876// {
877//     type Output = PathBuf;
878//
879//     fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
880//         let mut res = self.as_ref().to_owned();
881//         res.append(path)?;
882//         Ok(res)
883//     }
884//
885//     fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
886//         let mut res = self.as_ref().to_owned();
887//         res.set_file_name(file_name);
888//         res
889//     }
890//
891//     fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
892//         let mut res = self.as_ref().to_owned();
893//         res.set_extension(extension);
894//         res
895//     }
896// }
897
898impl PathOps for Path {
899    type Output = PathBuf;
900
901    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
902        let mut res = self.to_owned();
903        res.append(path)?;
904        Ok(res)
905    }
906
907    fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output {
908        Path::join(self, path)
909    }
910
911    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
912        let mut res = self.to_owned();
913        res.set_file_name(file_name);
914        res
915    }
916
917    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
918        let mut res = self.to_owned();
919        res.set_extension(extension);
920        res
921    }
922}
923
924impl PathOps for PathBuf {
925    type Output = PathBuf;
926
927    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
928        self.as_path().concat(path)
929    }
930
931    fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output {
932        Path::join(self, path)
933    }
934
935    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
936        self.as_path().with_file_name(file_name)
937    }
938
939    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
940        self.as_path().with_extension(extension)
941    }
942}
943
944impl PathOps for Arc<PathBuf> {
945    type Output = Arc<PathBuf>;
946
947    fn concat<P: AsRef<Path>>(&self, path: P) -> Result<Self::Output> {
948        let mut res = self.clone();
949        Arc::make_mut(&mut res).append(path)?;
950        Ok(res)
951    }
952
953    fn join<P: AsRef<Path>>(&self, path: P) -> Self::Output {
954        let buf = Path::join(self, path);
955        Arc::new(buf)
956    }
957
958    fn with_file_name<S: AsRef<ffi::OsStr>>(&self, file_name: S) -> Self::Output {
959        let mut res = self.clone();
960        Arc::make_mut(&mut res).set_file_name(file_name);
961        res
962    }
963
964    fn with_extension<S: AsRef<ffi::OsStr>>(&self, extension: S) -> Self::Output {
965        let mut res = self.clone();
966        Arc::make_mut(&mut res).set_extension(extension);
967        res
968    }
969}
970
971#[cfg(test)]
972mod tests {
973    use regex::{self, Regex};
974    use tempdir::TempDir;
975
976    use super::*;
977
978    macro_rules! assert_match {
979        ($re: expr, $err: expr) => {{
980            let re = Regex::new(&$re).unwrap();
981            let err = $err.to_string();
982            assert!(
983                re.is_match(&err),
984                "\nGot Err         : {:?}\nMatching against: {:?}",
985                err.to_string(),
986                $re
987            );
988        }};
989    }
990
991    fn escape<P: AsRef<Path>>(path: P) -> String {
992        regex::escape(&format!("{}", path.as_ref().display()))
993    }
994
995    #[test]
996    /// Tests to make sure the error messages look like we expect.
997    fn sanity_errors() {
998        let tmp_dir = TempDir::new("example").expect("create temp dir");
999        let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs");
1000
1001        {
1002            let foo_path = tmp_abs.concat("foo.txt").expect("path foo.txt");
1003            let foo = PathFile::create(foo_path).expect("create foo.txt");
1004            foo.clone().remove().unwrap();
1005            let pat = if cfg!(unix) {
1006                format!(
1007                    r"No such file or directory \(os error \d+\) when opening {}",
1008                    escape(&foo)
1009                )
1010            } else {
1011                format!(
1012                    r"The system cannot find the file specified. \(os error \d+\) when opening {}",
1013                    escape(&foo)
1014                )
1015            };
1016            assert_match!(pat, foo.open_edit().unwrap_err())
1017        }
1018    }
1019
1020    #[cfg(test)]
1021    mod windows {
1022        use super::*;
1023
1024        #[cfg_attr(windows, test)]
1025        fn _test_pathinfo_parent() {
1026            let p = PathBuf::from(r"C:\foo\bar");
1027
1028            let actual = <PathBuf as PathInfo>::parent(&p).expect("could not find parent?");
1029            let expected = PathBuf::from(r"C:\foo");
1030            assert_eq!(actual, expected);
1031
1032            let p = PathBuf::from(r"C:\");
1033            let actual = <PathBuf as PathInfo>::parent(&p).expect_err("root has a parent?");
1034            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1035            assert_eq!(actual.action(), "truncating to parent");
1036            assert_eq!(actual.path(), Path::new(r"C:\"));
1037        }
1038
1039        #[cfg_attr(windows, test)]
1040        fn _test_pathinfo_starts_with() {
1041            let p = PathBuf::from(r"foo\bar");
1042
1043            assert_eq!(
1044                <PathBuf as PathInfo>::starts_with(&p, Path::new("foo")),
1045                true,
1046            );
1047            assert_eq!(
1048                <PathBuf as PathInfo>::starts_with(&p, Path::new("bar")),
1049                false,
1050            );
1051        }
1052
1053        #[cfg_attr(windows, test)]
1054        fn _test_pathinfo_ends_with() {
1055            let p = PathBuf::from(r"foo\bar");
1056
1057            assert_eq!(
1058                <PathBuf as PathInfo>::ends_with(&p, Path::new("foo")),
1059                false,
1060            );
1061            assert_eq!(<PathBuf as PathInfo>::ends_with(&p, Path::new("bar")), true,);
1062        }
1063
1064        #[cfg_attr(windows, test)]
1065        fn _test_pathops_concat() {
1066            let actual = Path::new("foo")
1067                .concat(Path::new("bar"))
1068                .expect("Could not concat paths?");
1069            let expected = PathBuf::from(r"foo\bar");
1070            assert_eq!(actual, expected);
1071
1072            let actual = Path::new("foo")
1073                .concat(Path::new(r"bar\..\baz"))
1074                .expect("Could not concat path with ..?");
1075            let expected = PathBuf::from(r"foo\baz");
1076            assert_eq!(actual, expected);
1077
1078            let actual = Path::new("foo")
1079                .concat("..")
1080                .expect("Could not cancel path with ..?");
1081            let expected = PathBuf::from(r"");
1082            assert_eq!(actual, expected);
1083
1084            let actual = Path::new("foo")
1085                .concat(r"..\..")
1086                .expect("Could not escape prefix with ..?");
1087            let expected = PathBuf::from("../");
1088            assert_eq!(actual, expected);
1089
1090            let actual = Path::new(r"C:\foo")
1091                .concat(r"..\..")
1092                .expect_err("Could escape root with ..?");
1093            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1094            assert_eq!(actual.action(), "truncating to parent");
1095            assert_eq!(actual.path(), Path::new(r"C:\"));
1096
1097            let actual = Path::new("foo")
1098                .concat(Path::new(r"\windows\system32"))
1099                .expect("Could not concat path with RootDir?");
1100            let expected = PathBuf::from(r"foo\windows\system32");
1101            assert_eq!(actual, expected);
1102
1103            let actual = Path::new("foo")
1104                .concat(Path::new(r"C:bar"))
1105                .expect_err("Could concat path with prefix?");
1106            assert_eq!(actual.io_error().kind(), io::ErrorKind::Other);
1107            assert_eq!(actual.action(), "appending path");
1108            assert_eq!(actual.path(), Path::new(r"C:bar"));
1109        }
1110
1111        #[cfg_attr(windows, test)]
1112        fn _test_pathmut_append() {
1113            let mut actual = PathBuf::from("foo");
1114            actual
1115                .append(Path::new("bar"))
1116                .expect("Could not append paths?");
1117            let expected = PathBuf::from(r"foo\bar");
1118            assert_eq!(actual, expected);
1119
1120            let mut actual = PathBuf::from("foo");
1121            actual
1122                .append(Path::new(r"bar\..\baz"))
1123                .expect("Could not append path with ..?");
1124            let expected = PathBuf::from(r"foo\baz");
1125            assert_eq!(actual, expected);
1126
1127            let mut actual = PathBuf::from("foo");
1128            actual.append("..").expect("Could not cancel path with ..?");
1129            let expected = PathBuf::from(r"");
1130            assert_eq!(actual, expected);
1131
1132            let mut actual = PathBuf::from("foo");
1133            actual
1134                .append(r"..\..")
1135                .expect("Could not escape prefix with ..?");
1136            let expected = PathBuf::from("../");
1137            assert_eq!(actual, expected);
1138
1139            let actual = PathBuf::from(r"C:\foo")
1140                .append(r"..\..")
1141                .expect_err("Could escape root with ..?");
1142
1143            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1144            assert_eq!(actual.action(), "truncating to parent");
1145            assert_eq!(actual.path(), Path::new(r"C:\"));
1146
1147            let mut actual = PathBuf::from("foo");
1148            actual
1149                .append(Path::new(r"\windows\system32"))
1150                .expect("Could not append RootDir to path?");
1151            let expected = PathBuf::from(r"foo\windows\system32");
1152            assert_eq!(actual, expected);
1153
1154            let actual = PathBuf::from("foo")
1155                .append(Path::new(r"C:bar"))
1156                .expect_err("Could append prefix to path?");
1157            assert_eq!(actual.io_error().kind(), io::ErrorKind::Other);
1158            assert_eq!(actual.action(), "appending path");
1159            assert_eq!(actual.path(), Path::new(r"C:bar"));
1160        }
1161
1162        #[cfg_attr(windows, test)]
1163        fn _test_pathmut_pop_up() {
1164            let mut p = PathBuf::from(r"C:\foo\bar");
1165            p.pop_up().expect("could not find parent?");
1166            assert_eq!(p.as_path(), Path::new(r"C:\foo"));
1167
1168            let mut p = PathBuf::from(r"C:\");
1169            let actual = p.pop_up().expect_err("root has a parent?");
1170            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1171            assert_eq!(actual.action(), "truncating to parent");
1172            assert_eq!(actual.path(), Path::new(r"C:\"));
1173        }
1174
1175        #[cfg_attr(windows, test)]
1176        fn _test_pathmut_truncate_to_root() {
1177            let mut p = PathBuf::from(r"C:\foo\bar");
1178            p.truncate_to_root();
1179            assert_eq!(p.as_path(), Path::new(r"C:\"));
1180
1181            let mut p = PathBuf::from(r"C:foo");
1182            p.truncate_to_root();
1183            assert_eq!(p.as_path(), Path::new(r"C:"));
1184
1185            let mut p = PathBuf::from(r"\foo");
1186            p.truncate_to_root();
1187            assert_eq!(p.as_path(), Path::new(r"\"));
1188
1189            let mut p = PathBuf::from(r"foo");
1190            p.truncate_to_root();
1191            assert_eq!(p.as_path(), Path::new(r""));
1192        }
1193    }
1194
1195    mod any {
1196        use super::*;
1197
1198        #[test]
1199        fn test_pathinfo_is_absolute() {
1200            let p = PathBuf::from("/foo/bar");
1201
1202            let expected = !cfg!(windows);
1203            assert_eq!(<PathBuf as PathInfo>::is_absolute(&p), expected);
1204        }
1205
1206        #[test]
1207        fn test_pathinfo_parent() {
1208            let p = PathBuf::from("/foo/bar");
1209
1210            let actual = <PathBuf as PathInfo>::parent(&p).expect("could not find parent?");
1211            let expected = PathBuf::from("/foo");
1212
1213            assert_eq!(actual, expected);
1214
1215            let p = PathBuf::from("/");
1216
1217            let actual = <PathBuf as PathInfo>::parent(&p).expect_err("root has a parent?");
1218
1219            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1220            assert_eq!(actual.action(), "truncating to parent");
1221            assert_eq!(actual.path(), Path::new("/"));
1222        }
1223
1224        #[test]
1225        fn test_pathinfo_starts_with() {
1226            let p = PathBuf::from("foo/bar");
1227
1228            assert_eq!(
1229                <PathBuf as PathInfo>::starts_with(&p, Path::new("foo")),
1230                true,
1231            );
1232            assert_eq!(
1233                <PathBuf as PathInfo>::starts_with(&p, Path::new("bar")),
1234                false,
1235            );
1236        }
1237
1238        #[test]
1239        fn test_pathinfo_ends_with() {
1240            let p = PathBuf::from("foo/bar");
1241
1242            assert_eq!(
1243                <PathBuf as PathInfo>::ends_with(&p, Path::new("foo")),
1244                false,
1245            );
1246            assert_eq!(<PathBuf as PathInfo>::ends_with(&p, Path::new("bar")), true,);
1247        }
1248
1249        #[test]
1250        fn test_pathops_concat() {
1251            let actual = Path::new("foo")
1252                .concat(Path::new("bar"))
1253                .expect("Could not concat paths?");
1254            let expected = PathBuf::from("foo/bar");
1255
1256            assert_eq!(actual, expected);
1257
1258            let actual = Path::new("foo")
1259                .concat(Path::new("bar/../baz"))
1260                .expect("Could not concat path with ..?");
1261            let expected = PathBuf::from("foo/baz");
1262
1263            assert_eq!(actual, expected);
1264
1265            let actual = Path::new("foo")
1266                .concat("..")
1267                .expect("Could not cancel path with ..?");
1268            let expected = PathBuf::from(r"");
1269
1270            assert_eq!(actual, expected);
1271
1272            let actual = Path::new("foo")
1273                .concat("../..")
1274                .expect("Could not prefix with ..?");
1275            let expected = PathBuf::from(r"../");
1276            assert_eq!(actual, expected);
1277
1278            let actual = Path::new("/foo")
1279                .concat("../..")
1280                .expect_err("Could escape root with ..?");
1281
1282            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1283            assert_eq!(actual.action(), "truncating to parent");
1284            assert_eq!(actual.path(), Path::new("/"));
1285
1286            let actual = Path::new("foo")
1287                .concat(Path::new("/etc/passwd"))
1288                .expect("Could not concat RootDir to path?");
1289            let expected: PathBuf = PathBuf::from("foo/etc/passwd");
1290
1291            assert_eq!(actual, expected);
1292        }
1293
1294        #[test]
1295        fn test_pathops_concat_relative() {
1296            let actual = Path::new("../foo")
1297                .concat("bar")
1298                .expect("Could not create relative path with concat");
1299            let expected = PathBuf::from(r"../foo/bar");
1300            assert_eq!(actual, expected);
1301
1302            let actual = Path::new("../foo")
1303                .concat("..")
1304                .expect("Could not create relative path with concat");
1305            let expected = PathBuf::from(r"..");
1306            assert_eq!(actual, expected);
1307
1308            let actual = Path::new("../foo")
1309                .concat("../..")
1310                .expect("Could not create relative path with concat");
1311            let expected = PathBuf::from(r"../..");
1312            assert_eq!(actual, expected);
1313
1314            let actual = Path::new("../foo/../bar")
1315                .concat("../..")
1316                .expect("Could not create relative path with concat");
1317            let expected = PathBuf::from(r"../..");
1318            assert_eq!(actual, expected);
1319
1320            let actual = Path::new("../foo/../bar/..")
1321                .concat("../..")
1322                .expect("Could not create relative path with concat");
1323            let expected = PathBuf::from(r"../../..");
1324            assert_eq!(actual, expected);
1325
1326            let actual = PathBuf::from("../foo/..")
1327                .concat("../../baz")
1328                .expect("Could not create relative path with concat");
1329            let expected = PathBuf::from(r"../../../baz");
1330            assert_eq!(actual, expected);
1331        }
1332
1333        #[test]
1334        fn test_pathops_concat_cur() {
1335            // just check that pahts don't normalize...
1336            let actual = Path::new("foo/././..").as_os_str();
1337            let expected = ffi::OsStr::new("foo/././..");
1338            assert_eq!(actual, expected);
1339
1340            let actual = PathBuf::from("././foo/././..")
1341                .concat("../bar")
1342                .expect("Could not create relative path with concat");
1343            let expected = PathBuf::from(r"../bar");
1344            assert_eq!(actual, expected);
1345        }
1346
1347        #[test]
1348        fn test_pathops_concat_consume() {
1349            let actual = Path::new("foo")
1350                .concat("../../bar")
1351                .expect("Could not create relative path with concat");
1352            let expected = PathBuf::from(r"../bar");
1353            assert_eq!(actual, expected);
1354        }
1355
1356        #[test]
1357        fn test_pathmut_append() {
1358            let mut actual = PathBuf::from("foo");
1359            actual
1360                .append(Path::new("bar"))
1361                .expect("Could not append paths?");
1362            let expected = PathBuf::from("foo/bar");
1363            assert_eq!(actual, expected);
1364
1365            let mut actual = PathBuf::from("foo");
1366            actual
1367                .append(Path::new("bar/../baz"))
1368                .expect("Could not append path with ..?");
1369            let expected = PathBuf::from("foo/baz");
1370            assert_eq!(actual, expected);
1371
1372            let mut actual = PathBuf::from("foo");
1373            actual.append("..").expect("Could not cancel path with ..?");
1374            let expected = PathBuf::from(r"");
1375            assert_eq!(actual, expected);
1376
1377            let mut actual = PathBuf::from("foo");
1378            actual
1379                .append("../..")
1380                .expect("Could not escape prefix with ..?");
1381            let expected = PathBuf::from("../");
1382            assert_eq!(actual, expected);
1383
1384            let actual = PathBuf::from("/foo")
1385                .append("../..")
1386                .expect_err("Could escape root with ..?");
1387            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1388            assert_eq!(actual.action(), "truncating to parent");
1389            assert_eq!(actual.path(), Path::new("/"));
1390
1391            let mut actual = PathBuf::from("foo");
1392            actual
1393                .append(Path::new("/etc/passwd"))
1394                .expect("Could not append RootDir to path?");
1395            let expected: PathBuf = PathBuf::from("foo/etc/passwd");
1396
1397            assert_eq!(actual, expected);
1398        }
1399
1400        #[test]
1401        fn test_pathmut_pop_up() {
1402            let mut p = PathBuf::from("/foo/bar");
1403            p.pop_up().expect("could not find parent?");
1404
1405            assert_eq!(p.as_path(), Path::new("/foo"));
1406
1407            let mut p = PathBuf::from("/");
1408            let actual = p.pop_up().expect_err("root has a parent?");
1409
1410            assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound);
1411            assert_eq!(actual.action(), "truncating to parent");
1412            assert_eq!(actual.path(), Path::new("/"));
1413        }
1414
1415        #[test]
1416        fn test_pathmut_truncate_to_root() {
1417            let mut p = PathBuf::from("/foo/bar");
1418            p.truncate_to_root();
1419            assert_eq!(p.as_path(), Path::new("/"));
1420
1421            let mut p = PathBuf::from("foo/bar");
1422            p.truncate_to_root();
1423            assert_eq!(p.as_path(), Path::new(""));
1424        }
1425    }
1426}