├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── appveyor.yml ├── bors.toml ├── src ├── abs.rs ├── dir.rs ├── edit.rs ├── file.rs ├── lib.rs ├── open.rs ├── read.rs ├── ser.rs ├── ty.rs └── write.rs └── tests ├── absolute_extended_cwd.rs ├── absolute_helpers └── mod.rs ├── absolute_regular_cwd.rs ├── test_absolute.rs └── test_windows.rs /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | branches: 4 | only: 5 | - master 6 | # This is where pull requests from "bors r+" are built. 7 | - staging 8 | # This is where pull requests from "bors try" are built. 9 | - trying 10 | 11 | rust: 12 | - stable 13 | - beta 14 | - nightly 15 | 16 | matrix: 17 | allow_failures: 18 | - rust: nightly 19 | 20 | cache: cargo 21 | 22 | notifications: 23 | email: 24 | on_success: never 25 | 26 | script: 27 | - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast -- --nocapture 28 | - RUST_BACKTRACE=1 cargo test --verbose --all --no-fail-fast --no-default-features -- --nocapture 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Rett Berg "] 3 | description = "Ergonomic paths and files in rust." 4 | documentation = "https://docs.rs/path_abs" 5 | keywords = [ 6 | "filesystem", 7 | "path", 8 | "file", 9 | "types", 10 | "serde", 11 | ] 12 | license = "MIT OR Apache-2.0" 13 | name = "path_abs" 14 | readme = "README.md" 15 | repository = "https://github.com/vitiral/path_abs" 16 | version = "0.5.1" 17 | edition = "2018" 18 | 19 | [dependencies] 20 | std_prelude = "0.2.12" 21 | 22 | [dependencies.serde] 23 | optional = true 24 | version = "^1.0" 25 | 26 | [dependencies.serde_derive] 27 | optional = true 28 | version = "^1.0" 29 | 30 | [dependencies.stfu8] 31 | optional = true 32 | version = "^0.2.1" 33 | 34 | [dev-dependencies] 35 | pretty_assertions = "^0.4" 36 | regex = "^0.2" 37 | serde_json = "^1.0" 38 | tempfile = "^3" 39 | 40 | [features] 41 | default = ["serialize"] 42 | serialize = [ 43 | "serde", 44 | "serde_derive", 45 | "stfu8", 46 | ] 47 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Garrett Berg, vitiral@gmail.com 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # path_abs: ergonomic paths and files in rust. 2 | [![Build Status](https://travis-ci.org/vitiral/path_abs.svg?branch=windows)](https://travis-ci.org/vitiral/path_abs) 3 | [![Build status](https://ci.appveyor.com/api/projects/status/vgis54solhygre0n?svg=true)](https://ci.appveyor.com/project/vitiral/path-abs) 4 | [![Docs](https://docs.rs/path_abs/badge.svg)](https://docs.rs/path_abs) 5 | 6 | This library aims to provide ergonomic path and file operations to rust with 7 | reasonable performance. 8 | 9 | **See the [library docs](https://docs.rs/path_abs) for more information** 10 | 11 | # LICENSE 12 | The source code in this repository is Licensed under either of 13 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 14 | http://www.apache.org/licenses/LICENSE-2.0) 15 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or 16 | http://opensource.org/licenses/MIT) 17 | 18 | at your option. 19 | 20 | Unless you explicitly state otherwise, any contribution intentionally submitted 21 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 22 | be dual licensed as above, without any additional terms or conditions. 23 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Appveyor configuration template for Rust using rustup for Rust installation 2 | # https://github.com/starkat99/appveyor-rust 3 | 4 | ## Operating System (VM environment) ## 5 | 6 | # Rust needs at least Visual Studio 2013 Appveyor OS for MSVC targets. 7 | os: Visual Studio 2015 8 | 9 | # Bors configuration 10 | # branches: 11 | # only: 12 | # - mainline 13 | # # This is where pull requests from "bors r+" are built. 14 | # - staging 15 | # # This is where pull requests from "bors try" are built. 16 | # - trying 17 | 18 | ## Build Matrix ## 19 | 20 | # This configuration will setup a build for each channel & target combination (12 windows 21 | # combinations in all). 22 | # 23 | # There are 3 channels: stable, beta, and nightly. 24 | # 25 | # Alternatively, the full version may be specified for the channel to build using that specific 26 | # version (e.g. channel: 1.5.0) 27 | # 28 | # The values for target are the set of windows Rust build targets. Each value is of the form 29 | # 30 | # ARCH-pc-windows-TOOLCHAIN 31 | # 32 | # Where ARCH is the target architecture, either x86_64 or i686, and TOOLCHAIN is the linker 33 | # toolchain to use, either msvc or gnu. See https://www.rust-lang.org/downloads.html#win-foot for 34 | # a description of the toolchain differences. 35 | # See https://github.com/rust-lang-nursery/rustup.rs/#toolchain-specification for description of 36 | # toolchains and host triples. 37 | # 38 | # Comment out channel/target combos you do not wish to build in CI. 39 | # 40 | # You may use the `cargoflags` and `RUSTFLAGS` variables to set additional flags for cargo commands 41 | # and rustc, respectively. For instance, you can uncomment the cargoflags lines in the nightly 42 | # channels to enable unstable features when building for nightly. Or you could add additional 43 | # matrix entries to test different combinations of features. 44 | environment: 45 | RUST_BACKTRACE: 1 46 | matrix: 47 | 48 | ### MSVC Toolchains ### 49 | 50 | # Stable 64-bit MSVC 51 | - channel: stable 52 | target: x86_64-pc-windows-msvc 53 | # Stable 32-bit MSVC 54 | - channel: stable 55 | target: i686-pc-windows-msvc 56 | # # Beta 64-bit MSVC 57 | # - channel: beta 58 | # target: x86_64-pc-windows-msvc 59 | # # Beta 32-bit MSVC 60 | # - channel: beta 61 | # target: i686-pc-windows-msvc 62 | # # Nightly 64-bit MSVC 63 | # - channel: nightly 64 | # target: x86_64-pc-windows-msvc 65 | # #cargoflags: --features "unstable" 66 | # # Nightly 32-bit MSVC 67 | # - channel: nightly 68 | # target: i686-pc-windows-msvc 69 | # #cargoflags: --features "unstable" 70 | 71 | ### GNU Toolchains ### 72 | 73 | # Stable 64-bit GNU 74 | - channel: stable 75 | target: x86_64-pc-windows-gnu 76 | # Stable 32-bit GNU 77 | - channel: stable 78 | target: i686-pc-windows-gnu 79 | # # Beta 64-bit GNU 80 | # - channel: beta 81 | # target: x86_64-pc-windows-gnu 82 | # # Beta 32-bit GNU 83 | # - channel: beta 84 | # target: i686-pc-windows-gnu 85 | # # Nightly 64-bit GNU 86 | # - channel: nightly 87 | # target: x86_64-pc-windows-gnu 88 | # #cargoflags: --features "unstable" 89 | # # Nightly 32-bit GNU 90 | # - channel: nightly 91 | # target: i686-pc-windows-gnu 92 | # #cargoflags: --features "unstable" 93 | 94 | ### Allowed failures ### 95 | 96 | # See Appveyor documentation for specific details. In short, place any channel or targets you wish 97 | # to allow build failures on (usually nightly at least is a wise choice). This will prevent a build 98 | # or test failure in the matching channels/targets from failing the entire build. 99 | matrix: 100 | allow_failures: 101 | - channel: nightly 102 | 103 | # If you only care about stable channel build failures, uncomment the following line: 104 | #- channel: beta 105 | 106 | ## Install Script ## 107 | 108 | # This is the most important part of the Appveyor configuration. This installs the version of Rust 109 | # specified by the 'channel' and 'target' environment variables from the build matrix. This uses 110 | # rustup to install Rust. 111 | # 112 | # For simple configurations, instead of using the build matrix, you can simply set the 113 | # default-toolchain and default-host manually here. 114 | install: 115 | - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe 116 | - rustup-init -yv --default-toolchain %channel% --default-host %target% 117 | - set PATH=%PATH%;%USERPROFILE%\.cargo\bin 118 | - rustc -vV 119 | - cargo -vV 120 | 121 | ## Build Script ## 122 | 123 | # 'cargo test' takes care of building for us, so disable Appveyor's build stage. This prevents 124 | # the "directory does not contain a project or solution file" error. 125 | build: false 126 | 127 | # Uses 'cargo test' to run tests and build. Alternatively, the project may call compiled programs 128 | #directly or perform other testing commands. Rust will automatically be placed in the PATH 129 | # environment variable. 130 | test_script: 131 | - cargo test --verbose --all --no-fail-fast %cargoflags% -- --nocapture 132 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "continuous-integration/travis-ci/push", 3 | "continuous-integration/appveyor/branch" 4 | ] 5 | 6 | # Uncomment this to use a two hour timeout. 7 | # The default is one hour. 8 | # timeout_sec = 7200 9 | -------------------------------------------------------------------------------- /src/abs.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! The absolute path type, the root type for all `Path*` types in this module. 9 | use std::env; 10 | use std::ffi; 11 | use std::fmt; 12 | use std::io; 13 | use std::path::{Component, PrefixComponent}; 14 | use std_prelude::*; 15 | 16 | use super::{Error, PathMut, PathOps, Result}; 17 | 18 | /// Converts any PrefixComponent into verbatim ("extended-length") form. 19 | fn make_verbatim_prefix(prefix: &PrefixComponent<'_>) -> Result { 20 | let path_prefix = Path::new(prefix.as_os_str()); 21 | 22 | if prefix.kind().is_verbatim() { 23 | // This prefix already uses the extended-length 24 | // syntax, so we can use it as-is. 25 | Ok(path_prefix.to_path_buf()) 26 | } else { 27 | // This prefix needs canonicalization. 28 | let res = path_prefix 29 | .canonicalize() 30 | .map_err(|e| Error::new(e, "canonicalizing", path_prefix.to_path_buf().into()))?; 31 | Ok(res) 32 | } 33 | } 34 | 35 | /// Pops the last component from path, returning an error for a root path. 36 | fn pop_or_error(path: &mut PathBuf) -> ::std::result::Result<(), io::Error> { 37 | if path.pop() { 38 | Ok(()) 39 | } else { 40 | Err(io::Error::new(io::ErrorKind::NotFound, ".. consumed root")) 41 | } 42 | } 43 | 44 | #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 45 | /// An absolute (not _necessarily_ [canonicalized][1]) path that may or may not exist. 46 | /// 47 | /// [1]: https://doc.rust-lang.org/std/path/struct.Path.html?search=#method.canonicalize 48 | pub struct PathAbs(pub(crate) Arc); 49 | 50 | impl PathAbs { 51 | /// Construct an absolute path from an arbitrary (absolute or relative) one. 52 | /// 53 | /// This is different from [`canonicalize`] in that it _preserves_ symlinks 54 | /// and the destination may or may not exist. 55 | /// 56 | /// This function will: 57 | /// - Resolve relative paths against the current working directory. 58 | /// - Strip any `.` components (`/a/./c` -> `/a/c`) 59 | /// - Resolve `..` _semantically_ (not using the file system). So, `a/b/c/../d => a/b/d` will 60 | /// _always_ be true regardless of symlinks. If you want symlinks correctly resolved, use 61 | /// `canonicalize()` instead. 62 | /// 63 | /// > On windows, this will sometimes call `canonicalize()` on the first component to guarantee 64 | /// > it is the correct canonicalized prefix. For paths starting with root it also has to get 65 | /// > the [`current_dir`] 66 | /// 67 | /// > On linux, the only syscall this will make is to get the [`current_dir`] for relative 68 | /// > paths. 69 | /// 70 | /// [`canonicalize`]: struct.PathAbs.html#method.canonicalize 71 | /// [`current_dir`]: fn.current_dir.html 72 | /// 73 | /// # Examples 74 | /// 75 | /// ```rust 76 | /// use path_abs::{PathAbs, PathInfo}; 77 | /// 78 | /// # fn try_main() -> ::std::io::Result<()> { 79 | /// let lib = PathAbs::new("src/lib.rs")?; 80 | /// 81 | /// assert_eq!(lib.is_absolute(), true); 82 | /// # Ok(()) } fn main() { try_main().unwrap() } 83 | /// ``` 84 | pub fn new>(path: P) -> Result { 85 | let path = Arc::new(path.as_ref().to_path_buf()); 86 | let mut res = PathBuf::new(); 87 | 88 | fn maybe_init_res(res: &mut PathBuf, resolvee: Arc) -> Result<()> { 89 | if !res.as_os_str().is_empty() { 90 | // res has already been initialized, let's leave it alone. 91 | return Ok(()); 92 | } 93 | 94 | // res has not been initialized, let's initialize it to the 95 | // canonicalized current directory. 96 | let cwd = env::current_dir().map_err(|e| { 97 | Error::new(e, "getting current_dir while resolving absolute", resolvee) 98 | })?; 99 | *res = cwd 100 | .canonicalize() 101 | .map_err(|e| Error::new(e, "canonicalizing", cwd.into()))?; 102 | 103 | Ok(()) 104 | }; 105 | 106 | for each in path.components() { 107 | match each { 108 | Component::Prefix(p) => { 109 | // We don't care what's already in res, we can entirely 110 | // replace it.. 111 | res = make_verbatim_prefix(&p)?; 112 | } 113 | 114 | Component::RootDir => { 115 | if cfg!(windows) { 116 | // In an ideal world, we would say 117 | // 118 | // res = std::fs::canonicalize(each)?; 119 | // 120 | // ...to get a properly canonicalized path. 121 | // Unfortunately, Windows cannot canonicalize `\` if 122 | // the current directory happens to use extended-length 123 | // syntax (like `\\?\C:\Windows`), so we'll have to do 124 | // it manually: initialize `res` with the current 125 | // working directory (whatever it is), and truncate it 126 | // to its prefix by pushing `\`. 127 | maybe_init_res(&mut res, path.clone())?; 128 | res.push(each); 129 | } else { 130 | // On other platforms, a root path component is always 131 | // absolute so we can replace whatever's in res. 132 | res = Path::new(&each).to_path_buf(); 133 | } 134 | } 135 | 136 | // This does nothing and can be ignored. 137 | Component::CurDir => (), 138 | 139 | Component::ParentDir => { 140 | // A parent component is always relative to some existing 141 | // path. 142 | maybe_init_res(&mut res, path.clone())?; 143 | pop_or_error(&mut res) 144 | .map_err(|e| Error::new(e, "resolving absolute", path.clone()))?; 145 | } 146 | 147 | Component::Normal(c) => { 148 | // A normal component is always relative to some existing 149 | // path. 150 | maybe_init_res(&mut res, path.clone())?; 151 | res.push(c); 152 | } 153 | } 154 | } 155 | 156 | Ok(PathAbs(Arc::new(res))) 157 | } 158 | 159 | /// Create a PathAbs unchecked. 160 | /// 161 | /// This is mostly used for constructing during tests, or if the path was previously validated. 162 | /// This is effectively the same as a `Arc`. 163 | /// 164 | /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause 165 | /// > panics in some methods if the path was not properly validated. 166 | pub fn new_unchecked>>(path: P) -> PathAbs { 167 | PathAbs(path.into()) 168 | } 169 | 170 | /// Return a reference to a basic `std::path::Path` 171 | pub fn as_path(&self) -> &Path { 172 | self.as_ref() 173 | } 174 | } 175 | 176 | impl fmt::Debug for PathAbs { 177 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 178 | self.0.fmt(f) 179 | } 180 | } 181 | 182 | impl PathMut for PathAbs { 183 | fn append>(&mut self, path: P) -> Result<()> { 184 | self.0.append(path) 185 | } 186 | fn pop_up(&mut self) -> Result<()> { 187 | self.0.pop_up() 188 | } 189 | fn truncate_to_root(&mut self) { 190 | self.0.truncate_to_root() 191 | } 192 | fn set_file_name>(&mut self, file_name: S) { 193 | self.0.set_file_name(file_name) 194 | } 195 | fn set_extension>(&mut self, extension: S) -> bool { 196 | self.0.set_extension(extension) 197 | } 198 | } 199 | 200 | impl PathOps for PathAbs { 201 | type Output = PathAbs; 202 | 203 | fn concat>(&self, path: P) -> Result { 204 | Ok(PathAbs(self.0.concat(path)?)) 205 | } 206 | 207 | fn join>(&self, path: P) -> Self::Output { 208 | let buf = Path::join(self.as_path(), path); 209 | Self::Output::new_unchecked(buf) 210 | } 211 | 212 | fn with_file_name>(&self, file_name: S) -> Self::Output { 213 | PathAbs(self.0.with_file_name(file_name)) 214 | } 215 | 216 | fn with_extension>(&self, extension: S) -> Self::Output { 217 | PathAbs(self.0.with_extension(extension)) 218 | } 219 | } 220 | 221 | impl AsRef for PathAbs { 222 | fn as_ref(&self) -> &std::ffi::OsStr { 223 | self.0.as_ref().as_ref() 224 | } 225 | } 226 | 227 | impl AsRef for PathAbs { 228 | fn as_ref(&self) -> &Path { 229 | self.0.as_ref() 230 | } 231 | } 232 | 233 | impl AsRef for PathAbs { 234 | fn as_ref(&self) -> &PathBuf { 235 | self.0.as_ref() 236 | } 237 | } 238 | 239 | impl Borrow for PathAbs { 240 | fn borrow(&self) -> &Path { 241 | self.as_ref() 242 | } 243 | } 244 | 245 | impl Borrow for PathAbs { 246 | fn borrow(&self) -> &PathBuf { 247 | self.as_ref() 248 | } 249 | } 250 | 251 | impl<'a> Borrow for &'a PathAbs { 252 | fn borrow(&self) -> &Path { 253 | self.as_ref() 254 | } 255 | } 256 | 257 | impl<'a> Borrow for &'a PathAbs { 258 | fn borrow(&self) -> &PathBuf { 259 | self.as_ref() 260 | } 261 | } 262 | 263 | impl From for Arc { 264 | fn from(path: PathAbs) -> Arc { 265 | path.0 266 | } 267 | } 268 | 269 | impl From for PathBuf { 270 | fn from(path: PathAbs) -> PathBuf { 271 | match Arc::try_unwrap(path.0) { 272 | Ok(p) => p, 273 | Err(inner) => inner.as_ref().clone(), 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/dir.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Paths to Directories and associated methods. 9 | use std::ffi; 10 | use std::fmt; 11 | use std::fs; 12 | use std::io; 13 | use std_prelude::*; 14 | 15 | use super::{Error, Result}; 16 | use super::{PathAbs, PathInfo, PathOps, PathType}; 17 | 18 | #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 19 | /// A `PathAbs` that is guaranteed to be a directory, with associated methods. 20 | pub struct PathDir(pub(crate) PathAbs); 21 | 22 | impl PathDir { 23 | /// Instantiate a new `PathDir`. The directory must exist or `io::Error` will be returned. 24 | /// 25 | /// Returns `io::ErrorKind::InvalidInput` if the path exists but is not a directory. 26 | /// 27 | /// # Examples 28 | /// ```rust 29 | /// # extern crate path_abs; 30 | /// use path_abs::PathDir; 31 | /// 32 | /// # fn try_main() -> ::std::io::Result<()> { 33 | /// let src = PathDir::new("src")?; 34 | /// # Ok(()) } fn main() { try_main().unwrap() } 35 | /// ``` 36 | pub fn new>(path: P) -> Result { 37 | let abs = PathAbs::new(path)?; 38 | PathDir::try_from(abs) 39 | } 40 | 41 | /// Create a `PathDir` unchecked. 42 | /// 43 | /// This is mostly used for constructing during tests, or if the path was previously validated. 44 | /// This is effectively the same as a `Arc`. 45 | /// 46 | /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause 47 | /// > panics in some methods if the path was not properly validated. 48 | pub fn new_unchecked>>(path: P) -> PathDir { 49 | PathDir(PathAbs::new_unchecked(path)) 50 | } 51 | 52 | /// Returns the current working directory from the `env` as a `PathDir`. 53 | /// 54 | /// # Examples 55 | /// ```rust 56 | /// # extern crate path_abs; 57 | /// use path_abs::PathDir; 58 | /// # fn try_main() -> ::std::io::Result<()> { 59 | /// let cwd = PathDir::current_dir()?; 60 | /// # let env_cwd = ::std::fs::canonicalize(::std::env::current_dir()?)?; 61 | /// # let cwd_ref: &::std::path::PathBuf = cwd.as_ref(); 62 | /// # assert_eq!(cwd_ref, &env_cwd); 63 | /// # Ok(()) } fn main() { try_main().unwrap() } 64 | /// ``` 65 | pub fn current_dir() -> Result { 66 | let dir = ::std::env::current_dir().map_err(|err| { 67 | Error::new( 68 | err, 69 | "getting current_dir", 70 | Path::new("$CWD").to_path_buf().into(), 71 | ) 72 | })?; 73 | PathDir::new(dir) 74 | } 75 | 76 | /// Consume the `PathAbs` validating that the path is a directory and returning `PathDir`. The 77 | /// directory must exist or `io::Error` will be returned. 78 | /// 79 | /// If the path is actually a file returns `io::ErrorKind::InvalidInput`. 80 | /// 81 | /// # Examples 82 | /// ```rust 83 | /// # extern crate path_abs; 84 | /// use path_abs::{PathAbs, PathDir}; 85 | /// 86 | /// # fn try_main() -> ::std::io::Result<()> { 87 | /// let src_abs = PathAbs::new("src")?; 88 | /// let src_dir = PathDir::try_from(src_abs)?; 89 | /// # Ok(()) } fn main() { try_main().unwrap() } 90 | /// ``` 91 | pub fn try_from>(path: P) -> Result { 92 | let abs = path.into(); 93 | if abs.is_dir() { 94 | Ok(PathDir::new_unchecked(abs)) 95 | } else { 96 | Err(Error::new( 97 | io::Error::new(io::ErrorKind::InvalidInput, "path is not a dir"), 98 | "resolving", 99 | abs.into(), 100 | )) 101 | } 102 | } 103 | 104 | /// Instantiate a new `PathDir` to a directory, creating the directory if it doesn't exist. 105 | /// 106 | /// # Examples 107 | /// ```rust 108 | /// # extern crate path_abs; 109 | /// # extern crate tempfile; 110 | /// use path_abs::PathDir; 111 | /// 112 | /// # fn try_main() -> ::std::io::Result<()> { 113 | /// let example = "example"; 114 | /// # let tmp = tempfile::TempDir::new()?; 115 | /// # let example = &tmp.path().join(example); 116 | /// 117 | /// let dir = PathDir::create(example)?; 118 | /// 119 | /// // It can be done twice with no effect. 120 | /// let _ = PathDir::create(example)?; 121 | /// # Ok(()) } fn main() { try_main().unwrap() } 122 | /// ``` 123 | pub fn create>(path: P) -> Result { 124 | if let Err(err) = fs::create_dir(&path) { 125 | match err.kind() { 126 | io::ErrorKind::AlreadyExists => {} 127 | _ => { 128 | return Err(Error::new( 129 | err, 130 | "creating", 131 | path.as_ref().to_path_buf().into(), 132 | )); 133 | } 134 | } 135 | } 136 | PathDir::new(path) 137 | } 138 | 139 | /// Instantiate a new `PathDir` to a directory, recursively recreating it and all of its parent 140 | /// components if they are missing. 141 | /// 142 | /// # Examples 143 | /// ```rust 144 | /// # extern crate path_abs; 145 | /// # extern crate tempfile; 146 | /// use path_abs::PathDir; 147 | /// 148 | /// # fn try_main() -> ::std::io::Result<()> { 149 | /// let example = "example/long/path"; 150 | /// # let tmp = tempfile::TempDir::new()?; 151 | /// # let example = &tmp.path().join(example); 152 | /// 153 | /// let path = PathDir::create_all(example)?; 154 | /// 155 | /// // It can be done twice with no effect. 156 | /// let _ = PathDir::create_all(example)?; 157 | /// # Ok(()) } fn main() { try_main().unwrap() } 158 | /// ``` 159 | pub fn create_all>(path: P) -> Result { 160 | fs::create_dir_all(&path) 161 | .map_err(|err| Error::new(err, "creating-all", path.as_ref().to_path_buf().into()))?; 162 | PathDir::new(path) 163 | } 164 | 165 | /// Join a path onto the `PathDir`, expecting it to exist. Returns the resulting `PathType`. 166 | /// 167 | /// # Examples 168 | /// ```rust 169 | /// # extern crate path_abs; 170 | /// use path_abs::{PathDir, PathFile, PathInfo}; 171 | /// 172 | /// # fn try_main() -> ::std::io::Result<()> { 173 | /// let src = PathDir::new("src")?; 174 | /// let lib = src.join_abs("lib.rs")?.unwrap_file(); 175 | /// assert!(lib.is_file()); 176 | /// # Ok(()) } fn main() { try_main().unwrap() } 177 | /// ``` 178 | pub fn join_abs>(&self, path: P) -> Result { 179 | let joined = self.concat(path.as_ref())?; 180 | PathType::new(joined) 181 | } 182 | 183 | /// List the contents of the directory, returning an iterator of `PathType`s. 184 | /// 185 | /// # Examples 186 | /// ```rust 187 | /// # extern crate path_abs; 188 | /// # extern crate tempfile; 189 | /// use std::collections::HashSet; 190 | /// use path_abs::{PathDir, PathFile, PathType, PathOps}; 191 | /// 192 | /// # fn try_main() -> ::std::io::Result<()> { 193 | /// let example = "example"; 194 | /// # let tmp = tempfile::TempDir::new()?; 195 | /// # let example = &tmp.path().join("example"); 196 | /// 197 | /// let example_dir = PathDir::create(example)?; 198 | /// let foo_dir = PathDir::create(example_dir.concat("foo")?)?; 199 | /// let bar_file = PathFile::create(example_dir.concat("bar.txt")?)?; 200 | /// 201 | /// let mut result = HashSet::new(); 202 | /// for p in example_dir.list()? { 203 | /// result.insert(p?); 204 | /// } 205 | /// 206 | /// let mut expected = HashSet::new(); 207 | /// expected.insert(PathType::Dir(foo_dir)); 208 | /// expected.insert(PathType::File(bar_file)); 209 | /// 210 | /// assert_eq!(expected, result); 211 | /// # Ok(()) } fn main() { try_main().unwrap() } 212 | pub fn list(&self) -> Result { 213 | let fsread = fs::read_dir(self) 214 | .map_err(|err| Error::new(err, "reading dir", self.clone().into()))?; 215 | Ok(ListDir { 216 | dir: self.clone(), 217 | fsread: fsread, 218 | }) 219 | } 220 | 221 | /// Remove (delete) the _empty_ directory from the filesystem, consuming self. 222 | /// 223 | /// # Examples 224 | /// ```rust 225 | /// # extern crate path_abs; 226 | /// # extern crate tempfile; 227 | /// use std::path::Path; 228 | /// use path_abs::PathDir; 229 | /// 230 | /// # fn try_main() -> ::std::io::Result<()> { 231 | /// let example = Path::new("example/long/path"); 232 | /// # let tmp = tempfile::TempDir::new()?; 233 | /// # let example = &tmp.path().join(example); 234 | /// 235 | /// let dir = PathDir::create_all(example)?; 236 | /// let parent = dir.parent_dir().unwrap(); 237 | /// 238 | /// assert!(example.exists()); 239 | /// dir.remove()?; 240 | /// // assert!(dir.exists()); <--- COMPILE ERROR 241 | /// assert!(!example.exists()); 242 | /// parent.remove()?; 243 | /// # Ok(()) } fn main() { try_main().unwrap() } 244 | /// ``` 245 | pub fn remove(self) -> Result<()> { 246 | fs::remove_dir(&self).map_err(|err| Error::new(err, "removing", self.into())) 247 | } 248 | 249 | /// Remove (delete) the directory, after recursively removing its contents. Use carefully! 250 | /// 251 | /// # Examples 252 | /// ```rust 253 | /// # extern crate path_abs; 254 | /// # extern crate tempfile; 255 | /// use std::path::Path; 256 | /// use path_abs::PathDir; 257 | /// 258 | /// # fn try_main() -> ::std::io::Result<()> { 259 | /// let example = Path::new("example/long/path"); 260 | /// # let tmp = tempfile::TempDir::new()?; 261 | /// # let example = &tmp.path().join(example); 262 | /// 263 | /// let dir = PathDir::create_all(example)?; 264 | /// let parent = dir.parent_dir().unwrap(); 265 | /// 266 | /// assert!(example.exists()); 267 | /// parent.remove_all()?; 268 | /// assert!(!example.exists()); 269 | /// # Ok(()) } fn main() { try_main().unwrap() } 270 | /// ``` 271 | pub fn remove_all(self) -> Result<()> { 272 | fs::remove_dir_all(&self).map_err(|err| Error::new(err, "removing-all", self.into())) 273 | } 274 | 275 | /// Creates a new symbolic link on the filesystem to the dst. 276 | /// 277 | /// This handles platform specific behavior correctly. 278 | /// 279 | /// # Examples 280 | /// 281 | /// ```rust 282 | /// # extern crate path_abs; 283 | /// # extern crate tempfile; 284 | /// use path_abs::{PathDir, PathFile, PathOps}; 285 | /// use std::path::Path; 286 | /// 287 | /// # fn try_main() -> ::std::io::Result<()> { 288 | /// let example = "example"; 289 | /// let example_sym = "example_sym"; 290 | /// # let tmp = tempfile::TempDir::new()?; 291 | /// # let example = &tmp.path().join(example); 292 | /// # let example_sym = &tmp.path().join(example_sym); 293 | /// let dir = PathDir::create(example)?; 294 | /// let file = PathFile::create(dir.concat("example.txt")?)?; 295 | /// 296 | /// let dir_sym = dir.symlink(example_sym)?; 297 | /// 298 | /// // They have a different "absolute path" 299 | /// assert_ne!(dir, dir_sym); 300 | /// 301 | /// // But they can be canonicalized to the same file. 302 | /// let dir_can = dir_sym.canonicalize()?; 303 | /// assert_eq!(dir, dir_can); 304 | /// 305 | /// # Ok(()) } fn main() { try_main().unwrap() } 306 | /// ``` 307 | pub fn symlink>(&self, dst: P) -> Result { 308 | symlink_dir(&self, &dst).map_err(|err| { 309 | Error::new( 310 | err, 311 | &format!("linking from {} to", dst.as_ref().display()), 312 | self.clone().into(), 313 | ) 314 | })?; 315 | PathDir::new(dst) 316 | } 317 | 318 | /// Return a reference to a basic `std::path::Path` 319 | pub fn as_path(&self) -> &Path { 320 | self.as_ref() 321 | } 322 | 323 | /// Returns the canonical form of the path with all intermediate components normalized and 324 | /// symbolic links resolved. 325 | /// 326 | /// See [`PathAbs::canonicalize`] 327 | /// 328 | /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize 329 | pub fn canonicalize(&self) -> Result { 330 | Ok(PathDir(self.0.canonicalize()?)) 331 | } 332 | 333 | /// Get the parent directory of this directory as a `PathDir`. 334 | /// 335 | /// > This does not make aditional syscalls, as the parent by definition must be a directory 336 | /// > and exist. 337 | /// 338 | /// # Examples 339 | /// ```rust 340 | /// # extern crate path_abs; 341 | /// use path_abs::PathDir; 342 | /// 343 | /// # fn try_main() -> ::std::io::Result<()> { 344 | /// let src = PathDir::new("src")?; 345 | /// let proj = src.parent_dir().unwrap(); 346 | /// assert_eq!(PathDir::new("src/..")?, proj); 347 | /// # Ok(()) } fn main() { try_main().unwrap() } 348 | /// ``` 349 | pub fn parent_dir(&self) -> Option { 350 | match self.parent() { 351 | Ok(path) => Some(PathDir(PathAbs(Arc::new(path.to_path_buf())))), 352 | Err(_) => None, 353 | } 354 | } 355 | } 356 | 357 | /// An iterator over `PathType` objects, returned by `PathDir::list`. 358 | pub struct ListDir { 359 | // TODO: this should be a reference...? 360 | // Or is this a good excuse to use Arc under the hood everywhere? 361 | dir: PathDir, 362 | fsread: fs::ReadDir, 363 | } 364 | 365 | impl ::std::iter::Iterator for ListDir { 366 | type Item = Result; 367 | fn next(&mut self) -> Option> { 368 | let entry = match self.fsread.next() { 369 | Some(r) => match r { 370 | Ok(e) => e, 371 | Err(err) => { 372 | return Some(Err(Error::new( 373 | err, 374 | "iterating over", 375 | self.dir.clone().into(), 376 | ))); 377 | } 378 | }, 379 | None => return None, 380 | }; 381 | Some(PathType::new(entry.path())) 382 | } 383 | } 384 | 385 | impl fmt::Debug for PathDir { 386 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 387 | self.0.fmt(f) 388 | } 389 | } 390 | 391 | impl AsRef for PathDir { 392 | fn as_ref(&self) -> &std::ffi::OsStr { 393 | self.0.as_ref() 394 | } 395 | } 396 | 397 | impl AsRef for PathDir { 398 | fn as_ref(&self) -> &PathAbs { 399 | &self.0 400 | } 401 | } 402 | 403 | impl AsRef for PathDir { 404 | fn as_ref(&self) -> &Path { 405 | self.0.as_ref() 406 | } 407 | } 408 | 409 | impl AsRef for PathDir { 410 | fn as_ref(&self) -> &PathBuf { 411 | self.0.as_ref() 412 | } 413 | } 414 | 415 | impl Borrow for PathDir { 416 | fn borrow(&self) -> &PathAbs { 417 | self.as_ref() 418 | } 419 | } 420 | 421 | impl Borrow for PathDir { 422 | fn borrow(&self) -> &Path { 423 | self.as_ref() 424 | } 425 | } 426 | 427 | impl Borrow for PathDir { 428 | fn borrow(&self) -> &PathBuf { 429 | self.as_ref() 430 | } 431 | } 432 | 433 | impl<'a> Borrow for &'a PathDir { 434 | fn borrow(&self) -> &PathAbs { 435 | self.as_ref() 436 | } 437 | } 438 | 439 | impl<'a> Borrow for &'a PathDir { 440 | fn borrow(&self) -> &Path { 441 | self.as_ref() 442 | } 443 | } 444 | 445 | impl<'a> Borrow for &'a PathDir { 446 | fn borrow(&self) -> &PathBuf { 447 | self.as_ref() 448 | } 449 | } 450 | 451 | impl From for PathAbs { 452 | fn from(path: PathDir) -> PathAbs { 453 | path.0 454 | } 455 | } 456 | 457 | impl From for Arc { 458 | fn from(path: PathDir) -> Arc { 459 | let abs: PathAbs = path.into(); 460 | abs.into() 461 | } 462 | } 463 | 464 | impl From for PathBuf { 465 | fn from(path: PathDir) -> PathBuf { 466 | let abs: PathAbs = path.into(); 467 | abs.into() 468 | } 469 | } 470 | 471 | #[cfg(test)] 472 | mod tests { 473 | use super::super::{PathAbs, PathDir, PathFile, PathOps, PathType}; 474 | use std::collections::HashSet; 475 | use tempfile::TempDir; 476 | 477 | #[test] 478 | fn sanity_list() { 479 | let tmp_dir = TempDir::new().expect("create temp dir"); 480 | let tmp_abs = PathDir::new(tmp_dir.path()).unwrap(); 481 | 482 | let foo_path = tmp_abs.concat("foo").expect("path foo"); 483 | let foo_dir = PathDir::create(foo_path).unwrap(); 484 | 485 | let bar_path = tmp_abs.concat("bar").expect("path bar"); 486 | let bar_file = PathFile::create(bar_path).unwrap(); 487 | 488 | let mut result = HashSet::new(); 489 | for p in tmp_abs.list().unwrap() { 490 | result.insert(p.unwrap()); 491 | } 492 | 493 | let mut expected = HashSet::new(); 494 | expected.insert(PathType::Dir(foo_dir.clone())); 495 | expected.insert(PathType::File(bar_file.clone())); 496 | 497 | assert_eq!(expected, result); 498 | 499 | // just ensure that this compiles 500 | let _: PathAbs = foo_dir.into(); 501 | let _: PathAbs = bar_file.into(); 502 | } 503 | } 504 | 505 | impl PathOps for PathDir { 506 | type Output = PathAbs; 507 | 508 | fn concat>(&self, path: P) -> Result { 509 | Ok(self.0.concat(path)?) 510 | } 511 | 512 | fn join>(&self, path: P) -> Self::Output { 513 | let buf = Path::join(self.as_path(), path); 514 | Self::Output::new_unchecked(buf) 515 | } 516 | 517 | fn with_file_name>(&self, file_name: S) -> Self::Output { 518 | self.0.with_file_name(file_name) 519 | } 520 | 521 | fn with_extension>(&self, extension: S) -> Self::Output { 522 | self.0.with_extension(extension) 523 | } 524 | } 525 | 526 | #[cfg(target_os = "wasi")] 527 | fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 528 | std::os::wasi::fs::symlink_path(src, dst) 529 | } 530 | 531 | #[cfg(unix)] 532 | fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 533 | ::std::os::unix::fs::symlink(src, dst) 534 | } 535 | 536 | #[cfg(windows)] 537 | fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 538 | ::std::os::windows::fs::symlink_dir(src, dst) 539 | } 540 | -------------------------------------------------------------------------------- /src/edit.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Open editable (read+write) file paths and associated methods. 9 | 10 | use std::fmt; 11 | use std::fs; 12 | use std::io; 13 | use std_prelude::*; 14 | 15 | use super::open::FileOpen; 16 | use super::{Error, PathAbs, PathInfo, Result}; 17 | 18 | /// A read/write file handle with `path()` attached and improved error messages. Contains methods 19 | /// and trait implements for both readable _and_ writeable files. 20 | /// 21 | /// # Examples 22 | /// ```rust 23 | /// # extern crate path_abs; 24 | /// # extern crate tempfile; 25 | /// use std::io::{Read, Seek, Write, SeekFrom}; 26 | /// use path_abs::FileEdit; 27 | /// 28 | /// # fn try_main() -> ::std::io::Result<()> { 29 | /// let example = "example.txt"; 30 | /// # let tmp = tempfile::TempDir::new()?; 31 | /// # let example = &tmp.path().join(example); 32 | /// 33 | /// let expected = "foo\nbar"; 34 | /// let mut edit = FileEdit::create(example)?; 35 | /// 36 | /// let mut s = String::new(); 37 | /// edit.write_all(expected.as_bytes())?; 38 | /// edit.seek(SeekFrom::Start(0))?; 39 | /// edit.read_to_string(&mut s)?; 40 | /// 41 | /// assert_eq!(expected, s); 42 | /// # Ok(()) } fn main() { try_main().unwrap() } 43 | /// ``` 44 | pub struct FileEdit(pub(crate) FileOpen); 45 | 46 | impl FileEdit { 47 | /// Open the file with the given `OpenOptions` but always sets `read` and `write` to true. 48 | pub fn open>(path: P, mut options: fs::OpenOptions) -> Result { 49 | options.write(true); 50 | options.read(true); 51 | Ok(FileEdit(FileOpen::open(path, options)?)) 52 | } 53 | 54 | /// Shortcut to open the file if the path is already absolute. 55 | pub(crate) fn open_abs>( 56 | path: P, 57 | mut options: fs::OpenOptions, 58 | ) -> Result { 59 | options.write(true); 60 | options.read(true); 61 | Ok(FileEdit(FileOpen::open_abs(path, options)?)) 62 | } 63 | 64 | /// Open the file in editing mode, truncating it first if it exists and creating it 65 | /// otherwise. 66 | pub fn create>(path: P) -> Result { 67 | let mut options = fs::OpenOptions::new(); 68 | options.truncate(true); 69 | options.create(true); 70 | FileEdit::open(path, options) 71 | } 72 | 73 | /// Open the file for appending, creating it if it doesn't exist. 74 | pub fn append>(path: P) -> Result { 75 | let mut options = fs::OpenOptions::new(); 76 | options.append(true); 77 | options.create(true); 78 | FileEdit::open(path, options) 79 | } 80 | 81 | /// Open the file for editing (reading and writing) but do not create it 82 | /// if it doesn't exist. 83 | pub fn edit>(path: P) -> Result { 84 | let mut options = fs::OpenOptions::new(); 85 | options.read(true); 86 | FileEdit::open(path, options) 87 | } 88 | 89 | /// Attempts to sync all OS-internal metadata to disk. 90 | /// 91 | /// This function will attempt to ensure that all in-core data reaches the filesystem before 92 | /// returning. 93 | /// 94 | /// This function is identical to [std::fs::File::sync_all][0] except it has error 95 | /// messages which include the action and the path. 96 | /// 97 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all 98 | pub fn sync_all(&self) -> Result<()> { 99 | self.0 100 | .file 101 | .sync_all() 102 | .map_err(|err| Error::new(err, "syncing", self.0.path.clone().into())) 103 | } 104 | 105 | /// This function is similar to sync_all, except that it may not synchronize file metadata to 106 | /// the filesystem. 107 | /// 108 | /// This function is identical to [std::fs::File::sync_data][0] except it has error 109 | /// messages which include the action and the path. 110 | /// 111 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data 112 | pub fn sync_data(&self) -> Result<()> { 113 | self.0 114 | .file 115 | .sync_data() 116 | .map_err(|err| Error::new(err, "syncing data for", self.0.path.clone().into())) 117 | } 118 | 119 | /// Truncates or extends the underlying file, updating the size of this file to become size. 120 | /// 121 | /// This function is identical to [std::fs::File::set_len][0] except: 122 | /// 123 | /// - It has error messages which include the action and the path. 124 | /// - It takes `&mut self` instead of `&self`. 125 | /// 126 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len 127 | pub fn set_len(&mut self, size: u64) -> Result<()> { 128 | self.0 129 | .file 130 | .set_len(size) 131 | .map_err(|err| Error::new(err, "setting len for", self.0.path.clone().into())) 132 | } 133 | 134 | /// Changes the permissions on the underlying file. 135 | /// 136 | /// This function is identical to [std::fs::File::set_permissions][0] except: 137 | /// 138 | /// - It has error messages which include the action and the path. 139 | /// - It takes `&mut self` instead of `&self`. 140 | /// 141 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_permissions 142 | pub fn set_permissions(&mut self, perm: fs::Permissions) -> Result<()> { 143 | self.0 144 | .file 145 | .set_permissions(perm) 146 | .map_err(|err| Error::new(err, "setting permisions for", self.0.path.clone().into())) 147 | } 148 | 149 | /// Read what remains of the file to a `String`. 150 | pub fn read_string(&mut self) -> Result { 151 | let mut s = String::new(); 152 | self.0 153 | .file 154 | .read_to_string(&mut s) 155 | .map_err(|err| Error::new(err, "reading", self.0.path.clone().into()))?; 156 | Ok(s) 157 | } 158 | 159 | /// Shortcut to `self.write_all(s.as_bytes())` with slightly 160 | /// improved error message. 161 | pub fn write_str(&mut self, s: &str) -> Result<()> { 162 | self.0 163 | .file 164 | .write_all(s.as_bytes()) 165 | .map_err(|err| Error::new(err, "writing", self.0.path.clone().into())) 166 | } 167 | 168 | /// `std::io::File::flush` buth with the new error type. 169 | pub fn flush(&mut self) -> Result<()> { 170 | self.0 171 | .file 172 | .flush() 173 | .map_err(|err| Error::new(err, "flushing", self.0.path.clone().into())) 174 | } 175 | } 176 | 177 | impl fmt::Debug for FileEdit { 178 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 179 | write!(f, "FileEdit(")?; 180 | self.0.path.fmt(f)?; 181 | write!(f, ")") 182 | } 183 | } 184 | 185 | impl io::Read for FileEdit { 186 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 187 | self.0.file.read(buf).map_err(|err| { 188 | io::Error::new( 189 | err.kind(), 190 | format!("{} when reading {}", err, self.0.path().display()), 191 | ) 192 | }) 193 | } 194 | } 195 | 196 | impl io::Write for FileEdit { 197 | fn write(&mut self, buf: &[u8]) -> io::Result { 198 | self.0.file.write(buf).map_err(|err| { 199 | io::Error::new( 200 | err.kind(), 201 | format!("{} when writing to {}", err, self.0.path().display()), 202 | ) 203 | }) 204 | } 205 | 206 | fn flush(&mut self) -> io::Result<()> { 207 | self.0.file.flush().map_err(|err| { 208 | io::Error::new( 209 | err.kind(), 210 | format!("{} when flushing {}", err, self.0.path().display()), 211 | ) 212 | }) 213 | } 214 | } 215 | 216 | impl io::Seek for FileEdit { 217 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 218 | self.0.file.seek(pos).map_err(|err| { 219 | io::Error::new( 220 | err.kind(), 221 | format!("{} seeking {}", err, self.0.path().display()), 222 | ) 223 | }) 224 | } 225 | } 226 | 227 | impl AsRef for FileEdit { 228 | fn as_ref(&self) -> &FileOpen { 229 | &self.0 230 | } 231 | } 232 | 233 | impl AsRef for FileEdit { 234 | fn as_ref(&self) -> &File { 235 | &self.0.as_ref() 236 | } 237 | } 238 | 239 | impl Borrow for FileEdit { 240 | fn borrow(&self) -> &FileOpen { 241 | &self.0 242 | } 243 | } 244 | 245 | impl Borrow for FileEdit { 246 | fn borrow(&self) -> &File { 247 | &self.0.borrow() 248 | } 249 | } 250 | 251 | impl<'a> Borrow for &'a FileEdit { 252 | fn borrow(&self) -> &FileOpen { 253 | &self.0 254 | } 255 | } 256 | 257 | impl<'a> Borrow for &'a FileEdit { 258 | fn borrow(&self) -> &File { 259 | &self.0.borrow() 260 | } 261 | } 262 | 263 | impl From for FileOpen { 264 | fn from(orig: FileEdit) -> FileOpen { 265 | orig.0 266 | } 267 | } 268 | 269 | impl From for File { 270 | fn from(orig: FileEdit) -> File { 271 | orig.0.into() 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | use std::ffi; 9 | use std::fmt; 10 | use std::fs; 11 | use std::io; 12 | use std_prelude::*; 13 | 14 | use super::{Error, Result}; 15 | use super::{FileEdit, FileRead, FileWrite, PathAbs, PathDir, PathInfo, PathOps}; 16 | 17 | #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 18 | /// a `PathAbs` that was a file at the time of initialization, with associated methods. 19 | pub struct PathFile(pub(crate) PathAbs); 20 | 21 | impl PathFile { 22 | /// Instantiate a new `PathFile`. The file must exist or `io::Error` will be returned. 23 | /// 24 | /// Returns `io::ErrorKind::InvalidInput` if the path exists but is not a file. 25 | /// 26 | /// # Examples 27 | /// ```rust 28 | /// # extern crate path_abs; 29 | /// use path_abs::PathFile; 30 | /// 31 | /// # fn try_main() -> ::std::io::Result<()> { 32 | /// let lib = PathFile::new("src/lib.rs")?; 33 | /// # Ok(()) } fn main() { try_main().unwrap() } 34 | /// ``` 35 | pub fn new>(path: P) -> Result { 36 | let abs = PathAbs::new(path)?; 37 | PathFile::try_from(abs) 38 | } 39 | 40 | /// Create a `PathFile` unchecked. 41 | /// 42 | /// This is mostly used for constructing during tests, or if the path was previously validated. 43 | /// This is effectively the same as a `Arc`. 44 | /// 45 | /// > Note: This is memory safe, so is not marked `unsafe`. However, it could cause 46 | /// > panics in some methods if the path was not properly validated. 47 | pub fn new_unchecked>>(path: P) -> PathFile { 48 | PathFile(PathAbs::new_unchecked(path)) 49 | } 50 | 51 | /// Convert a `PathAbs` into a `PathFile`, first validating that the path is a file. 52 | /// 53 | /// # Error 54 | /// If the path is not a file. 55 | /// 56 | /// # Examples 57 | /// ```rust 58 | /// # extern crate path_abs; 59 | /// use path_abs::{PathAbs, PathFile}; 60 | /// 61 | /// # fn try_main() -> ::std::io::Result<()> { 62 | /// let lib_abs = PathAbs::new("src/lib.rs")?; 63 | /// let lib_file = PathFile::try_from(lib_abs)?; 64 | /// # Ok(()) } fn main() { try_main().unwrap() } 65 | /// ``` 66 | pub fn try_from>(path: P) -> Result { 67 | let abs = path.into(); 68 | if abs.is_file() { 69 | Ok(PathFile::new_unchecked(abs)) 70 | } else { 71 | Err(Error::new( 72 | io::Error::new(io::ErrorKind::InvalidInput, "path is not a file"), 73 | "resolving", 74 | abs.into(), 75 | )) 76 | } 77 | } 78 | 79 | /// Get the parent directory of this file as a `PathDir`. 80 | /// 81 | /// > This does not make aditional syscalls, as the parent by definition must be a directory 82 | /// > and exist. 83 | /// 84 | /// # Panics 85 | /// Panics if there is no parent. The only way this could happen is if 86 | /// it was constructed with `new_unchecked` using a relative path. 87 | /// 88 | /// # Examples 89 | /// ```rust 90 | /// # extern crate path_abs; 91 | /// use path_abs::{PathDir, PathFile}; 92 | /// 93 | /// # fn try_main() -> ::std::io::Result<()> { 94 | /// let lib = PathFile::new("src/lib.rs")?; 95 | /// let src = lib.parent_dir(); 96 | /// assert_eq!(PathDir::new("src")?, src); 97 | /// # Ok(()) } fn main() { try_main().unwrap() } 98 | /// ``` 99 | pub fn parent_dir(&self) -> PathDir { 100 | let path = self.parent().expect("PathFile did not have a parent."); 101 | PathDir::new_unchecked(PathAbs::new_unchecked(path.to_path_buf())) 102 | } 103 | 104 | /// Instantiate a new `PathFile`, creating an empty file if it doesn't exist. 105 | /// 106 | /// # Examples 107 | /// ```rust 108 | /// # extern crate path_abs; 109 | /// # extern crate tempfile; 110 | /// use path_abs::PathFile; 111 | /// 112 | /// # fn try_main() -> ::std::io::Result<()> { 113 | /// let example = "example.txt"; 114 | /// # let tmp = tempfile::TempDir::new()?; 115 | /// # let example = &tmp.path().join(example); 116 | /// 117 | /// # let tmp = tempfile::TempDir::new()?; 118 | /// # let example = &tmp.path().join(example); 119 | /// 120 | /// let file = PathFile::create(example)?; 121 | /// 122 | /// // It can be done twice with no effect. 123 | /// let _ = PathFile::create(example)?; 124 | /// # Ok(()) } fn main() { try_main().unwrap() } 125 | /// ``` 126 | pub fn create>(path: P) -> Result { 127 | fs::OpenOptions::new() 128 | .write(true) 129 | .create(true) 130 | .open(&path) 131 | .map_err(|err| Error::new(err, "opening", path.as_ref().to_path_buf().into()))?; 132 | PathFile::new(path) 133 | } 134 | 135 | /// Read the entire contents of the file into a `String`. 136 | /// 137 | /// # Examples 138 | /// ```rust 139 | /// # extern crate path_abs; 140 | /// # extern crate tempfile; 141 | /// use path_abs::PathFile; 142 | /// 143 | /// # fn try_main() -> ::std::io::Result<()> { 144 | /// let example = "example.txt"; 145 | /// # let tmp = tempfile::TempDir::new()?; 146 | /// # let example = &tmp.path().join(example); 147 | /// let file = PathFile::create(example)?; 148 | /// 149 | /// let expected = "foo\nbar"; 150 | /// file.write_str(expected)?; 151 | /// assert_eq!(expected, file.read_string()?); 152 | /// # Ok(()) } fn main() { try_main().unwrap() } 153 | /// ``` 154 | pub fn read_string(&self) -> Result { 155 | let mut f = self.open_read()?; 156 | f.read_string() 157 | } 158 | 159 | /// Write the `str` to a file, truncating it first if it exists and creating it otherwise. 160 | /// 161 | /// # Examples 162 | /// ```rust 163 | /// # extern crate path_abs; 164 | /// # extern crate tempfile; 165 | /// use path_abs::PathFile; 166 | /// 167 | /// # fn try_main() -> ::std::io::Result<()> { 168 | /// let example = "example.txt"; 169 | /// # let tmp = tempfile::TempDir::new()?; 170 | /// # let example = &tmp.path().join(example); 171 | /// let file = PathFile::create(example)?; 172 | /// 173 | /// let expected = "foo\nbar"; 174 | /// file.write_str(expected)?; 175 | /// assert_eq!(expected, file.read_string()?); 176 | /// # Ok(()) } fn main() { try_main().unwrap() } 177 | /// ``` 178 | pub fn write_str(&self, s: &str) -> Result<()> { 179 | let mut options = fs::OpenOptions::new(); 180 | options.create(true); 181 | options.truncate(true); 182 | let mut f = FileWrite::open_abs(self.clone(), options)?; 183 | if s.is_empty() { 184 | return Ok(()); 185 | } 186 | f.write_str(s)?; 187 | f.flush() 188 | } 189 | 190 | /// Append the `str` to a file, creating it if it doesn't exist. 191 | /// 192 | /// # Examples 193 | /// ```rust 194 | /// # extern crate path_abs; 195 | /// # extern crate tempfile; 196 | /// use path_abs::PathFile; 197 | /// 198 | /// # fn try_main() -> ::std::io::Result<()> { 199 | /// let example = "example.txt"; 200 | /// # let tmp = tempfile::TempDir::new()?; 201 | /// # let example = &tmp.path().join(example); 202 | /// let file = PathFile::create(example)?; 203 | /// 204 | /// let expected = "foo\nbar\nbaz"; 205 | /// file.append_str("foo\nbar")?; 206 | /// file.append_str("\nbaz")?; 207 | /// assert_eq!(expected, file.read_string()?); 208 | /// # Ok(()) } fn main() { try_main().unwrap() } 209 | /// ``` 210 | pub fn append_str(&self, s: &str) -> Result<()> { 211 | let mut f = self.open_append()?; 212 | if s.is_empty() { 213 | return Ok(()); 214 | } 215 | f.write_str(s)?; 216 | f.flush() 217 | } 218 | 219 | /// Open the file as read-only. 220 | /// 221 | /// # Examples 222 | /// ```rust 223 | /// # extern crate path_abs; 224 | /// # extern crate tempfile; 225 | /// use std::io::Read; 226 | /// use path_abs::PathFile; 227 | /// 228 | /// # fn try_main() -> ::std::io::Result<()> { 229 | /// let example = "example.txt"; 230 | /// # let tmp = tempfile::TempDir::new()?; 231 | /// # let example = &tmp.path().join(example); 232 | /// let file = PathFile::create(example)?; 233 | /// 234 | /// let expected = "foo\nbar"; 235 | /// file.write_str(expected)?; 236 | /// 237 | /// let mut read = file.open_read()?; 238 | /// let mut s = String::new(); 239 | /// read.read_to_string(&mut s)?; 240 | /// assert_eq!(expected, s); 241 | /// # Ok(()) } fn main() { try_main().unwrap() } 242 | /// ``` 243 | pub fn open_read(&self) -> Result { 244 | FileRead::open_abs(self.clone()) 245 | } 246 | 247 | /// Open the file as write-only in append mode. 248 | /// 249 | /// # Examples 250 | /// ```rust 251 | /// # extern crate path_abs; 252 | /// # extern crate tempfile; 253 | /// use std::io::Write; 254 | /// use path_abs::PathFile; 255 | /// 256 | /// # fn try_main() -> ::std::io::Result<()> { 257 | /// let example = "example.txt"; 258 | /// # let tmp = tempfile::TempDir::new()?; 259 | /// # let example = &tmp.path().join(example); 260 | /// let file = PathFile::create(example)?; 261 | /// 262 | /// let expected = "foo\nbar\n"; 263 | /// file.write_str("foo\n")?; 264 | /// 265 | /// let mut append = file.open_append()?; 266 | /// append.write_all(b"bar\n")?; 267 | /// append.flush(); 268 | /// assert_eq!(expected, file.read_string()?); 269 | /// # Ok(()) } fn main() { try_main().unwrap() } 270 | /// ``` 271 | pub fn open_append(&self) -> Result { 272 | let mut options = fs::OpenOptions::new(); 273 | options.append(true); 274 | FileWrite::open_abs(self.clone(), options) 275 | } 276 | 277 | /// Open the file for editing (reading and writing). 278 | /// 279 | /// # Examples 280 | /// ```rust 281 | /// # extern crate path_abs; 282 | /// # extern crate tempfile; 283 | /// use std::io::{Read, Seek, Write, SeekFrom}; 284 | /// use path_abs::PathFile; 285 | /// 286 | /// # fn try_main() -> ::std::io::Result<()> { 287 | /// let example = "example.txt"; 288 | /// # let tmp = tempfile::TempDir::new()?; 289 | /// # let example = &tmp.path().join(example); 290 | /// let file = PathFile::create(example)?; 291 | /// 292 | /// let expected = "foo\nbar"; 293 | /// 294 | /// let mut edit = file.open_edit()?; 295 | /// let mut s = String::new(); 296 | /// 297 | /// edit.write_all(expected.as_bytes())?; 298 | /// edit.seek(SeekFrom::Start(0))?; 299 | /// edit.read_to_string(&mut s)?; 300 | /// assert_eq!(expected, s); 301 | /// # Ok(()) } fn main() { try_main().unwrap() } 302 | /// ``` 303 | pub fn open_edit(&self) -> Result { 304 | FileEdit::open_abs(self.clone(), fs::OpenOptions::new()) 305 | } 306 | 307 | /// Copy the file to another location, including permission bits 308 | /// 309 | /// # Examples 310 | /// 311 | /// ```rust 312 | /// # extern crate path_abs; 313 | /// # extern crate tempfile; 314 | /// use path_abs::PathFile; 315 | /// use std::path::Path; 316 | /// 317 | /// # fn try_main() -> ::std::io::Result<()> { 318 | /// let example = "example.txt"; 319 | /// let example_bk = "example.txt.bk"; 320 | /// # let tmp = tempfile::TempDir::new()?; 321 | /// # let example = &tmp.path().join(example); 322 | /// # let example_bk = &tmp.path().join(example_bk); 323 | /// let file = PathFile::create(example)?; 324 | /// 325 | /// let contents = "This is some contents"; 326 | /// file.write_str(contents); 327 | /// let file_bk = file.copy(example_bk)?; 328 | /// assert_eq!(contents, file.read_string()?); 329 | /// assert_eq!(contents, file_bk.read_string()?); 330 | /// # Ok(()) } fn main() { try_main().unwrap() } 331 | /// ``` 332 | pub fn copy>(&self, path: P) -> Result { 333 | fs::copy(&self, &path).map_err(|err| { 334 | Error::new( 335 | err, 336 | &format!("copying {} from", path.as_ref().display()), 337 | self.clone().into(), 338 | ) 339 | })?; 340 | Ok(PathFile::new(path)?) 341 | } 342 | 343 | /// Rename a file, replacing the original file if `to` already exists. 344 | /// 345 | /// This will not work if the new name is on a different mount point. 346 | /// 347 | /// # Examples 348 | /// ```rust 349 | /// # extern crate path_abs; 350 | /// # extern crate tempfile; 351 | /// use path_abs::{PathFile, PathInfo}; 352 | /// use std::path::Path; 353 | /// 354 | /// # fn try_main() -> ::std::io::Result<()> { 355 | /// let example = "example.txt"; 356 | /// let example_bk = "example.txt.bk"; 357 | /// # let tmp = tempfile::TempDir::new()?; 358 | /// # let example = &tmp.path().join(example); 359 | /// # let example_bk = &tmp.path().join(example_bk); 360 | /// let file = PathFile::create(example)?; 361 | /// 362 | /// let contents = "This is some contents"; 363 | /// file.write_str(contents); 364 | /// let file_bk = file.clone().rename(example_bk)?; 365 | /// assert!(!file.exists()); 366 | /// assert_eq!(contents, file_bk.read_string()?); 367 | /// # Ok(()) } fn main() { try_main().unwrap() } 368 | /// ``` 369 | pub fn rename>(self, to: P) -> Result { 370 | fs::rename(&self, &to).map_err(|err| { 371 | Error::new( 372 | err, 373 | &format!("renaming to {} from", to.as_ref().display()), 374 | self.clone().into(), 375 | ) 376 | })?; 377 | Ok(PathFile::new(to)?) 378 | } 379 | 380 | /// Creates a new symbolic link on the filesystem to the dst. 381 | /// 382 | /// This handles platform specific behavior correctly. 383 | /// 384 | /// # Examples 385 | /// 386 | /// ```rust 387 | /// # extern crate path_abs; 388 | /// # extern crate tempfile; 389 | /// use path_abs::PathFile; 390 | /// use std::path::Path; 391 | /// 392 | /// # fn try_main() -> ::std::io::Result<()> { 393 | /// let example = "example.txt"; 394 | /// let example_sym = "example.txt.sym"; 395 | /// # let tmp = tempfile::TempDir::new()?; 396 | /// # let example = &tmp.path().join(example); 397 | /// # let example_sym = &tmp.path().join(example_sym); 398 | /// let file = PathFile::create(example)?; 399 | /// 400 | /// let contents = "This is some contents"; 401 | /// file.write_str(contents); 402 | /// let file_sym = file.symlink(example_sym)?; 403 | /// 404 | /// // They have a different "absolute path" 405 | /// assert_ne!(file, file_sym); 406 | /// 407 | /// // But they can be canonicalized to the same file. 408 | /// let file_can = file_sym.canonicalize()?; 409 | /// assert_eq!(file, file_can); 410 | /// # Ok(()) } fn main() { try_main().unwrap() } 411 | /// ``` 412 | pub fn symlink>(&self, dst: P) -> Result { 413 | symlink_file(&self, &dst).map_err(|err| { 414 | Error::new( 415 | err, 416 | &format!("linking from {} to", dst.as_ref().display()), 417 | self.clone().into(), 418 | ) 419 | })?; 420 | PathFile::new(dst) 421 | } 422 | 423 | /// Remove (delete) the file from the filesystem, consuming self. 424 | /// 425 | /// # Examples 426 | /// 427 | /// ```rust 428 | /// # extern crate path_abs; 429 | /// # extern crate tempfile; 430 | /// use path_abs::{PathFile, PathInfo}; 431 | /// use std::path::Path; 432 | /// 433 | /// # fn try_main() -> ::std::io::Result<()> { 434 | /// let example = "example.txt"; 435 | /// # let tmp = tempfile::TempDir::new()?; 436 | /// # let example = &tmp.path().join(example); 437 | /// let file = PathFile::create(example)?; 438 | /// assert!(file.exists()); 439 | /// file.remove()?; 440 | /// 441 | /// // file.exists() <--- COMPILER ERROR, `file` was consumed 442 | /// 443 | /// assert!(!Path::new(example).exists()); 444 | /// # Ok(()) } fn main() { try_main().unwrap() } 445 | /// ``` 446 | pub fn remove(self) -> Result<()> { 447 | fs::remove_file(&self).map_err(|err| Error::new(err, "removing", self.into())) 448 | } 449 | 450 | /// Return a reference to a basic `std::path::Path` 451 | pub fn as_path(&self) -> &Path { 452 | self.as_ref() 453 | } 454 | 455 | /// Returns the canonical form of the path with all intermediate components normalized and 456 | /// symbolic links resolved. 457 | /// 458 | /// See [`PathAbs::canonicalize`] 459 | /// 460 | /// [`PathAbs::canonicalize`]: struct.PathAbs.html#method.canonicalize 461 | pub fn canonicalize(&self) -> Result { 462 | Ok(PathFile(self.0.canonicalize()?)) 463 | } 464 | } 465 | 466 | impl fmt::Debug for PathFile { 467 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 468 | self.0.fmt(f) 469 | } 470 | } 471 | 472 | impl AsRef for PathFile { 473 | fn as_ref(&self) -> &std::ffi::OsStr { 474 | self.0.as_ref() 475 | } 476 | } 477 | 478 | impl AsRef for PathFile { 479 | fn as_ref(&self) -> &PathAbs { 480 | &self.0 481 | } 482 | } 483 | 484 | impl AsRef for PathFile { 485 | fn as_ref(&self) -> &Path { 486 | self.0.as_ref() 487 | } 488 | } 489 | 490 | impl AsRef for PathFile { 491 | fn as_ref(&self) -> &PathBuf { 492 | self.0.as_ref() 493 | } 494 | } 495 | 496 | impl Borrow for PathFile { 497 | fn borrow(&self) -> &PathAbs { 498 | self.as_ref() 499 | } 500 | } 501 | 502 | impl Borrow for PathFile { 503 | fn borrow(&self) -> &Path { 504 | self.as_ref() 505 | } 506 | } 507 | 508 | impl Borrow for PathFile { 509 | fn borrow(&self) -> &PathBuf { 510 | self.as_ref() 511 | } 512 | } 513 | 514 | impl<'a> Borrow for &'a PathFile { 515 | fn borrow(&self) -> &PathAbs { 516 | self.as_ref() 517 | } 518 | } 519 | 520 | impl<'a> Borrow for &'a PathFile { 521 | fn borrow(&self) -> &Path { 522 | self.as_ref() 523 | } 524 | } 525 | 526 | impl<'a> Borrow for &'a PathFile { 527 | fn borrow(&self) -> &PathBuf { 528 | self.as_ref() 529 | } 530 | } 531 | 532 | impl From for PathAbs { 533 | fn from(path: PathFile) -> PathAbs { 534 | path.0 535 | } 536 | } 537 | 538 | impl From for Arc { 539 | fn from(path: PathFile) -> Arc { 540 | let abs: PathAbs = path.into(); 541 | abs.into() 542 | } 543 | } 544 | 545 | impl From for PathBuf { 546 | fn from(path: PathFile) -> PathBuf { 547 | let abs: PathAbs = path.into(); 548 | abs.into() 549 | } 550 | } 551 | 552 | impl PathOps for PathFile { 553 | type Output = PathAbs; 554 | 555 | fn concat>(&self, path: P) -> Result { 556 | Ok(self.0.concat(path)?) 557 | } 558 | 559 | fn join>(&self, path: P) -> Self::Output { 560 | let buf = Path::join(self.as_path(), path); 561 | Self::Output::new_unchecked(buf) 562 | } 563 | 564 | fn with_file_name>(&self, file_name: S) -> Self::Output { 565 | self.0.with_file_name(file_name) 566 | } 567 | 568 | fn with_extension>(&self, extension: S) -> Self::Output { 569 | self.0.with_extension(extension) 570 | } 571 | } 572 | 573 | #[cfg(target_os = "wasi")] 574 | fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 575 | std::os::wasi::fs::symlink_path(src, dst) 576 | } 577 | 578 | #[cfg(unix)] 579 | fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 580 | ::std::os::unix::fs::symlink(src, dst) 581 | } 582 | 583 | #[cfg(windows)] 584 | fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { 585 | ::std::os::windows::fs::symlink_file(src, dst) 586 | } 587 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Ergonomic paths and files in rust. 9 | //! 10 | //! This library aims to provide ergonomic path and file operations to rust with reasonable 11 | //! performance. 12 | //! 13 | //! This includes: 14 | //! 15 | //! - Improved methods for the `std` path types using [`PathInfo`] [`PathMut`] and [`PathOps`] 16 | //! - Cleaner _absolute_ paths (which is distinct from canonicalized paths). 17 | //! - Improved error messages, see the [Better Errors](#better-errors) section. 18 | //! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a 19 | //! certain type. Obviously a file/dir can be deleted/changed by another process. 20 | //! - More stringent mutability requirements. See the 21 | //! [Differing Method Signatures](#differing-method-signatures) section. 22 | //! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem 23 | //! operations and allows more flexibility and ergonomics in the library for relatively low cost. 24 | //! 25 | //! ## Better Errors 26 | //! 27 | //! All errors include the **path** and **action** which caused the error, as well as the unaltered 28 | //! `std::io::Error` message. Errors are convertable into `std::io::Error`, giving almost complete 29 | //! compatibility with existing code. 30 | //! 31 | //! ### `set_len` (i.e. truncate a file): 32 | //! 33 | //! - [`/* */ std::fs::File::set_len(0)`][file_set_len]: `Invalid argument (os error 22)` 34 | //! - [`path_abs::FileWrite::set_len(0)`][path_set_len]: `Invalid argument (os error 22) when setting 35 | //! len for /path/to/example/foo.txt` 36 | //! 37 | //! > The above error is actually impossible because `FileWrite` is always writeable, and 38 | //! > `FileRead` does not implement `set_len`. However, it is kept for demonstration. 39 | //! 40 | //! ### `open_read` (open file for reading): 41 | //! 42 | //! - [`/**/ std::fs::File::read(path)`][file_read]: `No such file or directory (os error 2)` 43 | //! - [`path_abs::FileRead::open(path)`][path_read]: `No such file or directory (os error 2) when 44 | //! opening example/foo.txt` 45 | //! 46 | //! And every other method has similarily improved errors. If a method does not have pretty error 47 | //! messages please open a ticket. 48 | //! 49 | //! [file_set_len]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len 50 | //! [file_read]: https://doc.rust-lang.org/std/fs/struct.File.html#method.read 51 | //! [path_set_len]: struct.FileWrite.html#method.set_len 52 | //! [path_read]: struct.FileRead.html#method.open 53 | //! 54 | //! 55 | //! ## Exported Path Types 56 | //! 57 | //! These are the exported Path types. All of them are absolute. 58 | //! 59 | //! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_ 60 | //! canonicalized) path that is not necessarily guaranteed to exist. 61 | //! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to 62 | //! exist and be a file, with associated methods. 63 | //! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist 64 | //! and be a directory, with associated methods. 65 | //! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir. 66 | //! Returned by [`PathDir::list`][dir_list] 67 | //! 68 | //! In addition, all paths are serializable through serde (even on windows!) by using the crate 69 | //! [`stfu8`](https://crates.io/crates/stfu8) to encode/decode, allowing ill-formed UTF-16. See 70 | //! that crate for more details on how the resulting encoding can be edited (by hand) even in the 71 | //! case of what *would be* ill-formed UTF-16. 72 | //! 73 | //! [dir_list]: struct.PathDir.html#method.list 74 | //! 75 | //! 76 | //! ## Exported File Types 77 | //! 78 | //! All File types provide _type safe_ access to their relevant traits. For instance, you can't 79 | //! `read` with a `FileWrite` and you can't `write` with a `FileRead`. 80 | //! 81 | //! - [`FileRead`](struct.FileRead.html): a read-only file handle with `path()` attached and 82 | //! improved error messages. Contains only the methods and trait implementations which are 83 | //! allowed by a read-only file. 84 | //! - [`FileWrite`](struct.FileWrite.html): a write-only file handle with `path()` attached and 85 | //! improved error messages. Contains only the methods and trait implementations which are 86 | //! allowed by a write-only file. 87 | //! - [`FileEdit`](struct.FileEdit.html): a read/write file handle with `path()` attached and 88 | //! improved error messages. Contains methods and trait implements for both readable _and_ 89 | //! writeable files. 90 | //! 91 | //! ### Differing Method Signatures 92 | //! 93 | //! The type signatures of the `File*` types regarding `read`, `write` and other methods is 94 | //! slightly different than `std::fs::File` -- they all take `&mut` instead of `&`. This is to 95 | //! avoid a [common possible footgun](https://github.com/rust-lang/rust/issues/47708). 96 | //! 97 | //! To demonstrate, imagine the following scenario: 98 | //! 99 | //! - You pass your open `&File` to a method, which puts it in a thread. This thread constantly 100 | //! calls `seek(SeekFrom::Start(10))` 101 | //! - You periodically read from a file expecting new data, but are always getting the same data. 102 | //! 103 | //! Yes, this is actually allowed by the rust compiler since `seek` is implemented for 104 | //! [`&File`](https://doc.rust-lang.org/std/fs/struct.File.html#impl-Seek-1). Technically this is 105 | //! still _memory safe_ since the operating system will handle any contention, however many would 106 | //! argue that it isn't _expected_ that an immutable reference passed to another 107 | //! function can affect the seek position of a file. 108 | //! 109 | //! 110 | //! # Examples 111 | //! Recreating `Cargo.init` in `example/` 112 | //! 113 | //! ```rust 114 | //! # extern crate path_abs; 115 | //! # extern crate tempfile; 116 | //! use std::path::Path; 117 | //! use std::collections::HashSet; 118 | //! use path_abs::{ 119 | //! PathAbs, // absolute path 120 | //! PathDir, // absolute path to a directory 121 | //! PathFile, // absolute path to a file 122 | //! PathType, // enum of Dir or File 123 | //! PathInfo, // trait for query methods 124 | //! PathOps, // trait for methods that make new paths 125 | //! FileRead, // Open read-only file handler 126 | //! FileWrite, // Open write-only file handler 127 | //! FileEdit, // Open read/write file handler 128 | //! }; 129 | //! 130 | //! # fn try_main() -> ::std::io::Result<()> { 131 | //! let example = Path::new("example"); 132 | //! # let tmp = tempfile::TempDir::new()?; 133 | //! # let example = &tmp.path().join(example); 134 | //! 135 | //! // Create your paths 136 | //! let project = PathDir::create_all(example)?; 137 | //! let src = PathDir::create(project.concat("src")?)?; 138 | //! let lib = PathFile::create(src.concat("lib.rs")?)?; 139 | //! let cargo = PathFile::create(project.concat("Cargo.toml")?)?; 140 | //! 141 | //! // Write the templates 142 | //! lib.write_str(r#" 143 | //! #[cfg(test)] 144 | //! mod tests { 145 | //! #[test] 146 | //! fn it_works() { 147 | //! assert_eq!(2 + 2, 4); 148 | //! } 149 | //! }"#)?; 150 | //! 151 | //! cargo.write_str(r#" 152 | //! [package] 153 | //! name = "example" 154 | //! version = "0.1.0" 155 | //! authors = ["Garrett Berg "] 156 | //! 157 | //! [dependencies] 158 | //! "#)?; 159 | //! 160 | //! // Put our result into a HashMap so we can assert it 161 | //! let mut result = HashSet::new(); 162 | //! for p in project.list()? { 163 | //! result.insert(p?); 164 | //! } 165 | //! 166 | //! // Create our expected value 167 | //! let mut expected = HashSet::new(); 168 | //! expected.insert(PathType::Dir(src)); 169 | //! expected.insert(PathType::File(cargo)); 170 | //! 171 | //! assert_eq!(expected, result); 172 | //! 173 | //! // ---------------------------------- 174 | //! // Creating types from existing paths 175 | //! 176 | //! // Creating a generic path 177 | //! let lib_path = example.join("src").join("lib.rs"); 178 | //! let abs = PathAbs::new(&lib_path)?; 179 | //! 180 | //! // Or a path with a known type 181 | //! let file = PathType::new(&lib_path) 182 | //! ? 183 | //! .unwrap_file(); 184 | //! 185 | //! assert!(abs.is_file()); 186 | //! assert!(file.is_file()); 187 | //! 188 | //! // ---------------------------------- 189 | //! // Opening a File 190 | //! 191 | //! // open read-only using the PathFile method 192 | //! let read = file.open_read()?; 193 | //! 194 | //! // Or use the type directly: open for appending 195 | //! let write = FileWrite::open_append(&file)?; 196 | //! 197 | //! // Open for read/write editing. 198 | //! let edit = file.open_edit()?; 199 | //! # Ok(()) } fn main() { try_main().unwrap() } 200 | //! ``` 201 | //! 202 | //! [`PathInfo`]: trait.PathInfo.html 203 | //! [`PathOps`]: trait.PathOps.html 204 | //! [`PathMut`]: trait.PathMut.html 205 | 206 | #![cfg_attr(target_os = "wasi", 207 | feature(wasi_ext))] 208 | 209 | #[cfg(feature = "serialize")] 210 | extern crate serde; 211 | #[macro_use] 212 | #[cfg(feature = "serialize")] 213 | extern crate serde_derive; 214 | 215 | #[cfg(feature = "serialize")] 216 | extern crate stfu8; 217 | 218 | #[macro_use] 219 | #[cfg(test)] 220 | extern crate pretty_assertions; 221 | #[cfg(test)] 222 | extern crate regex; 223 | #[cfg(test)] 224 | extern crate serde_json; 225 | #[cfg(test)] 226 | extern crate tempfile; 227 | 228 | use std::error; 229 | use std::ffi; 230 | use std::fmt; 231 | use std::fs; 232 | use std::io; 233 | use std::path::{self, Component, Components}; 234 | use std_prelude::*; 235 | 236 | mod abs; 237 | mod dir; 238 | mod edit; 239 | mod file; 240 | pub mod open; 241 | mod read; 242 | #[cfg(feature = "serialize")] 243 | pub mod ser; 244 | mod ty; 245 | mod write; 246 | 247 | pub use crate::abs::PathAbs; 248 | pub use crate::dir::{ListDir, PathDir}; 249 | pub use crate::file::PathFile; 250 | #[cfg(feature = "serialize")] 251 | pub use crate::ser::PathSer; 252 | pub use crate::ty::PathType; 253 | 254 | pub use crate::edit::FileEdit; 255 | pub use crate::read::FileRead; 256 | pub use crate::write::FileWrite; 257 | 258 | pub type Result = ::std::result::Result; 259 | 260 | /// An error produced by performing an filesystem operation on a `Path`. 261 | /// 262 | /// This error type is a light wrapper around [`std::io::Error`]. In particular, it adds the 263 | /// following information: 264 | /// 265 | /// - The action being performed when the error occured 266 | /// - The path associated with the IO error. 267 | /// 268 | /// To maintain good ergonomics, this type has a `impl From for std::io::Error` defined so 269 | /// that you may use an [`io::Result`] with methods in this crate if you don't care about accessing 270 | /// the underlying error data in a structured form (the pretty format will be preserved however). 271 | /// 272 | /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 273 | /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html 274 | /// 275 | /// # Examples 276 | /// ```rust 277 | /// use path_abs::Error as PathError; 278 | /// use path_abs::PathFile; 279 | /// 280 | /// /// main function, note that you can use `io::Error` 281 | /// fn try_main() -> Result<(), ::std::io::Error> { 282 | /// let lib = PathFile::new("src/lib.rs")?; 283 | /// Ok(()) 284 | /// } 285 | /// 286 | /// ``` 287 | pub struct Error { 288 | io_err: io::Error, 289 | action: String, 290 | path: Arc, 291 | } 292 | 293 | impl Error { 294 | /// Create a new error when the path and action are known. 295 | pub fn new(io_err: io::Error, action: &str, path: Arc) -> Error { 296 | Error { 297 | io_err, 298 | action: action.into(), 299 | path, 300 | } 301 | } 302 | } 303 | 304 | impl fmt::Debug for Error { 305 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 306 | write!(f, "Error<{}>", self) 307 | } 308 | } 309 | 310 | impl fmt::Display for Error { 311 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 312 | write!( 313 | f, 314 | "{} when {} {}", 315 | self.io_err, 316 | self.action, 317 | self.path.display() 318 | ) 319 | } 320 | } 321 | 322 | impl Error { 323 | /// Returns the path associated with this error. 324 | pub fn path(&self) -> &Path { 325 | self.path.as_ref() 326 | } 327 | 328 | /// Returns the `std::io::Error` associated with this errors. 329 | pub fn io_error(&self) -> &io::Error { 330 | &self.io_err 331 | } 332 | 333 | /// Returns the action being performed when this error occured. 334 | pub fn action(&self) -> &str { 335 | &self.action 336 | } 337 | } 338 | 339 | impl error::Error for Error { 340 | fn description(&self) -> &str { 341 | self.io_err.description() 342 | } 343 | 344 | fn cause(&self) -> Option<&dyn error::Error> { 345 | Some(&self.io_err) 346 | } 347 | } 348 | 349 | impl From for io::Error { 350 | fn from(err: Error) -> io::Error { 351 | io::Error::new(err.io_err.kind(), err) 352 | } 353 | } 354 | 355 | /// Methods that return information about a path. 356 | /// 357 | /// This trait provides the familiar methods from `std::path::Path` 358 | /// for the `Path*` types. These methods take the same parameters and return 359 | /// the same types as the originals in the standard library, except where 360 | /// noted. 361 | /// 362 | /// As a general rule, methods that can return an error will return a rich 363 | /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will 364 | /// automatically convert into a `std::io::Error` with `?` if needed). 365 | /// 366 | /// [`path_abs::Error`]: struct.Error.html 367 | /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 368 | pub trait PathInfo { 369 | fn as_path(&self) -> &Path; 370 | 371 | fn to_arc_pathbuf(&self) -> Arc; 372 | 373 | fn as_os_str(&self) -> &ffi::OsStr { 374 | Path::as_os_str(self.as_path()) 375 | } 376 | 377 | fn to_str(&self) -> Option<&str> { 378 | Path::to_str(self.as_path()) 379 | } 380 | 381 | fn to_string_lossy(&self) -> Cow<'_, str> { 382 | Path::to_string_lossy(self.as_path()) 383 | } 384 | 385 | fn is_absolute(&self) -> bool { 386 | Path::is_absolute(self.as_path()) 387 | } 388 | 389 | fn is_relative(&self) -> bool { 390 | Path::is_relative(self.as_path()) 391 | } 392 | 393 | fn has_root(&self) -> bool { 394 | Path::has_root(self.as_path()) 395 | } 396 | 397 | fn ancestors(&self) -> path::Ancestors<'_> { 398 | Path::ancestors(self.as_path()) 399 | } 400 | 401 | fn file_name(&self) -> Option<&ffi::OsStr> { 402 | Path::file_name(self.as_path()) 403 | } 404 | 405 | fn strip_prefix

