Skip to content

Commit

Permalink
0.3.0 features: better error messages and File* types
Browse files Browse the repository at this point in the history
  • Loading branch information
vitiral committed Jan 25, 2018
1 parent ade0b07 commit b320e7e
Show file tree
Hide file tree
Showing 15 changed files with 1,520 additions and 260 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@ notifications:
on_success: never

script:
# TODO: test-threads=1 temporarily
- RUST_BACKTRACE=1 cargo test --verbose --all -- --nocapture --test-threads=1
- RUST_BACKTRACE=1 cargo test --verbose --all --no-default-features -- --nocapture --test-threads=1
- RUST_BACKTRACE=1 cargo test --verbose --all -- --nocapture
- RUST_BACKTRACE=1 cargo test --verbose --all --no-default-features -- --nocapture
15 changes: 8 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
[package]
authors = ["Garrett Berg <[email protected]>"]
description = "Absolute serializable path types and associated methods."
description = "Ergonomic paths and files in rust."
documentation = "https://docs.rs/path_abs"
keywords = [
"filesystem",
"path",
"cli",
"file",
"types",
"serde",
"serialize",
]
license = "MIT OR Apache-2.0"
name = "path_abs"
readme = "README.md"
repository = "https://github.com/vitiral/path_abs"
version = "0.2.3"
version = "0.3.0"

[dependencies.serde]
optional = true
Expand All @@ -28,9 +28,10 @@ optional = true
version = ">=0.2.1"

[dev-dependencies]
pretty_assertions = "0.4.1"
serde_json = "1.0.9"
tempdir = "0.3.5"
pretty_assertions = ">=0.4"
regex = ">=0.2"
serde_json = ">=1.0"
tempdir = ">=0.3"

