You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
14 KiB
516 lines
14 KiB
//! For parsing arguments
|
|
use super::*;
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::mem::Discriminant;
|
|
use std::fmt;
|
|
|
|
#[cfg(feature="inspect")] use config::OutputSerialisationMode;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
pub enum InspectKind
|
|
{
|
|
Treemap(Option<(u64, u64)>),
|
|
}
|
|
|
|
impl fmt::Display for InspectKind
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
match self {
|
|
Self::Treemap(None) => write!(f, "treemap"),
|
|
Self::Treemap(Some((x,y))) => write!(f, "treemap:{}:{}", x, y), // Width and height.
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for InspectKind
|
|
{
|
|
type Err = eyre::Report;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err>
|
|
{
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[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,
|
|
|
|
Inspect(InspectKind),
|
|
|
|
Input(String),
|
|
}
|
|
|
|
/// Kinds of modes of operation for the program.
|
|
///
|
|
/// These map to `super::Mode`.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Copy)]
|
|
#[non_exhaustive]
|
|
enum ModeKind
|
|
{
|
|
Normal,
|
|
|
|
Help
|
|
}
|
|
|
|
impl Default for ModeKind
|
|
{
|
|
#[inline]
|
|
fn default() -> Self
|
|
{
|
|
Self::Normal
|
|
}
|
|
}
|
|
|
|
|
|
impl Argument
|
|
{
|
|
/// What mode does this argument change to, if any?
|
|
fn mode_change_kind(&self) -> Option<ModeKind>
|
|
{
|
|
Some(match self
|
|
{
|
|
Self::ModeChangeHelp => ModeKind::Help,
|
|
_ => return None,
|
|
})
|
|
}
|
|
/// Insert this `Argument` into config
|
|
pub fn insert_into_cfg(self, cfg: &mut Config)
|
|
{
|
|
use Argument::*;
|
|
match self {
|
|
Inspect(InspectKind::Treemap(None)) => cfg.inspection.treemap = Some((640, 480)),
|
|
Inspect(InspectKind::Treemap(x)) => cfg.inspection.treemap = x,
|
|
|
|
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 = if limit.get() == 1 { config::Recursion::None } else { 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()! // Do nothing instead of panic.
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for Argument
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
|
{
|
|
use Argument::*;
|
|
match self
|
|
{
|
|
Inspect(ins) => write!(f, "--inspect {}", ins),
|
|
|
|
ModeChangeHelp => write!(f, "--help"),
|
|
LimitConcMaxProc => write!(f, "-m"),
|
|
LimitConc(limit) => write!(f, "--threads {}", limit),
|
|
UnlimitConc => write!(f, "-M (--threads 0)"),
|
|
|
|
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 (--recursive 0)"),
|
|
|
|
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<Argument>),
|
|
Many(&'static [Discriminant<Argument>]),
|
|
}
|
|
|
|
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<Argument>, 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<Discriminant<Argument>, MaybeVec<MX>> = {
|
|
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)]
|
|
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, and optionally return the last one here, which must be a mode change argument.
|
|
///
|
|
/// Returning this when the contained value is `Some` immediately terminates parsing and precedes to mode-switch. However, if it is `None`, parsing of chained short args is allowed to continue, although `Abort(None)` will be returned at the end regardless of subsequent `Continue` results from that change (unless one is an `Abort(Some(_))`, which immediately returns itself.)
|
|
// Box `Argument` to reduce the size of `Continue`, as it is returned from functions often and when its value is set to `Some` it will always be the last `Argument` processed anyway and the only one to be boxed here at all.
|
|
|
|
//TODO: Deprecate the early return of an `Argument` here. Either change it to `Mode`, or have no early return. Mode change happens at the bottom in `into_mode` now.
|
|
Abort(Option<Box<Mode>>),
|
|
}
|
|
|
|
impl Continue
|
|
{
|
|
/// Should we keep *parsing* args?
|
|
#[inline] pub fn keep_reading(&self) -> bool
|
|
{
|
|
if let Self::Yes = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Is this an abort?
|
|
#[inline] pub fn is_abort(&self) -> bool
|
|
{
|
|
if let Self::Abort(_) = self {
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Continue
|
|
{
|
|
#[inline]
|
|
fn default() -> Self
|
|
{
|
|
Self::Yes
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Continue
|
|
{
|
|
fn from(from: bool) -> Self
|
|
{
|
|
if from {
|
|
Self::Yes
|
|
} else {
|
|
Self::No
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
pub type Output = HashSet<Argument>;
|
|
#[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 addargument "))
|
|
.with_section(|| mx.to_string().header("Which is mutually exclusive with previously added"));
|
|
}
|
|
|
|
output.insert(item); //TODO: Warn when adding duplicate?
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_single<I>(_args: &mut I, output: &mut Output, this: char) -> eyre::Result<Continue>
|
|
where I: Iterator<Item=String>
|
|
{
|
|
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)
|
|
.with_section(|| this.header("Short argument was"))?;
|
|
|
|
Ok(Continue::Yes)
|
|
}
|
|
|
|
/// Consume this iterator into `Input`s
|
|
pub fn consume<I>(args: I, output: &mut Output)
|
|
where I: IntoIterator<Item=String>
|
|
{
|
|
output.extend(args.into_iter().map(Argument::Input));
|
|
}
|
|
|
|
pub fn parse_next<I>(args: &mut I, output: &mut Output, this: String) -> eyre::Result<Continue>
|
|
where I: Iterator<Item=String>
|
|
{
|
|
let mut keep_reading = Continue::Yes;
|
|
let item = match this.trim()
|
|
{
|
|
"--inspect" => {
|
|
let ins = args.next().ok_or(eyre!("`--inspect` expects a parameter"))
|
|
.with_suggestion(suggestion_intended_arg.clone())?;
|
|
Argument::Inspect(ins.parse().wrap_err(eyre!("Failed to parse parameter for `--inspect`"))?)
|
|
},
|
|
" --threads" => {
|
|
let max = args.next().ok_or(eyre!("`--threads` expects a parameter"))
|
|
.with_suggestion(suggestion_intended_arg.clone())?;
|
|
match NonZeroUsize::new(max.parse::<usize>()
|
|
.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,
|
|
}
|
|
},
|
|
"--recursive" => {
|
|
let max = args.next().ok_or(eyre!("`--recursive` expects a parameter"))
|
|
.with_suggestion(suggestion_intended_arg.clone())?;
|
|
match NonZeroUsize::new(max.parse::<usize>().wrap_err(eyre!("`--recursive` expects a non-negative number"))
|
|
.with_suggestion(suggestion_intended_arg.clone())
|
|
.with_section(move || max.header("Parameter given was"))?)
|
|
{
|
|
Some(x) => Argument::LimitRecurse(x),
|
|
None => Argument::UnlimitRecurse,
|
|
}
|
|
},
|
|
"--help" => {
|
|
return Ok(Continue::Abort(Some(Box::new(Mode::Help))));
|
|
},
|
|
"-" => {
|
|
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)
|
|
.wrap_err(eyre!("Error parsing short argument"))
|
|
.with_section(|| this.clone().header("Full short argument chain was"))? {
|
|
abort @ Continue::Abort(Some(_)) => return Ok(abort),
|
|
x @ Continue::No |
|
|
x @ Continue::Abort(_) if !x.is_abort() => keep_reading = x,
|
|
_ => (),
|
|
}
|
|
}
|
|
return Ok(keep_reading);
|
|
},
|
|
_ => {
|
|
keep_reading = Continue::No;
|
|
|
|
Argument::Input(this)
|
|
}
|
|
};
|
|
|
|
save_output(output, item)?;
|
|
|
|
Ok(keep_reading)
|
|
}
|
|
|
|
/// Converts parsed argument lists into a respective mode.
|
|
///
|
|
/// # Notes
|
|
/// These functions assume the mode has already been correctly calculated to be the mode pertaining to that function.
|
|
mod modes {
|
|
use super::*;
|
|
use config::Config;
|
|
|
|
/// Consume a parsed list of arguments in `Normal` mode into a `Normal` mode `Config` object.
|
|
pub fn normal(args: Output) -> eyre::Result<config::Config>
|
|
{
|
|
let mut cfg = Config::default();
|
|
|
|
for arg in args.into_iter()
|
|
{
|
|
arg.insert_into_cfg(&mut cfg);
|
|
}
|
|
|
|
Ok(cfg)
|
|
}
|
|
|
|
}
|
|
/// Consume this parsed list of arguments into a `Mode` and return it
|
|
pub fn into_mode(args: Output) -> eyre::Result<Mode>
|
|
{
|
|
let mut mode_kind = ModeKind::default(); //Normal.
|
|
|
|
for arg in args.iter() {
|
|
//find any mode change Argument (with `Argument::mode_change_kind()`) in `args`, changing `mode_kind` in turn. There should be at most 1.
|
|
if let Some(mode) = arg.mode_change_kind()
|
|
{
|
|
mode_kind = mode;
|
|
break;
|
|
}
|
|
}
|
|
//pass `args` to the respective mode generation function in mode `modes`, and wrap that mode around its return value.
|
|
match mode_kind
|
|
{
|
|
ModeKind::Normal => modes::normal(args).map(Mode::Normal),
|
|
ModeKind::Help => Ok(Mode::Help),
|
|
}
|
|
|
|
}
|