(&self, base: P) -> std::result::Result<&Path, path::StripPrefixError> 406 | where 407 | P: AsRef, 408 | { 409 | Path::strip_prefix(self.as_path(), base) 410 | } 411 | 412 | fn starts_with>(&self, base: P) -> bool { 413 | Path::starts_with(self.as_path(), base) 414 | } 415 | 416 | fn ends_with>(&self, base: P) -> bool { 417 | Path::ends_with(self.as_path(), base) 418 | } 419 | 420 | fn file_stem(&self) -> Option<&ffi::OsStr> { 421 | Path::file_stem(self.as_path()) 422 | } 423 | 424 | fn extension(&self) -> Option<&ffi::OsStr> { 425 | Path::extension(self.as_path()) 426 | } 427 | 428 | fn components(&self) -> Components<'_> { 429 | Path::components(self.as_path()) 430 | } 431 | 432 | fn iter(&self) -> path::Iter<'_> { 433 | Path::iter(self.as_path()) 434 | } 435 | 436 | fn display(&self) -> path::Display<'_> { 437 | Path::display(self.as_path()) 438 | } 439 | 440 | /// Queries the file system to get information about a file, directory, etc. 441 | /// 442 | /// The same as [`std::path::Path::metadata()`], except that it returns a 443 | /// rich [`path_abs::Error`] when a problem is encountered. 444 | /// 445 | /// [`path_abs::Error`]: struct.Error.html 446 | /// [`std::path::Path::metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.metadata 447 | fn metadata(&self) -> Result { 448 | Path::metadata(self.as_path()) 449 | .map_err(|err| Error::new(err, "getting metadata of", self.to_arc_pathbuf())) 450 | } 451 | 452 | /// Queries the metadata about a file without following symlinks. 453 | /// 454 | /// The same as [`std::path::Path::symlink_metadata()`], except that it 455 | /// returns a rich [`path_abs::Error`] when a problem is encountered. 456 | /// 457 | /// [`path_abs::Error`]: struct.Error.html 458 | /// [`std::path::Path::symlink_metadata()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.symlink_metadata 459 | fn symlink_metadata(&self) -> Result { 460 | Path::symlink_metadata(self.as_path()) 461 | .map_err(|err| Error::new(err, "getting symlink metadata of", self.to_arc_pathbuf())) 462 | } 463 | 464 | fn exists(&self) -> bool { 465 | Path::exists(self.as_path()) 466 | } 467 | 468 | fn is_file(&self) -> bool { 469 | Path::is_file(self.as_path()) 470 | } 471 | 472 | fn is_dir(&self) -> bool { 473 | Path::is_dir(self.as_path()) 474 | } 475 | 476 | /// Reads a symbolic link, returning the path that the link points to. 477 | /// 478 | /// The same as [`std::path::Path::read_link()`], except that it returns a 479 | /// rich [`path_abs::Error`] when a problem is encountered. 480 | /// 481 | /// [`path_abs::Error`]: struct.Error.html 482 | /// [`std::path::Pathdoc.rust-lang.org/stable/std/path/struct.Path.html#method.read_link 483 | fn read_link(&self) -> Result { 484 | Path::read_link(self.as_path()) 485 | .map_err(|err| Error::new(err, "reading link target of", self.to_arc_pathbuf())) 486 | } 487 | 488 | /// Returns the canonical, absolute form of the path with all intermediate 489 | /// components normalized and symbolic links resolved. 490 | /// 491 | /// The same as [`std::path::Path::canonicalize()`], 492 | /// - On success, returns a `path_abs::PathAbs` instead of a `PathBuf` 493 | /// - returns a rich [`path_abs::Error`] when a problem is encountered 494 | /// 495 | /// [`path_abs::Error`]: struct.Error.html 496 | /// [`std::path::Path::canonicalize()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.canonicalize 497 | fn canonicalize(&self) -> Result { 498 | Path::canonicalize(self.as_path()) 499 | .map(|path| PathAbs(path.into())) 500 | .map_err(|err| Error::new(err, "canonicalizing", self.to_arc_pathbuf())) 501 | } 502 | 503 | /// Returns the path without its final component, if there is one. 504 | /// 505 | /// The same as [`std::path::Path::parent()`], except that it returns a 506 | /// `Result` with a rich [`path_abs::Error`] when a problem is encountered. 507 | /// 508 | /// [`path_abs::Error`]: struct.Error.html 509 | /// [`std::path::Path::parent()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.parent 510 | fn parent(&self) -> Result<&Path> { 511 | let parent_path = Path::parent(self.as_path()); 512 | if let Some(p) = parent_path { 513 | Ok(p) 514 | } else { 515 | Err(Error::new( 516 | io::Error::new(io::ErrorKind::NotFound, "path has no parent"), 517 | "truncating to parent", 518 | self.to_arc_pathbuf(), 519 | )) 520 | } 521 | } 522 | } 523 | 524 | // TODO: I would like to be able to do this. 525 | // impl PathInfo for T 526 | // where 527 | // T: AsRef 528 | // { 529 | // fn as_path(&self) -> &Path { 530 | // PathBuf::as_path(self.borrow()) 531 | // } 532 | // fn to_arc_pathbuf(&self) -> Arc { 533 | // self.clone().into() 534 | // } 535 | // } 536 | 537 | impl PathInfo for T 538 | where 539 | T: Clone + Borrow + Into>, 540 | { 541 | fn as_path(&self) -> &Path { 542 | PathBuf::as_path(self.borrow()) 543 | } 544 | fn to_arc_pathbuf(&self) -> Arc { 545 | self.clone().into() 546 | } 547 | } 548 | 549 | impl PathInfo for Path { 550 | fn as_path(&self) -> &Path { 551 | &self 552 | } 553 | fn to_arc_pathbuf(&self) -> Arc { 554 | self.to_path_buf().into() 555 | } 556 | } 557 | 558 | /// Methods that modify a path. 559 | /// 560 | /// These methods are not implemented for all `path_abs` types because they 561 | /// may break the type's invariant. For example, if you could call 562 | /// `pop_up()` on a `PathFile`, it would no longer be the path to 563 | /// a file, but the path to a directory. 564 | /// 565 | /// As a general rule, methods that can return an error will return a rich 566 | /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will 567 | /// automatically convert into a `std::io::Error` with `?` if needed). 568 | pub trait PathMut: PathInfo { 569 | /// Appends `path` to this path. 570 | /// 571 | /// Note that this method represents pure concatenation, not "adjoinment" 572 | /// like [`PathBuf::push`], so absolute paths won't wholly replace the 573 | /// current path. 574 | /// 575 | /// `..` components are resolved using [`pop_up`], which can consume components 576 | /// on `self` 577 | /// 578 | /// # Errors 579 | /// 580 | /// This method returns an error if the result would try to go outside a filesystem root, 581 | /// like `/` on Unix or `C:\` on Windows. 582 | /// 583 | /// # Example 584 | /// 585 | /// ```rust 586 | /// use std::path::PathBuf; 587 | /// use path_abs::PathMut; 588 | /// 589 | /// let mut somepath = PathBuf::from("foo"); 590 | /// somepath.append("bar"); 591 | /// 592 | /// assert_eq!(somepath, PathBuf::from("foo/bar")); 593 | /// ``` 594 | /// 595 | /// [`pop_up`]: trait.PathMut.html#method.pop_up 596 | /// [`PathBuf::push`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.push 597 | fn append>(&mut self, path: P) -> Result<()>; 598 | 599 | /// Go "up" one directory. 600 | /// 601 | /// This removes the last component of this path. It also resolves any `..` that exist at the 602 | /// _end_ of the path until a real item can be truncated. If the path is relative, and no 603 | /// items remain then a `..` is appended to the path. 604 | /// 605 | /// # Errors 606 | /// 607 | /// This method returns an error if the result would try to go outside a filesystem root, 608 | /// like `/` on Unix or `C:\` on Windows. 609 | /// 610 | /// # Example 611 | /// 612 | /// ```rust 613 | /// # fn example() -> Result<(), path_abs::Error> { 614 | /// use std::path::Path; 615 | /// use path_abs::PathMut; 616 | /// 617 | /// let executable = Path::new("/usr/loca/bin/myapp"); 618 | /// let mut install_path = executable.to_path_buf(); 619 | /// install_path.pop_up()?; 620 | /// 621 | /// assert_eq!(install_path.as_path(), Path::new("/usr/local/bin")); 622 | /// # Ok(()) } 623 | /// ``` 624 | /// 625 | /// Example handling weird relative paths 626 | /// 627 | /// ```rust 628 | /// # fn example() -> Result<(), path_abs::Error> { 629 | /// use std::path::Path; 630 | /// use path_abs::PathMut; 631 | /// 632 | /// let executable = Path::new("../../weird/../relative/path/../../"); 633 | /// let mut install_path = executable.to_path_buf(); 634 | /// install_path.pop_up()?; 635 | /// 636 | /// assert_eq!(install_path.as_path(), Path::new("../../../")); 637 | /// # Ok(()) } 638 | /// ``` 639 | /// 640 | /// Error use case 641 | /// 642 | /// ```rust 643 | /// # fn example() -> Result<(), path_abs::Error> { 644 | /// use std::path::Path; 645 | /// use path_abs::PathMut; 646 | /// 647 | /// let tmp = Path::new("/tmp"); 648 | /// let mut relative = tmp.to_path_buf(); 649 | /// relative.pop_up()?; 650 | /// assert!(relative.pop_up().is_err()); 651 | /// # Ok(()) } 652 | /// ``` 653 | fn pop_up(&mut self) -> Result<()>; 654 | 655 | /// Removes all components after the root, if any. 656 | /// 657 | /// This is mostly useful on Windows, since it preserves the prefix before 658 | /// the root. 659 | /// 660 | /// # Example 661 | /// 662 | /// ```no_run 663 | /// use std::path::PathBuf; 664 | /// use path_abs::PathMut; 665 | /// 666 | /// let mut somepath = PathBuf::from(r"C:\foo\bar"); 667 | /// somepath.truncate_to_root(); 668 | /// 669 | /// assert_eq!(somepath, PathBuf::from(r"C:\")); 670 | /// ``` 671 | fn truncate_to_root(&mut self); 672 | 673 | fn set_file_name>(&mut self, file_name: S); 674 | 675 | fn set_extension>(&mut self, extension: S) -> bool; 676 | } 677 | 678 | impl PathMut for PathBuf { 679 | fn append>(&mut self, path: P) -> Result<()> { 680 | for each in path.as_ref().components() { 681 | match each { 682 | Component::Normal(c) => self.push(c), 683 | Component::CurDir => (), // "." does nothing 684 | Component::Prefix(_) => { 685 | return Err(Error::new( 686 | io::Error::new(io::ErrorKind::Other, "appended path has a prefix"), 687 | "appending path", 688 | path.as_ref().to_path_buf().into(), 689 | )); 690 | } 691 | Component::RootDir => (), // leading "/" does nothing 692 | Component::ParentDir => self.pop_up()?, 693 | } 694 | } 695 | 696 | Ok(()) 697 | } 698 | 699 | fn pop_up(&mut self) -> Result<()> { 700 | /// Pop off the parent components and return how 701 | /// many were removed. 702 | fn pop_parent_components(p: &mut PathBuf) -> usize { 703 | let mut cur_dirs: usize = 0; 704 | let mut parents: usize = 0; 705 | let mut components = p.components(); 706 | while let Some(c) = components.next_back() { 707 | match c { 708 | Component::CurDir => cur_dirs += 1, 709 | Component::ParentDir => parents += 1, 710 | _ => break, 711 | } 712 | } 713 | for _ in 0..(cur_dirs + parents) { 714 | p.pop(); 715 | } 716 | parents 717 | } 718 | 719 | let mut ending_parents = 0; 720 | loop { 721 | ending_parents += pop_parent_components(self); 722 | if ending_parents == 0 || self.file_name().is_none() { 723 | break; 724 | } else { 725 | // we have at least one "parent" to consume 726 | self.pop(); 727 | ending_parents -= 1; 728 | } 729 | } 730 | 731 | if self.pop() { 732 | // do nothing, success 733 | } else if self.has_root() { 734 | // We tried to pop off the root 735 | return Err(Error::new( 736 | io::Error::new(io::ErrorKind::NotFound, "cannot get parent of root path"), 737 | "truncating to parent", 738 | self.clone().into(), 739 | )); 740 | } else { 741 | // we are creating a relative path, `"../"` 742 | self.push("..") 743 | } 744 | 745 | // Put all unhandled parents back, creating a relative path. 746 | for _ in 0..ending_parents { 747 | self.push("..") 748 | } 749 | 750 | Ok(()) 751 | } 752 | 753 | fn truncate_to_root(&mut self) { 754 | let mut res = PathBuf::new(); 755 | for component in self.components().take(2) { 756 | match component { 757 | // We want to keep prefix and RootDir components of this path 758 | Component::Prefix(_) | Component::RootDir => res.push(component), 759 | // We want to discard all other components. 760 | _ => break, 761 | } 762 | } 763 | 764 | // Clobber ourselves with the new value. 765 | *self = res; 766 | } 767 | 768 | fn set_file_name>(&mut self, file_name: S) { 769 | self.set_file_name(file_name) 770 | } 771 | 772 | fn set_extension>(&mut self, extension: S) -> bool { 773 | self.set_extension(extension) 774 | } 775 | } 776 | 777 | impl PathMut for Arc { 778 | fn append>(&mut self, path: P) -> Result<()> { 779 | Arc::make_mut(self).append(path) 780 | } 781 | fn pop_up(&mut self) -> Result<()> { 782 | Arc::make_mut(self).pop_up() 783 | } 784 | fn truncate_to_root(&mut self) { 785 | Arc::make_mut(self).truncate_to_root() 786 | } 787 | fn set_file_name>(&mut self, file_name: S) { 788 | Arc::make_mut(self).set_file_name(file_name) 789 | } 790 | fn set_extension>(&mut self, extension: S) -> bool { 791 | Arc::make_mut(self).set_extension(extension) 792 | } 793 | } 794 | 795 | /// Methods that return new path-like objects. 796 | /// 797 | /// Like the methods of [`PathInfo`] and [`PathMut`], these methods are similar 798 | /// to ones from the standard library's [`PathBuf`] but may return a rich 799 | /// [`path_abs::Error`] instead of a [`std::io::Error`] (although it will 800 | /// automatically convert into a `std::io::Error` with `?` if needed). 801 | /// 802 | /// Unlike the methods of [`PathInfo`] and [`PathMut`], different types that 803 | /// implement this trait may have different return types. 804 | /// 805 | /// [`PathInfo`]: trait.PathInfo.html 806 | /// [`PathMut`]: trait.PathMut.html 807 | /// [`PathBuf`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html 808 | /// [`path_abs::Error`]: struct.Error.html 809 | /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html 810 | pub trait PathOps: PathInfo { 811 | type Output: PathOps; 812 | 813 | /// Returns a new value representing the concatenation of two paths. 814 | /// 815 | /// Note that this method represents pure concatenation, not "adjoinment" 816 | /// like [`PathBuf::join`], so absolute paths won't wholly replace the 817 | /// current path. See [`append`] for more information about how it works. 818 | /// 819 | /// # Errors 820 | /// 821 | /// This method returns an error if the result would try to go outside a filesystem root, 822 | /// like `/` on Unix or `C:\` on Windows. 823 | /// 824 | /// # Example 825 | /// 826 | /// ```rust 827 | /// use path_abs::{PathInfo, PathOps, Result}; 828 | /// 829 | /// fn find_config_file( 830 | /// search_path: &[P], 831 | /// file_name: &str, 832 | /// ) -> Option<