[features]
default = ["serialize"]
Expand Down
58 changes: 28 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
# path_abs: Absolute serializable path types and associated methods.
# path_abs: ergonomic paths and files in rust.
[![Build Status](https://travis-ci.org/vitiral/path_abs.svg?branch=windows)](https://travis-ci.org/vitiral/path_abs)
[![Build status](https://ci.appveyor.com/api/projects/status/vgis54solhygre0n?svg=true)](https://ci.appveyor.com/project/vitiral/path-abs)
[![Docs](https://docs.rs/path_abs/badge.svg)](https://docs.rs/path_abs)

**See the [library docs](https://docs.rs/path_abs) for information on the
types**

The rust `Path` and `PathBuf` are great when you are constructing paths on the
filesystem that may or may not exist, or you care *immensely* about performance
and don't want the overhead of creating absolute (canonicalized) paths.

However, they have several downsides:
- They are not ergonomic. Actually *using* paths requires you to go through
`fs` module, namely `File`, `OpenOptions`, `create_dir`, etc. It is NOT fun.
`PathAbs` provides convienient methods -- you already know the path *exists*,
now you just want to *do things with it*. You can read/write/etc using
methods defined directly on `PathFile` and `PathDir`.
- Comparing paths is not reliable. Is `/foo/bar/baz` the same path as
`bar/baz`? It's impossible to tell without knowing the current directory
and the state of symlinks.
- It is impossible to know from the type whether a path exists (or indeed, ever
existed) or what its filetype is. Most applications are not deleting files,
so validating that a path exists once is usually "good enough", but no such
validation is guaranteed with `Path`. This is not wrong -- `Path` is supposed
to represent just a "string of cross-platform bits". However, ensuring
that you are only "referencing paths that once existed" is very helpful to
reduce unexpected errors in your application.
- There is no way to serialize Paths in an effective manner. Actually getting
the data has to happen through `std::os::<platform>::ffi::OsStrExt` and
is different on windows and linux. Even worse, window's UTF-16 can be
ill-formed which is *invalid* UTF-8, so cannot be encoded into UTF-8
directly. `PathAbs` solves this by using the
[`stfu8`](https://github.com/vitiral/stfu8) crate under the hood.
This library aims to provide ergonomic path and file operations to rust with
reasonable performance.

The stdlib `Path`, `PathBuf` and `File` objects have non-helpful error messages
(they don't mention the path where an error is from!) and don't tell you
anything about the _type_ the path was or whether the path _even exists_. It is
also next to impossible to compare paths(does one have a symlink? What is the
current workind directory? Etc).

The path_abs crate aims to make working with paths and files ergonomic, so that
you can (in general) be protected from errors by the _types_ of your path/file.

From the type you can tell:

- The path you have existed (at least at _one time_).
- The path you have is a certain type (`PathFile` or `PathDir`)
- Any errors that happen when querying the filesystem will _include information
about the path_.
- Methods related to your type are within easy reach. For example, you can
`PathFile.append_str("something")` or `PathDir.list()`.
- Open files have types which only impelement traits/methods related to
their abilities. `FileRead` can only read, `FileWrite` can only write
and `FileEdit` can do both. These types are _guaranteed_ by their
constructors to be able to accomplish their method and trait implemenations,
and they give better errors too!

**See the [library docs](https://docs.rs/path_abs) for more information**

# LICENSE
The source code in this repository is Licensed under either of
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ build: false
#directly or perform other testing commands. Rust will automatically be placed in the PATH
# environment variable.
test_script:
- cargo test --verbose --all %cargoflags% -- --nocapture --test-threads=1
- cargo test --verbose --all %cargoflags% -- --nocapture
130 changes: 130 additions & 0 deletions src/abs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* Copyright (c) 2018 Garrett Berg, [email protected]
*
* Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
* http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
* http://opensource.org/licenses/MIT>, at your option. This file may not be
* copied, modified, or distributed except according to those terms.
*/
//! The absolute path type, the root type for _most_ `Path*` types in this module
//! (except for `PathArc`).
use std::fmt;
use std::ops::Deref;
use std::io;
use std::convert::AsRef;
use std::path::Path;

use super::{PathArc, PathDir, PathFile};

#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
/// An absolute ([canonicalized][1]) path that is guaranteed (when created) to exist.
///
/// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize
pub struct PathAbs(pub(crate) PathArc);

impl PathAbs {
/// Instantiate a new `PathAbs`. The path must exist or `io::Error` will be returned.
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
/// use path_abs::PathAbs;
///
/// # fn main() {
/// let lib = PathAbs::new("src/lib.rs").unwrap();
/// # }
/// ```
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<PathAbs> {
let arc = PathArc::new(path);
arc.canonicalize()
}

/// Resolve the `PathAbs` as a `PathFile`. Return an error if it is not a file.
pub fn into_file(self) -> io::Result<PathFile> {
PathFile::from_abs(self)
}

/// Resolve the `PathAbs` as a `PathDir`. Return an error if it is not a directory.
pub fn into_dir(self) -> io::Result<PathDir> {
PathDir::from_abs(self)
}

/// Get the parent directory of this path as a `PathDir`.
///
/// > This does not make aditional syscalls, as the parent by definition must be a directory
/// > and exist.
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
/// use path_abs::{PathDir, PathFile};
///
/// # fn main() {
/// let lib = PathFile::new("src/lib.rs").unwrap();
/// let src = lib.parent_dir().unwrap();
/// assert_eq!(PathDir::new("src").unwrap(), src);
/// # }
/// ```
pub fn parent_dir(&self) -> Option<PathDir> {
match self.parent() {
Some(p) => Some(PathDir(PathAbs(PathArc::new(p)))),
None => None,
}
}

/// For constructing mocked paths during tests. This is effectively the same as a `PathBuf`.
///
/// This is NOT checked for validity so the file may or may not actually exist and will
/// NOT be, in any way, an absolute or canonicalized path.
///
/// # Examples
/// ```rust
/// # extern crate path_abs;
/// use path_abs::PathAbs;
///
/// # fn main() {
/// // this file exist
/// let lib = PathAbs::new("src/lib.rs").unwrap();
///
/// let lib_mocked = PathAbs::mock("src/lib.rs");
///
/// // in this case, the mocked file exists
/// assert!(lib_mocked.exists());
///
/// // However, it is NOT equivalent to `lib`
/// assert_ne!(lib, lib_mocked);
///
/// // this file doesn't exist at all
/// let dne = PathAbs::mock("src/dne.rs");
/// assert!(!dne.exists());
/// # }
/// ```
pub fn mock<P: AsRef<Path>>(fake_path: P) -> PathAbs {
PathAbs(PathArc::new(fake_path))
}
}

impl fmt::Debug for PathAbs {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}

impl AsRef<PathArc> for PathAbs {
fn as_ref(&self) -> &PathArc {
&self.0
}
}

impl Deref for PathAbs {
type Target = PathArc;

fn deref(&self) -> &PathArc {
&self.0
}
}

impl Into<PathArc> for PathAbs {
fn into(self) -> PathArc {
self.0
}
}
Loading

0 comments on commit b320e7e

Please sign in to comment.