use anstyle::AnsiColor;
use anyhow::{Error, anyhow};
use clap::ValueHint;
use clap::builder::{ValueParser, styling::Styles};
use clap::{Parser, ValueEnum};
use normpath::PathExt;
use std::path::{Path, PathBuf};
use std::thread;
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Yellow.on_default())
.usage(AnsiColor::Green.on_default())
.literal(AnsiColor::Green.on_default())
.placeholder(AnsiColor::Green.on_default());
#[derive(Parser, Default, Debug, Clone)]
#[clap(author, version, about, long_about = None, styles=STYLES)]
pub struct Args {
#[clap(short = 'f', long, action = clap::ArgAction::Set, default_value_t = false, visible_short_alias = 'L')]
pub follow_symlinks: bool,
#[clap(short = 'o', long, action = clap::ArgAction::Set, default_value_t = true, visible_alias = "xdev")]
pub one_filesystem: bool,
#[clap(short = 'x', long, value_parser = ValueParser::new(parse_threads), default_value_t = thread::available_parallelism().map(| n | n.get()).unwrap_or(2))]
pub threads: usize,
#[clap(short = 'd', long, value_parser)]
pub max_depth: Option<usize>,
#[clap(short = 'n', long, value_parser, conflicts_with = "regex")]
pub name: Option<Vec<String>>,
#[clap(short = 'r', long, value_parser, conflicts_with = "name")]
pub regex: Option<Vec<String>>,
#[clap(short = 'i', long, action = clap::ArgAction::Set, default_value_t = false)]
pub case_insensitive: bool,
#[clap(short = 't', long, value_enum, default_values_t = [FileType::Directory, FileType::File, FileType::Symlink])]
pub file_type: Vec<FileType>,
#[clap(required = true, value_parser = ValueParser::new(parse_paths), value_hint = ValueHint::AnyPath)]
pub path: Vec<PathBuf>,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum, Debug)]
pub enum FileType {
#[value(alias = "e")]
Empty,
#[value(alias = "b")]
BlockDevice,
#[value(alias = "c")]
CharDevice,
#[value(alias = "d")]
Directory,
#[value(alias = "p")]
Pipe,
#[value(alias = "f")]
File,
#[value(alias = "l")]
Symlink,
#[value(alias = "s")]
Socket,
}
fn parse_threads(x: &str) -> Result<usize, Error> {
match x.parse::<usize>() {
Ok(v) => match v {
v if !(2..=65535).contains(&v) => {
Err(anyhow!("threads should be in (2..65536) range"))
}
v => Ok(v),
},
Err(e) => Err(Error::from(e)),
}
}
fn parse_paths(x: &str) -> Result<PathBuf, Error> {
let p = Path::new(x);
if directory_exists(p) {
Ok(p.normalize()?.into_path_buf())
} else {
Err(anyhow!("'{}' is not an existing directory", x))
}
}
#[inline]
fn directory_exists(x: &Path) -> bool {
x.is_dir() && x.normalize().is_ok()
}