diff --git a/Cargo.lock b/Cargo.lock index f7da584..18230ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ad-hoc-iter" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90a8dd76beceb5313687262230fcbaaf8d4e25c37541350cf0932e9adb8309c8" + [[package]] name = "addr2line" version = "0.14.1" @@ -118,6 +124,7 @@ dependencies = [ name = "dirstat" version = "0.1.0" dependencies = [ + "ad-hoc-iter", "async-compression", "cfg-if 1.0.0", "color-eyre", @@ -131,6 +138,7 @@ dependencies = [ "pin-project", "serde", "serde_cbor", + "smallvec", "tokio", ] @@ -609,6 +617,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "socket2" version = "0.3.19" diff --git a/Cargo.toml b/Cargo.toml index 85fee09..35eb35b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ defer-drop = [] splash = [] [dependencies] +ad-hoc-iter = "0.2.3" async-compression = {version = "0.3", features=["tokio-02", "bzip2"], optional=true} cfg-if = "1.0.0" color-eyre = {version = "0.5.10", default-features=false} @@ -45,4 +46,5 @@ once_cell = "1.5.2" pin-project = "1.0.5" serde = {version = "1.0.123", features=["derive"], optional=true} serde_cbor = {version = "0.11.1", optional=true} +smallvec = "1.6.1" tokio = {version = "0.2", features=["full"]} diff --git a/src/arg.rs b/src/arg/mod.rs similarity index 99% rename from src/arg.rs rename to src/arg/mod.rs index ba6c63b..9ee3235 100644 --- a/src/arg.rs +++ b/src/arg/mod.rs @@ -5,6 +5,8 @@ use std::fmt; use config::Config; +mod parsing; + /// Executable name pub fn program_name() -> &'static str { diff --git a/src/arg/parsing.rs b/src/arg/parsing.rs new file mode 100644 index 0000000..bc64572 --- /dev/null +++ b/src/arg/parsing.rs @@ -0,0 +1,286 @@ +//! For parsing arguments +use super::*; +use std::collections::{HashMap, HashSet}; +use std::mem::Discriminant; +use std::fmt; + +#[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 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 parse_single(args: &mut I, output: &mut Output, this: char) -> eyre::Result +where I: Iterator + +{ + //TODO: Parse single letter args + 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); + }, + //TODO: move rest of long args from `mod` to here + 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) + } + }; + + 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(keep_reading) +} diff --git a/src/ext.rs b/src/ext.rs index 27aea52..d6be313 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -18,6 +18,8 @@ pub mod prelude pub use super::StreamGateExt as _; pub use super::StreamLagExt as _; pub use super::INodeExt as _; + + pub use super::MaybeVec; } pub trait INodeExt @@ -346,3 +348,5 @@ impl fmt::Display for SoftAssertionFailedError } }; } + +pub type MaybeVec = smallvec::SmallVec<[T; 1]>; diff --git a/src/main.rs b/src/main.rs index 1837d4a..90ddf88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ #[macro_use] extern crate pin_project; #[macro_use] extern crate lazy_static; #[macro_use] extern crate cfg_if; +#[macro_use] extern crate ad_hoc_iter; #[cfg(feature="inspect")] use serde::{Serialize, Deserialize};