1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
/* Copyright (c) 2018 Garrett Berg, vitiral@gmail.com * * Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or * http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or * http://opensource.org/licenses/MIT>, at your option. This file may not be * copied, modified, or distributed except according to those terms. */ //! Ergonomic paths and files in rust. //! //! This library aims to provide ergonomic path and file operations to rust with reasonable //! performance. //! //! This includes: //! //! - Cleaner _absolute_ paths (which is distinct from canonicalized paths). //! - Improved error messages, see the **Better Errors** section. //! - Improved type safety. The types specify that a file/dir _once_ existed and was _once_ a certain //! type. Obviously a file/dir can be deleted/changed by another process. //! - More stringent mutability requirements. See the **Differing Method Signatures** section. //! - Cheap cloning: all path types are `Arc`, which a cheap operation compared to filesystem //! operations and allows more flexibility and ergonomics in the library for relatively low cost. //! //! ## Better Errors //! //! All errors include the **path** and **action** which caused the error, as well as the unaltered //! `std::io::Error` message. Errors are convertable into `std::io::Error`, giving almost complete //! compatibility with existing code. //! //! ### `set_len` (i.e. truncate a file): //! //! - [`/* */ std::fs::File::set_len(0)`][file_set_len]: `Invalid argument (os error 22)` //! - [`path_abs::FileWrite::set_len(0)`][path_set_len]: `Invalid argument (os error 22) when setting //! len for /path/to/example/foo.txt` //! //! > The above error is actually impossible because `FileWrite` is always writeable, and //! > `FileRead` does not implement `set_len`. However, it is kept for demonstration. //! //! ### `read` (open file for reading): //! //! - [`/**/ std::fs::File::read(path)`][file_read]: `No such file or directory (os error 2)` //! - [`path_abs::FileRead::read(path)`][path_read]: `No such file or directory (os error 2) when //! opening example/foo.txt` //! //! And every other method has similarily improved errors. If a method does not have pretty error //! messages please open a ticket. //! //! [file_set_len]: https://doc.rust-lang.org/std/fs/struct.File.html#method.set_len //! [file_read]: https://doc.rust-lang.org/std/fs/struct.File.html#method.read //! [path_set_len]: struct.FileWrite.html#method.set_len //! [path_read]: struct.FileRead.html#method.read //! //! //! ## Exported Path Types //! //! These are the exported Path types. All of them are absolute except for `PathArc`, which //! is just an `Arc<PathBuf>` with methods that have better error reporting. //! //! - [`PathArc`](struct.PathArc.html): a reference counted `PathBuf` with methods reimplemented //! with better error messages. Use this for a generic serializable path that may or may //! not exist. //! - [`PathAbs`](struct.PathAbs.html): a reference counted absolute (_not necessarily_ //! canonicalized) path that is not necessarily guaranteed to exist. //! - [`PathFile`](struct.PathFile.html): a `PathAbs` that is guaranteed (at instantiation) to //! exist and be a file, with associated methods. //! - [`PathDir`](struct.PathDir.html): a `PathAbs` that is guaranteed (at instantiation) to exist //! and be a directory, with associated methods. //! - [`PathType`](struct.PathType.html): an enum containing either a PathFile or a PathDir. //! Returned by [`PathDir::list`][dir_list] //! //! In addition, all paths are serializable through serde (even on windows!) by using the crate //! [`stfu8`](https://crates.io/crates/stfu8) to encode/decode, allowing ill-formed UTF-16. See //! that crate for more details on how the resulting encoding can be edited (by hand) even in the //! case of what *would be* ill-formed UTF-16. //! //! [dir_list]: struct.PathDir.html#method.list //! //! //! ## Exported File Types //! //! All File types provide _type safe_ access to their relevant traits. For instance, you can't //! `read` with a `FileWrite` and you can't `write` with a `FileRead`. //! //! - [`FileRead`](struct.FileRead.html): a read-only file handle with `path()` attached and //! improved error messages. Contains only the methods and trait implementations which are //! allowed by a read-only file. //! - [`FileWrite`](struct.FileWrite.html): a write-only file handle with `path()` attached and //! improved error messages. Contains only the methods and trait implementations which are //! allowed by a write-only file. //! - [`FileEdit`](struct.FileEdit.html): a read/write file handle with `path()` attached and //! improved error messages. Contains methods and trait implements for both readable _and_ //! writeable files. //! //! ### Differing Method Signatures //! //! The type signatures of the `File*` types regarding `read`, `write` and other methods is //! slightly different than `std::fs::File` -- they all take `&mut` instead of `&`. This is to //! avoid a [common possible footgun](https://github.com/rust-lang/rust/issues/47708). //! //! To demonstrate, imagine the following scenario: //! //! - You pass your open `&File` to a method, which puts it in a thread. This thread constantly //! calls `seek(SeekFrom::Start(10))` //! - You periodically read from a file expecting new data, but are always getting the same data. //! //! Yes, this is actually allowed by the rust compiler since `seek` is implemented for //! [`&File`](https://doc.rust-lang.org/std/fs/struct.File.html#impl-Seek-1). Technically this is //! still _memory safe_ since the operating system will handle any contention, however many would //! argue that it isn't _expected_ that an immutable reference passed to another //! function can affect the seek position of a file. //! //! //! # Examples //! Recreating `Cargo.init` in `example/` //! //! ```rust //! # extern crate path_abs; //! # extern crate tempdir; //! use std::path::Path; //! use std::collections::HashSet; //! use path_abs::{ //! PathAbs, // absolute path //! PathDir, // absolute path to a directory //! PathFile, // absolute path to a file //! PathType, // enum of Dir or File //! FileRead, // Open read-only file handler //! FileWrite, // Open write-only file handler //! FileEdit, // Open read/write file handler //! }; //! //! # fn try_main() -> ::std::io::Result<()> { //! let example = Path::new("example"); //! # let tmp = tempdir::TempDir::new("ex")?; //! # let example = &tmp.path().join(example); //! //! // Create your paths //! let project = PathDir::create_all(example)?; //! let src = PathDir::create(project.join("src"))?; //! let lib = PathFile::create(src.join("lib.rs"))?; //! let cargo = PathFile::create(project.join("Cargo.toml"))?; //! //! // Write the templates //! lib.write_str(r#" //! #[cfg(test)] //! mod tests { //! #[test] //! fn it_works() { //! assert_eq!(2 + 2, 4); //! } //! }"#)?; //! //! cargo.write_str(r#" //! [package] //! name = "example" //! version = "0.1.0" //! authors = ["Garrett Berg <vitiral@gmail.com>"] //! //! [dependencies] //! "#)?; //! //! // Put our result into a HashMap so we can assert it //! let mut result = HashSet::new(); //! for p in project.list()? { //! result.insert(p?); //! } //! //! // Create our expected value //! let mut expected = HashSet::new(); //! expected.insert(PathType::Dir(src)); //! expected.insert(PathType::File(cargo)); //! //! assert_eq!(expected, result); //! //! // ---------------------------------- //! // Creating types from existing paths //! //! // Creating a generic path //! let lib_path = example.join("src").join("lib.rs"); //! let abs = PathAbs::new(&lib_path)?; //! //! // Or a path with a known type //! let file = PathType::new(&lib_path) //! ? //! .unwrap_file(); //! //! // Or use `PathAbs::into_file` //! let file2 = abs.clone().into_file()?; //! //! assert!(abs.is_file()); //! assert!(file.is_file()); //! assert!(file2.is_file()); //! //! // ---------------------------------- //! // Opening a File //! //! // open read-only using the PathFile method //! let read = file.read()?; //! //! // Or use the type directly: open for appending //! let write = FileWrite::append(&file)?; //! //! // Open for read/write editing. //! let edit = file.edit()?; //! # Ok(()) } fn main() { try_main().unwrap() } //! ``` #[cfg(feature = "serialize")] extern crate serde; #[macro_use] #[cfg(feature = "serialize")] extern crate serde_derive; extern crate std_prelude; #[cfg(feature = "serialize")] extern crate stfu8; #[macro_use] #[cfg(test)] extern crate pretty_assertions; #[cfg(test)] extern crate regex; #[cfg(test)] extern crate serde_json; #[cfg(test)] extern crate tempdir; use std::io; use std::error; use std::fmt; use std_prelude::*; mod abs; mod arc; mod dir; mod edit; mod file; pub mod open; #[cfg(feature = "serialize")] mod ser; mod ty; mod write; mod read; pub use abs::PathAbs; pub use arc::{PathArc, current_dir}; pub use dir::{ListDir, PathDir}; pub use file::PathFile; pub use ty::PathType; pub use edit::FileEdit; pub use write::FileWrite; pub use read::FileRead; pub type Result<T> = ::std::result::Result<T, Error>; /// An error produced by performing an filesystem operation on a `Path`. /// /// This error type is a light wrapper around [`std::io::Error`]. In particular, it adds the /// following information: /// /// - The action being performed when the error occured /// - The path associated with the IO error. /// /// To maintain good ergonomics, this type has a `impl From<Error> for std::io::Error` defined so /// that you may use an [`io::Result`] with methods in this crate if you don't care about accessing /// the underlying error data in a structured form (the pretty format will be preserved however). /// /// [`std::io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html /// [`io::Result`]: https://doc.rust-lang.org/stable/std/io/type.Result.html /// /// # Examples /// ```rust /// use path_abs::Error as PathError; /// use path_abs::PathFile; /// /// /// main function, note that you can use `io::Error` /// fn try_main() -> Result<(), ::std::io::Error> { /// let lib = PathFile::new("src/lib.rs")?; /// Ok(()) /// } /// /// ``` pub struct Error { io_err: io::Error, action: String, path: PathArc, } impl Error { /// Create a new error when the path and action are known. pub fn new(io_err: io::Error, action: &str, path: PathArc) -> Error { Error { io_err: io_err, action: action.into(), path: path, } } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Error<{}>", self) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{} when {} {}", self.io_err, self.action, self.path.display() ) } } impl Error { /// Returns the path associated with this error. pub fn path(&self) -> &Path { self.path.as_ref() } /// Returns the `std::io::Error` associated with this errors. pub fn io_error(&self) -> &io::Error { &self.io_err } /// Returns the action being performed when this error occured. pub fn action(&self) -> &str { &self.action } } impl error::Error for Error { fn description(&self) -> &str { self.io_err.description() } fn cause(&self) -> Option<&error::Error> { Some(&self.io_err) } } impl From<Error> for io::Error { fn from(err: Error) -> io::Error { io::Error::new(err.io_err.kind(), err) } } #[cfg(test)] mod tests { use std::path::Path; use tempdir::TempDir; use regex::{self, Regex}; use super::*; macro_rules! assert_match { ($re: expr, $err: expr) => {{ let re = Regex::new(&$re).unwrap(); let err = $err.to_string(); assert!( re.is_match(&err), "\nGot Err : {:?}\nMatching against: {:?}", err.to_string(), $re ); }}; } fn escape<P: AsRef<Path>>(path: P) -> String { regex::escape(&format!("{}", path.as_ref().display())) } #[test] /// Tests to make sure the error messages look like we expect. fn sanity_errors() { let tmp_dir = TempDir::new("example").expect("create temp dir"); let tmp_abs = PathDir::new(tmp_dir.path()).expect("tmp_abs"); { let foo = PathFile::create(tmp_abs.join("foo.txt")).expect("foo.txt"); foo.clone().remove().unwrap(); let pat = if cfg!(unix) { format!( r"No such file or directory \(os error \d+\) when opening {}", escape(&foo) ) } else { format!( r"The system cannot find the file specified. \(os error \d+\) when opening {}", escape(&foo) ) }; assert_match!(pat, foo.edit().unwrap_err()) } } }