path_abs-0.5.1/.cargo_vcs_info.json0000644000000001120000000000000126110ustar { "git": { "sha1": "90b2546cbaece49381a2e24f1916259dd351b5c1" } } path_abs-0.5.1/.gitignore000064400000000000000000000000400000000000000133470ustar 00000000000000 /target/ **/*.rs.bk Cargo.lock path_abs-0.5.1/.travis.yml000064400000000000000000000010210000000000000134700ustar 00000000000000language: rust branches: only: - master # This is where pull requests from "bors r+" are built. - staging # This is where pull requests from "bors try" are built. - trying rust: - stable - beta - nightly matrix: allow_failures: - rust: nightly cache: cargo notifications: email: on_success: never script: - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast -- --nocapture - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast --no-default-features -- --nocapture path_abs-0.5.1/Cargo.toml0000644000000025460000000000000106240ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "path_abs" version = "0.5.1" authors = ["Rett Berg "] description = "Ergonomic paths and files in rust." documentation = "https://docs.rs/path_abs" readme = "README.md" keywords = ["filesystem", "path", "file", "types", "serde"] license = "MIT OR Apache-2.0" repository = "https://github.com/vitiral/path_abs" [dependencies.serde] version = "^1.0" optional = true [dependencies.serde_derive] version = "^1.0" optional = true [dependencies.std_prelude] version = "0.2.12" [dependencies.stfu8] version = "^0.2.1" optional = true [dev-dependencies.pretty_assertions] version = "^0.4" [dev-dependencies.regex] version = "^0.2" [dev-dependencies.serde_json] version = "^1.0" [dev-dependencies.tempdir] version = "^0.3" [features] default = ["serialize"] serialize = ["serde", "serde_derive", "stfu8"] path_abs-0.5.1/Cargo.toml.orig000064400000000000000000000014350000000000000142570ustar 00000000000000[package] authors = ["Rett Berg "] description = "Ergonomic paths and files in rust." documentation = "https://docs.rs/path_abs" keywords = [ "filesystem", "path", "file", "types", "serde", ] license = "MIT OR Apache-2.0" name = "path_abs" readme = "README.md" repository = "https://github.com/vitiral/path_abs" version = "0.5.1" edition = "2018" [dependencies] std_prelude = "0.2.12" [dependencies.serde] optional = true version = "^1.0" [dependencies.serde_derive] optional = true version = "^1.0" [dependencies.stfu8] optional = true version = "^0.2.1" [dev-dependencies] pretty_assertions = "^0.4" regex = "^0.2" serde_json = "^1.0" tempdir = "^0.3" [features] default = ["serialize"] serialize = [ "serde", "serde_derive", "stfu8", ] path_abs-0.5.1/LICENSE-APACHE000064400000000000000000000010740000000000000133130ustar 00000000000000Copyright 2018 Garrett Berg, vitiral@gmail.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. path_abs-0.5.1/LICENSE-MIT000064400000000000000000000021120000000000000130150ustar 00000000000000The MIT License (MIT) Copyright (c) 2018 Garrett Berg, vitiral@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. path_abs-0.5.1/README.md000064400000000000000000000020560000000000000126470ustar 00000000000000# 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) This library aims to provide ergonomic path and file operations to rust with reasonable performance. **See the [library docs](https://docs.rs/path_abs) for more information** # LICENSE The source code in this repository is Licensed under either of - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. path_abs-0.5.1/appveyor.yml000064400000000000000000000106720000000000000137630ustar 00000000000000# Appveyor configuration template for Rust using rustup for Rust installation # https://github.com/starkat99/appveyor-rust ## Operating System (VM environment) ## # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. os: Visual Studio 2015 # Bors configuration # branches: # only: # - mainline # # This is where pull requests from "bors r+" are built. # - staging # # This is where pull requests from "bors try" are built. # - trying ## Build Matrix ## # This configuration will setup a build for each channel & target combination (12 windows # combinations in all). # # There are 3 channels: stable, beta, and nightly. # # Alternatively, the full version may be specified for the channel to build using that specific # version (e.g. channel: 1.5.0) # # The values for target are the set of windows Rust build targets. Each value is of the form # # ARCH-pc-windows-TOOLCHAIN # # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for # a description of the toolchain differences. # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of # toolchains and host triples. # # Comment out channel/target combos you do not wish to build in CI. # # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly # channels to enable unstable features when building for nightly. Or you could add additional # matrix entries to test different combinations of features. environment: RUST_BACKTRACE: 1 matrix: ### MSVC Toolchains ### # Stable 64-bit MSVC - channel: stable target: x86_64-pc-windows-msvc # Stable 32-bit MSVC - channel: stable target: i686-pc-windows-msvc # # Beta 64-bit MSVC # - channel: beta # target: x86_64-pc-windows-msvc # # Beta 32-bit MSVC # - channel: beta # target: i686-pc-windows-msvc # # Nightly 64-bit MSVC # - channel: nightly # target: x86_64-pc-windows-msvc # #cargoflags: --features "unstable" # # Nightly 32-bit MSVC # - channel: nightly # target: i686-pc-windows-msvc # #cargoflags: --features "unstable" ### GNU Toolchains ### # Stable 64-bit GNU - channel: stable target: x86_64-pc-windows-gnu # Stable 32-bit GNU - channel: stable target: i686-pc-windows-gnu # # Beta 64-bit GNU # - channel: beta # target: x86_64-pc-windows-gnu # # Beta 32-bit GNU # - channel: beta # target: i686-pc-windows-gnu # # Nightly 64-bit GNU # - channel: nightly # target: x86_64-pc-windows-gnu # #cargoflags: --features "unstable" # # Nightly 32-bit GNU # - channel: nightly # target: i686-pc-windows-gnu # #cargoflags: --features "unstable" ### Allowed failures ### # See Appveyor documentation for specific details. In short, place any channel or targets you wish # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build # or test failure in the matching channels/targets from failing the entire build. matrix: allow_failures: - channel: nightly # If you only care about stable channel build failures, uncomment the following line: #- channel: beta ## Install Script ## # This is the most important part of the Appveyor configuration. This installs the version of Rust # specified by the 'channel' and 'target' environment variables from the build matrix. This uses # rustup to install Rust. # # For simple configurations, instead of using the build matrix, you can simply set the # default-toolchain and default-host manually here. install: - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init -yv --default-toolchain %channel% --default-host %target% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV ## Build Script ## # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents # the "directory does not contain a project or solution file" error. build: false # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs #directly or perform other testing commands. Rust will automatically be placed in the PATH # environment variable. test_script: - cargo test --verbose --all --no-fail-fast %cargoflags% -- --nocapture path_abs-0.5.1/bors.toml000064400000000000000000000003000000000000000132200ustar 00000000000000status = [ "continuous-integration/travis-ci/push", "continuous-integration/appveyor/branch" ] # Uncomment this to use a two hour timeout. # The default is one hour. # timeout_sec = 7200 path_abs-0.5.1/src/abs.rs000064400000000000000000000217530000000000000132770ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , 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 all `Path*` types in this module. use std::env; use std::ffi; use std::fmt; use std::io; use std::path::{Component, PrefixComponent}; use std_prelude::*; use super::{Error, PathMut, PathOps, Result}; /// Converts any PrefixComponent into verbatim ("extended-length") form. fn make_verbatim_prefix(prefix: &PrefixComponent<'_>) -> Result { let path_prefix = Path::new(prefix.as_os_str()); if prefix.kind().is_verbatim() { // This prefix already uses the extended-length // syntax, so we can use it as-is. Ok(path_prefix.to_path_buf()) } else { // This prefix needs canonicalization. let res = path_prefix .canonicalize() .map_err(|e| Error::new(e, "canonicalizing", path_prefix.to_path_buf().into()))?; Ok(res) } } /// Pops the last component from path, returning an error for a root path. fn pop_or_error(path: &mut PathBuf) -> ::std::result::Result<(), io::Error> { if path.pop() { Ok(()) } else { Err(io::Error::new(io::ErrorKind::NotFound, ".. consumed root")) } } #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] /// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist. /// /// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize pub struct PathAbs(pub(crate) Arc); impl PathAbs { /// Construct an absolute path from an arbitrary (absolute or relative) one. /// /// This is different from [`canonicalize`] in that it _preserves_ symlinks /// and the destination may or may not exist. /// /// This function will: /// - Resolve relative paths against the current working directory. /// - Strip any `.` components (`/a/./c` -> `/a/c`) /// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will /// _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use /// `canonicalize()` instead. /// /// > On windows, this will sometimes call `canonicalize()` on the first component to guarantee /// > it is the correct canonicalized prefix. For paths starting with root it also has to get /// > the [`current_dir`] /// /// > On linux, the only syscall this will make is to get the [`current_dir`] for relative /// > paths. /// /// [`canonicalize`]: struct.PathAbs.html#method.canonicalize /// [`current_dir`]: fn.current_dir.html /// /// # Examples /// /// ```rust /// use path_abs::{PathAbs, PathInfo}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let lib = PathAbs::new("src/lib.rs")?; /// /// assert_eq!(lib.is_absolute(), true); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn new>(path: P) -> Result { let path = Arc::new(path.as_ref().to_path_buf()); let mut res = PathBuf::new(); fn maybe_init_res(res: &mut PathBuf, resolvee: Arc) -> Result<()> { if !res.as_os_str().is_empty() { // res has already been initialized, let's leave it alone. return Ok(()); } // res has not been initialized, let's initialize it to the // canonicalized current directory. let cwd = env::current_dir().map_err(|e| { Error::new(e, "getting current_dir while resolving absolute", resolvee) })?; *res = cwd .canonicalize() .map_err(|e| Error::new(e, "canonicalizing", cwd.into()))?; Ok(()) }; for each in path.components() { match each { Component::Prefix(p) => { // We don't care what's already in res, we can entirely // replace it.. res = make_verbatim_prefix(&p)?; } Component::RootDir => { if cfg!(windows) { // In an ideal world, we would say // // res = std::fs::canonicalize(each)?; // // ...to get a properly canonicalized path. // Unfortunately, Windows cannot canonicalize `\` if // the current directory happens to use extended-length // syntax (like `\\?\C:\Windows`), so we'll have to do // it manually: initialize `res` with the current // working directory (whatever it is), and truncate it // to its prefix by pushing `\`. maybe_init_res(&mut res, path.clone())?; res.push(each); } else { // On other platforms, a root path component is always // absolute so we can replace whatever's in res. res = Path::new(&each).to_path_buf(); } } // This does nothing and can be ignored. Component::CurDir => (), Component::ParentDir => { // A parent component is always relative to some existing // path. maybe_init_res(&mut res, path.clone())?; pop_or_error(&mut res) .map_err(|e| Error::new(e, "resolving absolute", path.clone()))?; } Component::Normal(c) => { // A normal component is always relative to some existing // path. maybe_init_res(&mut res, path.clone())?; res.push(c); } } } Ok(PathAbs(Arc::new(res))) } /// Create a PathAbs unchecked. /// /// This is mostly used for constructing during tests, or if the path was previously validated. /// This is effectively the same as a `Arc`. /// /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause /// > panics in some methods if the path was not properly validated. pub fn new_unchecked>>(path: P) -> PathAbs { PathAbs(path.into()) } /// Return a reference to a basic `std::path::Path` pub fn as_path(&self) -> &Path { self.as_ref() } } impl fmt::Debug for PathAbs { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl PathMut for PathAbs { fn append>(&mut self, path: P) -> Result<()> { self.0.append(path) } fn pop_up(&mut self) -> Result<()> { self.0.pop_up() } fn truncate_to_root(&mut self) { self.0.truncate_to_root() } fn set_file_name>(&mut self, file_name: S) { self.0.set_file_name(file_name) } fn set_extension>(&mut self, extension: S) -> bool { self.0.set_extension(extension) } } impl PathOps for PathAbs { type Output = PathAbs; fn concat>(&self, path: P) -> Result { Ok(PathAbs(self.0.concat(path)?)) } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self.as_path(), path); Self::Output::new_unchecked(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { PathAbs(self.0.with_file_name(file_name)) } fn with_extension>(&self, extension: S) -> Self::Output { PathAbs(self.0.with_extension(extension)) } } impl AsRef for PathAbs { fn as_ref(&self) -> &std::ffi::OsStr { self.0.as_ref().as_ref() } } impl AsRef for PathAbs { fn as_ref(&self) -> &Path { self.0.as_ref() } } impl AsRef for PathAbs { fn as_ref(&self) -> &PathBuf { self.0.as_ref() } } impl Borrow for PathAbs { fn borrow(&self) -> &Path { self.as_ref() } } impl Borrow for PathAbs { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl<'a> Borrow for &'a PathAbs { fn borrow(&self) -> &Path { self.as_ref() } } impl<'a> Borrow for &'a PathAbs { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl From for Arc { fn from(path: PathAbs) -> Arc { path.0 } } impl From for PathBuf { fn from(path: PathAbs) -> PathBuf { match Arc::try_unwrap(path.0) { Ok(p) => p, Err(inner) => inner.as_ref().clone(), } } } path_abs-0.5.1/src/dir.rs000064400000000000000000000404620000000000000133060ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Paths to Directories and associated methods. use std::ffi; use std::fmt; use std::fs; use std::io; use std_prelude::*; use super::{Error, Result}; use super::{PathAbs, PathInfo, PathOps, PathType}; #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] /// A `PathAbs` that is guaranteed to be a directory, with associated methods. pub struct PathDir(pub(crate) PathAbs); impl PathDir { /// Instantiate a new `PathDir`. The directory must exist or `io::Error` will be returned. /// /// Returns `io::ErrorKind::InvalidInput` if the path exists but is not a directory. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathDir; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src = PathDir::new("src")?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn new>(path: P) -> Result { let abs = PathAbs::new(path)?; PathDir::try_from(abs) } /// Create a `PathDir` unchecked. /// /// This is mostly used for constructing during tests, or if the path was previously validated. /// This is effectively the same as a `Arc`. /// /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause /// > panics in some methods if the path was not properly validated. pub fn new_unchecked>>(path: P) -> PathDir { PathDir(PathAbs::new_unchecked(path)) } /// Returns the current working directory from the `env` as a `PathDir`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathDir; /// # fn try_main() -> ::std::io::Result<()> { /// let cwd = PathDir::current_dir()?; /// # let env_cwd = ::std::fs::canonicalize(::std::env::current_dir()?)?; /// # let cwd_ref: &::std::path::PathBuf = cwd.as_ref(); /// # assert_eq!(cwd_ref, &env_cwd); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn current_dir() -> Result { let dir = ::std::env::current_dir().map_err(|err| { Error::new( err, "getting current_dir", Path::new("$CWD").to_path_buf().into(), ) })?; PathDir::new(dir) } /// Consume the `PathAbs` validating that the path is a directory and returning `PathDir`. The /// directory must exist or `io::Error` will be returned. /// /// If the path is actually a file returns `io::ErrorKind::InvalidInput`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::{PathAbs, PathDir}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src_abs = PathAbs::new("src")?; /// let src_dir = PathDir::try_from(src_abs)?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn try_from>(path: P) -> Result { let abs = path.into(); if abs.is_dir() { Ok(PathDir::new_unchecked(abs)) } else { Err(Error::new( io::Error::new(io::ErrorKind::InvalidInput, "path is not a dir"), "resolving", abs.into(), )) } } /// Instantiate a new `PathDir` to a directory, creating the directory if it doesn't exist. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathDir; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let dir = PathDir::create(example)?; /// /// // It can be done twice with no effect. /// let _ = PathDir::create(example)?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn create>(path: P) -> Result { if let Err(err) = fs::create_dir(&path) { match err.kind() { io::ErrorKind::AlreadyExists => {} _ => { return Err(Error::new( err, "creating", path.as_ref().to_path_buf().into(), )); } } } PathDir::new(path) } /// Instantiate a new `PathDir` to a directory, recursively recreating it and all of its parent /// components if they are missing. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathDir; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example/long/path"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let path = PathDir::create_all(example)?; /// /// // It can be done twice with no effect. /// let _ = PathDir::create_all(example)?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn create_all>(path: P) -> Result { fs::create_dir_all(&path) .map_err(|err| Error::new(err, "creating-all", path.as_ref().to_path_buf().into()))?; PathDir::new(path) } /// Join a path onto the `PathDir`, expecting it to exist. Returns the resulting `PathType`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::{PathDir, PathFile, PathInfo}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src = PathDir::new("src")?; /// let lib = src.join_abs("lib.rs")?.unwrap_file(); /// assert!(lib.is_file()); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn join_abs>(&self, path: P) -> Result { let joined = self.concat(path.as_ref())?; PathType::new(joined) } /// List the contents of the directory, returning an iterator of `PathType`s. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::collections::HashSet; /// use path_abs::{PathDir, PathFile, PathType, PathOps}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join("example"); /// /// let example_dir = PathDir::create(example)?; /// let foo_dir = PathDir::create(example_dir.concat("foo")?)?; /// let bar_file = PathFile::create(example_dir.concat("bar.txt")?)?; /// /// let mut result = HashSet::new(); /// for p in example_dir.list()? { /// result.insert(p?); /// } /// /// let mut expected = HashSet::new(); /// expected.insert(PathType::Dir(foo_dir)); /// expected.insert(PathType::File(bar_file)); /// /// assert_eq!(expected, result); /// # Ok(()) } fn main() { try_main().unwrap() } pub fn list(&self) -> Result { let fsread = fs::read_dir(self) .map_err(|err| Error::new(err, "reading dir", self.clone().into()))?; Ok(ListDir { dir: self.clone(), fsread: fsread, }) } /// Remove (delete) the _empty_ directory from the filesystem, consuming self. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::path::Path; /// use path_abs::PathDir; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = Path::new("example/long/path"); /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let dir = PathDir::create_all(example)?; /// let parent = dir.parent_dir().unwrap(); /// /// assert!(example.exists()); /// dir.remove()?; /// // assert!(dir.exists()); <--- COMPILE ERROR /// assert!(!example.exists()); /// parent.remove()?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn remove(self) -> Result<()> { fs::remove_dir(&self).map_err(|err| Error::new(err, "removing", self.into())) } /// Remove (delete) the directory, after recursively removing its contents. Use carefully! /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::path::Path; /// use path_abs::PathDir; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = Path::new("example/long/path"); /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let dir = PathDir::create_all(example)?; /// let parent = dir.parent_dir().unwrap(); /// /// assert!(example.exists()); /// parent.remove_all()?; /// assert!(!example.exists()); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn remove_all(self) -> Result<()> { fs::remove_dir_all(&self).map_err(|err| Error::new(err, "removing-all", self.into())) } /// Creates a new symbolic link on the filesystem to the dst. /// /// This handles platform specific behavior correctly. /// /// # Examples /// /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::{PathDir, PathFile, PathOps}; /// use std::path::Path; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example"; /// let example_sym = "example_sym"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// # let example_sym = &tmp.path().join(example_sym); /// let dir = PathDir::create(example)?; /// let file = PathFile::create(dir.concat("example.txt")?)?; /// /// let dir_sym = dir.symlink(example_sym)?; /// /// // They have a different "absolute path" /// assert_ne!(dir, dir_sym); /// /// // But they can be canonicalized to the same file. /// let dir_can = dir_sym.canonicalize()?; /// assert_eq!(dir, dir_can); /// /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn symlink>(&self, dst: P) -> Result { symlink_dir(&self, &dst).map_err(|err| { Error::new( err, &format!("linking from {} to", dst.as_ref().display()), self.clone().into(), ) })?; PathDir::new(dst) } /// Return a reference to a basic `std::path::Path` pub fn as_path(&self) -> &Path { self.as_ref() } /// Returns the canonical form of the path with all intermediate components normalized and /// symbolic links resolved. /// /// See [`PathAbs::canonicalize`] /// /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize pub fn canonicalize(&self) -> Result { Ok(PathDir(self.0.canonicalize()?)) } /// Get the parent directory of this directory 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; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src = PathDir::new("src")?; /// let proj = src.parent_dir().unwrap(); /// assert_eq!(PathDir::new("src/..")?, proj); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn parent_dir(&self) -> Option { match self.parent() { Ok(path) => Some(PathDir(PathAbs(Arc::new(path.to_path_buf())))), Err(_) => None, } } } /// An iterator over `PathType` objects, returned by `PathDir::list`. pub struct ListDir { // TODO: this should be a reference...? // Or is this a good excuse to use Arc under the hood everywhere? dir: PathDir, fsread: fs::ReadDir, } impl ::std::iter::Iterator for ListDir { type Item = Result; fn next(&mut self) -> Option> { let entry = match self.fsread.next() { Some(r) => match r { Ok(e) => e, Err(err) => { return Some(Err(Error::new( err, "iterating over", self.dir.clone().into(), ))); } }, None => return None, }; Some(PathType::new(entry.path())) } } impl fmt::Debug for PathDir { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl AsRef for PathDir { fn as_ref(&self) -> &std::ffi::OsStr { self.0.as_ref() } } impl AsRef for PathDir { fn as_ref(&self) -> &PathAbs { &self.0 } } impl AsRef for PathDir { fn as_ref(&self) -> &Path { self.0.as_ref() } } impl AsRef for PathDir { fn as_ref(&self) -> &PathBuf { self.0.as_ref() } } impl Borrow for PathDir { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl Borrow for PathDir { fn borrow(&self) -> &Path { self.as_ref() } } impl Borrow for PathDir { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl<'a> Borrow for &'a PathDir { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl<'a> Borrow for &'a PathDir { fn borrow(&self) -> &Path { self.as_ref() } } impl<'a> Borrow for &'a PathDir { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl From for PathAbs { fn from(path: PathDir) -> PathAbs { path.0 } } impl From for Arc { fn from(path: PathDir) -> Arc { let abs: PathAbs = path.into(); abs.into() } } impl From for PathBuf { fn from(path: PathDir) -> PathBuf { let abs: PathAbs = path.into(); abs.into() } } #[cfg(test)] mod tests { use super::super::{PathAbs, PathDir, PathFile, PathOps, PathType}; use std::collections::HashSet; use tempdir::TempDir; #[test] fn sanity_list() { let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_abs = PathDir::new(tmp_dir.path()).unwrap(); let foo_path = tmp_abs.concat("foo").expect("path foo"); let foo_dir = PathDir::create(foo_path).unwrap(); let bar_path = tmp_abs.concat("bar").expect("path bar"); let bar_file = PathFile::create(bar_path).unwrap(); let mut result = HashSet::new(); for p in tmp_abs.list().unwrap() { result.insert(p.unwrap()); } let mut expected = HashSet::new(); expected.insert(PathType::Dir(foo_dir.clone())); expected.insert(PathType::File(bar_file.clone())); assert_eq!(expected, result); // just ensure that this compiles let _: PathAbs = foo_dir.into(); let _: PathAbs = bar_file.into(); } } impl PathOps for PathDir { type Output = PathAbs; fn concat>(&self, path: P) -> Result { Ok(self.0.concat(path)?) } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self.as_path(), path); Self::Output::new_unchecked(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { self.0.with_file_name(file_name) } fn with_extension>(&self, extension: S) -> Self::Output { self.0.with_extension(extension) } } #[cfg(target_os = "wasi")] fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { std::os::wasi::fs::symlink_path(src, dst) } #[cfg(unix)] fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { ::std::os::unix::fs::symlink(src, dst) } #[cfg(windows)] fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { ::std::os::windows::fs::symlink_dir(src, dst) } path_abs-0.5.1/src/edit.rs000064400000000000000000000203420000000000000134500ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Open editable (read+write) file paths and associated methods. use std::fmt; use std::fs; use std::io; use std_prelude::*; use super::open::FileOpen; use super::{Error, PathAbs, PathInfo, Result}; /// A read/write file handle with `path()` attached and improved error messages. Contains methods /// and trait implements for both readable _and_ writeable files. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::{Read, Seek, Write, SeekFrom}; /// use path_abs::FileEdit; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let expected = "foo\nbar"; /// let mut edit = FileEdit::create(example)?; /// /// let mut s = String::new(); /// edit.write_all(expected.as_bytes())?; /// edit.seek(SeekFrom::Start(0))?; /// edit.read_to_string(&mut s)?; /// /// assert_eq!(expected, s); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub struct FileEdit(pub(crate) FileOpen); impl FileEdit { /// Open the file with the given `OpenOptions` but always sets `read` and `write` to true. pub fn open>(path: P, mut options: fs::OpenOptions) -> Result { options.write(true); options.read(true); Ok(FileEdit(FileOpen::open(path, options)?)) } /// Shortcut to open the file if the path is already absolute. pub(crate) fn open_abs>( path: P, mut options: fs::OpenOptions, ) -> Result { options.write(true); options.read(true); Ok(FileEdit(FileOpen::open_abs(path, options)?)) } /// Open the file in editing mode, truncating it first if it exists and creating it /// otherwise. pub fn create>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.truncate(true); options.create(true); FileEdit::open(path, options) } /// Open the file for appending, creating it if it doesn't exist. pub fn append>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.append(true); options.create(true); FileEdit::open(path, options) } /// Open the file for editing (reading and writing) but do not create it /// if it doesn't exist. pub fn edit>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.read(true); FileEdit::open(path, options) } /// Attempts to sync all OS-internal metadata to disk. /// /// This function will attempt to ensure that all in-core data reaches the filesystem before /// returning. /// /// This function is identical to [std::fs::File::sync_all][0] except it has error /// messages which include the action and the path. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all pub fn sync_all(&self) -> Result<()> { self.0 .file .sync_all() .map_err(|err| Error::new(err, "syncing", self.0.path.clone().into())) } /// This function is similar to sync_all, except that it may not synchronize file metadata to /// the filesystem. /// /// This function is identical to [std::fs::File::sync_data][0] except it has error /// messages which include the action and the path. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data pub fn sync_data(&self) -> Result<()> { self.0 .file .sync_data() .map_err(|err| Error::new(err, "syncing data for", self.0.path.clone().into())) } /// Truncates or extends the underlying file, updating the size of this file to become size. /// /// This function is identical to [std::fs::File::set_len][0] except: /// /// - It has error messages which include the action and the path. /// - It takes `&mut self` instead of `&self`. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len pub fn set_len(&mut self, size: u64) -> Result<()> { self.0 .file .set_len(size) .map_err(|err| Error::new(err, "setting len for", self.0.path.clone().into())) } /// Changes the permissions on the underlying file. /// /// This function is identical to [std::fs::File::set_permissions][0] except: /// /// - It has error messages which include the action and the path. /// - It takes `&mut self` instead of `&self`. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_permissions pub fn set_permissions(&mut self, perm: fs::Permissions) -> Result<()> { self.0 .file .set_permissions(perm) .map_err(|err| Error::new(err, "setting permisions for", self.0.path.clone().into())) } /// Read what remains of the file to a `String`. pub fn read_string(&mut self) -> Result { let mut s = String::new(); self.0 .file .read_to_string(&mut s) .map_err(|err| Error::new(err, "reading", self.0.path.clone().into()))?; Ok(s) } /// Shortcut to `self.write_all(s.as_bytes())` with slightly /// improved error message. pub fn write_str(&mut self, s: &str) -> Result<()> { self.0 .file .write_all(s.as_bytes()) .map_err(|err| Error::new(err, "writing", self.0.path.clone().into())) } /// `std::io::File::flush` buth with the new error type. pub fn flush(&mut self) -> Result<()> { self.0 .file .flush() .map_err(|err| Error::new(err, "flushing", self.0.path.clone().into())) } } impl fmt::Debug for FileEdit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FileEdit(")?; self.0.path.fmt(f)?; write!(f, ")") } } impl io::Read for FileEdit { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.file.read(buf).map_err(|err| { io::Error::new( err.kind(), format!("{} when reading {}", err, self.0.path().display()), ) }) } } impl io::Write for FileEdit { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.file.write(buf).map_err(|err| { io::Error::new( err.kind(), format!("{} when writing to {}", err, self.0.path().display()), ) }) } fn flush(&mut self) -> io::Result<()> { self.0.file.flush().map_err(|err| { io::Error::new( err.kind(), format!("{} when flushing {}", err, self.0.path().display()), ) }) } } impl io::Seek for FileEdit { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { self.0.file.seek(pos).map_err(|err| { io::Error::new( err.kind(), format!("{} seeking {}", err, self.0.path().display()), ) }) } } impl AsRef for FileEdit { fn as_ref(&self) -> &FileOpen { &self.0 } } impl AsRef for FileEdit { fn as_ref(&self) -> &File { &self.0.as_ref() } } impl Borrow for FileEdit { fn borrow(&self) -> &FileOpen { &self.0 } } impl Borrow for FileEdit { fn borrow(&self) -> &File { &self.0.borrow() } } impl<'a> Borrow for &'a FileEdit { fn borrow(&self) -> &FileOpen { &self.0 } } impl<'a> Borrow for &'a FileEdit { fn borrow(&self) -> &File { &self.0.borrow() } } impl From for FileOpen { fn from(orig: FileEdit) -> FileOpen { orig.0 } } impl From for File { fn from(orig: FileEdit) -> File { orig.0.into() } } path_abs-0.5.1/src/file.rs000064400000000000000000000433250000000000000134500ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ use std::ffi; use std::fmt; use std::fs; use std::io; use std_prelude::*; use super::{Error, Result}; use super::{FileEdit, FileRead, FileWrite, PathAbs, PathDir, PathInfo, PathOps}; #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] /// a `PathAbs` that was a file at the time of initialization, with associated methods. pub struct PathFile(pub(crate) PathAbs); impl PathFile { /// Instantiate a new `PathFile`. The file must exist or `io::Error` will be returned. /// /// Returns `io::ErrorKind::InvalidInput` if the path exists but is not a file. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let lib = PathFile::new("src/lib.rs")?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn new>(path: P) -> Result { let abs = PathAbs::new(path)?; PathFile::try_from(abs) } /// Create a `PathFile` unchecked. /// /// This is mostly used for constructing during tests, or if the path was previously validated. /// This is effectively the same as a `Arc`. /// /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause /// > panics in some methods if the path was not properly validated. pub fn new_unchecked>>(path: P) -> PathFile { PathFile(PathAbs::new_unchecked(path)) } /// Convert a `PathAbs` into a `PathFile`, first validating that the path is a file. /// /// # Error /// If the path is not a file. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::{PathAbs, PathFile}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let lib_abs = PathAbs::new("src/lib.rs")?; /// let lib_file = PathFile::try_from(lib_abs)?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn try_from>(path: P) -> Result { let abs = path.into(); if abs.is_file() { Ok(PathFile::new_unchecked(abs)) } else { Err(Error::new( io::Error::new(io::ErrorKind::InvalidInput, "path is not a file"), "resolving", abs.into(), )) } } /// Get the parent directory of this file as a `PathDir`. /// /// > This does not make aditional syscalls, as the parent by definition must be a directory /// > and exist. /// /// # Panics /// Panics if there is no parent. The only way this could happen is if /// it was constructed with `new_unchecked` using a relative path. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::{PathDir, PathFile}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let lib = PathFile::new("src/lib.rs")?; /// let src = lib.parent_dir(); /// assert_eq!(PathDir::new("src")?, src); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn parent_dir(&self) -> PathDir { let path = self.parent().expect("PathFile did not have a parent."); PathDir::new_unchecked(PathAbs::new_unchecked(path.to_path_buf())) } /// Instantiate a new `PathFile`, creating an empty file if it doesn't exist. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let file = PathFile::create(example)?; /// /// // It can be done twice with no effect. /// let _ = PathFile::create(example)?; /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn create>(path: P) -> Result { fs::OpenOptions::new() .write(true) .create(true) .open(&path) .map_err(|err| Error::new(err, "opening", path.as_ref().to_path_buf().into()))?; PathFile::new(path) } /// Read the entire contents of the file into a `String`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar"; /// file.write_str(expected)?; /// assert_eq!(expected, file.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn read_string(&self) -> Result { let mut f = self.open_read()?; f.read_string() } /// Write the `str` to a file, truncating it first if it exists and creating it otherwise. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar"; /// file.write_str(expected)?; /// assert_eq!(expected, file.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn write_str(&self, s: &str) -> Result<()> { let mut options = fs::OpenOptions::new(); options.create(true); options.truncate(true); let mut f = FileWrite::open_abs(self.clone(), options)?; if s.is_empty() { return Ok(()); } f.write_str(s)?; f.flush() } /// Append the `str` to a file, creating it if it doesn't exist. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar\nbaz"; /// file.append_str("foo\nbar")?; /// file.append_str("\nbaz")?; /// assert_eq!(expected, file.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn append_str(&self, s: &str) -> Result<()> { let mut f = self.open_append()?; if s.is_empty() { return Ok(()); } f.write_str(s)?; f.flush() } /// Open the file as read-only. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::Read; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar"; /// file.write_str(expected)?; /// /// let mut read = file.open_read()?; /// let mut s = String::new(); /// read.read_to_string(&mut s)?; /// assert_eq!(expected, s); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn open_read(&self) -> Result { FileRead::open_abs(self.clone()) } /// Open the file as write-only in append mode. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::Write; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar\n"; /// file.write_str("foo\n")?; /// /// let mut append = file.open_append()?; /// append.write_all(b"bar\n")?; /// append.flush(); /// assert_eq!(expected, file.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn open_append(&self) -> Result { let mut options = fs::OpenOptions::new(); options.append(true); FileWrite::open_abs(self.clone(), options) } /// Open the file for editing (reading and writing). /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::{Read, Seek, Write, SeekFrom}; /// use path_abs::PathFile; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar"; /// /// let mut edit = file.open_edit()?; /// let mut s = String::new(); /// /// edit.write_all(expected.as_bytes())?; /// edit.seek(SeekFrom::Start(0))?; /// edit.read_to_string(&mut s)?; /// assert_eq!(expected, s); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn open_edit(&self) -> Result { FileEdit::open_abs(self.clone(), fs::OpenOptions::new()) } /// Copy the file to another location, including permission bits /// /// # Examples /// /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// use std::path::Path; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// let example_bk = "example.txt.bk"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// # let example_bk = &tmp.path().join(example_bk); /// let file = PathFile::create(example)?; /// /// let contents = "This is some contents"; /// file.write_str(contents); /// let file_bk = file.copy(example_bk)?; /// assert_eq!(contents, file.read_string()?); /// assert_eq!(contents, file_bk.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn copy>(&self, path: P) -> Result { fs::copy(&self, &path).map_err(|err| { Error::new( err, &format!("copying {} from", path.as_ref().display()), self.clone().into(), ) })?; Ok(PathFile::new(path)?) } /// Rename a file, replacing the original file if `to` already exists. /// /// This will not work if the new name is on a different mount point. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::{PathFile, PathInfo}; /// use std::path::Path; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// let example_bk = "example.txt.bk"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// # let example_bk = &tmp.path().join(example_bk); /// let file = PathFile::create(example)?; /// /// let contents = "This is some contents"; /// file.write_str(contents); /// let file_bk = file.clone().rename(example_bk)?; /// assert!(!file.exists()); /// assert_eq!(contents, file_bk.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn rename>(self, to: P) -> Result { fs::rename(&self, &to).map_err(|err| { Error::new( err, &format!("renaming to {} from", to.as_ref().display()), self.clone().into(), ) })?; Ok(PathFile::new(to)?) } /// Creates a new symbolic link on the filesystem to the dst. /// /// This handles platform specific behavior correctly. /// /// # Examples /// /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::PathFile; /// use std::path::Path; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// let example_sym = "example.txt.sym"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// # let example_sym = &tmp.path().join(example_sym); /// let file = PathFile::create(example)?; /// /// let contents = "This is some contents"; /// file.write_str(contents); /// let file_sym = file.symlink(example_sym)?; /// /// // They have a different "absolute path" /// assert_ne!(file, file_sym); /// /// // But they can be canonicalized to the same file. /// let file_can = file_sym.canonicalize()?; /// assert_eq!(file, file_can); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn symlink>(&self, dst: P) -> Result { symlink_file(&self, &dst).map_err(|err| { Error::new( err, &format!("linking from {} to", dst.as_ref().display()), self.clone().into(), ) })?; PathFile::new(dst) } /// Remove (delete) the file from the filesystem, consuming self. /// /// # Examples /// /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use path_abs::{PathFile, PathInfo}; /// use std::path::Path; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// assert!(file.exists()); /// file.remove()?; /// /// // file.exists() <--- COMPILER ERROR, `file` was consumed /// /// assert!(!Path::new(example).exists()); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub fn remove(self) -> Result<()> { fs::remove_file(&self).map_err(|err| Error::new(err, "removing", self.into())) } /// Return a reference to a basic `std::path::Path` pub fn as_path(&self) -> &Path { self.as_ref() } /// Returns the canonical form of the path with all intermediate components normalized and /// symbolic links resolved. /// /// See [`PathAbs::canonicalize`] /// /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize pub fn canonicalize(&self) -> Result { Ok(PathFile(self.0.canonicalize()?)) } } impl fmt::Debug for PathFile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl AsRef for PathFile { fn as_ref(&self) -> &std::ffi::OsStr { self.0.as_ref() } } impl AsRef for PathFile { fn as_ref(&self) -> &PathAbs { &self.0 } } impl AsRef for PathFile { fn as_ref(&self) -> &Path { self.0.as_ref() } } impl AsRef for PathFile { fn as_ref(&self) -> &PathBuf { self.0.as_ref() } } impl Borrow for PathFile { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl Borrow for PathFile { fn borrow(&self) -> &Path { self.as_ref() } } impl Borrow for PathFile { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl<'a> Borrow for &'a PathFile { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl<'a> Borrow for &'a PathFile { fn borrow(&self) -> &Path { self.as_ref() } } impl<'a> Borrow for &'a PathFile { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl From for PathAbs { fn from(path: PathFile) -> PathAbs { path.0 } } impl From for Arc { fn from(path: PathFile) -> Arc { let abs: PathAbs = path.into(); abs.into() } } impl From for PathBuf { fn from(path: PathFile) -> PathBuf { let abs: PathAbs = path.into(); abs.into() } } impl PathOps for PathFile { type Output = PathAbs; fn concat>(&self, path: P) -> Result { Ok(self.0.concat(path)?) } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self.as_path(), path); Self::Output::new_unchecked(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { self.0.with_file_name(file_name) } fn with_extension>(&self, extension: S) -> Self::Output { self.0.with_extension(extension) } } #[cfg(target_os = "wasi")] fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { std::os::wasi::fs::symlink_path(src, dst) } #[cfg(unix)] fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { ::std::os::unix::fs::symlink(src, dst) } #[cfg(windows)] fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { ::std::os::windows::fs::symlink_file(src, dst) } path_abs-0.5.1/src/lib.rs000064400000000000000000001371150000000000000133000ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Ergonomic paths and files in rust. //! //! This library aims to provide ergonomic path and file operations to rust with reasonable //! performance. //! //! This includes: //! //! - Improved methods for the `std` path types using [`PathInfo`] [`PathMut`] and [`PathOps`] //! - Cleaner _absolute_ paths (which is distinct from canonicalized paths). //! - Improved error messages, see the [Better Errors](#better-errors) section. //! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a //! certain type. Obviously a file/dir can be deleted/changed by another process. //! - More stringent mutability requirements. See the //! [Differing Method Signatures](#differing-method-signatures) section. //! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem //! operations and allows more flexibility and ergonomics in the library for relatively low cost. //! //! ## Better Errors //! //! All errors include the **path** and **action** which caused the error, as well as the unaltered //! `std::io::Error` message. Errors are convertable into `std::io::Error`, giving almost complete //! compatibility with existing code. //! //! ### `set_len` (i.e. truncate a file): //! //! - [`/* */ std::fs::File::set_len(0)`][file_set_len]: `Invalid argument (os error 22)` //! - [`path_abs::FileWrite::set_len(0)`][path_set_len]: `Invalid argument (os error 22) when setting //! len for /path/to/example/foo.txt` //! //! > The above error is actually impossible because `FileWrite` is always writeable, and //! > `FileRead` does not implement `set_len`. However, it is kept for demonstration. //! //! ### `open_read` (open file for reading): //! //! - [`/**/ std::fs::File::read(path)`][file_read]: `No such file or directory (os error 2)` //! - [`path_abs::FileRead::open(path)`][path_read]: `No such file or directory (os error 2) when //! opening example/foo.txt` //! //! And every other method has similarily improved errors. If a method does not have pretty error //! messages please open a ticket. //! //! [file_set_len]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len //! [file_read]: https://doc.rust-lang.org/std/fs/struct.File.html#method.read //! [path_set_len]: struct.FileWrite.html#method.set_len //! [path_read]: struct.FileRead.html#method.open //! //! //! ## Exported Path Types //! //! These are the exported Path types. All of them are absolute. //! //! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_ //! canonicalized) path that is not necessarily guaranteed to exist. //! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to //! exist and be a file, with associated methods. //! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist //! and be a directory, with associated methods. //! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir. //! Returned by [`PathDir::list`][dir_list] //! //! In addition, all paths are serializable through serde (even on windows!) by using the crate //! [`stfu8`](https://crates.io/crates/stfu8) to encode/decode, allowing ill-formed UTF-16. See //! that crate for more details on how the resulting encoding can be edited (by hand) even in the //! case of what *would be* ill-formed UTF-16. //! //! [dir_list]: struct.PathDir.html#method.list //! //! //! ## Exported File Types //! //! All File types provide _type safe_ access to their relevant traits. For instance, you can't //! `read` with a `FileWrite` and you can't `write` with a `FileRead`. //! //! - [`FileRead`](struct.FileRead.html): a read-only file handle with `path()` attached and //! improved error messages. Contains only the methods and trait implementations which are //! allowed by a read-only file. //! - [`FileWrite`](struct.FileWrite.html): a write-only file handle with `path()` attached and //! improved error messages. Contains only the methods and trait implementations which are //! allowed by a write-only file. //! - [`FileEdit`](struct.FileEdit.html): a read/write file handle with `path()` attached and //! improved error messages. Contains methods and trait implements for both readable _and_ //! writeable files. //! //! ### Differing Method Signatures //! //! The type signatures of the `File*` types regarding `read`, `write` and other methods is //! slightly different than `std::fs::File` -- they all take `&mut` instead of `&`. This is to //! avoid a [common possible footgun](https://github.com/rust-lang/rust/issues/47708). //! //! To demonstrate, imagine the following scenario: //! //! - You pass your open `&File` to a method, which puts it in a thread. This thread constantly //! calls `seek(SeekFrom::Start(10))` //! - You periodically read from a file expecting new data, but are always getting the same data. //! //! Yes, this is actually allowed by the rust compiler since `seek` is implemented for //! [`&File`](https://doc.rust-lang.org/std/fs/struct.File.html#impl-Seek-1). Technically this is //! still _memory safe_ since the operating system will handle any contention, however many would //! argue that it isn't _expected_ that an immutable reference passed to another //! function can affect the seek position of a file. //! //! //! # Examples //! Recreating `Cargo.init` in `example/` //! //! ```rust //! # extern crate path_abs; //! # extern crate tempdir; //! use std::path::Path; //! use std::collections::HashSet; //! use path_abs::{ //! PathAbs, // absolute path //! PathDir, // absolute path to a directory //! PathFile, // absolute path to a file //! PathType, // enum of Dir or File //! PathInfo, // trait for query methods //! PathOps, // trait for methods that make new paths //! FileRead, // Open read-only file handler //! FileWrite, // Open write-only file handler //! FileEdit, // Open read/write file handler //! }; //! //! # fn try_main() -> ::std::io::Result<()> { //! let example = Path::new("example"); //! # let tmp = tempdir::TempDir::new("ex")?; //! # let example = &tmp.path().join(example); //! //! // Create your paths //! let project = PathDir::create_all(example)?; //! let src = PathDir::create(project.concat("src")?)?; //! let lib = PathFile::create(src.concat("lib.rs")?)?; //! let cargo = PathFile::create(project.concat("Cargo.toml")?)?; //! //! // Write the templates //! lib.write_str(r#" //! #[cfg(test)] //! mod tests { //! #[test] //! fn it_works() { //! assert_eq!(2 + 2, 4); //! } //! }"#)?; //! //! cargo.write_str(r#" //! [package] //! name = "example" //! version = "0.1.0" //! authors = ["Garrett Berg "] //! //! [dependencies] //! "#)?; //! //! // Put our result into a HashMap so we can assert it //! let mut result = HashSet::new(); //! for p in project.list()? { //! result.insert(p?); //! } //! //! // Create our expected value //! let mut expected = HashSet::new(); //! expected.insert(PathType::Dir(src)); //! expected.insert(PathType::File(cargo)); //! //! assert_eq!(expected, result); //! //! // ---------------------------------- //! // Creating types from existing paths //! //! // Creating a generic path //! let lib_path = example.join("src").join("lib.rs"); //! let abs = PathAbs::new(&lib_path)?; //! //! // Or a path with a known type //! let file = PathType::new(&lib_path) //! ? //! .unwrap_file(); //! //! assert!(abs.is_file()); //! assert!(file.is_file()); //! //! // ---------------------------------- //! // Opening a File //! //! // open read-only using the PathFile method //! let read = file.open_read()?; //! //! // Or use the type directly: open for appending //! let write = FileWrite::open_append(&file)?; //! //! // Open for read/write editing. //! let edit = file.open_edit()?; //! # Ok(()) } fn main() { try_main().unwrap() } //! ``` //! //! [`PathInfo`]: trait.PathInfo.html //! [`PathOps`]: trait.PathOps.html //! [`PathMut`]: trait.PathMut.html #![cfg_attr(target_os = "wasi", feature(wasi_ext))] #[cfg(feature = "serialize")] extern crate serde; #[macro_use] #[cfg(feature = "serialize")] extern crate serde_derive; #[cfg(feature = "serialize")] extern crate stfu8; #[macro_use] #[cfg(test)] extern crate pretty_assertions; #[cfg(test)] extern crate regex; #[cfg(test)] extern crate serde_json; #[cfg(test)] extern crate tempdir; use std::error; use std::ffi; use std::fmt; use std::fs; use std::io; use std::path::{self, Component, Components}; use std_prelude::*; mod abs; mod dir; mod edit; mod file; pub mod open; mod read; #[cfg(feature = "serialize")] pub mod ser; mod ty; mod write; pub use crate::abs::PathAbs; pub use crate::dir::{ListDir, PathDir}; pub use crate::file::PathFile; #[cfg(feature = "serialize")] pub use crate::ser::PathSer; pub use crate::ty::PathType; pub use crate::edit::FileEdit; pub use crate::read::FileRead; pub use crate::write::FileWrite; pub type Result = ::std::result::Result; /// An error produced by performing an filesystem operation on a `Path`. /// /// This error type is a light wrapper around [`std::io::Error`]. In particular, it adds the /// following information: /// /// - The action being performed when the error occured /// - The path associated with the IO error. /// /// To maintain good ergonomics, this type has a `impl From for std::io::Error` defined so /// that you may use an [`io::Result`] with methods in this crate if you don't care about accessing /// the underlying error data in a structured form (the pretty format will be preserved however). /// /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html /// /// # Examples /// ```rust /// use path_abs::Error as PathError; /// use path_abs::PathFile; /// /// /// main function, note that you can use `io::Error` /// fn try_main() -> Result<(), ::std::io::Error> { /// let lib = PathFile::new("src/lib.rs")?; /// Ok(()) /// } /// /// ``` pub struct Error { io_err: io::Error, action: String, path: Arc, } impl Error { /// Create a new error when the path and action are known. pub fn new(io_err: io::Error, action: &str, path: Arc) -> Error { Error { io_err, action: action.into(), path, } } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error<{}>", self) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} when {} {}", self.io_err, self.action, self.path.display() ) } } impl Error { /// Returns the path associated with this error. pub fn path(&self) -> &Path { self.path.as_ref() } /// Returns the `std::io::Error` associated with this errors. pub fn io_error(&self) -> &io::Error { &self.io_err } /// Returns the action being performed when this error occured. pub fn action(&self) -> &str { &self.action } } impl error::Error for Error { fn description(&self) -> &str { self.io_err.description() } fn cause(&self) -> Option<&dyn error::Error> { Some(&self.io_err) } } impl From for io::Error { fn from(err: Error) -> io::Error { io::Error::new(err.io_err.kind(), err) } } /// Methods that return information about a path. /// /// This trait provides the familiar methods from `std::path::Path` /// for the `Path*` types. These methods take the same parameters and return /// the same types as the originals in the standard library, except where /// noted. /// /// As a general rule, methods that can return an error will return a rich /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will /// automatically convert into a `std::io::Error` with `?` if needed). /// /// [`path_abs::Error`]: struct.Error.html /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html pub trait PathInfo { fn as_path(&self) -> &Path; fn to_arc_pathbuf(&self) -> Arc; fn as_os_str(&self) -> &ffi::OsStr { Path::as_os_str(self.as_path()) } fn to_str(&self) -> Option<&str> { Path::to_str(self.as_path()) } fn to_string_lossy(&self) -> Cow<'_, str> { Path::to_string_lossy(self.as_path()) } fn is_absolute(&self) -> bool { Path::is_absolute(self.as_path()) } fn is_relative(&self) -> bool { Path::is_relative(self.as_path()) } fn has_root(&self) -> bool { Path::has_root(self.as_path()) } fn ancestors(&self) -> path::Ancestors<'_> { Path::ancestors(self.as_path()) } fn file_name(&self) -> Option<&ffi::OsStr> { Path::file_name(self.as_path()) } fn strip_prefix

