//! For parsing arguments use super::*; use std::collections::{HashMap, HashSet}; use std::mem::Discriminant; use std::fmt; use config::OutputSerialisationMode; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Argument { ModeChangeHelp, LimitConcMaxProc, LimitConc(NonZeroUsize), UnlimitConc, Save(String), SaveStdout, SaveRaw(String), SaveRawStdout, LimitRecurse(NonZeroUsize), UnlimitRecurse, LogVerbose, LogQuiet, LogSilent, StopReading, Input(String), } impl Argument { /// Insert this `Argument` into config pub fn insert_into_cfg(self, cfg: &mut Config) { use Argument::*; match self { LimitConcMaxProc => cfg.max_tasks = config::max_tasks_cpus(), LimitConc(max) => cfg.max_tasks = Some(max), UnlimitConc => cfg.max_tasks = None, #[cfg(feature="inspect")] Save(output) => cfg.serialise_output = Some(OutputSerialisationMode::File(output.into())), #[cfg(feature="inspect")] SaveStdout => cfg.serialise_output = Some(OutputSerialisationMode::Stdout), #[cfg(feature="inspect")] SaveRaw(output) => { cfg_if! { if #[cfg(feature="prealloc")] { cfg.serialise_output = Some(OutputSerialisationMode::PreallocFile(output.into())); } else { cfg.serialise_output = Some(OutputSerialisationMode::RawFile(output.into())); } } }, #[cfg(feature="inspect")] SaveRawStdout => cfg.serialise_output = Some(OutputSerialisationMode::RawStdout), LimitRecurse(limit) => cfg.recursive = config::Recursion::Limited(limit), UnlimitRecurse => cfg.recursive = config::Recursion::Unlimited, LogVerbose => cfg.output_level = config::OutputLevel::Verbose, LogQuiet => cfg.output_level = config::OutputLevel::Quiet, LogSilent => cfg.output_level = config::OutputLevel::Silent, Input(path) => cfg.paths.push(path.into()), _ => unreachable!(), } } } impl fmt::Display for Argument { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Argument::*; match self { ModeChangeHelp => write!(f, "--help"), LimitConcMaxProc => write!(f, "-m"), LimitConc(limit) => write!(f, "--threads {}", limit), UnlimitConc => write!(f, "-M"), Save(s) => write!(f, "--save {:?}", s), SaveStdout => write!(f, "-D"), SaveRaw(s) => write!(f, "--save-raw {:?}", s), SaveRawStdout => write!(f, "-R"), LimitRecurse(rec) => write!(f, "--recursive {}", rec), UnlimitRecurse => write!(f, "-r"), LogVerbose => write!(f, "-v"), LogQuiet => write!(f, "-q"), LogSilent => write!(f, "-Q"), StopReading => write!(f, "-"), Input(input) => write!(f, "<{}>", input), } } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)] enum MX { None, Itself, All, Only(Discriminant), Many(&'static [Discriminant]), } impl Default for MX { #[inline] fn default() -> Self { Self::Itself } } impl MX { /// Is this argument discriminant mutually exclusive with this other argument? pub fn is_mx(&self, this: Discriminant, other: &Argument) -> bool { use std::mem::discriminant; let other = discriminant(other); match self { Self::Itself if other == this => true, Self::All => true, Self::Only(disc) if other == *disc => true, Self::Many(discs) if discs.contains(&other) => true, _ => false, } } } impl Argument { /// Is this `Argument` mutually exclusive with another? pub fn is_mx_with(&self, other: &Self) -> bool { use std::mem::discriminant; lazy_static! { static ref MX_REF: HashMap, MaybeVec> = { let mut out = HashMap::new(); macro_rules! mx { (@) => { std::iter::empty() }; (@ self $($tt:tt)*) => { iter![MX::Itself].chain(mx!(@ $($tt)*)) }; (@ [$inner:expr] $($tt:tt)*) => { iter![MX::Only(discriminant(&$inner))].chain(mx!(@ $($tt)*)) }; (@ [$($inner:expr),*] $($tt:tt)*) => { iter![MX::Many(vec![$(discriminant(&$inner)),*].leak())].chain(mx!(@ $($tt)*)) }; (@ $ident:ident $($tt:tt)*) => { iter![MX::$ident].chain(mx!(@ $($tt)*)) }; ($disc:expr => $($tt:tt)*) => { out.insert(discriminant(&$disc), mx!(@ $($tt)*).collect()); }; } mx!(Argument::ModeChangeHelp => All); mx!(Argument::LimitConcMaxProc => self [Argument::UnlimitConc, Argument::LimitConc(unsafe{NonZeroUsize::new_unchecked(1)})]); mx!(Argument::UnlimitConc => self [Argument::LimitConcMaxProc, Argument::LimitConc(unsafe{NonZeroUsize::new_unchecked(1)})]); mx!(Argument::LimitConc(unsafe{NonZeroUsize::new_unchecked(1)}) => self [Argument::LimitConcMaxProc, Argument::UnlimitConc]); mx!(Argument::Save(String::default()) => self [Argument::SaveStdout, Argument::SaveRaw(Default::default()), Argument::SaveRawStdout]); mx!(Argument::SaveStdout => self [Argument::Save(String::default()), Argument::SaveRaw(Default::default()), Argument::SaveRawStdout]); mx!(Argument::SaveRaw(Default::default()) => self [Argument::Save(String::default()), Argument::SaveStdout, Argument::SaveRawStdout]); mx!(Argument::SaveRawStdout => self [Argument::Save(String::default()), Argument::SaveRaw(String::default()), Argument::SaveStdout]); mx!(Argument::LimitRecurse(unsafe{NonZeroUsize::new_unchecked(1)}) => self [Argument::UnlimitRecurse]); mx!(Argument::UnlimitRecurse => self [Argument::LimitRecurse(unsafe{NonZeroUsize::new_unchecked(1)})]); mx!(Argument::LogVerbose => self [Argument::LogQuiet, Argument::LogSilent]); mx!(Argument::LogQuiet => self [Argument::LogVerbose, Argument::LogSilent]); mx!(Argument::LogSilent => self [Argument::LogQuiet, Argument::LogVerbose]); mx!(Argument::StopReading => All); mx!(Argument::Input(String::default()) => None); out }; } let this = discriminant(self); match MX_REF.get(&this) { Some(mx) if mx.iter().filter(|mx| mx.is_mx(this, other)).next().is_some() => true, _ => false, } } } /// Should we continue parsing and/or reading arguments? #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)] pub enum Continue { /// Keep parsing the arguments Yes, /// Stop parsing arguments, add the rest of args as `Input`s No, /// On mode change, we don't need to parse the rest of the argument. Stop reading entirely. Abort, } impl Continue { /// Should we keep *parsing* args? #[inline] pub fn keep_reading(&self) -> bool { if let Self::Yes = self { true } else { false } } } impl Default for Continue { #[inline] fn default() -> Self { Self::Yes } } impl From for Continue { fn from(from: bool) -> Self { if from { Self::Yes } else { Self::No } } } pub type Output = HashSet; #[inline] const fn suggestion_intended_arg() -> &'static str { "If this was intended as a path instead of an option, use option `-` before it." } fn save_output(output: &mut Output, item: Argument) -> eyre::Result<()> { if let Some(mx) = output.iter().filter(|arg| item.is_mx_with(arg)).next() { return Err(eyre!("Arguments are mutually exclusive")) .with_section(|| item.header("Trying to add")) .with_section(|| mx.to_string().header("Which is mutually exclusive with")); } output.insert(item); //TODO: Warn when adding duplicate? Ok(()) } fn parse_single(_args: &mut I, output: &mut Output, this: char) -> eyre::Result where I: Iterator { let item = match this { 'r' => Argument::UnlimitRecurse, #[cfg(feature="inspect")] 'D' => Argument::SaveStdout, #[cfg(feature="inspect")] 'R' => Argument::SaveRawStdout, 'v' => Argument::LogVerbose, 'q' => Argument::LogQuiet, 'Q' => Argument::LogSilent, 'm' => Argument::LimitConcMaxProc, 'M' => Argument::UnlimitConc, unknown => { return Err(eyre!("Unknown short argument {:?}", unknown)) .with_suggestion(suggestion_intended_arg.clone()); }, }; save_output(output, item)?; Ok(Continue::Yes) } pub fn parse_next(args: &mut I, output: &mut Output, this: String) -> eyre::Result where I: Iterator { let mut keep_reading = Continue::Yes; let item = match this.trim() { "--threads" => { let max = args.next().ok_or(eyre!("`--threads` expects a parameter")) .with_suggestion(suggestion_intended_arg.clone())?; match NonZeroUsize::new(max.parse::() .wrap_err(eyre!("`--threads` expects a non-negative number")) .with_suggestion(suggestion_intended_arg.clone()) .with_section(move || max.header("Parameter given was"))?) { Some(max) => Argument::LimitConc(max), None => Argument::UnlimitConc, } }, "--help" => { keep_reading = Continue::Abort; Argument::ModeChangeHelp }, "-" => { return Ok(Continue::No); }, #[cfg(feature="inspect")] "--save" => { let file = args.next().ok_or(eyre!("`--save` expects a parameter")) .with_suggestion(suggestion_intended_arg.clone())?; Argument::Save(file) }, #[cfg(feature="inspect")] "--save-raw" => { let file = args.next().ok_or(eyre!("`--save` expects a parameter")) .with_suggestion(suggestion_intended_arg.clone())?; Argument::SaveRaw(file) }, single if single.starts_with("-") => { for ch in single.chars().skip(1) { match parse_single(args, output, ch)? { x @ Continue::No | x @ Continue::Abort => keep_reading = x, _ => (), } } return Ok(keep_reading); }, _ => { keep_reading = Continue::No; Argument::Input(this) } }; save_output(output, item)?; Ok(keep_reading) }