jwalk 0.9.0

Use `dua-core` instead
Documentation
use std::env;
use std::error;
use std::fs::{self, File};
use std::io::{self};
use std::path::{Path, PathBuf};
use std::result;

use jwalk::*;

/// Helper to create a read-only fixture from a script
pub fn fixture(script_name: &str) -> PathBuf {
    gix_testtools::scripted_fixture_read_only(script_name).unwrap()
}

/// Returns representative parallelism options for testing
pub fn parallelism_options() -> Vec<Parallelism> {
    vec![
        Parallelism::Serial,
        Parallelism::RayonDefaultPool {
            busy_timeout: std::time::Duration::from_secs(1),
        },
        Parallelism::RayonNewPool(2),
    ]
}

/// Creates a temporary test directory with the contents of tests/assets/test_dir
pub fn test_dir() -> (PathBuf, tempfile::TempDir) {
    let template = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/assets/test_dir");
    let temp_dir = tempfile::tempdir().unwrap();
    let options = fs_extra::dir::CopyOptions::new();
    fs_extra::dir::copy(&template, &temp_dir, &options).unwrap();
    let mut test_dir = temp_dir.path().to_path_buf();
    test_dir.push(template.file_name().unwrap());
    (test_dir, temp_dir)
}

/// A helper for managing a read-only directory created from a fixture script.
#[derive(Debug, Clone)]
pub struct ReadOnlyDir {
    path: PathBuf,
}

impl ReadOnlyDir {
    /// Create a read-only directory from a fixture script.
    pub fn from_fixture(script_name: &str) -> ReadOnlyDir {
        let path = gix_testtools::scripted_fixture_read_only(script_name).unwrap();
        // Convert to absolute path to match Dir::tmp() behavior
        let path = env::current_dir().unwrap().join(path);
        ReadOnlyDir { path }
    }

    /// Return the path to this directory.
    pub fn path(&self) -> &Path {
        &self.path
    }

    /// Return a path joined to the path to this directory.
    pub fn join<P: AsRef<Path>>(&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<C, I>(&self, it: I) -> RecursiveResults<C>
    where
        C: ClientState,
        I: IntoIterator<Item = result::Result<DirEntry<C>, Error>>,
    {
        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 an error from a format!-like syntax.
#[macro_export]
macro_rules! err {
    ($($tt:tt)*) => {
        Box::<dyn error::Error + Send + Sync>::from(format!($($tt)*))
    }
}

/// A convenient result type alias.
pub type Result<T> = result::Result<T, Box<dyn error::Error + Send + Sync>>;

/// The result of running a recursive directory iterator on a single directory.
#[derive(Debug)]
pub struct RecursiveResults<C: ClientState> {
    ents: Vec<DirEntry<C>>,
    errs: Vec<Error>,
}

impl<C: ClientState> RecursiveResults<C> {
    /// 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<C>] {
        &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<PathBuf> {
        self.ents.iter().map(|d| d.path()).collect()
    }

    /*
    /// Return all the successfully retrieved directory entries, sorted
    /// lexicographically by their full file path.
    pub fn sorted_ents(&self) -> Vec<DirEntry<C>> {
        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<PathBuf> {
        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<P: AsRef<Path>>(&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<C, I>(&self, it: I) -> RecursiveResults<C>
    where
        C: ClientState,
        I: IntoIterator<Item = result::Result<DirEntry<C>, Error>>,
    {
        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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P: AsRef<Path>>(&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<P1: AsRef<Path>, P2: AsRef<Path>>(&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<P1: AsRef<Path>, P2: AsRef<Path>>(&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<TempDir> {
        #[allow(deprecated)]
        use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};

        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
    }
}