//! 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
{
/// Convert into a mode change if this is a mode change
// We consume the whole output here in case the mode change needs to traverse it for information. As of now, we have only one non-`Normal` mode: `Help`, which doesn't require any extra information.
pub fn try_into_mode ( self , _rest : Output ) -> Result < Mode , ( Self , Output ) >
{
match self {
Self ::ModeChangeHelp = > Ok ( Mode ::Help ) ,
no = > Err ( ( no , _rest ) ) ,
}
}
/// 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 < 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, Hash) ]
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.
Abort ( Option < Box < Argument > > ) ,
}
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 add" ) )
. with_section ( | | mx . to_string ( ) . header ( "Which is mutually exclusive with" ) ) ;
}
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 ) ? ;
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 ( )
{
"--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 ,
}
} ,
"--help" = > {
return Ok ( Continue ::Abort ( Some ( Box ::new ( 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 ) ? {
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 ( ) ;
//TODO: Consume `args` into `cfg` for Normal mode, then return `cfg.`
Ok ( cfg )
}
}
/// Consume this parsed list of arguments into a `Mode` and return it
pub fn into_mode ( args : Output ) -> eyre ::Result < Mode >
{
//TODO: Find out what mode `args` is in.
//TODO: pass `args` to the respective mode generation function in mode `modes`, and wrap that mode around its return value.
todo! ( )
}