diskit 0.1.1

Utilities for intercepting disk requests.
Documentation
//! Transparent replica of [`WalkDir`](walkdir::WalkDir)
//!
//! For more information see the [struct level documentation](WalkDir).

use std::{
    io::{Error, ErrorKind},
    mem::replace,
    path::PathBuf,
};

use walkdir::IntoIter;

use crate::{dir_entry::DirEntry, Diskit};

/// Options a [`WalkDir`] can have
///
/// This struct contains all the configuration data a [`WalkDir`] can
/// have.  These can be set by the setter methods on [`WalkDir`].
// See lib.rs for justification.
#[allow(missing_docs)]
// False positive of pedantic lint.  I think this is the best name.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
pub struct WalkdirOptions
{
    pub path: PathBuf,
    pub min_depth: usize,
    pub max_depth: usize,
    pub follow_links: bool,
    pub max_open: usize,
    pub contents_first: bool,
    pub same_file_system: bool,
}

/// Transparent replica of [`WalkDir`](walkdir::WalkDir)
///
/// This is a builder struct to create a custom [`WalkdirIterator`].
/// ```
/// use diskit::{dir_entry::DirEntry, diskit_extend::DiskitExt, VirtualDiskit};
///
/// # fn main() -> Result<(), std::io::Error>
/// # {
/// let diskit = VirtualDiskit::default();
/// let walkdir = diskit.walkdir("/");
///
/// for file in walkdir
/// {
///     let file: DirEntry = file?;
///     // The root directory is the only file in this `VirtualDiskit`
///     // currently.
///     assert_eq!(file.path().to_str().unwrap(), "/");
/// }
///
/// # Ok(())
/// # }
/// ```
/// Many methods correspond directly to the normal ones from [walkdir]
/// and should behave mostly the same, see therefore their
/// documentation for the precise intended behavior.
// See lib.rs for justification.
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub struct WalkDir<D>
where
    D: Diskit,
{
    pub diskit: D,
    pub options: WalkdirOptions,
}

/// Implementation helper for [`WalkdirIterator`]
///
/// This is the struct which the methods of the [`Diskit`](crate::Diskit)
/// trait receive when they need a [`WalkdirIterator`].
///
/// [`walkdir`](Self::walkdir) wraps an [`walkdir::WalkDir`] so that
/// [`StdDiskit`](crate::StdDiskit) is more efficient, so it shouldn't
/// be used by any other diskit.
///
/// In [`val`](Self::val) you can store one [`usize`] worth of data.
/// If this is not sufficient you have to store the excess data in
/// your type that implements [`Diskit`](crate::Diskit) and make this
/// an index in your struct.
///
/// [`original`](Self::original) is copy of the options used to create
/// this iterator.
///
/// While all this struct's fields are (necessarily) `pub` they should
/// not be changed by anyone or anything apart from the original
/// diskit that created it.
// See lib.rs for justification.
#[allow(missing_docs)]
// False positive of pedantic lint.  I think this is the best name.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct WalkdirIteratorInner
{
    pub walkdir: Option<IntoIter>,
    pub val: usize,
    pub original: WalkdirOptions,
}

/// The iterator for recursively descending into a directory
///
/// Please see [`WalkDir`]'s documentation for more information.
// See lib.rs for justification.
#[allow(missing_docs)]
// False positive of pedantic lint.  I think this is the best name.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub struct WalkdirIterator<D>
where
    D: Diskit,
{
    pub inner: Result<(WalkdirIteratorInner, D), Error>,
}

impl<D> Iterator for WalkdirIterator<D>
where
    D: Diskit,
{
    type Item = Result<DirEntry, Error>;

    fn next(&mut self) -> Option<Self::Item>
    {
        let inner = match &mut self.inner
        {
            Ok(inner) => inner,
            Err(err) =>
            {
                // 8142... is the SHA-256 hash of "already handled".
                let err = replace(
                    err,
                    Error::new(
                        ErrorKind::Other,
                        "81421d77ff6267c7de0b99591150bcf908dd116bd4fa561fdf4a232f17cdd7d3",
                    ),
                );

                if err
                    .get_ref()
                    .map_or(false, |x| format!("{:?}", x) == "\"81421d77ff6267c7de0b99591150bcf908dd116bd4fa561fdf4a232f17cdd7d3\"")
                {
                    return None;
                }

                return Some(Err(err));
            }
        };
        inner.1.walkdir_next_inner(&mut inner.0)
    }
}

