dirwalk 1.1.1

Platform-optimized recursive directory walker with metadata
Documentation
use std::str::FromStr;

/// Specifies parallelism for directory scanning.
///
/// - `0` or `1/1` — all available cores
/// - `4` — exactly 4 threads
/// - `1/2` — half of available cores (rounded up)
/// - `3/4` — three quarters, etc.
#[derive(Clone, Debug)]
pub struct Threads(ThreadsInner);

#[derive(Clone, Debug)]
enum ThreadsInner {
    Count(usize),
    Fraction(usize, usize),
}

impl Threads {
    pub(crate) fn resolve(&self) -> usize {
        let available = std::thread::available_parallelism()
            .map(|n| n.get())
            .unwrap_or(1);
        match self.0 {
            ThreadsInner::Count(0) => available,
            ThreadsInner::Count(n) => n,
            ThreadsInner::Fraction(num, den) => (available * num).div_ceil(den).max(1),
        }
    }
}

impl From<usize> for Threads {
    fn from(n: usize) -> Self {
        Self(ThreadsInner::Count(n))
    }
}

impl FromStr for Threads {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some((num_str, den_str)) = s.split_once('/') {
            let num = num_str
                .trim()
                .parse::<usize>()
                .map_err(|_| format!("invalid numerator in '{s}'"))?;
            let den = den_str
                .trim()
                .parse::<usize>()
                .map_err(|_| format!("invalid denominator in '{s}'"))?;
            if den == 0 {
                return Err("denominator must be non-zero".into());
            }
            if num == 0 {
                return Err("numerator must be non-zero".into());
            }
            if num > den {
                return Err(format!("fraction {num}/{den} exceeds 1"));
            }
            return Ok(Self(ThreadsInner::Fraction(num, den)));
        }

        let n = s.trim().parse::<usize>().map_err(|_| {
            format!(
                "invalid thread spec '{s}': expected a number (e.g. '4') or fraction (e.g. '1/2')"
            )
        })?;
        Ok(Self(ThreadsInner::Count(n)))
    }
}