(&self, base: P) -> std::result::Result<&Path, path::StripPrefixError> where P: AsRef, { Path::strip_prefix(self.as_path(), base) } fn starts_with>(&self, base: P) -> bool { Path::starts_with(self.as_path(), base) } fn ends_with>(&self, base: P) -> bool { Path::ends_with(self.as_path(), base) } fn file_stem(&self) -> Option<&ffi::OsStr> { Path::file_stem(self.as_path()) } fn extension(&self) -> Option<&ffi::OsStr> { Path::extension(self.as_path()) } fn components(&self) -> Components<'_> { Path::components(self.as_path()) } fn iter(&self) -> path::Iter<'_> { Path::iter(self.as_path()) } fn display(&self) -> path::Display<'_> { Path::display(self.as_path()) } /// Queries the file system to get information about a file, directory, etc. /// /// The same as [`std::path::Path::metadata()`], except that it returns a /// rich [`path_abs::Error`] when a problem is encountered. /// /// [`path_abs::Error`]: struct.Error.html /// [`std::path::Path::metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.metadata fn metadata(&self) -> Result { Path::metadata(self.as_path()) .map_err(|err| Error::new(err, "getting metadata of", self.to_arc_pathbuf())) } /// Queries the metadata about a file without following symlinks. /// /// The same as [`std::path::Path::symlink_metadata()`], except that it /// returns a rich [`path_abs::Error`] when a problem is encountered. /// /// [`path_abs::Error`]: struct.Error.html /// [`std::path::Path::symlink_metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.symlink_metadata fn symlink_metadata(&self) -> Result { Path::symlink_metadata(self.as_path()) .map_err(|err| Error::new(err, "getting symlink metadata of", self.to_arc_pathbuf())) } fn exists(&self) -> bool { Path::exists(self.as_path()) } fn is_file(&self) -> bool { Path::is_file(self.as_path()) } fn is_dir(&self) -> bool { Path::is_dir(self.as_path()) } /// Reads a symbolic link, returning the path that the link points to. /// /// The same as [`std::path::Path::read_link()`], except that it returns a /// rich [`path_abs::Error`] when a problem is encountered. /// /// [`path_abs::Error`]: struct.Error.html /// [`std::path::Pathdoc.rust-lang.org/stable/std/path/struct.Path.html#method.read_link fn read_link(&self) -> Result { Path::read_link(self.as_path()) .map_err(|err| Error::new(err, "reading link target of", self.to_arc_pathbuf())) } /// Returns the canonical, absolute form of the path with all intermediate /// components normalized and symbolic links resolved. /// /// The same as [`std::path::Path::canonicalize()`], /// - On success, returns a `path_abs::PathAbs` instead of a `PathBuf` /// - returns a rich [`path_abs::Error`] when a problem is encountered /// /// [`path_abs::Error`]: struct.Error.html /// [`std::path::Path::canonicalize()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.canonicalize fn canonicalize(&self) -> Result { Path::canonicalize(self.as_path()) .map(|path| PathAbs(path.into())) .map_err(|err| Error::new(err, "canonicalizing", self.to_arc_pathbuf())) } /// Returns the path without its final component, if there is one. /// /// The same as [`std::path::Path::parent()`], except that it returns a /// `Result` with a rich [`path_abs::Error`] when a problem is encountered. /// /// [`path_abs::Error`]: struct.Error.html /// [`std::path::Path::parent()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.parent fn parent(&self) -> Result<&Path> { let parent_path = Path::parent(self.as_path()); if let Some(p) = parent_path { Ok(p) } else { Err(Error::new( io::Error::new(io::ErrorKind::NotFound, "path has no parent"), "truncating to parent", self.to_arc_pathbuf(), )) } } } // TODO: I would like to be able to do this. // impl PathInfo for T // where // T: AsRef // { // fn as_path(&self) -> &Path { // PathBuf::as_path(self.borrow()) // } // fn to_arc_pathbuf(&self) -> Arc { // self.clone().into() // } // } impl PathInfo for T where T: Clone + Borrow + Into>, { fn as_path(&self) -> &Path { PathBuf::as_path(self.borrow()) } fn to_arc_pathbuf(&self) -> Arc { self.clone().into() } } impl PathInfo for Path { fn as_path(&self) -> &Path { &self } fn to_arc_pathbuf(&self) -> Arc { self.to_path_buf().into() } } /// Methods that modify a path. /// /// These methods are not implemented for all `path_abs` types because they /// may break the type's invariant. For example, if you could call /// `pop_up()` on a `PathFile`, it would no longer be the path to /// a file, but the path to a directory. /// /// As a general rule, methods that can return an error will return a rich /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will /// automatically convert into a `std::io::Error` with `?` if needed). pub trait PathMut: PathInfo { /// Appends `path` to this path. /// /// Note that this method represents pure concatenation, not "adjoinment" /// like [`PathBuf::push`], so absolute paths won't wholly replace the /// current path. /// /// `..` components are resolved using [`pop_up`], which can consume components /// on `self` /// /// # Errors /// /// This method returns an error if the result would try to go outside a filesystem root, /// like `/` on Unix or `C:\` on Windows. /// /// # Example /// /// ```rust /// use std::path::PathBuf; /// use path_abs::PathMut; /// /// let mut somepath = PathBuf::from("foo"); /// somepath.append("bar"); /// /// assert_eq!(somepath, PathBuf::from("foo/bar")); /// ``` /// /// [`pop_up`]: trait.PathMut.html#method.pop_up /// [`PathBuf::push`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.push fn append>(&mut self, path: P) -> Result<()>; /// Go "up" one directory. /// /// This removes the last component of this path. It also resolves any `..` that exist at the /// _end_ of the path until a real item can be truncated. If the path is relative, and no /// items remain then a `..` is appended to the path. /// /// # Errors /// /// This method returns an error if the result would try to go outside a filesystem root, /// like `/` on Unix or `C:\` on Windows. /// /// # Example /// /// ```rust /// # fn example() -> Result<(), path_abs::Error> { /// use std::path::Path; /// use path_abs::PathMut; /// /// let executable = Path::new("/usr/loca/bin/myapp"); /// let mut install_path = executable.to_path_buf(); /// install_path.pop_up()?; /// /// assert_eq!(install_path.as_path(), Path::new("/usr/local/bin")); /// # Ok(()) } /// ``` /// /// Example handling weird relative paths /// /// ```rust /// # fn example() -> Result<(), path_abs::Error> { /// use std::path::Path; /// use path_abs::PathMut; /// /// let executable = Path::new("../../weird/../relative/path/../../"); /// let mut install_path = executable.to_path_buf(); /// install_path.pop_up()?; /// /// assert_eq!(install_path.as_path(), Path::new("../../../")); /// # Ok(()) } /// ``` /// /// Error use case /// /// ```rust /// # fn example() -> Result<(), path_abs::Error> { /// use std::path::Path; /// use path_abs::PathMut; /// /// let tmp = Path::new("/tmp"); /// let mut relative = tmp.to_path_buf(); /// relative.pop_up()?; /// assert!(relative.pop_up().is_err()); /// # Ok(()) } /// ``` fn pop_up(&mut self) -> Result<()>; /// Removes all components after the root, if any. /// /// This is mostly useful on Windows, since it preserves the prefix before /// the root. /// /// # Example /// /// ```no_run /// use std::path::PathBuf; /// use path_abs::PathMut; /// /// let mut somepath = PathBuf::from(r"C:\foo\bar"); /// somepath.truncate_to_root(); /// /// assert_eq!(somepath, PathBuf::from(r"C:\")); /// ``` fn truncate_to_root(&mut self); fn set_file_name>(&mut self, file_name: S); fn set_extension>(&mut self, extension: S) -> bool; } impl PathMut for PathBuf { fn append>(&mut self, path: P) -> Result<()> { for each in path.as_ref().components() { match each { Component::Normal(c) => self.push(c), Component::CurDir => (), // "." does nothing Component::Prefix(_) => { return Err(Error::new( io::Error::new(io::ErrorKind::Other, "appended path has a prefix"), "appending path", path.as_ref().to_path_buf().into(), )); } Component::RootDir => (), // leading "/" does nothing Component::ParentDir => self.pop_up()?, } } Ok(()) } fn pop_up(&mut self) -> Result<()> { /// Pop off the parent components and return how /// many were removed. fn pop_parent_components(p: &mut PathBuf) -> usize { let mut cur_dirs: usize = 0; let mut parents: usize = 0; let mut components = p.components(); while let Some(c) = components.next_back() { match c { Component::CurDir => cur_dirs += 1, Component::ParentDir => parents += 1, _ => break, } } for _ in 0..(cur_dirs + parents) { p.pop(); } parents } let mut ending_parents = 0; loop { ending_parents += pop_parent_components(self); if ending_parents == 0 || self.file_name().is_none() { break; } else { // we have at least one "parent" to consume self.pop(); ending_parents -= 1; } } if self.pop() { // do nothing, success } else if self.has_root() { // We tried to pop off the root return Err(Error::new( io::Error::new(io::ErrorKind::NotFound, "cannot get parent of root path"), "truncating to parent", self.clone().into(), )); } else { // we are creating a relative path, `"../"` self.push("..") } // Put all unhandled parents back, creating a relative path. for _ in 0..ending_parents { self.push("..") } Ok(()) } fn truncate_to_root(&mut self) { let mut res = PathBuf::new(); for component in self.components().take(2) { match component { // We want to keep prefix and RootDir components of this path Component::Prefix(_) | Component::RootDir => res.push(component), // We want to discard all other components. _ => break, } } // Clobber ourselves with the new value. *self = res; } fn set_file_name>(&mut self, file_name: S) { self.set_file_name(file_name) } fn set_extension>(&mut self, extension: S) -> bool { self.set_extension(extension) } } impl PathMut for Arc { fn append>(&mut self, path: P) -> Result<()> { Arc::make_mut(self).append(path) } fn pop_up(&mut self) -> Result<()> { Arc::make_mut(self).pop_up() } fn truncate_to_root(&mut self) { Arc::make_mut(self).truncate_to_root() } fn set_file_name>(&mut self, file_name: S) { Arc::make_mut(self).set_file_name(file_name) } fn set_extension>(&mut self, extension: S) -> bool { Arc::make_mut(self).set_extension(extension) } } /// Methods that return new path-like objects. /// /// Like the methods of [`PathInfo`] and [`PathMut`], these methods are similar /// to ones from the standard library's [`PathBuf`] but may return a rich /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will /// automatically convert into a `std::io::Error` with `?` if needed). /// /// Unlike the methods of [`PathInfo`] and [`PathMut`], different types that /// implement this trait may have different return types. /// /// [`PathInfo`]: trait.PathInfo.html /// [`PathMut`]: trait.PathMut.html /// [`PathBuf`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html /// [`path_abs::Error`]: struct.Error.html /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html pub trait PathOps: PathInfo { type Output: PathOps; /// Returns a new value representing the concatenation of two paths. /// /// Note that this method represents pure concatenation, not "adjoinment" /// like [`PathBuf::join`], so absolute paths won't wholly replace the /// current path. See [`append`] for more information about how it works. /// /// # Errors /// /// This method returns an error if the result would try to go outside a filesystem root, /// like `/` on Unix or `C:\` on Windows. /// /// # Example /// /// ```rust /// use path_abs::{PathInfo, PathOps, Result}; /// /// fn find_config_file( /// search_path: &[P], /// file_name: &str, /// ) -> Option<