impl<D> IntoIterator for WalkDir<D>
where
    D: Diskit,
{
    type Item = Result<DirEntry, Error>;

    type IntoIter = WalkdirIterator<D>;

    fn into_iter(self) -> Self::IntoIter
    {
        self.diskit.clone().into_walkdir_iterator(self)
    }
}

impl<D> WalkDir<D>
where
    D: Diskit,
{
    /// Converts a [`diskit::WalkDir`](crate::walkdir::WalkDir) to a
    /// [`walkdir::WalkDir`]
    ///
    /// This function converts a
    /// [`diskit::WalkDir`](crate::walkdir::WalkDir) to a
    /// [`walkdir::WalkDir`].
    pub fn to_original(&self) -> ::walkdir::WalkDir
    {
        ::walkdir::WalkDir::new(&self.options.path)
            .min_depth(self.options.min_depth)
            .max_depth(self.options.max_depth)
            .follow_links(self.options.follow_links)
            .max_open(self.options.max_open)
            .contents_first(self.options.contents_first)
            .same_file_system(self.options.same_file_system)
    }

    /// Creates a new standard [`WalkDir`]
    ///
    /// This function returns a new [`WalkDir`] with the standard
    /// configuration of [`walkdir::WalkDir`]s.
    pub const fn new_default(diskit: D, path: PathBuf) -> Self
    {
        Self {
            diskit,
            options: WalkdirOptions {
                path,
                min_depth: 0,
                max_depth: std::usize::MAX,
                follow_links: false,
                max_open: 10,
                contents_first: false,
                same_file_system: false,
            },
        }
    }
}

// This pedantic lint guards against implementing constructors and
// then throwing away the result.  This is wrong here since these
// aren't constructors and the `Self` they return is just a nice help
// for writing method-style function chains (or however that concept
// is called).
#[allow(clippy::return_self_not_must_use)]
impl<D> WalkDir<D>
where
    D: Diskit,
{
    /// Sets the minimal depth of the [`WalkDir`]
    ///
    /// This function sets the minimal depth of the [`WalkDir`].  For
    /// more information please look at
    /// [`walkdir::WalkDir::min_depth`].
    pub fn set_min_depth(&mut self, min_depth: usize) -> Self
    {
        self.options.min_depth = min_depth;
        self.clone()
    }

    /// Sets the maximal depth of the [`WalkDir`]
    ///
    /// This function sets the maximal depth of the [`WalkDir`].  For
    /// more information please look at
    /// [`walkdir::WalkDir::max_depth`].
    pub fn set_max_depth(&mut self, max_depth: usize) -> Self
    {
        self.options.max_depth = max_depth;
        self.clone()
    }

    /// Sets whether the [`WalkDir`] follows links
    ///
    /// This function sets whether the [`WalkDir`] follows links.  For
    /// more information please look at
    /// [`walkdir::WalkDir::follow_links`].
    pub fn set_follow_links(&mut self, follow_links: bool) -> Self
    {
        self.options.follow_links = follow_links;
        self.clone()
    }

    /// Sets the maximal number of opened files of the [`WalkDir`]
    ///
    /// This function sets the maximal number of opened files of the
    /// [`WalkDir`].  For more information please look at
    /// [`walkdir::WalkDir::max_open`].
    pub fn set_max_open(&mut self, max_open: usize) -> Self
    {
        self.options.max_open = max_open;
        self.clone()
    }

    /// Sets whether the content is returned first by the [`WalkDir`]
    ///
    /// This function sets whether the content is returned first by
    /// the [`WalkDir`] .  For more information please look at
    /// [`walkdir::WalkDir::contents_first`].
    pub fn set_contents_first(&mut self, contents_first: bool) -> Self
    {
        self.options.contents_first = contents_first;
        self.clone()
    }

    /// Sets whether the [`WalkDir`] stays on the same file system
    ///
    /// This function sets whether the [`WalkDir`] stays on the same
    /// file system.  For more information please look at
    /// [`walkdir::WalkDir::same_file_system`].
    pub fn set_same_file_system(&mut self, same_file_system: bool) -> Self
    {
        self.options.same_file_system = same_file_system;
        self.clone()
    }
}