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}