::Output> { /// for each in search_path.iter() { /// if let Ok(maybe_config) = each.concat(file_name) { /// if maybe_config.is_file() { return Some(maybe_config); } /// } /// } /// /// None /// } /// ``` /// /// [`append`]: trait.PathMut.html#method.append /// [`PathBuf::join`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.join fn concat>(&self, path: P) -> Result; /// An exact replica of `std::path::Path::join` with all of its gotchas and pitfalls,, except /// returns a more relevant type. /// /// In general, prefer [`concat`] /// /// [`concat`]: trait.PathOps.html#method.concat fn join>(&self, path: P) -> Self::Output; /// Creates a new path object like `self` but with the given file name. /// /// The same as [`std::path::Path::with_file_name()`], except that the /// return type depends on the trait implementation. /// /// [`std::path::Path::with_file_name()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_file_name fn with_file_name>(&self, file_name: S) -> Self::Output; /// Creates a new path object like `self` but with the given extension. /// /// The same as [`std::path::Path::with_extension()`], except that the /// return type depends on the trait implementation. /// /// [`std::path::Path::with_extension()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_extension fn with_extension>(&self, extension: S) -> Self::Output; } // impl PathOps for T // where // T: PathInfo // // { // type Output = PathBuf; // // fn concat>(&self, path: P) -> Result { // let mut res = self.as_ref().to_owned(); // res.append(path)?; // Ok(res) // } // // fn with_file_name>(&self, file_name: S) -> Self::Output { // let mut res = self.as_ref().to_owned(); // res.set_file_name(file_name); // res // } // // fn with_extension>(&self, extension: S) -> Self::Output { // let mut res = self.as_ref().to_owned(); // res.set_extension(extension); // res // } // } impl PathOps for Path { type Output = PathBuf; fn concat>(&self, path: P) -> Result { let mut res = self.to_owned(); res.append(path)?; Ok(res) } fn join>(&self, path: P) -> Self::Output { Path::join(self, path) } fn with_file_name>(&self, file_name: S) -> Self::Output { let mut res = self.to_owned(); res.set_file_name(file_name); res } fn with_extension>(&self, extension: S) -> Self::Output { let mut res = self.to_owned(); res.set_extension(extension); res } } impl PathOps for PathBuf { type Output = PathBuf; fn concat>(&self, path: P) -> Result { self.as_path().concat(path) } fn join>(&self, path: P) -> Self::Output { Path::join(self, path) } fn with_file_name>(&self, file_name: S) -> Self::Output { self.as_path().with_file_name(file_name) } fn with_extension>(&self, extension: S) -> Self::Output { self.as_path().with_extension(extension) } } impl PathOps for Arc { type Output = Arc; fn concat>(&self, path: P) -> Result { let mut res = self.clone(); Arc::make_mut(&mut res).append(path)?; Ok(res) } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self, path); Arc::new(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { let mut res = self.clone(); Arc::make_mut(&mut res).set_file_name(file_name); res } fn with_extension>(&self, extension: S) -> Self::Output { let mut res = self.clone(); Arc::make_mut(&mut res).set_extension(extension); res } } #[cfg(test)] mod tests { use regex::{self, Regex}; use tempdir::TempDir; use super::*; macro_rules! assert_match { ($re: expr, $err: expr) => {{ let re = Regex::new(&$re).unwrap(); let err = $err.to_string(); assert!( re.is_match(&err), "\nGot Err : {:?}\nMatching against: {:?}", err.to_string(), $re ); }}; } fn escape>(path: P) -> String { regex::escape(&format!("{}", path.as_ref().display())) } #[test] /// Tests to make sure the error messages look like we expect. fn sanity_errors() { let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs"); { let foo_path = tmp_abs.concat("foo.txt").expect("path foo.txt"); let foo = PathFile::create(foo_path).expect("create foo.txt"); foo.clone().remove().unwrap(); let pat = if cfg!(unix) { format!( r"No such file or directory \(os error \d+\) when opening {}", escape(&foo) ) } else { format!( r"The system cannot find the file specified. \(os error \d+\) when opening {}", escape(&foo) ) }; assert_match!(pat, foo.open_edit().unwrap_err()) } } #[cfg(test)] mod windows { use super::*; #[cfg_attr(windows, test)] fn _test_pathinfo_parent() { let p = PathBuf::from(r"C:\foo\bar"); let actual = ::parent(&p).expect("could not find parent?"); let expected = PathBuf::from(r"C:\foo"); assert_eq!(actual, expected); let p = PathBuf::from(r"C:\"); let actual = ::parent(&p).expect_err("root has a parent?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new(r"C:\")); } #[cfg_attr(windows, test)] fn _test_pathinfo_starts_with() { let p = PathBuf::from(r"foo\bar"); assert_eq!( ::starts_with(&p, Path::new("foo")), true, ); assert_eq!( ::starts_with(&p, Path::new("bar")), false, ); } #[cfg_attr(windows, test)] fn _test_pathinfo_ends_with() { let p = PathBuf::from(r"foo\bar"); assert_eq!( ::ends_with(&p, Path::new("foo")), false, ); assert_eq!(::ends_with(&p, Path::new("bar")), true,); } #[cfg_attr(windows, test)] fn _test_pathops_concat() { let actual = Path::new("foo") .concat(Path::new("bar")) .expect("Could not concat paths?"); let expected = PathBuf::from(r"foo\bar"); assert_eq!(actual, expected); let actual = Path::new("foo") .concat(Path::new(r"bar\..\baz")) .expect("Could not concat path with ..?"); let expected = PathBuf::from(r"foo\baz"); assert_eq!(actual, expected); let actual = Path::new("foo") .concat("..") .expect("Could not cancel path with ..?"); let expected = PathBuf::from(r""); assert_eq!(actual, expected); let actual = Path::new("foo") .concat(r"..\..") .expect("Could not escape prefix with ..?"); let expected = PathBuf::from("../"); assert_eq!(actual, expected); let actual = Path::new(r"C:\foo") .concat(r"..\..") .expect_err("Could escape root with ..?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new(r"C:\")); let actual = Path::new("foo") .concat(Path::new(r"\windows\system32")) .expect("Could not concat path with RootDir?"); let expected = PathBuf::from(r"foo\windows\system32"); assert_eq!(actual, expected); let actual = Path::new("foo") .concat(Path::new(r"C:bar")) .expect_err("Could concat path with prefix?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::Other); assert_eq!(actual.action(), "appending path"); assert_eq!(actual.path(), Path::new(r"C:bar")); } #[cfg_attr(windows, test)] fn _test_pathmut_append() { let mut actual = PathBuf::from("foo"); actual .append(Path::new("bar")) .expect("Could not append paths?"); let expected = PathBuf::from(r"foo\bar"); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual .append(Path::new(r"bar\..\baz")) .expect("Could not append path with ..?"); let expected = PathBuf::from(r"foo\baz"); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual.append("..").expect("Could not cancel path with ..?"); let expected = PathBuf::from(r""); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual .append(r"..\..") .expect("Could not escape prefix with ..?"); let expected = PathBuf::from("../"); assert_eq!(actual, expected); let actual = PathBuf::from(r"C:\foo") .append(r"..\..") .expect_err("Could escape root with ..?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new(r"C:\")); let mut actual = PathBuf::from("foo"); actual .append(Path::new(r"\windows\system32")) .expect("Could not append RootDir to path?"); let expected = PathBuf::from(r"foo\windows\system32"); assert_eq!(actual, expected); let actual = PathBuf::from("foo") .append(Path::new(r"C:bar")) .expect_err("Could append prefix to path?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::Other); assert_eq!(actual.action(), "appending path"); assert_eq!(actual.path(), Path::new(r"C:bar")); } #[cfg_attr(windows, test)] fn _test_pathmut_pop_up() { let mut p = PathBuf::from(r"C:\foo\bar"); p.pop_up().expect("could not find parent?"); assert_eq!(p.as_path(), Path::new(r"C:\foo")); let mut p = PathBuf::from(r"C:\"); let actual = p.pop_up().expect_err("root has a parent?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new(r"C:\")); } #[cfg_attr(windows, test)] fn _test_pathmut_truncate_to_root() { let mut p = PathBuf::from(r"C:\foo\bar"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new(r"C:\")); let mut p = PathBuf::from(r"C:foo"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new(r"C:")); let mut p = PathBuf::from(r"\foo"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new(r"\")); let mut p = PathBuf::from(r"foo"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new(r"")); } } mod any { use super::*; #[test] fn test_pathinfo_is_absolute() { let p = PathBuf::from("/foo/bar"); let expected = !cfg!(windows); assert_eq!(::is_absolute(&p), expected); } #[test] fn test_pathinfo_parent() { let p = PathBuf::from("/foo/bar"); let actual = ::parent(&p).expect("could not find parent?"); let expected = PathBuf::from("/foo"); assert_eq!(actual, expected); let p = PathBuf::from("/"); let actual = ::parent(&p).expect_err("root has a parent?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new("/")); } #[test] fn test_pathinfo_starts_with() { let p = PathBuf::from("foo/bar"); assert_eq!( ::starts_with(&p, Path::new("foo")), true, ); assert_eq!( ::starts_with(&p, Path::new("bar")), false, ); } #[test] fn test_pathinfo_ends_with() { let p = PathBuf::from("foo/bar"); assert_eq!( ::ends_with(&p, Path::new("foo")), false, ); assert_eq!(::ends_with(&p, Path::new("bar")), true,); } #[test] fn test_pathops_concat() { let actual = Path::new("foo") .concat(Path::new("bar")) .expect("Could not concat paths?"); let expected = PathBuf::from("foo/bar"); assert_eq!(actual, expected); let actual = Path::new("foo") .concat(Path::new("bar/../baz")) .expect("Could not concat path with ..?"); let expected = PathBuf::from("foo/baz"); assert_eq!(actual, expected); let actual = Path::new("foo") .concat("..") .expect("Could not cancel path with ..?"); let expected = PathBuf::from(r""); assert_eq!(actual, expected); let actual = Path::new("foo") .concat("../..") .expect("Could not prefix with ..?"); let expected = PathBuf::from(r"../"); assert_eq!(actual, expected); let actual = Path::new("/foo") .concat("../..") .expect_err("Could escape root with ..?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new("/")); let actual = Path::new("foo") .concat(Path::new("/etc/passwd")) .expect("Could not concat RootDir to path?"); let expected: PathBuf = PathBuf::from("foo/etc/passwd"); assert_eq!(actual, expected); } #[test] fn test_pathops_concat_relative() { let actual = Path::new("../foo") .concat("bar") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../foo/bar"); assert_eq!(actual, expected); let actual = Path::new("../foo") .concat("..") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r".."); assert_eq!(actual, expected); let actual = Path::new("../foo") .concat("../..") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../.."); assert_eq!(actual, expected); let actual = Path::new("../foo/../bar") .concat("../..") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../.."); assert_eq!(actual, expected); let actual = Path::new("../foo/../bar/..") .concat("../..") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../../.."); assert_eq!(actual, expected); let actual = PathBuf::from("../foo/..") .concat("../../baz") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../../../baz"); assert_eq!(actual, expected); } #[test] fn test_pathops_concat_cur() { // just check that pahts don't normalize... let actual = Path::new("foo/././..").as_os_str(); let expected = ffi::OsStr::new("foo/././.."); assert_eq!(actual, expected); let actual = PathBuf::from("././foo/././..") .concat("../bar") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../bar"); assert_eq!(actual, expected); } #[test] fn test_pathops_concat_consume() { let actual = Path::new("foo") .concat("../../bar") .expect("Could not create relative path with concat"); let expected = PathBuf::from(r"../bar"); assert_eq!(actual, expected); } #[test] fn test_pathmut_append() { let mut actual = PathBuf::from("foo"); actual .append(Path::new("bar")) .expect("Could not append paths?"); let expected = PathBuf::from("foo/bar"); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual .append(Path::new("bar/../baz")) .expect("Could not append path with ..?"); let expected = PathBuf::from("foo/baz"); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual.append("..").expect("Could not cancel path with ..?"); let expected = PathBuf::from(r""); assert_eq!(actual, expected); let mut actual = PathBuf::from("foo"); actual .append("../..") .expect("Could not escape prefix with ..?"); let expected = PathBuf::from("../"); assert_eq!(actual, expected); let actual = PathBuf::from("/foo") .append("../..") .expect_err("Could escape root with ..?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new("/")); let mut actual = PathBuf::from("foo"); actual .append(Path::new("/etc/passwd")) .expect("Could not append RootDir to path?"); let expected: PathBuf = PathBuf::from("foo/etc/passwd"); assert_eq!(actual, expected); } #[test] fn test_pathmut_pop_up() { let mut p = PathBuf::from("/foo/bar"); p.pop_up().expect("could not find parent?"); assert_eq!(p.as_path(), Path::new("/foo")); let mut p = PathBuf::from("/"); let actual = p.pop_up().expect_err("root has a parent?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "truncating to parent"); assert_eq!(actual.path(), Path::new("/")); } #[test] fn test_pathmut_truncate_to_root() { let mut p = PathBuf::from("/foo/bar"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new("/")); let mut p = PathBuf::from("foo/bar"); p.truncate_to_root(); assert_eq!(p.as_path(), Path::new("")); } } } path_abs-0.5.1/src/open.rs000064400000000000000000000066410000000000000134720ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Open file paths and associated methods. use std::fmt; use std::fs; use std_prelude::*; use super::{Error, PathAbs, PathFile, Result}; /// **INTERNAL TYPE: do not use directly.** /// /// Use `FileRead`, `FileWrite` or `FileEdit` instead. pub struct FileOpen { pub(crate) path: PathFile, pub(crate) file: fs::File, } impl FileOpen { /// Open the file with the given `OpenOptions`. pub fn open>(path: P, options: fs::OpenOptions) -> Result { let file = options .open(&path) .map_err(|err| Error::new(err, "opening", path.as_ref().to_path_buf().into()))?; let path = PathFile::new(path)?; Ok(FileOpen { path: path, file }) } /// Shortcut to open the file if the path is already absolute. /// /// Typically you should use `PathFile::open` instead (i.e. `file.open(options)` or /// `file.read()`). pub fn open_abs>(path: P, options: fs::OpenOptions) -> Result { let path = path.into(); let file = options .open(&path) .map_err(|err| Error::new(err, "opening", path.clone().into()))?; Ok(FileOpen { path: PathFile::new_unchecked(path), file, }) } /// Get the path associated with the open file. pub fn path(&self) -> &PathFile { &self.path } /// Queries metadata about the underlying file. /// /// This function is identical to [std::fs::File::metadata][0] except it has error /// messages which include the action and the path. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata pub fn metadata(&self) -> Result { self.file .metadata() .map_err(|err| Error::new(err, "getting metadata for", self.path.clone().into())) } /// Creates a new independently owned handle to the underlying file. /// /// This function is identical to [std::fs::File::try_clone][0] except it has error /// messages which include the action and the path and it returns a `FileOpen` object. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.try_clone pub fn try_clone(&self) -> Result { let file = self .file .try_clone() .map_err(|err| Error::new(err, "cloning file handle for", self.path.clone().into()))?; Ok(FileOpen { file, path: self.path.clone(), }) } } impl fmt::Debug for FileOpen { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Open(")?; self.path.fmt(f)?; write!(f, ")") } } impl AsRef for FileOpen { fn as_ref(&self) -> &fs::File { &self.file } } impl Borrow for FileOpen { fn borrow(&self) -> &fs::File { &self.file } } impl<'a> Borrow for &'a FileOpen { fn borrow(&self) -> &fs::File { &self.file } } impl From for fs::File { fn from(orig: FileOpen) -> fs::File { orig.file } } path_abs-0.5.1/src/read.rs000064400000000000000000000075060000000000000134450ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Open file paths that are read-only. use std::fmt; use std::fs; use std::io; use std_prelude::*; use super::open::FileOpen; use super::{Error, PathAbs, PathFile, PathInfo, Result}; /// A read-only file handle with `path()` attached and improved error messages. Contains only the /// methods and trait implementations which are allowed by a read-only file. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::Read; /// use path_abs::{PathFile, FileRead}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// let file = PathFile::create(example)?; /// /// let expected = "foo\nbar"; /// file.write_str(expected)?; /// /// let mut read = FileRead::open(example)?; /// let mut s = String::new(); /// read.read_to_string(&mut s)?; /// assert_eq!(expected, s); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub struct FileRead(pub(crate) FileOpen); impl FileRead { /// Open the file as read-only. pub fn open>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.read(true); Ok(FileRead(FileOpen::open(path, options)?)) } /// Shortcut to open the file if the path is already absolute. pub(crate) fn open_abs>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.read(true); Ok(FileRead(FileOpen::open_abs(path, options)?)) } pub fn path(&self) -> &PathFile { &self.0.path } /// Read what remains of the file to a `String`. pub fn read_string(&mut self) -> Result { let mut s = String::new(); self.0 .file .read_to_string(&mut s) .map_err(|err| Error::new(err, "reading", self.0.path.clone().into()))?; Ok(s) } } impl fmt::Debug for FileRead { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FileRead(")?; self.0.path.fmt(f)?; write!(f, ")") } } impl io::Read for FileRead { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.file.read(buf).map_err(|err| { io::Error::new( err.kind(), format!("{} when reading {}", err, self.path().display()), ) }) } } impl io::Seek for FileRead { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { self.0.file.seek(pos).map_err(|err| { io::Error::new( err.kind(), format!("{} seeking {}", err, self.path().display()), ) }) } } impl AsRef for FileRead { fn as_ref(&self) -> &FileOpen { &self.0 } } impl AsRef for FileRead { fn as_ref(&self) -> &File { self.0.as_ref() } } impl Borrow for FileRead { fn borrow(&self) -> &FileOpen { &self.0 } } impl Borrow for FileRead { fn borrow(&self) -> &File { self.0.borrow() } } impl<'a> Borrow for &'a FileRead { fn borrow(&self) -> &FileOpen { &self.0 } } impl<'a> Borrow for &'a FileRead { fn borrow(&self) -> &File { self.0.borrow() } } impl From for FileOpen { fn from(orig: FileRead) -> FileOpen { orig.0 } } impl From for File { fn from(orig: FileRead) -> File { orig.0.into() } } path_abs-0.5.1/src/ser.rs000064400000000000000000000217640000000000000133250ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::string::ToString; use std_prelude::*; use stfu8; use super::{PathMut, PathOps}; use std::ffi::{OsStr, OsString}; #[cfg(target_os = "wasi")] use std::os::wasi::ffi::{OsStrExt, OsStringExt}; #[cfg(unix)] use std::os::unix::ffi::{OsStrExt, OsStringExt}; #[cfg(windows)] use std::os::windows::ffi::{OsStrExt, OsStringExt}; use super::{PathAbs, PathDir, PathFile}; #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct PathSer(Arc); pub trait ToStfu8 { fn to_stfu8(&self) -> String; } pub trait FromStfu8: Sized { fn from_stfu8(s: &str) -> Result; } impl PathSer { pub fn new>>(path: P) -> Self { PathSer(path.into()) } pub fn as_path(&self) -> &Path { self.as_ref() } } impl fmt::Debug for PathSer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl PathMut for PathSer { fn append>(&mut self, path: P) -> crate::Result<()> { self.0.append(path) } fn pop_up(&mut self) -> crate::Result<()> { self.0.pop_up() } fn truncate_to_root(&mut self) { self.0.truncate_to_root() } fn set_file_name>(&mut self, file_name: S) { self.0.set_file_name(file_name) } fn set_extension>(&mut self, extension: S) -> bool { self.0.set_extension(extension) } } impl PathOps for PathSer { type Output = PathSer; fn concat>(&self, path: P) -> crate::Result { Ok(PathSer(self.0.concat(path)?)) } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self.as_path(), path); Self::Output::new(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { PathSer(self.0.with_file_name(file_name)) } fn with_extension>(&self, extension: S) -> Self::Output { PathSer(self.0.with_extension(extension)) } } impl AsRef for PathSer { fn as_ref(&self) -> &std::ffi::OsStr { self.0.as_ref().as_ref() } } impl AsRef for PathSer { fn as_ref(&self) -> &Path { self.0.as_ref() } } impl AsRef for PathSer { fn as_ref(&self) -> &PathBuf { self.0.as_ref() } } impl Borrow for PathSer { fn borrow(&self) -> &Path { self.as_ref() } } impl Borrow for PathSer { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl<'a> Borrow for &'a PathSer { fn borrow(&self) -> &Path { self.as_ref() } } impl<'a> Borrow for &'a PathSer { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl> From

for PathSer { fn from(path: P) -> PathSer { PathSer::new(path.into()) } } impl From for Arc { fn from(path: PathSer) -> Arc { path.0 } } // impl From for PathSer { // fn from(path: PathAbs) -> PathSer { // PathSer(path.0) // } // } impl ToStfu8 for T where T: Borrow, { #[cfg(any(target_os = "wasi", unix))] fn to_stfu8(&self) -> String { let bytes = self.borrow().as_os_str().as_bytes(); stfu8::encode_u8(bytes) } #[cfg(windows)] fn to_stfu8(&self) -> String { let wide: Vec = self.borrow().as_os_str().encode_wide().collect(); stfu8::encode_u16(&wide) } } impl FromStfu8 for T where T: From, { #[cfg(any(target_os = "wasi", unix))] fn from_stfu8(s: &str) -> Result { let raw_path = stfu8::decode_u8(s)?; let os_str = OsString::from_vec(raw_path); let pathbuf: PathBuf = os_str.into(); Ok(pathbuf.into()) } #[cfg(windows)] fn from_stfu8(s: &str) -> Result { let raw_path = stfu8::decode_u16(&s)?; let os_str = OsString::from_wide(&raw_path); let pathbuf: PathBuf = os_str.into(); Ok(pathbuf.into()) } } macro_rules! stfu8_serialize { ($name:ident) => { impl Serialize for $name { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&self.to_stfu8()) } } }; } stfu8_serialize!(PathSer); stfu8_serialize!(PathAbs); stfu8_serialize!(PathFile); stfu8_serialize!(PathDir); impl<'de> Deserialize<'de> for PathSer { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; let path = PathBuf::from_stfu8(&s).map_err(|err| serde::de::Error::custom(&err.to_string()))?; Ok(PathSer(Arc::new(path))) } } impl<'de> Deserialize<'de> for PathAbs { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; let path = PathBuf::from_stfu8(&s).map_err(|err| serde::de::Error::custom(&err.to_string()))?; Ok(PathAbs(Arc::new(path))) } } impl<'de> Deserialize<'de> for PathFile { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let abs = PathAbs::deserialize(deserializer)?; PathFile::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string())) } } impl<'de> Deserialize<'de> for PathDir { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let abs = PathAbs::deserialize(deserializer)?; PathDir::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string())) } } #[cfg(test)] mod tests { use super::super::{PathDir, PathFile, PathInfo, PathMut, PathOps, PathType}; use super::*; #[cfg(any(target_os = "wasi", unix))] static SERIALIZED: &str = "[\ {\"type\":\"file\",\"path\":\"{0}/foo.txt\"},\ {\"type\":\"dir\",\"path\":\"{0}/bar\"},\ {\"type\":\"dir\",\"path\":\"{0}/foo/bar\"}\ ]"; #[cfg(windows)] static SERIALIZED: &str = "[\ {\"type\":\"file\",\"path\":\"{0}\\\\foo.txt\"},\ {\"type\":\"dir\",\"path\":\"{0}\\\\bar\"},\ {\"type\":\"dir\",\"path\":\"{0}\\\\foo\\\\bar\"}\ ]"; #[test] fn sanity_serde() { use serde_json; use tempdir::TempDir; let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs"); let ser_from_str = PathSer::from("example"); let ser_from_tmp_abs = PathSer::from(tmp_abs.as_path()); let foo = PathFile::create(tmp_abs.concat("foo.txt").unwrap()).expect("foo.txt"); let bar_dir = PathDir::create(tmp_abs.concat("bar").unwrap()).expect("bar"); let foo_bar_dir = PathDir::create_all(tmp_abs.concat("foo").unwrap().concat("bar").unwrap()) .expect("foo/bar"); let expected = vec![ PathType::File(foo), PathType::Dir(bar_dir), PathType::Dir(foo_bar_dir), ]; let expected_str = SERIALIZED .replace("{0}", &tmp_abs.to_stfu8()) // JSON needs backslashes escaped. Be careful not to invoke BA'AL: // https://xkcd.com/1638/) .replace(r"\", r"\\"); println!("### EXPECTED:\n{}", expected_str); let result_str = serde_json::to_string(&expected).unwrap(); println!("### RESULT:\n{}", result_str); assert_eq!(expected_str, result_str); let result: Vec = serde_json::from_str(&result_str).unwrap(); assert_eq!(expected, result); } #[test] /// Just test that it has all the methods. fn sanity_ser() { let mut path = PathSer::from("example/path"); assert_eq!( path.join("joined").as_path(), Path::new("example/path/joined") ); assert_eq!(path.is_absolute(), false); path.append("appended").unwrap(); assert_eq!(path.as_path(), Path::new("example/path/appended")); path.pop_up().unwrap(); assert_eq!(path.as_path(), Path::new("example/path")); assert_eq!( path.concat("/concated").unwrap().as_path(), Path::new("example/path/concated") ); } } path_abs-0.5.1/src/ty.rs000064400000000000000000000143120000000000000131570ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ use std::ffi; use std_prelude::*; use super::Result; use super::{PathAbs, PathDir, PathFile, PathInfo, PathOps}; #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] #[cfg_attr( feature = "serialize", serde(tag = "type", content = "path", rename_all = "lowercase") )] #[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] /// An an enum containing either a file or a directory. /// /// This is used primarily for: /// - The items returned from `PathDir::list` /// - Serializing paths of different types. /// /// Note that for symlinks, this returns the underlying file type. pub enum PathType { File(PathFile), Dir(PathDir), } impl PathType { /// Resolves and returns the `PathType` of the given path. /// /// > If the path exists but is not a file or a directory (i.e. is a symlink), then /// > `io::ErrorKind::InvalidInput` is returned. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathType; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src = PathType::new("src")?; /// # Ok(()) } fn main() { try_main().unwrap() } pub fn new>(path: P) -> Result { let abs = PathAbs::new(&path)?; PathType::try_from(abs) } /// Consume the `PathAbs` returning the `PathType`. pub fn try_from>(path: P) -> Result { let abs = path.into(); let ty = abs.metadata()?.file_type(); if ty.is_file() { Ok(PathType::File(PathFile(abs))) } else if ty.is_dir() { Ok(PathType::Dir(PathDir(abs))) } else { unreachable!("rust docs: The fs::metadata function follows symbolic links") } } /// Unwrap the `PathType` as a `PathFile`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathType; /// /// # fn try_main() -> ::std::io::Result<()> { /// let lib = PathType::new("src/lib.rs")?.unwrap_file(); /// # Ok(()) } fn main() { try_main().unwrap() } pub fn unwrap_file(self) -> PathFile { match self { PathType::File(f) => f, PathType::Dir(d) => { panic!("unwrap_file called on {}, which is not a file", d.display()) } } } /// Unwrap the `PathType` as a `PathDir`. /// /// # Examples /// ```rust /// # extern crate path_abs; /// use path_abs::PathType; /// /// # fn try_main() -> ::std::io::Result<()> { /// let src = PathType::new("src")?.unwrap_dir(); /// # Ok(()) } fn main() { try_main().unwrap() } pub fn unwrap_dir(self) -> PathDir { match self { PathType::Dir(d) => d, PathType::File(f) => panic!( "unwrap_dir called on {}, which is not a directory", f.display() ), } } /// Return whether this variant is `PathType::Dir`. pub fn is_dir(&self) -> bool { if let PathType::Dir(_) = *self { true } else { false } } /// Return whether this variant is `PathType::File`. pub fn is_file(&self) -> bool { if let PathType::File(_) = *self { true } else { false } } } impl AsRef for PathType { fn as_ref(&self) -> &std::ffi::OsStr { self.as_path().as_ref() } } impl AsRef for PathType { fn as_ref(&self) -> &PathAbs { match *self { PathType::File(ref file) => file.as_ref(), PathType::Dir(ref dir) => dir.as_ref(), } } } impl AsRef for PathType { fn as_ref(&self) -> &Path { let r: &PathAbs = self.as_ref(); r.as_ref() } } impl AsRef for PathType { fn as_ref(&self) -> &PathBuf { let r: &PathAbs = self.as_ref(); r.as_ref() } } impl Borrow for PathType { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl Borrow for PathType { fn borrow(&self) -> &Path { self.as_ref() } } impl Borrow for PathType { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl<'a> Borrow for &'a PathType { fn borrow(&self) -> &PathAbs { self.as_ref() } } impl<'a> Borrow for &'a PathType { fn borrow(&self) -> &Path { self.as_ref() } } impl<'a> Borrow for &'a PathType { fn borrow(&self) -> &PathBuf { self.as_ref() } } impl From for PathAbs { fn from(path: PathType) -> PathAbs { match path { PathType::File(p) => p.into(), PathType::Dir(p) => p.into(), } } } impl From for Arc { fn from(path: PathType) -> Arc { let abs: PathAbs = path.into(); abs.into() } } impl From for PathBuf { fn from(path: PathType) -> PathBuf { let abs: PathAbs = path.into(); abs.into() } } impl PathOps for PathType { type Output = PathAbs; fn concat>(&self, path: P) -> Result { match self { PathType::File(p) => p.concat(path), PathType::Dir(p) => p.concat(path), } } fn join>(&self, path: P) -> Self::Output { let buf = Path::join(self.as_path(), path); Self::Output::new_unchecked(buf) } fn with_file_name>(&self, file_name: S) -> Self::Output { match self { PathType::File(p) => p.with_file_name(file_name), PathType::Dir(p) => p.with_file_name(file_name), } } fn with_extension>(&self, extension: S) -> Self::Output { match self { PathType::File(p) => p.with_extension(extension), PathType::Dir(p) => p.with_extension(extension), } } } path_abs-0.5.1/src/write.rs000064400000000000000000000171720000000000000136640ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Open write-only file paths and associated methods. use std::fmt; use std::fs; use std::io; use std_prelude::*; use super::open::FileOpen; use super::{Error, PathAbs, PathFile, PathInfo, Result}; /// A write-only file handle with `path()` attached and improved error messages. Contains only the /// methods and trait implementations which are allowed by a write-only file. /// /// # Examples /// ```rust /// # extern crate path_abs; /// # extern crate tempdir; /// use std::io::Write; /// use path_abs::{PathFile, FileWrite}; /// /// # fn try_main() -> ::std::io::Result<()> { /// let example = "example.txt"; /// # let tmp = tempdir::TempDir::new("ex")?; /// # let example = &tmp.path().join(example); /// /// let expected = "foo\nbar"; /// let mut write = FileWrite::create(example)?; /// write.write_all(expected.as_bytes())?; /// write.flush(); /// /// let file = PathFile::new(example)?; /// assert_eq!(expected, file.read_string()?); /// # Ok(()) } fn main() { try_main().unwrap() } /// ``` pub struct FileWrite(pub(crate) FileOpen); impl FileWrite { /// Open the file with the given `OpenOptions` but always sets `write` to true. pub fn open>(path: P, mut options: fs::OpenOptions) -> Result { options.write(true); Ok(FileWrite(FileOpen::open(path, options)?)) } /// Shortcut to open the file if the path is already absolute. pub(crate) fn open_abs>( path: P, mut options: fs::OpenOptions, ) -> Result { options.write(true); Ok(FileWrite(FileOpen::open_abs(path, options)?)) } /// Open the file in write-only mode, truncating it first if it exists and creating it /// otherwise. pub fn create>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.truncate(true); options.create(true); FileWrite::open(path, options) } /// Open the file for appending, creating it if it doesn't exist. pub fn open_append>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.append(true); options.create(true); FileWrite::open(path, options) } /// Open the file for editing (reading and writing) but do not create it /// if it doesn't exist. pub fn open_edit>(path: P) -> Result { let mut options = fs::OpenOptions::new(); options.read(true); FileWrite::open(path, options) } pub fn path(&self) -> &PathFile { &self.0.path } /// Attempts to sync all OS-internal metadata to disk. /// /// This function will attempt to ensure that all in-core data reaches the filesystem before /// returning. /// /// This function is identical to [std::fs::File::sync_all][0] except it has error /// messages which include the action and the path. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all pub fn sync_all(&self) -> Result<()> { self.0 .file .sync_all() .map_err(|err| Error::new(err, "syncing", self.0.path.clone().into())) } /// This function is similar to sync_all, except that it may not synchronize file metadata to /// the filesystem. /// /// This function is identical to [std::fs::File::sync_data][0] except it has error /// messages which include the action and the path. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data pub fn sync_data(&self) -> Result<()> { self.0 .file .sync_data() .map_err(|err| Error::new(err, "syncing data for", self.0.path.clone().into())) } /// Truncates or extends the underlying file, updating the size of this file to become size. /// /// This function is identical to [std::fs::File::set_len][0] except: /// /// - It has error messages which include the action and the path. /// - It takes `&mut self` instead of `&self`. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len pub fn set_len(&mut self, size: u64) -> Result<()> { self.0 .file .set_len(size) .map_err(|err| Error::new(err, "setting len for", self.0.path.clone().into())) } /// Changes the permissions on the underlying file. /// /// This function is identical to [std::fs::File::set_permissions][0] except: /// /// - It has error messages which include the action and the path. /// - It takes `&mut self` instead of `&self`. /// /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_permissions pub fn set_permissions(&mut self, perm: fs::Permissions) -> Result<()> { self.0 .file .set_permissions(perm) .map_err(|err| Error::new(err, "setting permisions for", self.0.path.clone().into())) } /// Shortcut to `self.write_all(s.as_bytes())` with slightly /// improved error message. pub fn write_str(&mut self, s: &str) -> Result<()> { self.0 .file .write_all(s.as_bytes()) .map_err(|err| Error::new(err, "writing", self.0.path.clone().into())) } /// `std::io::File::flush` buth with the new error type. pub fn flush(&mut self) -> Result<()> { self.0 .file .flush() .map_err(|err| Error::new(err, "flushing", self.0.path.clone().into())) } } impl fmt::Debug for FileWrite { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FileWrite(")?; self.0.path.fmt(f)?; write!(f, ")") } } impl io::Write for FileWrite { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.file.write(buf).map_err(|err| { io::Error::new( err.kind(), format!("{} when writing to {}", err, self.path().display()), ) }) } fn flush(&mut self) -> io::Result<()> { self.0.file.flush().map_err(|err| { io::Error::new( err.kind(), format!("{} when flushing {}", err, self.path().display()), ) }) } } impl io::Seek for FileWrite { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { self.0.file.seek(pos).map_err(|err| { io::Error::new( err.kind(), format!("{} seeking {}", err, self.path().display()), ) }) } } impl AsRef for FileWrite { fn as_ref(&self) -> &FileOpen { &self.0 } } impl AsRef for FileWrite { fn as_ref(&self) -> &File { self.0.as_ref() } } impl Borrow for FileWrite { fn borrow(&self) -> &FileOpen { &self.0 } } impl Borrow for FileWrite { fn borrow(&self) -> &File { self.0.borrow() } } impl<'a> Borrow for &'a FileWrite { fn borrow(&self) -> &FileOpen { &self.0 } } impl<'a> Borrow for &'a FileWrite { fn borrow(&self) -> &File { self.0.borrow() } } impl From for FileOpen { fn from(orig: FileWrite) -> FileOpen { orig.0 } } impl From for File { fn from(orig: FileWrite) -> File { orig.0.into() } } path_abs-0.5.1/tests/absolute_extended_cwd.rs000064400000000000000000000007130000000000000174310ustar 00000000000000//! This file tests PathAbs::new() for Windows when the current directory //! uses extended-length path syntax (like `\\?\C:\`). // These tests are already run for Unix in absolute_regular_cwd.rs, and Unix // doesn't have "extended-length path syntax", so we can make them Windows-only // here. #[cfg(windows)] mod absolute_helpers; #[cfg(windows)] fn setup() { std::env::set_current_dir(r"\\?\C:\").expect("Could not change to a regular directory"); } path_abs-0.5.1/tests/absolute_helpers/mod.rs000064400000000000000000000126140000000000000172200ustar 00000000000000use std::env; use std::fs; use std::io; use std::path; use path_abs::PathAbs; use path_abs::PathInfo; use tempdir::TempDir; fn symlink_dir(src: P, dst: Q) where P: AsRef, Q: AsRef, { #[cfg(windows)] { use std::os::windows::fs as winfs; let dst = dst.as_ref(); winfs::symlink_dir(src, &dst).expect( "Could not create symbolic link. \ Run as Administrator, or on Windows 10 in Developer Mode", ); dst.symlink_metadata().expect( "Link creation succeeded, but can't read link? \ If you're using Wine, see bug 44948", ); } #[cfg(unix)] { use std::os::unix::fs as unixfs; unixfs::symlink(src, dst).expect("Could not create symbolic link"); } #[cfg(all(not(windows), not(unix)))] unreachable!(); } #[test] fn absolute_path_is_idempotent() { crate::setup(); // The current_dir() result is always absolute, // so absolutizing it should not change it. let actual = PathAbs::new(env::current_dir().unwrap()).unwrap(); let expected = env::current_dir().unwrap().canonicalize().unwrap(); assert_eq!(actual.as_path(), expected.as_path()); } #[test] fn absolute_path_removes_currentdir_component() { crate::setup(); let actual = PathAbs::new("foo/./bar").unwrap(); let expected = PathAbs::new("foo/bar").unwrap(); assert_eq!(actual, expected); } #[test] fn absolute_path_removes_empty_component() { crate::setup(); let actual = PathAbs::new("foo//bar").unwrap(); let expected = PathAbs::new("foo/bar").unwrap(); assert_eq!(actual, expected); } #[test] fn absolute_path_lexically_resolves_parentdir_component() { crate::setup(); let tmp_dir = TempDir::new("normalize_parentdir").unwrap(); let a_dir = tmp_dir.path().join("a"); fs::create_dir_all(&a_dir).unwrap(); let b_dir = tmp_dir.path().join("b"); fs::create_dir_all(&b_dir).unwrap(); fs::create_dir_all(&b_dir.join("target")).unwrap(); let link_path = a_dir.join("link"); symlink_dir("../b/target", link_path); // Because of the symlink, a/link/../foo is actually b/foo, but // lexically resolving the path produces a/foo. let actual = PathAbs::new(a_dir.join("link/../foo")).unwrap(); let expected = PathAbs::new(a_dir.join("foo")).unwrap(); assert_eq!(actual, expected); } #[test] fn absolute_path_interprets_relative_to_current_directory() { crate::setup(); let actual = PathAbs::new("foo").unwrap(); let expected = PathAbs::new(env::current_dir().unwrap().join("foo")).unwrap(); assert_eq!(actual, expected); } #[cfg(unix)] mod unix { use super::*; use path_abs::PathInfo; #[test] fn absolute_path_need_not_exist() { crate::setup(); // It's not likely this path would exist, but let's be sure. let raw_path = path::Path::new("/does/not/exist"); assert_eq!( raw_path.metadata().unwrap_err().kind(), io::ErrorKind::NotFound, ); let path = PathAbs::new(raw_path).unwrap(); assert_eq!(path.as_os_str(), "/does/not/exist"); } #[test] fn absolute_path_cannot_go_above_root() { crate::setup(); let err = PathAbs::new("/foo/../..").unwrap_err(); assert_eq!(err.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(err.io_error().to_string(), ".. consumed root"); assert_eq!(err.action(), "resolving absolute"); assert_eq!(err.path(), path::Path::new("/foo/../..")); } } #[cfg(windows)] mod windows { use super::*; #[test] fn absolute_path_need_not_exist() { crate::setup(); // It's not likely this path would exist, but let's be sure. let raw_path = path::Path::new(r"C:\does\not\exist"); assert_eq!( raw_path.metadata().unwrap_err().kind(), io::ErrorKind::NotFound, ); let path = PathAbs::new(raw_path).unwrap(); assert_eq!(path.as_os_str(), r"\\?\C:\does\not\exist"); } #[test] fn absolute_path_cannot_go_above_root() { crate::setup(); let err = PathAbs::new(r"C:\foo\..\..").unwrap_err(); assert_eq!(err.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(err.io_error().to_string(), ".. consumed root"); assert_eq!(err.action(), "resolving absolute"); assert_eq!(err.path(), path::Path::new(r"C:\foo\..\..")); } #[test] fn absolute_supports_root_only_relative_path() { crate::setup(); let actual = PathAbs::new(r"\foo").unwrap(); let mut current_drive_root = path::PathBuf::new(); current_drive_root.extend( env::current_dir().unwrap().components().take(2), // the prefix (C:) and root (\) components ); let expected = PathAbs::new(current_drive_root.join("foo")).unwrap(); assert_eq!(actual, expected); } #[test] fn absolute_supports_prefix_only_relative_path() { crate::setup(); let actual = PathAbs::new(r"C:foo").unwrap(); let expected = PathAbs::new(path::Path::new(r"C:").canonicalize().unwrap().join("foo")).unwrap(); assert_eq!(actual, expected); } #[test] fn absolute_accepts_bogus_prefix() { crate::setup(); let path = PathAbs::new(r"\\?\bogus\path\").unwrap(); assert_eq!(path.as_os_str(), r"\\?\bogus\path"); } } path_abs-0.5.1/tests/absolute_regular_cwd.rs000064400000000000000000000003770000000000000173000ustar 00000000000000mod absolute_helpers; fn setup() { #[cfg(windows)] std::env::set_current_dir(r"C:\").expect("Could not change to a regular directory"); // For cfg(unix), we're always in a regular directory, so we don't need to // do anything special. } path_abs-0.5.1/tests/test_absolute.rs000064400000000000000000000133210000000000000157520ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! The absolute paths have some gotchas that need to be tested. //! //! - Using the current working directory //! - `..` paths that consume the "root" #[macro_use] extern crate pretty_assertions; use tempdir; use path_abs::*; use std::env; use std::io; use std::path::{Path, PathBuf}; #[test] fn test_absolute() { if cfg!(windows) { let result = Path::new(r"\").canonicalize(); assert!( result.is_ok(), "Should work before set_current_dir is called: {:?}", result ); } let tmp = tempdir::TempDir::new("ex").unwrap(); let tmp = tmp.path(); let tmp_abs = PathAbs::new(&tmp).unwrap(); env::set_current_dir(&tmp_abs).unwrap(); if cfg!(windows) { let result = Path::new(r"\").canonicalize(); assert!(result.is_err()); println!("Got ERR cananonicalizing root: {}", result.unwrap_err()); } // Create directory like: // a/ // + e/ -> b/c/d // + b/ // + c/ // + d/ let a = PathDir::create(&tmp.join("a")).unwrap(); let b = PathDir::create(&a.concat("b").unwrap()).unwrap(); let c = PathDir::create(&b.concat("c").unwrap()).unwrap(); let d = PathDir::create(&c.concat("d").unwrap()).unwrap(); // create symbolic link from a/e -> a/b/c/d let e_sym = d.symlink(&a.concat("e").unwrap()).unwrap(); let ty = e_sym.symlink_metadata().unwrap().file_type(); assert!(ty.is_symlink(), "{}", e_sym.display()); assert_ne!(d, e_sym); assert_eq!(d, e_sym.canonicalize().unwrap()); let a_cwd = Path::new("a"); let b_cwd = a.concat("b").unwrap(); let c_cwd = b.concat("c").unwrap(); let d_cwd = c.concat("d").unwrap(); let e_cwd = a.concat("e").unwrap(); assert_eq!(a, PathDir::new(&a_cwd).unwrap()); assert_eq!(b, PathDir::new(&b_cwd).unwrap()); assert_eq!(c, PathDir::new(&c_cwd).unwrap()); assert_eq!(d, PathDir::new(&d_cwd).unwrap()); assert_eq!(e_sym, PathDir::new(&e_cwd).unwrap()); assert_eq!(b, PathDir::new(c.concat("..").unwrap()).unwrap()); assert_eq!( a, PathDir::new(c.concat("..").unwrap().concat("..").unwrap()).unwrap() ); // just create a PathType let _ = PathType::new(&e_sym).unwrap(); let mut root_dots: PathBuf = tmp_abs.clone().into(); let mut dots = tmp_abs.components().count() - 1; if cfg!(windows) { // windows has _two_ "roots", prefix _and_ "root". dots -= 1; } for _ in 0..dots { root_dots.push(".."); } let root = PathDir::new(root_dots).unwrap(); if cfg!(windows) { assert_eq!(PathDir::new("\\").unwrap(), root); } else { assert_eq!(PathDir::new("/").unwrap(), root); } assert!(root.concat("..").is_err()); if cfg!(windows) { // Test that /-separated and \-separated paths can be joined let ac1 = a.concat(r"b/c").unwrap(); assert!(ac1.metadata().is_ok()); let ac2 = a.concat(r"b\c").unwrap(); assert!(ac2.metadata().is_ok()); } } /// Check that issue #34 is fixed /// /// After calling join(), the metadata are accessed to check that the computed path is valid. #[test] fn test_forward_and_backward_slashes() { let tmp = tempdir::TempDir::new("ex").unwrap(); let tmp = tmp.path(); // Create directories: // a/ // + b/ // + c/ let a = PathDir::create(&tmp.join("a")).unwrap(); let b = PathDir::create(&a.concat("b").unwrap()).unwrap(); let c = PathDir::create(&b.concat("c").unwrap()).unwrap(); let a_abs = PathAbs::new(a).unwrap(); // Join /-separated relative path and check that the metadata are accessible let forward_slash = a_abs.concat(r"b/c").unwrap(); assert!(forward_slash.metadata().is_ok()); assert_eq!(c, PathDir::new(forward_slash).unwrap()); // Join \-separated relative path and check that the metadata are accessible // The following test only make sense on windows because the \ character isn't illegal in a // directory name on linux, so the call to `concat(r"b\c")` would just add the single directory // named "b\c" if cfg!(windows) { let backward_slash = a_abs.concat(r"b\c").unwrap(); assert!(backward_slash.metadata().is_ok()); assert_eq!(c, PathDir::new(backward_slash).unwrap()); } } #[test] fn test_root_parent() { let actual = PathAbs::new("/a/../..").expect_err("Can go outside of `/`?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "resolving absolute"); assert_eq!(actual.path(), Path::new(r"/a/../..")); } #[cfg_attr(windows, test)] fn _test_root_parent_windows() { let actual = PathAbs::new(r"\a\..\..").expect_err(r"Can go outside of \?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "resolving absolute"); assert_eq!(actual.path(), Path::new(r"/a/../..")); let actual = PathAbs::new(r"C:\a\..\..").expect_err(r"Can go outside of C:\?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "resolving absolute"); assert_eq!(actual.path(), Path::new(r"C:\a\..\..")); let actual = PathAbs::new(r"\\?\C:\a\..\..").expect_err(r"Can go outside of \\?\C:\?"); assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); assert_eq!(actual.action(), "resolving absolute"); assert_eq!(actual.path(), Path::new(r"\\?\C:\a\..\..")); } path_abs-0.5.1/tests/test_windows.rs000064400000000000000000000111520000000000000156260ustar 00000000000000/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Test assumptions about windows #![cfg_attr(not(windows), allow(dead_code))] #[macro_use] extern crate pretty_assertions; use std::path::Path; use std::process::Command; macro_rules! expect_err { [$s:expr] => {{ let p = Path::new($s); match p.canonicalize() { Ok(p) => { panic!("Got {:?} when canonicalizing {:?}, expected err", p, $s); } Err(err) => { println!("EXPECTED ERR Canonicalizing {:?} => {}", $s, err); } } }} } macro_rules! expect_path { [$expect:expr, $s:expr] => {{ let expect = Path::new($expect); let p = Path::new($s); match p.canonicalize() { Ok(p) => { assert_eq!(expect, p); println!("EXPECTED OK Canonicalizing {:?} => {:?}", $s, p); } Err(err) => { panic!("Got {:?} when canonicalizing {:?}, expected {:?}", err, $s, $expect); } } }} } fn share() -> String { // http://www.tech-recipes.com/rx/2953/windows_list_shared_drives_folders_command_line/ if cfg!(windows) { let shared = Command::new("wmic") .arg("share") .arg("get") .arg("caption,name,path") .output() .expect("could not `wmic share`") .stdout; let out = ::std::str::from_utf8(&shared).unwrap().trim().to_string(); println!("### SHARED:\n{}\n###", out); out } else { "NONE SHARED".to_string() } } fn hostname() -> String { let hostname = Command::new("hostname") .output() .expect("could not get hostname") .stdout; let out = ::std::str::from_utf8(&hostname).unwrap().trim().to_string(); println!("HOSTNAME: {}", out); out } // TODO: doesn't work, can't get coms // fn coms() -> String { // let coms = Command::new("mode") // .output() // .expect("could not get `mode` comports") // .stdout; // let out = ::std::str::from_utf8(&coms).unwrap().trim().to_string(); // println!("### COMS:\n{}\n###", out); // out // } // TODO: I don't know what is even a valid verbatum path, and I can't list it directly // ERROR: "The filename, directory name, or volume label syntax is incorrect." // // #[cfg_attr(windows, test)] // fn canonicalize_verbatim() { // // CURRENT DIR: C:\projects\path-abs // println!("CURRENT DIR: {}", ::std::env::current_dir().unwrap().display()); // // let verbatim_root = Path::new(r"\\?\"); // let list: Vec<_> = ::std::fs::read_dir(verbatim_root).unwrap().collect(); // println!("LIST VERBATIM: {:?}", list); // // // TODO: // // EXPECTED ERR Canonicalizing "\\\\?\\projects" => The system cannot find the file specified. // // (os error 2) // expect_err!(r"\\?\projects"); // } #[cfg_attr(windows, test)] fn canonicalize_verbatim_unc() { // HOSTNAME: APPVYR-WIN // ### SHARED: // Caption Name Path // Remote Admin ADMIN$ C:\windows // Default share C$ C:\ // Remote IPC IPC$ // ### // TODO: Only works on Windows hosts with the default administrative // file shares enabled. let _ = share(); // FIXME: just printing for now let p = format!(r"\\?\UNC\{}\C$", hostname()); expect_path!(&p, &p); } #[cfg_attr(windows, test)] fn canonicalize_verbatim_disk() { let with_root = r"\\?\C:\"; expect_path!(with_root, with_root); // EXPECTED ERR Canonicalizing "\\\\?\\C:" => Incorrect function. (os error 1) expect_err!(r"\\?\C:") } // TODO: can't list COMS // #[cfg_attr(windows, test)] // fn canonicalize_device_ns() { // // TODO: EXPECTED ERR Canonicalizing "\\\\.\\com1" => The system cannot find the file // // specified. (os error 2) // let _ = coms(); // expect_err!(r"\\.\COM1") // } #[cfg_attr(windows, test)] fn canonicalize_unc() { // TODO: Only works on Windows hosts with the default administrative // file shares enabled. let h = hostname(); let unc = format!(r"\\{}\C$", h); let verbatim = format!(r"\\?\UNC\{}\C$", h); let result = Path::new(&unc).canonicalize().unwrap(); assert_eq!(Path::new(&verbatim), result); } #[cfg_attr(windows, test)] fn canonicalize_disk() { expect_path!(r"\\?\C:\", r"C:\") }