::Output> { 833 | /// for each in search_path.iter() { 834 | /// if let Ok(maybe_config) = each.concat(file_name) { 835 | /// if maybe_config.is_file() { return Some(maybe_config); } 836 | /// } 837 | /// } 838 | /// 839 | /// None 840 | /// } 841 | /// ``` 842 | /// 843 | /// [`append`]: trait.PathMut.html#method.append 844 | /// [`PathBuf::join`]: https://doc.rust-lang.org/stable/std/path/struct.PathBuf.html#method.join 845 | fn concat>(&self, path: P) -> Result; 846 | 847 | /// An exact replica of `std::path::Path::join` with all of its gotchas and pitfalls,, except 848 | /// returns a more relevant type. 849 | /// 850 | /// In general, prefer [`concat`] 851 | /// 852 | /// [`concat`]: trait.PathOps.html#method.concat 853 | fn join>(&self, path: P) -> Self::Output; 854 | 855 | /// Creates a new path object like `self` but with the given file name. 856 | /// 857 | /// The same as [`std::path::Path::with_file_name()`], except that the 858 | /// return type depends on the trait implementation. 859 | /// 860 | /// [`std::path::Path::with_file_name()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_file_name 861 | fn with_file_name>(&self, file_name: S) -> Self::Output; 862 | 863 | /// Creates a new path object like `self` but with the given extension. 864 | /// 865 | /// The same as [`std::path::Path::with_extension()`], except that the 866 | /// return type depends on the trait implementation. 867 | /// 868 | /// [`std::path::Path::with_extension()`]: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.with_extension 869 | fn with_extension>(&self, extension: S) -> Self::Output; 870 | } 871 | 872 | // impl PathOps for T 873 | // where 874 | // T: PathInfo 875 | // 876 | // { 877 | // type Output = PathBuf; 878 | // 879 | // fn concat>(&self, path: P) -> Result { 880 | // let mut res = self.as_ref().to_owned(); 881 | // res.append(path)?; 882 | // Ok(res) 883 | // } 884 | // 885 | // fn with_file_name>(&self, file_name: S) -> Self::Output { 886 | // let mut res = self.as_ref().to_owned(); 887 | // res.set_file_name(file_name); 888 | // res 889 | // } 890 | // 891 | // fn with_extension>(&self, extension: S) -> Self::Output { 892 | // let mut res = self.as_ref().to_owned(); 893 | // res.set_extension(extension); 894 | // res 895 | // } 896 | // } 897 | 898 | impl PathOps for Path { 899 | type Output = PathBuf; 900 | 901 | fn concat>(&self, path: P) -> Result { 902 | let mut res = self.to_owned(); 903 | res.append(path)?; 904 | Ok(res) 905 | } 906 | 907 | fn join>(&self, path: P) -> Self::Output { 908 | Path::join(self, path) 909 | } 910 | 911 | fn with_file_name>(&self, file_name: S) -> Self::Output { 912 | let mut res = self.to_owned(); 913 | res.set_file_name(file_name); 914 | res 915 | } 916 | 917 | fn with_extension>(&self, extension: S) -> Self::Output { 918 | let mut res = self.to_owned(); 919 | res.set_extension(extension); 920 | res 921 | } 922 | } 923 | 924 | impl PathOps for PathBuf { 925 | type Output = PathBuf; 926 | 927 | fn concat>(&self, path: P) -> Result { 928 | self.as_path().concat(path) 929 | } 930 | 931 | fn join>(&self, path: P) -> Self::Output { 932 | Path::join(self, path) 933 | } 934 | 935 | fn with_file_name>(&self, file_name: S) -> Self::Output { 936 | self.as_path().with_file_name(file_name) 937 | } 938 | 939 | fn with_extension>(&self, extension: S) -> Self::Output { 940 | self.as_path().with_extension(extension) 941 | } 942 | } 943 | 944 | impl PathOps for Arc { 945 | type Output = Arc; 946 | 947 | fn concat>(&self, path: P) -> Result { 948 | let mut res = self.clone(); 949 | Arc::make_mut(&mut res).append(path)?; 950 | Ok(res) 951 | } 952 | 953 | fn join>(&self, path: P) -> Self::Output { 954 | let buf = Path::join(self, path); 955 | Arc::new(buf) 956 | } 957 | 958 | fn with_file_name>(&self, file_name: S) -> Self::Output { 959 | let mut res = self.clone(); 960 | Arc::make_mut(&mut res).set_file_name(file_name); 961 | res 962 | } 963 | 964 | fn with_extension>(&self, extension: S) -> Self::Output { 965 | let mut res = self.clone(); 966 | Arc::make_mut(&mut res).set_extension(extension); 967 | res 968 | } 969 | } 970 | 971 | #[cfg(test)] 972 | mod tests { 973 | use regex::{self, Regex}; 974 | use tempfile::TempDir; 975 | 976 | use super::*; 977 | 978 | macro_rules! assert_match { 979 | ($re: expr, $err: expr) => {{ 980 | let re = Regex::new(&$re).unwrap(); 981 | let err = $err.to_string(); 982 | assert!( 983 | re.is_match(&err), 984 | "\nGot Err : {:?}\nMatching against: {:?}", 985 | err.to_string(), 986 | $re 987 | ); 988 | }}; 989 | } 990 | 991 | fn escape>(path: P) -> String { 992 | regex::escape(&format!("{}", path.as_ref().display())) 993 | } 994 | 995 | #[test] 996 | /// Tests to make sure the error messages look like we expect. 997 | fn sanity_errors() { 998 | let tmp_dir = TempDir::new().expect("create temp dir"); 999 | let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs"); 1000 | 1001 | { 1002 | let foo_path = tmp_abs.concat("foo.txt").expect("path foo.txt"); 1003 | let foo = PathFile::create(foo_path).expect("create foo.txt"); 1004 | foo.clone().remove().unwrap(); 1005 | let pat = if cfg!(unix) { 1006 | format!( 1007 | r"No such file or directory \(os error \d+\) when opening {}", 1008 | escape(&foo) 1009 | ) 1010 | } else { 1011 | format!( 1012 | r"The system cannot find the file specified. \(os error \d+\) when opening {}", 1013 | escape(&foo) 1014 | ) 1015 | }; 1016 | assert_match!(pat, foo.open_edit().unwrap_err()) 1017 | } 1018 | } 1019 | 1020 | #[cfg(test)] 1021 | mod windows { 1022 | use super::*; 1023 | 1024 | #[cfg_attr(windows, test)] 1025 | fn _test_pathinfo_parent() { 1026 | let p = PathBuf::from(r"C:\foo\bar"); 1027 | 1028 | let actual = ::parent(&p).expect("could not find parent?"); 1029 | let expected = PathBuf::from(r"C:\foo"); 1030 | assert_eq!(actual, expected); 1031 | 1032 | let p = PathBuf::from(r"C:\"); 1033 | let actual = ::parent(&p).expect_err("root has a parent?"); 1034 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1035 | assert_eq!(actual.action(), "truncating to parent"); 1036 | assert_eq!(actual.path(), Path::new(r"C:\")); 1037 | } 1038 | 1039 | #[cfg_attr(windows, test)] 1040 | fn _test_pathinfo_starts_with() { 1041 | let p = PathBuf::from(r"foo\bar"); 1042 | 1043 | assert_eq!( 1044 | ::starts_with(&p, Path::new("foo")), 1045 | true, 1046 | ); 1047 | assert_eq!( 1048 | ::starts_with(&p, Path::new("bar")), 1049 | false, 1050 | ); 1051 | } 1052 | 1053 | #[cfg_attr(windows, test)] 1054 | fn _test_pathinfo_ends_with() { 1055 | let p = PathBuf::from(r"foo\bar"); 1056 | 1057 | assert_eq!( 1058 | ::ends_with(&p, Path::new("foo")), 1059 | false, 1060 | ); 1061 | assert_eq!(::ends_with(&p, Path::new("bar")), true,); 1062 | } 1063 | 1064 | #[cfg_attr(windows, test)] 1065 | fn _test_pathops_concat() { 1066 | let actual = Path::new("foo") 1067 | .concat(Path::new("bar")) 1068 | .expect("Could not concat paths?"); 1069 | let expected = PathBuf::from(r"foo\bar"); 1070 | assert_eq!(actual, expected); 1071 | 1072 | let actual = Path::new("foo") 1073 | .concat(Path::new(r"bar\..\baz")) 1074 | .expect("Could not concat path with ..?"); 1075 | let expected = PathBuf::from(r"foo\baz"); 1076 | assert_eq!(actual, expected); 1077 | 1078 | let actual = Path::new("foo") 1079 | .concat("..") 1080 | .expect("Could not cancel path with ..?"); 1081 | let expected = PathBuf::from(r""); 1082 | assert_eq!(actual, expected); 1083 | 1084 | let actual = Path::new("foo") 1085 | .concat(r"..\..") 1086 | .expect("Could not escape prefix with ..?"); 1087 | let expected = PathBuf::from("../"); 1088 | assert_eq!(actual, expected); 1089 | 1090 | let actual = Path::new(r"C:\foo") 1091 | .concat(r"..\..") 1092 | .expect_err("Could escape root with ..?"); 1093 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1094 | assert_eq!(actual.action(), "truncating to parent"); 1095 | assert_eq!(actual.path(), Path::new(r"C:\")); 1096 | 1097 | let actual = Path::new("foo") 1098 | .concat(Path::new(r"\windows\system32")) 1099 | .expect("Could not concat path with RootDir?"); 1100 | let expected = PathBuf::from(r"foo\windows\system32"); 1101 | assert_eq!(actual, expected); 1102 | 1103 | let actual = Path::new("foo") 1104 | .concat(Path::new(r"C:bar")) 1105 | .expect_err("Could concat path with prefix?"); 1106 | assert_eq!(actual.io_error().kind(), io::ErrorKind::Other); 1107 | assert_eq!(actual.action(), "appending path"); 1108 | assert_eq!(actual.path(), Path::new(r"C:bar")); 1109 | } 1110 | 1111 | #[cfg_attr(windows, test)] 1112 | fn _test_pathmut_append() { 1113 | let mut actual = PathBuf::from("foo"); 1114 | actual 1115 | .append(Path::new("bar")) 1116 | .expect("Could not append paths?"); 1117 | let expected = PathBuf::from(r"foo\bar"); 1118 | assert_eq!(actual, expected); 1119 | 1120 | let mut actual = PathBuf::from("foo"); 1121 | actual 1122 | .append(Path::new(r"bar\..\baz")) 1123 | .expect("Could not append path with ..?"); 1124 | let expected = PathBuf::from(r"foo\baz"); 1125 | assert_eq!(actual, expected); 1126 | 1127 | let mut actual = PathBuf::from("foo"); 1128 | actual.append("..").expect("Could not cancel path with ..?"); 1129 | let expected = PathBuf::from(r""); 1130 | assert_eq!(actual, expected); 1131 | 1132 | let mut actual = PathBuf::from("foo"); 1133 | actual 1134 | .append(r"..\..") 1135 | .expect("Could not escape prefix with ..?"); 1136 | let expected = PathBuf::from("../"); 1137 | assert_eq!(actual, expected); 1138 | 1139 | let actual = PathBuf::from(r"C:\foo") 1140 | .append(r"..\..") 1141 | .expect_err("Could escape root with ..?"); 1142 | 1143 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1144 | assert_eq!(actual.action(), "truncating to parent"); 1145 | assert_eq!(actual.path(), Path::new(r"C:\")); 1146 | 1147 | let mut actual = PathBuf::from("foo"); 1148 | actual 1149 | .append(Path::new(r"\windows\system32")) 1150 | .expect("Could not append RootDir to path?"); 1151 | let expected = PathBuf::from(r"foo\windows\system32"); 1152 | assert_eq!(actual, expected); 1153 | 1154 | let actual = PathBuf::from("foo") 1155 | .append(Path::new(r"C:bar")) 1156 | .expect_err("Could append prefix to path?"); 1157 | assert_eq!(actual.io_error().kind(), io::ErrorKind::Other); 1158 | assert_eq!(actual.action(), "appending path"); 1159 | assert_eq!(actual.path(), Path::new(r"C:bar")); 1160 | } 1161 | 1162 | #[cfg_attr(windows, test)] 1163 | fn _test_pathmut_pop_up() { 1164 | let mut p = PathBuf::from(r"C:\foo\bar"); 1165 | p.pop_up().expect("could not find parent?"); 1166 | assert_eq!(p.as_path(), Path::new(r"C:\foo")); 1167 | 1168 | let mut p = PathBuf::from(r"C:\"); 1169 | let actual = p.pop_up().expect_err("root has a parent?"); 1170 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1171 | assert_eq!(actual.action(), "truncating to parent"); 1172 | assert_eq!(actual.path(), Path::new(r"C:\")); 1173 | } 1174 | 1175 | #[cfg_attr(windows, test)] 1176 | fn _test_pathmut_truncate_to_root() { 1177 | let mut p = PathBuf::from(r"C:\foo\bar"); 1178 | p.truncate_to_root(); 1179 | assert_eq!(p.as_path(), Path::new(r"C:\")); 1180 | 1181 | let mut p = PathBuf::from(r"C:foo"); 1182 | p.truncate_to_root(); 1183 | assert_eq!(p.as_path(), Path::new(r"C:")); 1184 | 1185 | let mut p = PathBuf::from(r"\foo"); 1186 | p.truncate_to_root(); 1187 | assert_eq!(p.as_path(), Path::new(r"\")); 1188 | 1189 | let mut p = PathBuf::from(r"foo"); 1190 | p.truncate_to_root(); 1191 | assert_eq!(p.as_path(), Path::new(r"")); 1192 | } 1193 | } 1194 | 1195 | mod any { 1196 | use super::*; 1197 | 1198 | #[test] 1199 | fn test_pathinfo_is_absolute() { 1200 | let p = PathBuf::from("/foo/bar"); 1201 | 1202 | let expected = !cfg!(windows); 1203 | assert_eq!(::is_absolute(&p), expected); 1204 | } 1205 | 1206 | #[test] 1207 | fn test_pathinfo_parent() { 1208 | let p = PathBuf::from("/foo/bar"); 1209 | 1210 | let actual = ::parent(&p).expect("could not find parent?"); 1211 | let expected = PathBuf::from("/foo"); 1212 | 1213 | assert_eq!(actual, expected); 1214 | 1215 | let p = PathBuf::from("/"); 1216 | 1217 | let actual = ::parent(&p).expect_err("root has a parent?"); 1218 | 1219 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1220 | assert_eq!(actual.action(), "truncating to parent"); 1221 | assert_eq!(actual.path(), Path::new("/")); 1222 | } 1223 | 1224 | #[test] 1225 | fn test_pathinfo_starts_with() { 1226 | let p = PathBuf::from("foo/bar"); 1227 | 1228 | assert_eq!( 1229 | ::starts_with(&p, Path::new("foo")), 1230 | true, 1231 | ); 1232 | assert_eq!( 1233 | ::starts_with(&p, Path::new("bar")), 1234 | false, 1235 | ); 1236 | } 1237 | 1238 | #[test] 1239 | fn test_pathinfo_ends_with() { 1240 | let p = PathBuf::from("foo/bar"); 1241 | 1242 | assert_eq!( 1243 | ::ends_with(&p, Path::new("foo")), 1244 | false, 1245 | ); 1246 | assert_eq!(::ends_with(&p, Path::new("bar")), true,); 1247 | } 1248 | 1249 | #[test] 1250 | fn test_pathops_concat() { 1251 | let actual = Path::new("foo") 1252 | .concat(Path::new("bar")) 1253 | .expect("Could not concat paths?"); 1254 | let expected = PathBuf::from("foo/bar"); 1255 | 1256 | assert_eq!(actual, expected); 1257 | 1258 | let actual = Path::new("foo") 1259 | .concat(Path::new("bar/../baz")) 1260 | .expect("Could not concat path with ..?"); 1261 | let expected = PathBuf::from("foo/baz"); 1262 | 1263 | assert_eq!(actual, expected); 1264 | 1265 | let actual = Path::new("foo") 1266 | .concat("..") 1267 | .expect("Could not cancel path with ..?"); 1268 | let expected = PathBuf::from(r""); 1269 | 1270 | assert_eq!(actual, expected); 1271 | 1272 | let actual = Path::new("foo") 1273 | .concat("../..") 1274 | .expect("Could not prefix with ..?"); 1275 | let expected = PathBuf::from(r"../"); 1276 | assert_eq!(actual, expected); 1277 | 1278 | let actual = Path::new("/foo") 1279 | .concat("../..") 1280 | .expect_err("Could escape root with ..?"); 1281 | 1282 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1283 | assert_eq!(actual.action(), "truncating to parent"); 1284 | assert_eq!(actual.path(), Path::new("/")); 1285 | 1286 | let actual = Path::new("foo") 1287 | .concat(Path::new("/etc/passwd")) 1288 | .expect("Could not concat RootDir to path?"); 1289 | let expected: PathBuf = PathBuf::from("foo/etc/passwd"); 1290 | 1291 | assert_eq!(actual, expected); 1292 | } 1293 | 1294 | #[test] 1295 | fn test_pathops_concat_relative() { 1296 | let actual = Path::new("../foo") 1297 | .concat("bar") 1298 | .expect("Could not create relative path with concat"); 1299 | let expected = PathBuf::from(r"../foo/bar"); 1300 | assert_eq!(actual, expected); 1301 | 1302 | let actual = Path::new("../foo") 1303 | .concat("..") 1304 | .expect("Could not create relative path with concat"); 1305 | let expected = PathBuf::from(r".."); 1306 | assert_eq!(actual, expected); 1307 | 1308 | let actual = Path::new("../foo") 1309 | .concat("../..") 1310 | .expect("Could not create relative path with concat"); 1311 | let expected = PathBuf::from(r"../.."); 1312 | assert_eq!(actual, expected); 1313 | 1314 | let actual = Path::new("../foo/../bar") 1315 | .concat("../..") 1316 | .expect("Could not create relative path with concat"); 1317 | let expected = PathBuf::from(r"../.."); 1318 | assert_eq!(actual, expected); 1319 | 1320 | let actual = Path::new("../foo/../bar/..") 1321 | .concat("../..") 1322 | .expect("Could not create relative path with concat"); 1323 | let expected = PathBuf::from(r"../../.."); 1324 | assert_eq!(actual, expected); 1325 | 1326 | let actual = PathBuf::from("../foo/..") 1327 | .concat("../../baz") 1328 | .expect("Could not create relative path with concat"); 1329 | let expected = PathBuf::from(r"../../../baz"); 1330 | assert_eq!(actual, expected); 1331 | } 1332 | 1333 | #[test] 1334 | fn test_pathops_concat_cur() { 1335 | // just check that pahts don't normalize... 1336 | let actual = Path::new("foo/././..").as_os_str(); 1337 | let expected = ffi::OsStr::new("foo/././.."); 1338 | assert_eq!(actual, expected); 1339 | 1340 | let actual = PathBuf::from("././foo/././..") 1341 | .concat("../bar") 1342 | .expect("Could not create relative path with concat"); 1343 | let expected = PathBuf::from(r"../bar"); 1344 | assert_eq!(actual, expected); 1345 | } 1346 | 1347 | #[test] 1348 | fn test_pathops_concat_consume() { 1349 | let actual = Path::new("foo") 1350 | .concat("../../bar") 1351 | .expect("Could not create relative path with concat"); 1352 | let expected = PathBuf::from(r"../bar"); 1353 | assert_eq!(actual, expected); 1354 | } 1355 | 1356 | #[test] 1357 | fn test_pathmut_append() { 1358 | let mut actual = PathBuf::from("foo"); 1359 | actual 1360 | .append(Path::new("bar")) 1361 | .expect("Could not append paths?"); 1362 | let expected = PathBuf::from("foo/bar"); 1363 | assert_eq!(actual, expected); 1364 | 1365 | let mut actual = PathBuf::from("foo"); 1366 | actual 1367 | .append(Path::new("bar/../baz")) 1368 | .expect("Could not append path with ..?"); 1369 | let expected = PathBuf::from("foo/baz"); 1370 | assert_eq!(actual, expected); 1371 | 1372 | let mut actual = PathBuf::from("foo"); 1373 | actual.append("..").expect("Could not cancel path with ..?"); 1374 | let expected = PathBuf::from(r""); 1375 | assert_eq!(actual, expected); 1376 | 1377 | let mut actual = PathBuf::from("foo"); 1378 | actual 1379 | .append("../..") 1380 | .expect("Could not escape prefix with ..?"); 1381 | let expected = PathBuf::from("../"); 1382 | assert_eq!(actual, expected); 1383 | 1384 | let actual = PathBuf::from("/foo") 1385 | .append("../..") 1386 | .expect_err("Could escape root with ..?"); 1387 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1388 | assert_eq!(actual.action(), "truncating to parent"); 1389 | assert_eq!(actual.path(), Path::new("/")); 1390 | 1391 | let mut actual = PathBuf::from("foo"); 1392 | actual 1393 | .append(Path::new("/etc/passwd")) 1394 | .expect("Could not append RootDir to path?"); 1395 | let expected: PathBuf = PathBuf::from("foo/etc/passwd"); 1396 | 1397 | assert_eq!(actual, expected); 1398 | } 1399 | 1400 | #[test] 1401 | fn test_pathmut_pop_up() { 1402 | let mut p = PathBuf::from("/foo/bar"); 1403 | p.pop_up().expect("could not find parent?"); 1404 | 1405 | assert_eq!(p.as_path(), Path::new("/foo")); 1406 | 1407 | let mut p = PathBuf::from("/"); 1408 | let actual = p.pop_up().expect_err("root has a parent?"); 1409 | 1410 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 1411 | assert_eq!(actual.action(), "truncating to parent"); 1412 | assert_eq!(actual.path(), Path::new("/")); 1413 | } 1414 | 1415 | #[test] 1416 | fn test_pathmut_truncate_to_root() { 1417 | let mut p = PathBuf::from("/foo/bar"); 1418 | p.truncate_to_root(); 1419 | assert_eq!(p.as_path(), Path::new("/")); 1420 | 1421 | let mut p = PathBuf::from("foo/bar"); 1422 | p.truncate_to_root(); 1423 | assert_eq!(p.as_path(), Path::new("")); 1424 | } 1425 | } 1426 | } 1427 | -------------------------------------------------------------------------------- /src/open.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Open file paths and associated methods. 9 | 10 | use std::fmt; 11 | use std::fs; 12 | use std_prelude::*; 13 | 14 | use super::{Error, PathAbs, PathFile, Result}; 15 | 16 | /// **INTERNAL TYPE: do not use directly.** 17 | /// 18 | /// Use `FileRead`, `FileWrite` or `FileEdit` instead. 19 | pub struct FileOpen { 20 | pub(crate) path: PathFile, 21 | pub(crate) file: fs::File, 22 | } 23 | 24 | impl FileOpen { 25 | /// Open the file with the given `OpenOptions`. 26 | pub fn open>(path: P, options: fs::OpenOptions) -> Result { 27 | let file = options 28 | .open(&path) 29 | .map_err(|err| Error::new(err, "opening", path.as_ref().to_path_buf().into()))?; 30 | 31 | let path = PathFile::new(path)?; 32 | Ok(FileOpen { path: path, file }) 33 | } 34 | 35 | /// Shortcut to open the file if the path is already absolute. 36 | /// 37 | /// Typically you should use `PathFile::open` instead (i.e. `file.open(options)` or 38 | /// `file.read()`). 39 | pub fn open_abs>(path: P, options: fs::OpenOptions) -> Result { 40 | let path = path.into(); 41 | let file = options 42 | .open(&path) 43 | .map_err(|err| Error::new(err, "opening", path.clone().into()))?; 44 | 45 | Ok(FileOpen { 46 | path: PathFile::new_unchecked(path), 47 | file, 48 | }) 49 | } 50 | 51 | /// Get the path associated with the open file. 52 | pub fn path(&self) -> &PathFile { 53 | &self.path 54 | } 55 | 56 | /// Queries metadata about the underlying file. 57 | /// 58 | /// This function is identical to [std::fs::File::metadata][0] except it has error 59 | /// messages which include the action and the path. 60 | /// 61 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.metadata 62 | pub fn metadata(&self) -> Result { 63 | self.file 64 | .metadata() 65 | .map_err(|err| Error::new(err, "getting metadata for", self.path.clone().into())) 66 | } 67 | 68 | /// Creates a new independently owned handle to the underlying file. 69 | /// 70 | /// This function is identical to [std::fs::File::try_clone][0] except it has error 71 | /// messages which include the action and the path and it returns a `FileOpen` object. 72 | /// 73 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.try_clone 74 | pub fn try_clone(&self) -> Result { 75 | let file = self 76 | .file 77 | .try_clone() 78 | .map_err(|err| Error::new(err, "cloning file handle for", self.path.clone().into()))?; 79 | Ok(FileOpen { 80 | file, 81 | path: self.path.clone(), 82 | }) 83 | } 84 | } 85 | 86 | impl fmt::Debug for FileOpen { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | write!(f, "Open(")?; 89 | self.path.fmt(f)?; 90 | write!(f, ")") 91 | } 92 | } 93 | 94 | impl AsRef for FileOpen { 95 | fn as_ref(&self) -> &fs::File { 96 | &self.file 97 | } 98 | } 99 | 100 | impl Borrow for FileOpen { 101 | fn borrow(&self) -> &fs::File { 102 | &self.file 103 | } 104 | } 105 | 106 | impl<'a> Borrow for &'a FileOpen { 107 | fn borrow(&self) -> &fs::File { 108 | &self.file 109 | } 110 | } 111 | 112 | impl From for fs::File { 113 | fn from(orig: FileOpen) -> fs::File { 114 | orig.file 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/read.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Open file paths that are read-only. 9 | 10 | use std::fmt; 11 | use std::fs; 12 | use std::io; 13 | use std_prelude::*; 14 | 15 | use super::open::FileOpen; 16 | use super::{Error, PathAbs, PathFile, PathInfo, Result}; 17 | 18 | /// A read-only file handle with `path()` attached and improved error messages. Contains only the 19 | /// methods and trait implementations which are allowed by a read-only file. 20 | /// 21 | /// # Examples 22 | /// ```rust 23 | /// # extern crate path_abs; 24 | /// # extern crate tempfile; 25 | /// use std::io::Read; 26 | /// use path_abs::{PathFile, FileRead}; 27 | /// 28 | /// # fn try_main() -> ::std::io::Result<()> { 29 | /// let example = "example.txt"; 30 | /// # let tmp = tempfile::TempDir::new()?; 31 | /// # let example = &tmp.path().join(example); 32 | /// let file = PathFile::create(example)?; 33 | /// 34 | /// let expected = "foo\nbar"; 35 | /// file.write_str(expected)?; 36 | /// 37 | /// let mut read = FileRead::open(example)?; 38 | /// let mut s = String::new(); 39 | /// read.read_to_string(&mut s)?; 40 | /// assert_eq!(expected, s); 41 | /// # Ok(()) } fn main() { try_main().unwrap() } 42 | /// ``` 43 | pub struct FileRead(pub(crate) FileOpen); 44 | 45 | impl FileRead { 46 | /// Open the file as read-only. 47 | pub fn open>(path: P) -> Result { 48 | let mut options = fs::OpenOptions::new(); 49 | options.read(true); 50 | Ok(FileRead(FileOpen::open(path, options)?)) 51 | } 52 | 53 | /// Shortcut to open the file if the path is already absolute. 54 | pub(crate) fn open_abs>(path: P) -> Result { 55 | let mut options = fs::OpenOptions::new(); 56 | options.read(true); 57 | Ok(FileRead(FileOpen::open_abs(path, options)?)) 58 | } 59 | 60 | pub fn path(&self) -> &PathFile { 61 | &self.0.path 62 | } 63 | 64 | /// Read what remains of the file to a `String`. 65 | pub fn read_string(&mut self) -> Result { 66 | let mut s = String::new(); 67 | self.0 68 | .file 69 | .read_to_string(&mut s) 70 | .map_err(|err| Error::new(err, "reading", self.0.path.clone().into()))?; 71 | Ok(s) 72 | } 73 | } 74 | 75 | impl fmt::Debug for FileRead { 76 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 77 | write!(f, "FileRead(")?; 78 | self.0.path.fmt(f)?; 79 | write!(f, ")") 80 | } 81 | } 82 | 83 | impl io::Read for FileRead { 84 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 85 | self.0.file.read(buf).map_err(|err| { 86 | io::Error::new( 87 | err.kind(), 88 | format!("{} when reading {}", err, self.path().display()), 89 | ) 90 | }) 91 | } 92 | } 93 | 94 | impl io::Seek for FileRead { 95 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 96 | self.0.file.seek(pos).map_err(|err| { 97 | io::Error::new( 98 | err.kind(), 99 | format!("{} seeking {}", err, self.path().display()), 100 | ) 101 | }) 102 | } 103 | } 104 | 105 | impl AsRef for FileRead { 106 | fn as_ref(&self) -> &FileOpen { 107 | &self.0 108 | } 109 | } 110 | 111 | impl AsRef for FileRead { 112 | fn as_ref(&self) -> &File { 113 | self.0.as_ref() 114 | } 115 | } 116 | 117 | impl Borrow for FileRead { 118 | fn borrow(&self) -> &FileOpen { 119 | &self.0 120 | } 121 | } 122 | 123 | impl Borrow for FileRead { 124 | fn borrow(&self) -> &File { 125 | self.0.borrow() 126 | } 127 | } 128 | 129 | impl<'a> Borrow for &'a FileRead { 130 | fn borrow(&self) -> &FileOpen { 131 | &self.0 132 | } 133 | } 134 | 135 | impl<'a> Borrow for &'a FileRead { 136 | fn borrow(&self) -> &File { 137 | self.0.borrow() 138 | } 139 | } 140 | 141 | impl From for FileOpen { 142 | fn from(orig: FileRead) -> FileOpen { 143 | orig.0 144 | } 145 | } 146 | 147 | impl From for File { 148 | fn from(orig: FileRead) -> File { 149 | orig.0.into() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/ser.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; 9 | use std::fmt; 10 | use std::string::ToString; 11 | use std_prelude::*; 12 | use stfu8; 13 | 14 | use super::{PathMut, PathOps}; 15 | 16 | use std::ffi::{OsStr, OsString}; 17 | #[cfg(target_os = "wasi")] 18 | use std::os::wasi::ffi::{OsStrExt, OsStringExt}; 19 | #[cfg(unix)] 20 | use std::os::unix::ffi::{OsStrExt, OsStringExt}; 21 | #[cfg(windows)] 22 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; 23 | 24 | use super::{PathAbs, PathDir, PathFile}; 25 | 26 | #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 27 | pub struct PathSer(Arc); 28 | 29 | pub trait ToStfu8 { 30 | fn to_stfu8(&self) -> String; 31 | } 32 | 33 | pub trait FromStfu8: Sized { 34 | fn from_stfu8(s: &str) -> Result; 35 | } 36 | 37 | impl PathSer { 38 | pub fn new>>(path: P) -> Self { 39 | PathSer(path.into()) 40 | } 41 | 42 | pub fn as_path(&self) -> &Path { 43 | self.as_ref() 44 | } 45 | } 46 | 47 | impl fmt::Debug for PathSer { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | self.0.fmt(f) 50 | } 51 | } 52 | 53 | impl PathMut for PathSer { 54 | fn append>(&mut self, path: P) -> crate::Result<()> { 55 | self.0.append(path) 56 | } 57 | fn pop_up(&mut self) -> crate::Result<()> { 58 | self.0.pop_up() 59 | } 60 | fn truncate_to_root(&mut self) { 61 | self.0.truncate_to_root() 62 | } 63 | fn set_file_name>(&mut self, file_name: S) { 64 | self.0.set_file_name(file_name) 65 | } 66 | fn set_extension>(&mut self, extension: S) -> bool { 67 | self.0.set_extension(extension) 68 | } 69 | } 70 | 71 | impl PathOps for PathSer { 72 | type Output = PathSer; 73 | 74 | fn concat>(&self, path: P) -> crate::Result { 75 | Ok(PathSer(self.0.concat(path)?)) 76 | } 77 | 78 | fn join>(&self, path: P) -> Self::Output { 79 | let buf = Path::join(self.as_path(), path); 80 | Self::Output::new(buf) 81 | } 82 | 83 | fn with_file_name>(&self, file_name: S) -> Self::Output { 84 | PathSer(self.0.with_file_name(file_name)) 85 | } 86 | 87 | fn with_extension>(&self, extension: S) -> Self::Output { 88 | PathSer(self.0.with_extension(extension)) 89 | } 90 | } 91 | 92 | impl AsRef for PathSer { 93 | fn as_ref(&self) -> &std::ffi::OsStr { 94 | self.0.as_ref().as_ref() 95 | } 96 | } 97 | 98 | impl AsRef for PathSer { 99 | fn as_ref(&self) -> &Path { 100 | self.0.as_ref() 101 | } 102 | } 103 | 104 | impl AsRef for PathSer { 105 | fn as_ref(&self) -> &PathBuf { 106 | self.0.as_ref() 107 | } 108 | } 109 | 110 | impl Borrow for PathSer { 111 | fn borrow(&self) -> &Path { 112 | self.as_ref() 113 | } 114 | } 115 | 116 | impl Borrow for PathSer { 117 | fn borrow(&self) -> &PathBuf { 118 | self.as_ref() 119 | } 120 | } 121 | 122 | impl<'a> Borrow for &'a PathSer { 123 | fn borrow(&self) -> &Path { 124 | self.as_ref() 125 | } 126 | } 127 | 128 | impl<'a> Borrow for &'a PathSer { 129 | fn borrow(&self) -> &PathBuf { 130 | self.as_ref() 131 | } 132 | } 133 | 134 | impl> From

for PathSer { 135 | fn from(path: P) -> PathSer { 136 | PathSer::new(path.into()) 137 | } 138 | } 139 | 140 | impl From for Arc { 141 | fn from(path: PathSer) -> Arc { 142 | path.0 143 | } 144 | } 145 | 146 | // impl From for PathSer { 147 | // fn from(path: PathAbs) -> PathSer { 148 | // PathSer(path.0) 149 | // } 150 | // } 151 | 152 | impl ToStfu8 for T 153 | where 154 | T: Borrow, 155 | { 156 | #[cfg(any(target_os = "wasi", unix))] 157 | fn to_stfu8(&self) -> String { 158 | let bytes = self.borrow().as_os_str().as_bytes(); 159 | stfu8::encode_u8(bytes) 160 | } 161 | 162 | #[cfg(windows)] 163 | fn to_stfu8(&self) -> String { 164 | let wide: Vec = self.borrow().as_os_str().encode_wide().collect(); 165 | stfu8::encode_u16(&wide) 166 | } 167 | } 168 | 169 | impl FromStfu8 for T 170 | where 171 | T: From, 172 | { 173 | #[cfg(any(target_os = "wasi", unix))] 174 | fn from_stfu8(s: &str) -> Result { 175 | let raw_path = stfu8::decode_u8(s)?; 176 | let os_str = OsString::from_vec(raw_path); 177 | let pathbuf: PathBuf = os_str.into(); 178 | Ok(pathbuf.into()) 179 | } 180 | 181 | #[cfg(windows)] 182 | fn from_stfu8(s: &str) -> Result { 183 | let raw_path = stfu8::decode_u16(&s)?; 184 | let os_str = OsString::from_wide(&raw_path); 185 | let pathbuf: PathBuf = os_str.into(); 186 | Ok(pathbuf.into()) 187 | } 188 | } 189 | 190 | macro_rules! stfu8_serialize { 191 | ($name:ident) => { 192 | impl Serialize for $name { 193 | fn serialize(&self, serializer: S) -> Result 194 | where 195 | S: Serializer, 196 | { 197 | serializer.serialize_str(&self.to_stfu8()) 198 | } 199 | } 200 | }; 201 | } 202 | 203 | stfu8_serialize!(PathSer); 204 | stfu8_serialize!(PathAbs); 205 | stfu8_serialize!(PathFile); 206 | stfu8_serialize!(PathDir); 207 | 208 | impl<'de> Deserialize<'de> for PathSer { 209 | fn deserialize(deserializer: D) -> Result 210 | where 211 | D: Deserializer<'de>, 212 | { 213 | let s = String::deserialize(deserializer)?; 214 | let path = 215 | PathBuf::from_stfu8(&s).map_err(|err| serde::de::Error::custom(&err.to_string()))?; 216 | Ok(PathSer(Arc::new(path))) 217 | } 218 | } 219 | 220 | impl<'de> Deserialize<'de> for PathAbs { 221 | fn deserialize(deserializer: D) -> Result 222 | where 223 | D: Deserializer<'de>, 224 | { 225 | let s = String::deserialize(deserializer)?; 226 | let path = 227 | PathBuf::from_stfu8(&s).map_err(|err| serde::de::Error::custom(&err.to_string()))?; 228 | Ok(PathAbs(Arc::new(path))) 229 | } 230 | } 231 | 232 | impl<'de> Deserialize<'de> for PathFile { 233 | fn deserialize(deserializer: D) -> Result 234 | where 235 | D: Deserializer<'de>, 236 | { 237 | let abs = PathAbs::deserialize(deserializer)?; 238 | PathFile::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string())) 239 | } 240 | } 241 | 242 | impl<'de> Deserialize<'de> for PathDir { 243 | fn deserialize(deserializer: D) -> Result 244 | where 245 | D: Deserializer<'de>, 246 | { 247 | let abs = PathAbs::deserialize(deserializer)?; 248 | PathDir::try_from(abs).map_err(|err| serde::de::Error::custom(&err.to_string())) 249 | } 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use super::super::{PathDir, PathFile, PathInfo, PathMut, PathOps, PathType}; 255 | use super::*; 256 | 257 | #[cfg(any(target_os = "wasi", unix))] 258 | static SERIALIZED: &str = "[\ 259 | {\"type\":\"file\",\"path\":\"{0}/foo.txt\"},\ 260 | {\"type\":\"dir\",\"path\":\"{0}/bar\"},\ 261 | {\"type\":\"dir\",\"path\":\"{0}/foo/bar\"}\ 262 | ]"; 263 | 264 | #[cfg(windows)] 265 | static SERIALIZED: &str = "[\ 266 | {\"type\":\"file\",\"path\":\"{0}\\\\foo.txt\"},\ 267 | {\"type\":\"dir\",\"path\":\"{0}\\\\bar\"},\ 268 | {\"type\":\"dir\",\"path\":\"{0}\\\\foo\\\\bar\"}\ 269 | ]"; 270 | 271 | #[test] 272 | fn sanity_serde() { 273 | use serde_json; 274 | use tempfile::TempDir; 275 | 276 | let tmp_dir = TempDir::new().expect("create temp dir"); 277 | let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs"); 278 | 279 | let ser_from_str = PathSer::from("example"); 280 | let ser_from_tmp_abs = PathSer::from(tmp_abs.as_path()); 281 | 282 | let foo = PathFile::create(tmp_abs.concat("foo.txt").unwrap()).expect("foo.txt"); 283 | let bar_dir = PathDir::create(tmp_abs.concat("bar").unwrap()).expect("bar"); 284 | let foo_bar_dir = 285 | PathDir::create_all(tmp_abs.concat("foo").unwrap().concat("bar").unwrap()) 286 | .expect("foo/bar"); 287 | 288 | let expected = vec![ 289 | PathType::File(foo), 290 | PathType::Dir(bar_dir), 291 | PathType::Dir(foo_bar_dir), 292 | ]; 293 | 294 | let expected_str = SERIALIZED 295 | .replace("{0}", &tmp_abs.to_stfu8()) 296 | // JSON needs backslashes escaped. Be careful not to invoke BA'AL: 297 | // https://xkcd.com/1638/) 298 | .replace(r"\", r"\\"); 299 | 300 | println!("### EXPECTED:\n{}", expected_str); 301 | let result_str = serde_json::to_string(&expected).unwrap(); 302 | println!("### RESULT:\n{}", result_str); 303 | assert_eq!(expected_str, result_str); 304 | 305 | let result: Vec = serde_json::from_str(&result_str).unwrap(); 306 | assert_eq!(expected, result); 307 | } 308 | 309 | #[test] 310 | /// Just test that it has all the methods. 311 | fn sanity_ser() { 312 | let mut path = PathSer::from("example/path"); 313 | assert_eq!( 314 | path.join("joined").as_path(), 315 | Path::new("example/path/joined") 316 | ); 317 | assert_eq!(path.is_absolute(), false); 318 | 319 | path.append("appended").unwrap(); 320 | assert_eq!(path.as_path(), Path::new("example/path/appended")); 321 | path.pop_up().unwrap(); 322 | assert_eq!(path.as_path(), Path::new("example/path")); 323 | 324 | assert_eq!( 325 | path.concat("/concated").unwrap().as_path(), 326 | Path::new("example/path/concated") 327 | ); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/ty.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | use std::ffi; 9 | use std_prelude::*; 10 | 11 | use super::Result; 12 | use super::{PathAbs, PathDir, PathFile, PathInfo, PathOps}; 13 | 14 | #[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] 15 | #[cfg_attr( 16 | feature = "serialize", 17 | serde(tag = "type", content = "path", rename_all = "lowercase") 18 | )] 19 | #[derive(Debug, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] 20 | /// An an enum containing either a file or a directory. 21 | /// 22 | /// This is used primarily for: 23 | /// - The items returned from `PathDir::list` 24 | /// - Serializing paths of different types. 25 | /// 26 | /// Note that for symlinks, this returns the underlying file type. 27 | pub enum PathType { 28 | File(PathFile), 29 | Dir(PathDir), 30 | } 31 | 32 | impl PathType { 33 | /// Resolves and returns the `PathType` of the given path. 34 | /// 35 | /// > If the path exists but is not a file or a directory (i.e. is a symlink), then 36 | /// > `io::ErrorKind::InvalidInput` is returned. 37 | /// 38 | /// # Examples 39 | /// ```rust 40 | /// # extern crate path_abs; 41 | /// use path_abs::PathType; 42 | /// 43 | /// # fn try_main() -> ::std::io::Result<()> { 44 | /// let src = PathType::new("src")?; 45 | /// # Ok(()) } fn main() { try_main().unwrap() } 46 | pub fn new>(path: P) -> Result { 47 | let abs = PathAbs::new(&path)?; 48 | PathType::try_from(abs) 49 | } 50 | 51 | /// Consume the `PathAbs` returning the `PathType`. 52 | pub fn try_from>(path: P) -> Result { 53 | let abs = path.into(); 54 | let ty = abs.metadata()?.file_type(); 55 | if ty.is_file() { 56 | Ok(PathType::File(PathFile(abs))) 57 | } else if ty.is_dir() { 58 | Ok(PathType::Dir(PathDir(abs))) 59 | } else { 60 | unreachable!("rust docs: The fs::metadata function follows symbolic links") 61 | } 62 | } 63 | 64 | /// Unwrap the `PathType` as a `PathFile`. 65 | /// 66 | /// # Examples 67 | /// ```rust 68 | /// # extern crate path_abs; 69 | /// use path_abs::PathType; 70 | /// 71 | /// # fn try_main() -> ::std::io::Result<()> { 72 | /// let lib = PathType::new("src/lib.rs")?.unwrap_file(); 73 | /// # Ok(()) } fn main() { try_main().unwrap() } 74 | pub fn unwrap_file(self) -> PathFile { 75 | match self { 76 | PathType::File(f) => f, 77 | PathType::Dir(d) => { 78 | panic!("unwrap_file called on {}, which is not a file", d.display()) 79 | } 80 | } 81 | } 82 | 83 | /// Unwrap the `PathType` as a `PathDir`. 84 | /// 85 | /// # Examples 86 | /// ```rust 87 | /// # extern crate path_abs; 88 | /// use path_abs::PathType; 89 | /// 90 | /// # fn try_main() -> ::std::io::Result<()> { 91 | /// let src = PathType::new("src")?.unwrap_dir(); 92 | /// # Ok(()) } fn main() { try_main().unwrap() } 93 | pub fn unwrap_dir(self) -> PathDir { 94 | match self { 95 | PathType::Dir(d) => d, 96 | PathType::File(f) => panic!( 97 | "unwrap_dir called on {}, which is not a directory", 98 | f.display() 99 | ), 100 | } 101 | } 102 | 103 | /// Return whether this variant is `PathType::Dir`. 104 | pub fn is_dir(&self) -> bool { 105 | if let PathType::Dir(_) = *self { 106 | true 107 | } else { 108 | false 109 | } 110 | } 111 | 112 | /// Return whether this variant is `PathType::File`. 113 | pub fn is_file(&self) -> bool { 114 | if let PathType::File(_) = *self { 115 | true 116 | } else { 117 | false 118 | } 119 | } 120 | } 121 | 122 | impl AsRef for PathType { 123 | fn as_ref(&self) -> &std::ffi::OsStr { 124 | self.as_path().as_ref() 125 | } 126 | } 127 | 128 | impl AsRef for PathType { 129 | fn as_ref(&self) -> &PathAbs { 130 | match *self { 131 | PathType::File(ref file) => file.as_ref(), 132 | PathType::Dir(ref dir) => dir.as_ref(), 133 | } 134 | } 135 | } 136 | 137 | impl AsRef for PathType { 138 | fn as_ref(&self) -> &Path { 139 | let r: &PathAbs = self.as_ref(); 140 | r.as_ref() 141 | } 142 | } 143 | 144 | impl AsRef for PathType { 145 | fn as_ref(&self) -> &PathBuf { 146 | let r: &PathAbs = self.as_ref(); 147 | r.as_ref() 148 | } 149 | } 150 | 151 | impl Borrow for PathType { 152 | fn borrow(&self) -> &PathAbs { 153 | self.as_ref() 154 | } 155 | } 156 | 157 | impl Borrow for PathType { 158 | fn borrow(&self) -> &Path { 159 | self.as_ref() 160 | } 161 | } 162 | 163 | impl Borrow for PathType { 164 | fn borrow(&self) -> &PathBuf { 165 | self.as_ref() 166 | } 167 | } 168 | 169 | impl<'a> Borrow for &'a PathType { 170 | fn borrow(&self) -> &PathAbs { 171 | self.as_ref() 172 | } 173 | } 174 | 175 | impl<'a> Borrow for &'a PathType { 176 | fn borrow(&self) -> &Path { 177 | self.as_ref() 178 | } 179 | } 180 | 181 | impl<'a> Borrow for &'a PathType { 182 | fn borrow(&self) -> &PathBuf { 183 | self.as_ref() 184 | } 185 | } 186 | 187 | impl From for PathAbs { 188 | fn from(path: PathType) -> PathAbs { 189 | match path { 190 | PathType::File(p) => p.into(), 191 | PathType::Dir(p) => p.into(), 192 | } 193 | } 194 | } 195 | 196 | impl From for Arc { 197 | fn from(path: PathType) -> Arc { 198 | let abs: PathAbs = path.into(); 199 | abs.into() 200 | } 201 | } 202 | 203 | impl From for PathBuf { 204 | fn from(path: PathType) -> PathBuf { 205 | let abs: PathAbs = path.into(); 206 | abs.into() 207 | } 208 | } 209 | 210 | impl PathOps for PathType { 211 | type Output = PathAbs; 212 | 213 | fn concat>(&self, path: P) -> Result { 214 | match self { 215 | PathType::File(p) => p.concat(path), 216 | PathType::Dir(p) => p.concat(path), 217 | } 218 | } 219 | 220 | fn join>(&self, path: P) -> Self::Output { 221 | let buf = Path::join(self.as_path(), path); 222 | Self::Output::new_unchecked(buf) 223 | } 224 | 225 | fn with_file_name>(&self, file_name: S) -> Self::Output { 226 | match self { 227 | PathType::File(p) => p.with_file_name(file_name), 228 | PathType::Dir(p) => p.with_file_name(file_name), 229 | } 230 | } 231 | 232 | fn with_extension>(&self, extension: S) -> Self::Output { 233 | match self { 234 | PathType::File(p) => p.with_extension(extension), 235 | PathType::Dir(p) => p.with_extension(extension), 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/write.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Open write-only file paths and associated methods. 9 | 10 | use std::fmt; 11 | use std::fs; 12 | use std::io; 13 | use std_prelude::*; 14 | 15 | use super::open::FileOpen; 16 | use super::{Error, PathAbs, PathFile, PathInfo, Result}; 17 | 18 | /// A write-only file handle with `path()` attached and improved error messages. Contains only the 19 | /// methods and trait implementations which are allowed by a write-only file. 20 | /// 21 | /// # Examples 22 | /// ```rust 23 | /// # extern crate path_abs; 24 | /// # extern crate tempfile; 25 | /// use std::io::Write; 26 | /// use path_abs::{PathFile, FileWrite}; 27 | /// 28 | /// # fn try_main() -> ::std::io::Result<()> { 29 | /// let example = "example.txt"; 30 | /// # let tmp = tempfile::TempDir::new()?; 31 | /// # let example = &tmp.path().join(example); 32 | /// 33 | /// let expected = "foo\nbar"; 34 | /// let mut write = FileWrite::create(example)?; 35 | /// write.write_all(expected.as_bytes())?; 36 | /// write.flush(); 37 | /// 38 | /// let file = PathFile::new(example)?; 39 | /// assert_eq!(expected, file.read_string()?); 40 | /// # Ok(()) } fn main() { try_main().unwrap() } 41 | /// ``` 42 | pub struct FileWrite(pub(crate) FileOpen); 43 | 44 | impl FileWrite { 45 | /// Open the file with the given `OpenOptions` but always sets `write` to true. 46 | pub fn open>(path: P, mut options: fs::OpenOptions) -> Result { 47 | options.write(true); 48 | Ok(FileWrite(FileOpen::open(path, options)?)) 49 | } 50 | 51 | /// Shortcut to open the file if the path is already absolute. 52 | pub(crate) fn open_abs>( 53 | path: P, 54 | mut options: fs::OpenOptions, 55 | ) -> Result { 56 | options.write(true); 57 | Ok(FileWrite(FileOpen::open_abs(path, options)?)) 58 | } 59 | 60 | /// Open the file in write-only mode, truncating it first if it exists and creating it 61 | /// otherwise. 62 | pub fn create>(path: P) -> Result { 63 | let mut options = fs::OpenOptions::new(); 64 | options.truncate(true); 65 | options.create(true); 66 | FileWrite::open(path, options) 67 | } 68 | 69 | /// Open the file for appending, creating it if it doesn't exist. 70 | pub fn open_append>(path: P) -> Result { 71 | let mut options = fs::OpenOptions::new(); 72 | options.append(true); 73 | options.create(true); 74 | FileWrite::open(path, options) 75 | } 76 | 77 | /// Open the file for editing (reading and writing) but do not create it 78 | /// if it doesn't exist. 79 | pub fn open_edit>(path: P) -> Result { 80 | let mut options = fs::OpenOptions::new(); 81 | options.read(true); 82 | FileWrite::open(path, options) 83 | } 84 | 85 | pub fn path(&self) -> &PathFile { 86 | &self.0.path 87 | } 88 | 89 | /// Attempts to sync all OS-internal metadata to disk. 90 | /// 91 | /// This function will attempt to ensure that all in-core data reaches the filesystem before 92 | /// returning. 93 | /// 94 | /// This function is identical to [std::fs::File::sync_all][0] except it has error 95 | /// messages which include the action and the path. 96 | /// 97 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all 98 | pub fn sync_all(&self) -> Result<()> { 99 | self.0 100 | .file 101 | .sync_all() 102 | .map_err(|err| Error::new(err, "syncing", self.0.path.clone().into())) 103 | } 104 | 105 | /// This function is similar to sync_all, except that it may not synchronize file metadata to 106 | /// the filesystem. 107 | /// 108 | /// This function is identical to [std::fs::File::sync_data][0] except it has error 109 | /// messages which include the action and the path. 110 | /// 111 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_data 112 | pub fn sync_data(&self) -> Result<()> { 113 | self.0 114 | .file 115 | .sync_data() 116 | .map_err(|err| Error::new(err, "syncing data for", self.0.path.clone().into())) 117 | } 118 | 119 | /// Truncates or extends the underlying file, updating the size of this file to become size. 120 | /// 121 | /// This function is identical to [std::fs::File::set_len][0] except: 122 | /// 123 | /// - It has error messages which include the action and the path. 124 | /// - It takes `&mut self` instead of `&self`. 125 | /// 126 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len 127 | pub fn set_len(&mut self, size: u64) -> Result<()> { 128 | self.0 129 | .file 130 | .set_len(size) 131 | .map_err(|err| Error::new(err, "setting len for", self.0.path.clone().into())) 132 | } 133 | 134 | /// Changes the permissions on the underlying file. 135 | /// 136 | /// This function is identical to [std::fs::File::set_permissions][0] except: 137 | /// 138 | /// - It has error messages which include the action and the path. 139 | /// - It takes `&mut self` instead of `&self`. 140 | /// 141 | /// [0]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_permissions 142 | pub fn set_permissions(&mut self, perm: fs::Permissions) -> Result<()> { 143 | self.0 144 | .file 145 | .set_permissions(perm) 146 | .map_err(|err| Error::new(err, "setting permisions for", self.0.path.clone().into())) 147 | } 148 | 149 | /// Shortcut to `self.write_all(s.as_bytes())` with slightly 150 | /// improved error message. 151 | pub fn write_str(&mut self, s: &str) -> Result<()> { 152 | self.0 153 | .file 154 | .write_all(s.as_bytes()) 155 | .map_err(|err| Error::new(err, "writing", self.0.path.clone().into())) 156 | } 157 | 158 | /// `std::io::File::flush` buth with the new error type. 159 | pub fn flush(&mut self) -> Result<()> { 160 | self.0 161 | .file 162 | .flush() 163 | .map_err(|err| Error::new(err, "flushing", self.0.path.clone().into())) 164 | } 165 | } 166 | 167 | impl fmt::Debug for FileWrite { 168 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 169 | write!(f, "FileWrite(")?; 170 | self.0.path.fmt(f)?; 171 | write!(f, ")") 172 | } 173 | } 174 | 175 | impl io::Write for FileWrite { 176 | fn write(&mut self, buf: &[u8]) -> io::Result { 177 | self.0.file.write(buf).map_err(|err| { 178 | io::Error::new( 179 | err.kind(), 180 | format!("{} when writing to {}", err, self.path().display()), 181 | ) 182 | }) 183 | } 184 | 185 | fn flush(&mut self) -> io::Result<()> { 186 | self.0.file.flush().map_err(|err| { 187 | io::Error::new( 188 | err.kind(), 189 | format!("{} when flushing {}", err, self.path().display()), 190 | ) 191 | }) 192 | } 193 | } 194 | 195 | impl io::Seek for FileWrite { 196 | fn seek(&mut self, pos: io::SeekFrom) -> io::Result { 197 | self.0.file.seek(pos).map_err(|err| { 198 | io::Error::new( 199 | err.kind(), 200 | format!("{} seeking {}", err, self.path().display()), 201 | ) 202 | }) 203 | } 204 | } 205 | 206 | impl AsRef for FileWrite { 207 | fn as_ref(&self) -> &FileOpen { 208 | &self.0 209 | } 210 | } 211 | 212 | impl AsRef for FileWrite { 213 | fn as_ref(&self) -> &File { 214 | self.0.as_ref() 215 | } 216 | } 217 | 218 | impl Borrow for FileWrite { 219 | fn borrow(&self) -> &FileOpen { 220 | &self.0 221 | } 222 | } 223 | 224 | impl Borrow for FileWrite { 225 | fn borrow(&self) -> &File { 226 | self.0.borrow() 227 | } 228 | } 229 | 230 | impl<'a> Borrow for &'a FileWrite { 231 | fn borrow(&self) -> &FileOpen { 232 | &self.0 233 | } 234 | } 235 | 236 | impl<'a> Borrow for &'a FileWrite { 237 | fn borrow(&self) -> &File { 238 | self.0.borrow() 239 | } 240 | } 241 | 242 | impl From for FileOpen { 243 | fn from(orig: FileWrite) -> FileOpen { 244 | orig.0 245 | } 246 | } 247 | 248 | impl From for File { 249 | fn from(orig: FileWrite) -> File { 250 | orig.0.into() 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/absolute_extended_cwd.rs: -------------------------------------------------------------------------------- 1 | //! This file tests PathAbs::new() for Windows when the current directory 2 | //! uses extended-length path syntax (like `\\?\C:\`). 3 | 4 | // These tests are already run for Unix in absolute_regular_cwd.rs, and Unix 5 | // doesn't have "extended-length path syntax", so we can make them Windows-only 6 | // here. 7 | #[cfg(windows)] 8 | mod absolute_helpers; 9 | 10 | #[cfg(windows)] 11 | fn setup() { 12 | std::env::set_current_dir(r"\\?\C:\").expect("Could not change to a regular directory"); 13 | } 14 | -------------------------------------------------------------------------------- /tests/absolute_helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io; 4 | use std::path; 5 | 6 | use path_abs::PathAbs; 7 | use path_abs::PathInfo; 8 | 9 | use tempfile::TempDir; 10 | 11 | fn symlink_dir(src: P, dst: Q) 12 | where 13 | P: AsRef, 14 | Q: AsRef, 15 | { 16 | #[cfg(windows)] 17 | { 18 | use std::os::windows::fs as winfs; 19 | 20 | let dst = dst.as_ref(); 21 | 22 | winfs::symlink_dir(src, &dst).expect( 23 | "Could not create symbolic link. \ 24 | Run as Administrator, or on Windows 10 in Developer Mode", 25 | ); 26 | dst.symlink_metadata().expect( 27 | "Link creation succeeded, but can't read link? \ 28 | If you're using Wine, see bug 44948", 29 | ); 30 | } 31 | 32 | #[cfg(unix)] 33 | { 34 | use std::os::unix::fs as unixfs; 35 | 36 | unixfs::symlink(src, dst).expect("Could not create symbolic link"); 37 | } 38 | 39 | #[cfg(all(not(windows), not(unix)))] 40 | unreachable!(); 41 | } 42 | 43 | #[test] 44 | fn absolute_path_is_idempotent() { 45 | crate::setup(); 46 | // The current_dir() result is always absolute, 47 | // so absolutizing it should not change it. 48 | 49 | let actual = PathAbs::new(env::current_dir().unwrap()).unwrap(); 50 | let expected = env::current_dir().unwrap().canonicalize().unwrap(); 51 | 52 | assert_eq!(actual.as_path(), expected.as_path()); 53 | } 54 | 55 | #[test] 56 | fn absolute_path_removes_currentdir_component() { 57 | crate::setup(); 58 | let actual = PathAbs::new("foo/./bar").unwrap(); 59 | let expected = PathAbs::new("foo/bar").unwrap(); 60 | 61 | assert_eq!(actual, expected); 62 | } 63 | 64 | #[test] 65 | fn absolute_path_removes_empty_component() { 66 | crate::setup(); 67 | let actual = PathAbs::new("foo//bar").unwrap(); 68 | let expected = PathAbs::new("foo/bar").unwrap(); 69 | 70 | assert_eq!(actual, expected); 71 | } 72 | 73 | #[test] 74 | fn absolute_path_lexically_resolves_parentdir_component() { 75 | crate::setup(); 76 | let tmp_dir = TempDir::new().unwrap(); 77 | let a_dir = tmp_dir.path().join("a"); 78 | fs::create_dir_all(&a_dir).unwrap(); 79 | 80 | let b_dir = tmp_dir.path().join("b"); 81 | fs::create_dir_all(&b_dir).unwrap(); 82 | 83 | fs::create_dir_all(&b_dir.join("target")).unwrap(); 84 | 85 | let link_path = a_dir.join("link"); 86 | symlink_dir("../b/target", link_path); 87 | 88 | // Because of the symlink, a/link/../foo is actually b/foo, but 89 | // lexically resolving the path produces a/foo. 90 | let actual = PathAbs::new(a_dir.join("link/../foo")).unwrap(); 91 | let expected = PathAbs::new(a_dir.join("foo")).unwrap(); 92 | 93 | assert_eq!(actual, expected); 94 | } 95 | 96 | #[test] 97 | fn absolute_path_interprets_relative_to_current_directory() { 98 | crate::setup(); 99 | let actual = PathAbs::new("foo").unwrap(); 100 | let expected = PathAbs::new(env::current_dir().unwrap().join("foo")).unwrap(); 101 | 102 | assert_eq!(actual, expected); 103 | } 104 | 105 | #[cfg(unix)] 106 | mod unix { 107 | use super::*; 108 | use path_abs::PathInfo; 109 | 110 | #[test] 111 | fn absolute_path_need_not_exist() { 112 | crate::setup(); 113 | 114 | // It's not likely this path would exist, but let's be sure. 115 | let raw_path = path::Path::new("/does/not/exist"); 116 | assert_eq!( 117 | raw_path.metadata().unwrap_err().kind(), 118 | io::ErrorKind::NotFound, 119 | ); 120 | 121 | let path = PathAbs::new(raw_path).unwrap(); 122 | 123 | assert_eq!(path.as_os_str(), "/does/not/exist"); 124 | } 125 | 126 | #[test] 127 | fn absolute_path_cannot_go_above_root() { 128 | crate::setup(); 129 | let err = PathAbs::new("/foo/../..").unwrap_err(); 130 | 131 | assert_eq!(err.io_error().kind(), io::ErrorKind::NotFound); 132 | assert_eq!(err.io_error().to_string(), ".. consumed root"); 133 | assert_eq!(err.action(), "resolving absolute"); 134 | assert_eq!(err.path(), path::Path::new("/foo/../..")); 135 | } 136 | } 137 | 138 | #[cfg(windows)] 139 | mod windows { 140 | use super::*; 141 | 142 | #[test] 143 | fn absolute_path_need_not_exist() { 144 | crate::setup(); 145 | 146 | // It's not likely this path would exist, but let's be sure. 147 | let raw_path = path::Path::new(r"C:\does\not\exist"); 148 | assert_eq!( 149 | raw_path.metadata().unwrap_err().kind(), 150 | io::ErrorKind::NotFound, 151 | ); 152 | 153 | let path = PathAbs::new(raw_path).unwrap(); 154 | assert_eq!(path.as_os_str(), r"\\?\C:\does\not\exist"); 155 | } 156 | 157 | #[test] 158 | fn absolute_path_cannot_go_above_root() { 159 | crate::setup(); 160 | let err = PathAbs::new(r"C:\foo\..\..").unwrap_err(); 161 | 162 | assert_eq!(err.io_error().kind(), io::ErrorKind::NotFound); 163 | assert_eq!(err.io_error().to_string(), ".. consumed root"); 164 | assert_eq!(err.action(), "resolving absolute"); 165 | assert_eq!(err.path(), path::Path::new(r"C:\foo\..\..")); 166 | } 167 | 168 | #[test] 169 | fn absolute_supports_root_only_relative_path() { 170 | crate::setup(); 171 | let actual = PathAbs::new(r"\foo").unwrap(); 172 | 173 | let mut current_drive_root = path::PathBuf::new(); 174 | current_drive_root.extend( 175 | env::current_dir().unwrap().components().take(2), // the prefix (C:) and root (\) components 176 | ); 177 | 178 | let expected = PathAbs::new(current_drive_root.join("foo")).unwrap(); 179 | 180 | assert_eq!(actual, expected); 181 | } 182 | 183 | #[test] 184 | fn absolute_supports_prefix_only_relative_path() { 185 | crate::setup(); 186 | let actual = PathAbs::new(r"C:foo").unwrap(); 187 | 188 | let expected = 189 | PathAbs::new(path::Path::new(r"C:").canonicalize().unwrap().join("foo")).unwrap(); 190 | 191 | assert_eq!(actual, expected); 192 | } 193 | 194 | #[test] 195 | fn absolute_accepts_bogus_prefix() { 196 | crate::setup(); 197 | let path = PathAbs::new(r"\\?\bogus\path\").unwrap(); 198 | 199 | assert_eq!(path.as_os_str(), r"\\?\bogus\path"); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /tests/absolute_regular_cwd.rs: -------------------------------------------------------------------------------- 1 | mod absolute_helpers; 2 | 3 | fn setup() { 4 | #[cfg(windows)] 5 | std::env::set_current_dir(r"C:\").expect("Could not change to a regular directory"); 6 | 7 | // For cfg(unix), we're always in a regular directory, so we don't need to 8 | // do anything special. 9 | } 10 | -------------------------------------------------------------------------------- /tests/test_absolute.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! The absolute paths have some gotchas that need to be tested. 9 | //! 10 | //! - Using the current working directory 11 | //! - `..` paths that consume the "root" 12 | 13 | #[macro_use] 14 | extern crate pretty_assertions; 15 | use tempfile; 16 | 17 | use path_abs::*; 18 | use std::env; 19 | use std::io; 20 | use std::path::{Path, PathBuf}; 21 | 22 | #[test] 23 | fn test_absolute() { 24 | if cfg!(windows) { 25 | let result = Path::new(r"\").canonicalize(); 26 | assert!( 27 | result.is_ok(), 28 | "Should work before set_current_dir is called: {:?}", 29 | result 30 | ); 31 | } 32 | let tmp = tempfile::TempDir::new().unwrap(); 33 | let tmp = tmp.path(); 34 | let tmp_abs = PathAbs::new(&tmp).unwrap(); 35 | env::set_current_dir(&tmp_abs).unwrap(); 36 | if cfg!(windows) { 37 | let result = Path::new(r"\").canonicalize(); 38 | assert!(result.is_err()); 39 | println!("Got ERR cananonicalizing root: {}", result.unwrap_err()); 40 | } 41 | 42 | // Create directory like: 43 | // a/ 44 | // + e/ -> b/c/d 45 | // + b/ 46 | // + c/ 47 | // + d/ 48 | 49 | let a = PathDir::create(&tmp.join("a")).unwrap(); 50 | let b = PathDir::create(&a.concat("b").unwrap()).unwrap(); 51 | let c = PathDir::create(&b.concat("c").unwrap()).unwrap(); 52 | let d = PathDir::create(&c.concat("d").unwrap()).unwrap(); 53 | 54 | // create symbolic link from a/e -> a/b/c/d 55 | let e_sym = d.symlink(&a.concat("e").unwrap()).unwrap(); 56 | let ty = e_sym.symlink_metadata().unwrap().file_type(); 57 | assert!(ty.is_symlink(), "{}", e_sym.display()); 58 | 59 | assert_ne!(d, e_sym); 60 | assert_eq!(d, e_sym.canonicalize().unwrap()); 61 | 62 | let a_cwd = Path::new("a"); 63 | let b_cwd = a.concat("b").unwrap(); 64 | let c_cwd = b.concat("c").unwrap(); 65 | let d_cwd = c.concat("d").unwrap(); 66 | let e_cwd = a.concat("e").unwrap(); 67 | 68 | assert_eq!(a, PathDir::new(&a_cwd).unwrap()); 69 | assert_eq!(b, PathDir::new(&b_cwd).unwrap()); 70 | assert_eq!(c, PathDir::new(&c_cwd).unwrap()); 71 | assert_eq!(d, PathDir::new(&d_cwd).unwrap()); 72 | assert_eq!(e_sym, PathDir::new(&e_cwd).unwrap()); 73 | 74 | assert_eq!(b, PathDir::new(c.concat("..").unwrap()).unwrap()); 75 | assert_eq!( 76 | a, 77 | PathDir::new(c.concat("..").unwrap().concat("..").unwrap()).unwrap() 78 | ); 79 | // just create a PathType 80 | let _ = PathType::new(&e_sym).unwrap(); 81 | 82 | let mut root_dots: PathBuf = tmp_abs.clone().into(); 83 | let mut dots = tmp_abs.components().count() - 1; 84 | if cfg!(windows) { 85 | // windows has _two_ "roots", prefix _and_ "root". 86 | dots -= 1; 87 | } 88 | for _ in 0..dots { 89 | root_dots.push(".."); 90 | } 91 | let root = PathDir::new(root_dots).unwrap(); 92 | if cfg!(windows) { 93 | assert_eq!(PathDir::new("\\").unwrap(), root); 94 | } else { 95 | assert_eq!(PathDir::new("/").unwrap(), root); 96 | } 97 | assert!(root.concat("..").is_err()); 98 | 99 | if cfg!(windows) { 100 | // Test that /-separated and \-separated paths can be joined 101 | let ac1 = a.concat(r"b/c").unwrap(); 102 | assert!(ac1.metadata().is_ok()); 103 | 104 | let ac2 = a.concat(r"b\c").unwrap(); 105 | assert!(ac2.metadata().is_ok()); 106 | } 107 | } 108 | 109 | /// Check that issue #34 is fixed 110 | /// 111 | /// After calling join(), the metadata are accessed to check that the computed path is valid. 112 | #[test] 113 | fn test_forward_and_backward_slashes() { 114 | let tmp = tempfile::TempDir::new().unwrap(); 115 | let tmp = tmp.path(); 116 | 117 | // Create directories: 118 | // a/ 119 | // + b/ 120 | // + c/ 121 | let a = PathDir::create(&tmp.join("a")).unwrap(); 122 | let b = PathDir::create(&a.concat("b").unwrap()).unwrap(); 123 | let c = PathDir::create(&b.concat("c").unwrap()).unwrap(); 124 | 125 | let a_abs = PathAbs::new(a).unwrap(); 126 | 127 | // Join /-separated relative path and check that the metadata are accessible 128 | let forward_slash = a_abs.concat(r"b/c").unwrap(); 129 | assert!(forward_slash.metadata().is_ok()); 130 | assert_eq!(c, PathDir::new(forward_slash).unwrap()); 131 | 132 | // Join \-separated relative path and check that the metadata are accessible 133 | // The following test only make sense on windows because the \ character isn't illegal in a 134 | // directory name on linux, so the call to `concat(r"b\c")` would just add the single directory 135 | // named "b\c" 136 | if cfg!(windows) { 137 | let backward_slash = a_abs.concat(r"b\c").unwrap(); 138 | assert!(backward_slash.metadata().is_ok()); 139 | assert_eq!(c, PathDir::new(backward_slash).unwrap()); 140 | } 141 | } 142 | 143 | #[test] 144 | fn test_root_parent() { 145 | let actual = PathAbs::new("/a/../..").expect_err("Can go outside of `/`?"); 146 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 147 | assert_eq!(actual.action(), "resolving absolute"); 148 | assert_eq!(actual.path(), Path::new(r"/a/../..")); 149 | } 150 | 151 | #[cfg_attr(windows, test)] 152 | fn _test_root_parent_windows() { 153 | let actual = PathAbs::new(r"\a\..\..").expect_err(r"Can go outside of \?"); 154 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 155 | assert_eq!(actual.action(), "resolving absolute"); 156 | assert_eq!(actual.path(), Path::new(r"/a/../..")); 157 | 158 | let actual = PathAbs::new(r"C:\a\..\..").expect_err(r"Can go outside of C:\?"); 159 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 160 | assert_eq!(actual.action(), "resolving absolute"); 161 | assert_eq!(actual.path(), Path::new(r"C:\a\..\..")); 162 | 163 | let actual = PathAbs::new(r"\\?\C:\a\..\..").expect_err(r"Can go outside of \\?\C:\?"); 164 | assert_eq!(actual.io_error().kind(), io::ErrorKind::NotFound); 165 | assert_eq!(actual.action(), "resolving absolute"); 166 | assert_eq!(actual.path(), Path::new(r"\\?\C:\a\..\..")); 167 | } 168 | -------------------------------------------------------------------------------- /tests/test_windows.rs: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com 2 | * 3 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | * copied, modified, or distributed except according to those terms. 7 | */ 8 | //! Test assumptions about windows 9 | #![cfg_attr(not(windows), allow(dead_code))] 10 | 11 | #[macro_use] 12 | extern crate pretty_assertions; 13 | 14 | use std::path::Path; 15 | use std::process::Command; 16 | 17 | macro_rules! expect_err { 18 | [$s:expr] => {{ 19 | let p = Path::new($s); 20 | match p.canonicalize() { 21 | Ok(p) => { 22 | panic!("Got {:?} when canonicalizing {:?}, expected err", p, $s); 23 | } 24 | Err(err) => { 25 | println!("EXPECTED ERR Canonicalizing {:?} => {}", $s, err); 26 | } 27 | } 28 | }} 29 | } 30 | 31 | macro_rules! expect_path { 32 | [$expect:expr, $s:expr] => {{ 33 | let expect = Path::new($expect); 34 | let p = Path::new($s); 35 | match p.canonicalize() { 36 | Ok(p) => { 37 | assert_eq!(expect, p); 38 | println!("EXPECTED OK Canonicalizing {:?} => {:?}", $s, p); 39 | } 40 | Err(err) => { 41 | panic!("Got {:?} when canonicalizing {:?}, expected {:?}", err, $s, $expect); 42 | } 43 | } 44 | }} 45 | } 46 | 47 | fn share() -> String { 48 | // http://www.tech-recipes.com/rx/2953/windows_list_shared_drives_folders_command_line/ 49 | if cfg!(windows) { 50 | let shared = Command::new("wmic") 51 | .arg("share") 52 | .arg("get") 53 | .arg("caption,name,path") 54 | .output() 55 | .expect("could not `wmic share`") 56 | .stdout; 57 | let out = ::std::str::from_utf8(&shared).unwrap().trim().to_string(); 58 | println!("### SHARED:\n{}\n###", out); 59 | out 60 | } else { 61 | "NONE SHARED".to_string() 62 | } 63 | } 64 | 65 | fn hostname() -> String { 66 | let hostname = Command::new("hostname") 67 | .output() 68 | .expect("could not get hostname") 69 | .stdout; 70 | let out = ::std::str::from_utf8(&hostname).unwrap().trim().to_string(); 71 | println!("HOSTNAME: {}", out); 72 | out 73 | } 74 | 75 | // TODO: doesn't work, can't get coms 76 | // fn coms() -> String { 77 | // let coms = Command::new("mode") 78 | // .output() 79 | // .expect("could not get `mode` comports") 80 | // .stdout; 81 | // let out = ::std::str::from_utf8(&coms).unwrap().trim().to_string(); 82 | // println!("### COMS:\n{}\n###", out); 83 | // out 84 | // } 85 | 86 | // TODO: I don't know what is even a valid verbatum path, and I can't list it directly 87 | // ERROR: "The filename, directory name, or volume label syntax is incorrect." 88 | // 89 | // #[cfg_attr(windows, test)] 90 | // fn canonicalize_verbatim() { 91 | // // CURRENT DIR: C:\projects\path-abs 92 | // println!("CURRENT DIR: {}", ::std::env::current_dir().unwrap().display()); 93 | // 94 | // let verbatim_root = Path::new(r"\\?\"); 95 | // let list: Vec<_> = ::std::fs::read_dir(verbatim_root).unwrap().collect(); 96 | // println!("LIST VERBATIM: {:?}", list); 97 | // 98 | // // TODO: 99 | // // EXPECTED ERR Canonicalizing "\\\\?\\projects" => The system cannot find the file specified. 100 | // // (os error 2) 101 | // expect_err!(r"\\?\projects"); 102 | // } 103 | 104 | #[cfg_attr(windows, test)] 105 | fn canonicalize_verbatim_unc() { 106 | // HOSTNAME: APPVYR-WIN 107 | // ### SHARED: 108 | // Caption Name Path 109 | // Remote Admin ADMIN$ C:\windows 110 | // Default share C$ C:\ 111 | // Remote IPC IPC$ 112 | // ### 113 | 114 | // TODO: Only works on Windows hosts with the default administrative 115 | // file shares enabled. 116 | let _ = share(); // FIXME: just printing for now 117 | let p = format!(r"\\?\UNC\{}\C$", hostname()); 118 | expect_path!(&p, &p); 119 | } 120 | 121 | #[cfg_attr(windows, test)] 122 | fn canonicalize_verbatim_disk() { 123 | let with_root = r"\\?\C:\"; 124 | expect_path!(with_root, with_root); 125 | 126 | // EXPECTED ERR Canonicalizing "\\\\?\\C:" => Incorrect function. (os error 1) 127 | expect_err!(r"\\?\C:") 128 | } 129 | 130 | // TODO: can't list COMS 131 | // #[cfg_attr(windows, test)] 132 | // fn canonicalize_device_ns() { 133 | // // TODO: EXPECTED ERR Canonicalizing "\\\\.\\com1" => The system cannot find the file 134 | // // specified. (os error 2) 135 | // let _ = coms(); 136 | // expect_err!(r"\\.\COM1") 137 | // } 138 | 139 | #[cfg_attr(windows, test)] 140 | fn canonicalize_unc() { 141 | // TODO: Only works on Windows hosts with the default administrative 142 | // file shares enabled. 143 | let h = hostname(); 144 | let unc = format!(r"\\{}\C$", h); 145 | let verbatim = format!(r"\\?\UNC\{}\C$", h); 146 | let result = Path::new(&unc).canonicalize().unwrap(); 147 | assert_eq!(Path::new(&verbatim), result); 148 | } 149 | 150 | #[cfg_attr(windows, test)] 151 | fn canonicalize_disk() { 152 | expect_path!(r"\\?\C:\", r"C:\") 153 | } 154 | --------------------------------------------------------------------------------