use clap::{Args, Parser}; use figment::{value, Metadata, Profile, Provider}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tracing::{metadata::LevelFilter, Level}; use crate::{search, Config}; /// Tool for listing project directories. #[derive(Debug, Clone, Default, Parser, Serialize, Deserialize)] #[command(author, version, about)] pub struct Cli { #[command(flatten)] pub projects: Projects, #[command(flatten)] pub verbosity: Verbosity, } #[derive(Debug, Default, Clone, Args, Serialize, Deserialize)] #[serde(into = "Config")] pub struct Projects { /// Directory to search. /// /// Directories are searched recursively based on `--max-depth`. paths: Vec, /// Max depth to recurse. /// /// By default, no limit is set. Setting to 0 will only use the supplied directory. #[arg(short = 'd', long, default_value = "1")] max_depth: Option, /// Recurse into hidden directories. /// /// Traverse into hidden directories while searching. A directory is considered hidden /// if its name starts with a `.` sign (dot). If `--max-depth` is 0, this has no effect. #[arg(long)] hidden: bool, /// Match directories containing item named #[arg(long, short)] pattern: Option, /// Match git repositories #[cfg(feature = "git")] #[arg(long, short, default_value_t = true)] git: bool, } impl Provider for Projects { fn metadata(&self) -> Metadata { Metadata::named("Projectr cli provider") } fn data(&self) -> figment::error::Result> { Config::from(self.to_owned()).data() } } impl From for Config { fn from(value: Projects) -> Self { let paths = value .paths .iter() .cloned() .map(|path_buf| search::entry::Config { path_buf, hidden: value.hidden, max_depth: value.max_depth, pattern: value.pattern.to_owned(), #[cfg(feature = "git")] git: value.git, }) .collect(); Config { paths } } } #[derive(Debug, Default, Clone, Args, Serialize, Deserialize)] pub struct Verbosity { /// Print additional information per occurrence. /// /// Conflicts with `--quiet`. #[arg(short, long, global = true, action = clap::ArgAction::Count, conflicts_with = "quiet")] pub verbose: u8, /// Suppress all output. /// /// Conflicts with `--verbose`. #[arg(short, long, global = true, conflicts_with = "verbose")] pub quiet: bool, } impl From for Option { fn from(value: Verbosity) -> Self { match 1 + value.verbose - u8::from(value.quiet) { 0 => None, 1 => Some(Level::ERROR), 2 => Some(Level::WARN), 3 => Some(Level::INFO), 4 => Some(Level::DEBUG), _ => Some(Level::TRACE), } } } impl From for LevelFilter { fn from(value: Verbosity) -> Self { Option::::from(value).into() } }