use std::env; use std::error; use std::fs::{self, File}; use std::io; use std::path::{Path, PathBuf}; use std::result; use {DirEntry, Error}; /// Create an error from a format!-like syntax. #[macro_export] macro_rules! err { ($($tt:tt)*) => { Box::::from(format!($($tt)*)) } } /// A convenient result type alias. pub type Result = result::Result>; /// The result of running a recursive directory iterator on a single directory. #[derive(Debug)] pub struct RecursiveResults { ents: Vec, errs: Vec, } impl RecursiveResults { /// Return all of the errors encountered during traversal. pub fn errs(&self) -> &[Error] { &self.errs } /// Assert that no errors have occurred. pub fn assert_no_errors(&self) { assert!( self.errs.is_empty(), "expected to find no errors, but found: {:?}", self.errs ); } /// Return all the successfully retrieved directory entries in the order /// in which they were retrieved. pub fn ents(&self) -> &[DirEntry] { &self.ents } /// Return all paths from all successfully retrieved directory entries. /// /// This does not include paths that correspond to an error. pub fn paths(&self) -> Vec { self.ents.iter().map(|d| d.path().to_path_buf()).collect() } /// Return all the successfully retrieved directory entries, sorted /// lexicographically by their full file path. pub fn sorted_ents(&self) -> Vec { let mut ents = self.ents.clone(); ents.sort_by(|e1, e2| e1.path().cmp(e2.path())); ents } /// Return all paths from all successfully retrieved directory entries, /// sorted lexicographically. /// /// This does not include paths that correspond to an error. pub fn sorted_paths(&self) -> Vec { self.sorted_ents().into_iter().map(|d| d.into_path()).collect() } } /// A helper for managing a directory in which to run tests. /// /// When manipulating paths within this directory, paths are interpreted /// relative to this directory. #[derive(Debug)] pub struct Dir { dir: TempDir, } impl Dir { /// Create a new empty temporary directory. pub fn tmp() -> Dir { let dir = TempDir::new().unwrap(); Dir { dir } } /// Return the path to this directory. pub fn path(&self) -> &Path { self.dir.path() } /// Return a path joined to the path to this directory. pub fn join>(&self, path: P) -> PathBuf { self.path().join(path) } /// Run the given iterator and return the result as a distinct collection /// of directory entries and errors. pub fn run_recursive( &self, it: I, ) -> RecursiveResults where I: IntoIterator> { let mut results = RecursiveResults { ents: vec![], errs: vec![], }; for result in it { match result { Ok(ent) => results.ents.push(ent), Err(err) => results.errs.push(err), } } results } /// Create a directory at the given path, while creating all intermediate /// directories as needed. pub fn mkdirp>(&self, path: P) { let full = self.join(path); fs::create_dir_all(&full) .map_err(|e| { err!("failed to create directory {}: {}", full.display(), e) }) .unwrap(); } /// Create an empty file at the given path. All ancestor directories must /// already exists. pub fn touch>(&self, path: P) { let full = self.join(path); File::create(&full) .map_err(|e| { err!("failed to create file {}: {}", full.display(), e) }) .unwrap(); } /// Create empty files at the given paths. All ancestor directories must /// already exists. pub fn touch_all>(&self, paths: &[P]) { for p in paths { self.touch(p); } } /// Create a file symlink to the given src with the given link name. pub fn symlink_file, P2: AsRef>( &self, src: P1, link_name: P2, ) { #[cfg(windows)] fn imp(src: &Path, link_name: &Path) -> io::Result<()> { use std::os::windows::fs::symlink_file; symlink_file(src, link_name) } #[cfg(unix)] fn imp(src: &Path, link_name: &Path) -> io::Result<()> { use std::os::unix::fs::symlink; symlink(src, link_name) } let (src, link_name) = (self.join(src), self.join(link_name)); imp(&src, &link_name) .map_err(|e| { err!( "failed to symlink file {} with target {}: {}", src.display(), link_name.display(), e ) }) .unwrap() } /// Create a directory symlink to the given src with the given link name. pub fn symlink_dir, P2: AsRef>( &self, src: P1, link_name: P2, ) { #[cfg(windows)] fn imp(src: &Path, link_name: &Path) -> io::Result<()> { use std::os::windows::fs::symlink_dir; symlink_dir(src, link_name) } #[cfg(unix)] fn imp(src: &Path, link_name: &Path) -> io::Result<()> { use std::os::unix::fs::symlink; symlink(src, link_name) } let (src, link_name) = (self.join(src), self.join(link_name)); imp(&src, &link_name) .map_err(|e| { err!( "failed to symlink directory {} with target {}: {}", src.display(), link_name.display(), e ) }) .unwrap() } } /// A simple wrapper for creating a temporary directory that is automatically /// deleted when it's dropped. /// /// We use this in lieu of tempfile because tempfile brings in too many /// dependencies. #[derive(Debug)] pub struct TempDir(PathBuf); impl Drop for TempDir { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } impl TempDir { /// Create a new empty temporary directory under the system's configured /// temporary directory. pub fn new() -> Result { #[allow(deprecated)] use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering}; static TRIES: usize = 100; #[allow(deprecated)] static COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; let tmpdir = env::temp_dir(); for _ in 0..TRIES { let count = COUNTER.fetch_add(1, Ordering::SeqCst); let path = tmpdir.join("rust-walkdir").join(count.to_string()); if path.is_dir() { continue; } fs::create_dir_all(&path).map_err(|e| { err!("failed to create {}: {}", path.display(), e) })?; return Ok(TempDir(path)); } Err(err!("failed to create temp dir after {} tries", TRIES)) } /// Return the underlying path to this temporary directory. pub fn path(&self) -> &Path { &self.0 } }