cliproc/
cli.rs

1use crate::error::{utils, CapMode, ColorMode};
2use crate::help::Help;
3use crate::seqalin;
4use crate::seqalin::Cost;
5use crate::{arg::*, Command, Subcommand};
6use colored::Colorize;
7use stage::*;
8use std::collections::HashMap;
9use std::collections::HashSet;
10use std::marker::PhantomData;
11use std::ops::RangeBounds;
12use std::process::ExitCode;
13use std::str::FromStr;
14
15pub use crate::error::{Error, ErrorContext, ErrorKind};
16
17/// The return type for a [Command]'s interpretation process.
18pub type Result<T> = std::result::Result<T, Error>;
19
20mod symbol {
21    // series of characters to denote flags and switches
22    pub const SWITCH: &str = "-";
23    // @note: tokenizing depends on flag having the first character be the switch character
24    pub const FLAG: &str = "--";
25}
26
27#[derive(Debug, Eq, Hash, PartialEq)]
28enum Tag<T: AsRef<str>> {
29    Switch(T),
30    Flag(T),
31}
32
33impl<T: AsRef<str>> Tag<T> {
34    fn as_ref(&self) -> &T {
35        match self {
36            Self::Flag(s) => s,
37            Self::Switch(s) => s,
38        }
39    }
40}
41
42#[derive(Debug, PartialEq)]
43enum Token {
44    UnattachedArgument(usize, String),
45    AttachedArgument(usize, String),
46    Flag(usize),
47    Switch(usize, char),
48    EmptySwitch(usize),
49    Ignore(usize, String),
50    Terminator(usize),
51}
52
53impl Token {
54    fn take_str(self) -> String {
55        match self {
56            Self::UnattachedArgument(_, s) => s,
57            Self::AttachedArgument(_, s) => s,
58            Self::Ignore(_, s) => s,
59            _ => panic!("cannot call take_str on token without string"),
60        }
61    }
62
63    fn _get_index_ref(&self) -> &usize {
64        match self {
65            Self::UnattachedArgument(i, _) => i,
66            Self::AttachedArgument(i, _) => i,
67            Self::Flag(i) => i,
68            Self::EmptySwitch(i) => i,
69            Self::Switch(i, _) => i,
70            Self::Terminator(i) => i,
71            Self::Ignore(i, _) => i,
72        }
73    }
74}
75
76#[derive(Debug, PartialEq)]
77struct Slot {
78    pointers: Vec<usize>,
79    visited: bool,
80}
81
82impl Slot {
83    fn new() -> Self {
84        Self {
85            pointers: Vec::new(),
86            visited: false,
87        }
88    }
89
90    fn push(&mut self, i: usize) -> () {
91        self.pointers.push(i);
92    }
93
94    fn is_visited(&self) -> bool {
95        self.visited
96    }
97
98    fn visit(&mut self) -> () {
99        self.visited = true;
100    }
101
102    fn get_indices(&self) -> &Vec<usize> {
103        &self.pointers
104    }
105
106    fn first(&self) -> Option<&usize> {
107        self.pointers.first()
108    }
109}
110
111#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
112enum MemoryState {
113    Start,
114    ProcessingFlags,
115    ProcessingOptionals,
116    ProcessingPositionals,
117    ProcessingSubcommands,
118    End,
119}
120
121impl MemoryState {
122    /// Ensures the previous state (`self`) can transition to the next state (`next`).
123    pub fn proceed(&mut self, mut next: MemoryState) {
124        // panic if we are already advanced past the next state
125        if self > &mut next {
126            panic!("{}: argument discovery is in an invalid order: invalid state transition from {:?} to {:?}", "structural hazard".red().bold().underline(), self, next)
127        }
128        // println!("{:?} -> {:?}", self, next);
129        *self = next;
130    }
131
132    pub fn reset() -> Self {
133        Self::Start
134    }
135}
136
137pub mod stage {
138    /// The typestate pattern for the different stages in processing data from
139    /// the command-line.
140    pub trait ProcessorState {}
141
142    /// The first stage in the command-line processor. The processor can be
143    /// configured when it is in this state.
144    pub struct Build;
145
146    /// The second stage in the command-line processor. The processor can be
147    /// routed to different modes of processing.
148    pub struct Ready;
149
150    /// The third and final stage in the command-line processor. The processor
151    /// stores the command-line data and allows for requests to query what data
152    /// it captured.
153    pub struct Memory;
154
155    impl ProcessorState for Build {}
156
157    impl ProcessorState for Ready {}
158
159    impl ProcessorState for Memory {}
160}
161
162impl<S: ProcessorState> Cli<S> {
163    /// Perform a state transition for the command-line processor.
164    fn transition<T: ProcessorState>(self) -> Cli<T> {
165        Cli::<T> {
166            tokens: self.tokens,
167            store: self.store,
168            known_args: self.known_args,
169            asking_for_help: self.asking_for_help,
170            help: self.help,
171            state: self.state,
172            options: self.options,
173            _marker: PhantomData::<T>,
174        }
175    }
176}
177
178#[derive(Debug, PartialEq, Clone)]
179struct CliOptions {
180    pub prioritize_help: bool,
181    pub cap_mode: CapMode,
182    pub threshold: Cost,
183    pub capacity: usize,
184    pub color_mode: ColorMode,
185    pub err_prefix: String,
186    pub err_suffix: String,
187}
188
189impl CliOptions {
190    pub fn new() -> Self {
191        Self {
192            prioritize_help: true,
193            cap_mode: CapMode::new(),
194            threshold: 0,
195            capacity: 0,
196            color_mode: ColorMode::new(),
197            err_prefix: String::new(),
198            err_suffix: String::new(),
199        }
200    }
201}
202
203impl Default for CliOptions {
204    fn default() -> Self {
205        Self {
206            prioritize_help: true,
207            cap_mode: CapMode::default(),
208            threshold: 2,
209            capacity: 0,
210            color_mode: ColorMode::default(),
211            err_prefix: String::from(format!("{}: ", "error".red().bold())),
212            err_suffix: String::new(),
213        }
214    }
215}
216
217/// The command-line processor.
218#[derive(Debug, PartialEq)]
219pub struct Cli<S: ProcessorState> {
220    /// The order-preserved list of tokens
221    tokens: Vec<Option<Token>>,
222    /// A lookup table for identifying which positions in the token stream a given option is present
223    store: HashMap<Tag<String>, Slot>,
224    /// The list of arguments has they are processed by the Cli processor
225    known_args: Vec<ArgType>,
226    asking_for_help: bool,
227    help: Option<Help>,
228    state: MemoryState,
229    options: CliOptions,
230    _marker: PhantomData<S>,
231}
232
233impl Default for Cli<Build> {
234    fn default() -> Self {
235        Self {
236            tokens: Vec::default(),
237            store: HashMap::default(),
238            known_args: Vec::default(),
239            help: None,
240            asking_for_help: false,
241            state: MemoryState::Start,
242            options: CliOptions::default(),
243            _marker: PhantomData,
244        }
245    }
246}
247
248impl Cli<Build> {
249    /// Create a new command-line processor. The processor is configured with
250    /// minimal options enabled.
251    pub fn new() -> Self {
252        Self {
253            tokens: Vec::new(),
254            store: HashMap::new(),
255            known_args: Vec::new(),
256            help: None,
257            asking_for_help: false,
258            state: MemoryState::Start,
259            options: CliOptions::new(),
260            _marker: PhantomData,
261        }
262    }
263
264    /// Sets the initial capacity for the data structures that are used to hold
265    /// the processed command-line data.
266    pub fn with_capacity(mut self, cap: usize) -> Self {
267        self.options.capacity = cap;
268        self
269    }
270
271    /// Sets the maximum threshold value when comparing strings for character similiarity.
272    pub fn threshold(mut self, cost: Cost) -> Self {
273        self.options.threshold = cost;
274        self
275    }
276
277    /// Automatically uppercase error messages during program execution.
278    pub fn auto_uppercase_errors(mut self) -> Self {
279        self.options.cap_mode = CapMode::Upper;
280        self
281    }
282
283    /// Automatically lowercase error messages during program execution.
284    pub fn auto_lowercase_errors(mut self) -> Self {
285        self.options.cap_mode = CapMode::Lower;
286        self
287    }
288
289    /// Do not allow any formatting on error messages during program execution.
290    pub fn disable_auto_case_errors(mut self) -> Self {
291        self.options.cap_mode = CapMode::Manual;
292        self
293    }
294
295    /// Enables coloring for the output.
296    pub fn enable_color(mut self) -> Self {
297        self.options.color_mode = ColorMode::On;
298        self
299    }
300
301    /// Disables coloring for the output.
302    pub fn disable_color(mut self) -> Self {
303        self.options.color_mode = ColorMode::Off;
304        self
305    }
306
307    /// Allows the output to be colored, but determines coloring based on
308    /// other factors in the environment.
309    pub fn allow_color(mut self) -> Self {
310        self.options.color_mode = ColorMode::Normal;
311        self
312    }
313
314    /// Downplays the [Help] flag to not become a priority error over other errors
315    /// during interpretation.
316    ///
317    /// Help is prioritized by default.
318    pub fn deprioritize_help(mut self) -> Self {
319        self.options.prioritize_help = false;
320        self
321    }
322
323    /// Prioritizes the [Help] flag over other errors during interpretation.
324    ///
325    /// This is enabled by default.
326    pub fn prioritize_help(mut self) -> Self {
327        self.options.prioritize_help = true;
328        self
329    }
330
331    /// Sets the text to come before an error message if one is reported during
332    /// processing.
333    pub fn error_prefix<T: AsRef<str>>(mut self, prefix: T) -> Self {
334        self.options.err_prefix = String::from(prefix.as_ref());
335        self
336    }
337
338    /// Sets the text to come after an error message if one is reported during
339    /// processing.
340    pub fn error_suffix<T: AsRef<str>>(mut self, suffix: T) -> Self {
341        self.options.err_suffix = String::from(suffix.as_ref());
342        self
343    }
344
345    /// Builds the [Cli] struct by tokenizing the [String] iterator into a
346    /// representable form for further processing.
347    ///
348    /// This function transitions the [Cli] state to the [Ready] state.
349    pub fn parse<T: Iterator<Item = String>>(mut self, args: T) -> Cli<Ready> {
350        self.options.color_mode.sync();
351        let mut tokens = Vec::<Option<Token>>::with_capacity(self.options.capacity);
352        let mut store = HashMap::with_capacity(self.options.capacity);
353        let mut terminated = false;
354        let mut args = args.skip(1).enumerate();
355        while let Some((i, mut arg)) = args.next() {
356            // ignore all input after detecting the terminator
357            if terminated == true {
358                tokens.push(Some(Token::Ignore(i, arg)));
359            // handle an option
360            } else if arg.starts_with(symbol::SWITCH) == true {
361                // try to separate from '=' sign
362                let mut value: Option<String> = None;
363                let mut option: Option<String> = None;
364                {
365                    if let Some((opt, val)) = arg.split_once('=') {
366                        option = Some(opt.to_string());
367                        value = Some(val.to_string());
368                    }
369                }
370                // update arg to be the value split by '='
371                if let Some(opt) = option {
372                    arg = opt;
373                }
374                // handle long flag signal
375                if arg.starts_with(symbol::FLAG) == true {
376                    arg.replace_range(0..=1, "");
377                    // caught the terminator (purely "--")
378                    if arg.is_empty() == true {
379                        tokens.push(Some(Token::Terminator(i)));
380                        terminated = true;
381                    // caught a 'long option' flag
382                    } else {
383                        store
384                            .entry(Tag::Flag(arg))
385                            .or_insert(Slot::new())
386                            .push(tokens.len());
387                        tokens.push(Some(Token::Flag(i)));
388                    }
389                // handle short flag signal
390                } else {
391                    // skip the initial switch character/symbol (1 char)
392                    let mut arg = arg.chars().skip(1);
393                    // check if the switch is empty by evaulating the first possible switch position
394                    if let Some(c) = arg.next() {
395                        store
396                            .entry(Tag::Switch(c.to_string()))
397                            .or_insert(Slot::new())
398                            .push(tokens.len());
399                        tokens.push(Some(Token::Switch(i, c)));
400                    } else {
401                        store
402                            .entry(Tag::Switch(String::new()))
403                            .or_insert(Slot::new())
404                            .push(tokens.len());
405                        tokens.push(Some(Token::EmptySwitch(i)));
406                    }
407                    // continuously split switches into individual components
408                    while let Some(c) = arg.next() {
409                        store
410                            .entry(Tag::Switch(c.to_string()))
411                            .or_insert(Slot::new())
412                            .push(tokens.len());
413                        tokens.push(Some(Token::Switch(i, c)));
414                    }
415                }
416                // caught an argument directly attached to an option
417                if let Some(val) = value {
418                    tokens.push(Some(Token::AttachedArgument(i, val)));
419                }
420            // caught an argument
421            } else {
422                tokens.push(Some(Token::UnattachedArgument(i, arg)));
423            }
424        }
425        self.tokens = tokens;
426        self.store = store;
427        // proceed to the next state
428        Cli::transition(self)
429    }
430}
431
432impl Cli<Ready> {
433    /// Runs the remaining steps in the command-line processor.
434    ///
435    /// This function transitions the [Cli] processor to the [Memory] state, and
436    /// then calls the necessary methods for `T` to behave as an implementation
437    /// of the [Command] trait.
438    ///
439    /// 1. `T` interprets the command-line data into its own structural data
440    /// 2. `T` executes its task
441    ///
442    /// This function will handle errors and report them to `stderr` if one
443    /// is encountered. If an error is encountered, the function returns 101 as
444    /// the exit code. If no error is encountered, the function returns 0 as the
445    /// exit code.
446    pub fn go<T: Command>(self) -> ExitCode {
447        let mut cli: Cli<Memory> = self.save();
448
449        match T::interpret(&mut cli) {
450            // construct the application
451            Ok(program) => {
452                // verify the cli has no additional arguments if this is the top-level command being parsed
453                match cli.empty() {
454                    Ok(_) => {
455                        let cli_opts = cli.options.clone();
456                        std::mem::drop(cli);
457                        match program.execute() {
458                            Ok(_) => ExitCode::from(0),
459                            Err(err) => {
460                                eprintln!(
461                                    "{}{}{}",
462                                    cli_opts.err_prefix,
463                                    utils::format_err_msg(err.to_string(), cli_opts.cap_mode),
464                                    cli_opts.err_suffix
465                                );
466                                ExitCode::from(101)
467                            }
468                        }
469                    }
470                    // report cli error
471                    Err(err) => {
472                        let cli_opts = cli.options;
473                        match err.kind() {
474                            ErrorKind::Help => println!("{}", &err),
475                            _ => eprintln!(
476                                "{}{}{}",
477                                cli_opts.err_prefix,
478                                utils::format_err_msg(err.to_string(), cli_opts.cap_mode),
479                                cli_opts.err_suffix
480                            ),
481                        }
482                        ExitCode::from(err.code())
483                    }
484                }
485            }
486            // report cli error
487            Err(err) => {
488                let cli_opts = cli.options;
489                match err.kind() {
490                    ErrorKind::Help => println!("{}", &err),
491                    _ => eprintln!(
492                        "{}{}{}",
493                        cli_opts.err_prefix,
494                        utils::format_err_msg(err.to_string(), cli_opts.cap_mode),
495                        cli_opts.err_suffix
496                    ),
497                }
498                ExitCode::from(err.code())
499            }
500        }
501    }
502
503    /// Saves the data from the command-line processing to be recalled during
504    /// interpretation.
505    pub fn save(self) -> Cli<Memory> {
506        self.transition()
507    }
508}
509
510// Public API
511
512impl Cli<Memory> {
513    /// Checks if there are any arguments supplied to the command-line during
514    /// parsing.
515    pub fn is_empty(&self) -> bool {
516        self.tokens.len() == 0
517    }
518
519    /// Sets the [Help] information for the command-line processor.
520    ///
521    /// Once the help information is updated, this function returns true if help
522    /// is detected on the command-line only if help is configured as a priority.
523    pub fn help(&mut self, help: Help) -> Result<bool> {
524        self.help = Some(help);
525        // check for flag if not already raised
526        if self.asking_for_help == false && self.is_help_enabled() == true {
527            self.asking_for_help = self.check(self.help.as_ref().unwrap().get_arg())?;
528        }
529        Ok(self.asking_for_help)
530    }
531
532    /// Attempts to display the currently available help information if help was
533    /// detected on the command-line.
534    pub fn raise_help(&self) -> Result<()> {
535        self.try_to_help()
536    }
537
538    /// Clears the status flag indicating if help was detected on the command-line
539    pub fn lower_help(&mut self) -> () {
540        self.asking_for_help = false;
541    }
542
543    /// Removes the current help information stored for the command-line processor.
544    pub fn unset_help(&mut self) -> () {
545        self.help = None;
546    }
547
548    /// Determines if an `UnattachedArg` exists to be served as a subcommand.
549    ///
550    /// If so, it will call `interpret` on the type defined. If not, it will return none.
551    pub fn nest<'a, T: Subcommand<U>, U>(
552        &mut self,
553        subcommand: Arg<Callable>,
554    ) -> Result<Option<T>> {
555        self.known_args.push(ArgType::from(subcommand));
556        // check but do not remove if an unattached arg exists
557        let command_exists = self
558            .tokens
559            .iter()
560            .find(|f| match f {
561                Some(Token::UnattachedArgument(_, _)) => true,
562                _ => false,
563            })
564            .is_some();
565        if command_exists == true {
566            // reset the parser state upon entering new subcommand
567            self.state = MemoryState::reset();
568            let sub = Some(T::interpret(self)?);
569            self.state.proceed(MemoryState::ProcessingSubcommands);
570            Ok(sub)
571        } else {
572            self.state.proceed(MemoryState::ProcessingSubcommands);
573            return Ok(None);
574        }
575    }
576
577    /// Tries to match the next positional argument against an array of strings in `bank`.
578    ///
579    /// If fails, it will attempt to offer a spelling suggestion if the name is close depending
580    /// on the configured cost threshold for string alignment.
581    ///
582    /// Panics if there is not a next positional argument. This command should only be
583    /// called immediately in the nested subcommand's [interpret][super::Command::interpret] method, which is
584    /// triggered on a successful call to the previous command's call to [nest][Cli::nest].
585    pub fn select<T: AsRef<str> + std::cmp::PartialEq>(&mut self, bank: &[T]) -> Result<String> {
586        // find the unattached arg's index before it is removed from the token stream
587        let i: usize = self
588            .tokens
589            .iter()
590            .find_map(|f| match f {
591                Some(Token::UnattachedArgument(i, _)) => Some(*i),
592                _ => None,
593            })
594            .expect("an unattached argument must exist before calling `match(...)`");
595        let command = self
596            .next_uarg()
597            .expect("`nest(...)` must be called before this function");
598
599        // perform partial clean to ensure no arguments are remaining behind the command (uncaught options)
600        let ooc_arg = self.capture_bad_flag(i)?;
601
602        if bank.iter().find(|p| p.as_ref() == command).is_some() {
603            if let Some((prefix, key, pos)) = ooc_arg {
604                if pos < i {
605                    self.try_to_help()?;
606                    return Err(Error::new(
607                        self.help.clone(),
608                        ErrorKind::OutOfContextArgSuggest,
609                        ErrorContext::OutofContextArgSuggest(format!("{}{}", prefix, key), command),
610                        self.options.cap_mode,
611                    ));
612                }
613            }
614            Ok(command)
615        // try to offer a spelling suggestion otherwise say we've hit an unexpected argument
616        } else {
617            // bypass sequence alignment algorithm if threshold == 0
618            if let Some(w) = if self.options.threshold > 0 {
619                seqalin::sel_min_edit_str(&command, &bank, self.options.threshold)
620            } else {
621                None
622            } {
623                Err(Error::new(
624                    self.help.clone(),
625                    ErrorKind::SuggestSubcommand,
626                    ErrorContext::SuggestWord(command, w.to_string()),
627                    self.options.cap_mode,
628                ))
629            } else {
630                self.try_to_help()?;
631                Err(Error::new(
632                    self.help.clone(),
633                    ErrorKind::UnknownSubcommand,
634                    ErrorContext::UnknownSubcommand(
635                        self.known_args.pop().expect("requires positional argument"),
636                        command,
637                    ),
638                    self.options.cap_mode,
639                ))
640            }
641        }
642    }
643
644    /// Returns the existence of `arg`.
645    ///
646    /// - If `arg` is a flag, then it checks for the associated name.
647    ///
648    /// If `arg` is found, then the result is `true`. If `arg` is not found, then
649    /// the result is `false`.
650    ///
651    /// This function errors if a value is associated with the `arg` or if the `arg`
652    /// is found multiple times.
653    pub fn check<'a>(&mut self, arg: Arg<Raisable>) -> Result<bool> {
654        match ArgType::from(arg) {
655            ArgType::Flag(fla) => self.check_flag(fla),
656            _ => panic!("impossible code condition"),
657        }
658    }
659
660    /// Returns the number of instances that `arg` exists.
661    ///
662    /// - If `arg` is a flag, then it checks for all references of its associated name.
663    ///
664    /// If `arg` is found, then the result is the number of times it is found.
665    /// If `arg` is not found, then the result is 0.
666    ///
667    /// This function errors if a value is associated with an instances of `arg`.
668    pub fn check_all<'a>(&mut self, arg: Arg<Raisable>) -> Result<usize> {
669        match ArgType::from(arg) {
670            ArgType::Flag(fla) => self.check_flag_all(fla),
671            _ => panic!("impossible code condition"),
672        }
673    }
674
675    /// Returns the number of instances that `arg` exists, up until an amount equal to `limit`.
676    ///
677    /// - If `arg` is a flag, then it checks for all references of its associated name.
678    ///
679    /// If `arg` is found, then the result is the number of times it is found.
680    /// If `arg` is not found, then the result is 0. The result is guaranteed to
681    /// be between 0 and no more than `limit`.
682    ///
683    /// This function errors if a value is associated with an instances of `arg` or
684    /// if the number of flag instances exceeds the `limit`.
685    pub fn check_until<'a>(&mut self, arg: Arg<Raisable>, limit: usize) -> Result<usize> {
686        match ArgType::from(arg) {
687            ArgType::Flag(fla) => self.check_flag_until(fla, limit),
688            _ => panic!("impossible code condition"),
689        }
690    }
691
692    /// Returns the number of instances that `arg` exists, between a range determined by `span`.
693    ///
694    /// - If `arg` is a flag, then it checks for all references of its associated name.
695    ///
696    /// If `arg` is found, then the result is the number of times it is found.
697    /// If `arg` is not found, then the result is 0. The result is guaranteed to
698    /// be 0 or a value contained in `span`.
699    ///
700    /// This function errors if a value is associated with an instances of `arg` or
701    /// if the number of flag instances is not contained within `span`.
702    pub fn check_between<'a, R: RangeBounds<usize>>(
703        &mut self,
704        arg: Arg<Raisable>,
705        span: R,
706    ) -> Result<usize> {
707        match ArgType::from(arg) {
708            ArgType::Flag(fla) => self.check_flag_between(fla, span),
709            _ => panic!("impossible code condition"),
710        }
711    }
712
713    /// Returns a single value associated with `arg`, if one exists.
714    ///
715    /// - If `arg` is a positional argument, then it takes the next unnamed argument.
716    /// - If `arg` is an option argument, then it takes the value associated with its name.
717    ///
718    /// If no value exists for `arg`, the result is `None`.
719    ///
720    /// This function errors if parsing into type `T` fails or if the number of values found
721    /// is greater than 1.
722    pub fn get<'a, T: FromStr>(&mut self, arg: Arg<Valuable>) -> Result<Option<T>>
723    where
724        <T as FromStr>::Err: 'static + std::error::Error,
725    {
726        match ArgType::from(arg) {
727            ArgType::Optional(opt) => self.get_option(opt),
728            ArgType::Positional(pos) => self.get_positional(pos),
729            _ => panic!("impossible code condition"),
730        }
731    }
732
733    /// Returns all values associated with `arg`, if they exist.
734    ///
735    /// - If `arg` is a positional argument, then it takes all the following unnamed arguments.
736    /// - If `arg` is an option argument, then it takes all the values associated with its name.
737    ///
738    /// If no values exists for `arg`, the result is `None`. If values do exist,
739    /// then the resulting vector is guaranteed to have `1 <= len()`.
740    ///
741    /// This function errors if parsing into type `T` fails.
742    pub fn get_all<'a, T: FromStr>(&mut self, arg: Arg<Valuable>) -> Result<Option<Vec<T>>>
743    where
744        <T as FromStr>::Err: 'static + std::error::Error,
745    {
746        match ArgType::from(arg) {
747            ArgType::Optional(opt) => self.get_option_all(opt),
748            ArgType::Positional(pos) => self.get_positional_all(pos),
749            _ => panic!("impossible code condition"),
750        }
751    }
752
753    /// Returns all values associated with `arg` up until an amount equal to `limit`, if they exist.
754    ///
755    /// - If `arg` is a positional argument, then it takes all remaining unnamed arguments up until `limit`.  
756    /// - If `arg` is an option argument, then it takes an arbitrary amount of values associated with its name up until `limit`.
757    ///
758    /// If no values exists for `arg`, the result is `None`. If values do exist,
759    /// then the resulting vector is guaranteed to have `1 <= len() <= limit`.
760    ///
761    /// This function errors if parsing into type `T` fails or if the number of
762    /// values found exceeds the specified `limit`.
763    pub fn get_until<'a, T: FromStr>(
764        &mut self,
765        arg: Arg<Valuable>,
766        limit: usize,
767    ) -> Result<Option<Vec<T>>>
768    where
769        <T as FromStr>::Err: 'static + std::error::Error,
770    {
771        match ArgType::from(arg) {
772            ArgType::Optional(opt) => self.get_option_until(opt, limit),
773            ArgType::Positional(pos) => self.get_positional_until(pos, limit),
774            _ => panic!("impossible code condition"),
775        }
776    }
777
778    /// Returns all values associated with `arg` between a range determined by `span`, if they exist.
779    ///
780    /// - If `arg` is a positional argument, then it takes all remaining unnamed arguments contained in `span`.
781    /// - If `arg` is an option argument, then it takes an arbitrary amount of values associated with its name contained in `span`.
782    ///
783    /// If no values exists for `arg`, the result is `None`. If values do exist,
784    /// then the resulting vector is guaranteed to have `span.inclusive_start() <= len() <= span.inclusive_end()`.
785    ///
786    /// This function errors if parsing into type `T` fails or if the number of
787    /// values found is not contained within `span`.
788    pub fn get_between<'a, T: FromStr, R: RangeBounds<usize>>(
789        &mut self,
790        arg: Arg<Valuable>,
791        span: R,
792    ) -> Result<Option<Vec<T>>>
793    where
794        <T as FromStr>::Err: 'static + std::error::Error,
795    {
796        match ArgType::from(arg) {
797            ArgType::Optional(opt) => self.get_option_between(opt, span),
798            ArgType::Positional(pos) => self.get_positional_between(pos, span),
799            _ => panic!("impossible code condition"),
800        }
801    }
802
803    /// Returns a single value associated with `arg`.
804    ///
805    /// - If `arg` is a positional argument, then it takes the next unnamed argument.
806    /// - If `arg` is an option argument, then it takes the value associated with its name.
807    ///
808    /// This function errors if parsing into type `T` fails or if the number of values found
809    /// is not exactly equal to 1.
810    pub fn require<'a, T: FromStr>(&mut self, arg: Arg<Valuable>) -> Result<T>
811    where
812        <T as FromStr>::Err: 'static + std::error::Error,
813    {
814        match ArgType::from(arg) {
815            ArgType::Optional(opt) => self.require_option(opt),
816            ArgType::Positional(pos) => self.require_positional(pos),
817            _ => panic!("impossible code condition"),
818        }
819    }
820
821    /// Returns all values associated with `arg`.
822    ///
823    /// - If `arg` is a positional argument, then it takes all remaining unnamed arguments.  
824    /// - If `arg` is an option argument, then it takes an arbitrary amount of values associated with its name.
825    ///
826    /// This function errors if parsing into type `T` fails or if zero values are found.
827    ///
828    /// The resulting vector is guaranteed to have `1 <= len()`.
829    pub fn require_all<'a, T: FromStr>(&mut self, arg: Arg<Valuable>) -> Result<Vec<T>>
830    where
831        <T as FromStr>::Err: 'static + std::error::Error,
832    {
833        match ArgType::from(arg) {
834            ArgType::Optional(opt) => self.require_option_all(opt),
835            ArgType::Positional(pos) => self.require_positional_all(pos),
836            _ => panic!("impossible code condition"),
837        }
838    }
839
840    /// Returns all values associated with `arg` up until an amount equal to `limit`.
841    ///
842    /// - If `arg` is a positional argument, then it takes all remaining unnamed arguments up until `limit`.  
843    /// - If `arg` is an option argument, then it takes an arbitrary amount of values associated with its name up until `limit`.
844    ///
845    /// This function errors if parsing into type `T` fails, if zero values are found, or
846    /// if the number of values found exceeds the specified `limit`.
847    ///
848    /// The resulting vector is guaranteed to have `1 <= len() <= limit`.
849    pub fn require_until<'a, T: FromStr>(
850        &mut self,
851        arg: Arg<Valuable>,
852        limit: usize,
853    ) -> Result<Vec<T>>
854    where
855        <T as FromStr>::Err: 'static + std::error::Error,
856    {
857        match ArgType::from(arg) {
858            ArgType::Optional(opt) => self.require_option_until(opt, limit),
859            ArgType::Positional(pos) => self.require_positional_until(pos, limit),
860            _ => panic!("impossible code condition"),
861        }
862    }
863
864    /// Returns all values associated with `arg` between a range determined by `span`.
865    ///
866    /// - If `arg` is a positional argument, then it takes all remaining unnamed arguments contained in `span`.  
867    /// - If `arg` is an option argument, then it takes an arbitrary amount of values associated with its name contained in `span`.
868    ///
869    /// This function errors if parsing into type `T` fails or
870    /// if the number of values found is not contained within `span`.
871    ///
872    /// The resulting vector is guaranteed to have `1 <= len() <= limit`.
873    pub fn require_between<'a, T: FromStr, R: RangeBounds<usize>>(
874        &mut self,
875        arg: Arg<Valuable>,
876        span: R,
877    ) -> Result<Vec<T>>
878    where
879        <T as FromStr>::Err: 'static + std::error::Error,
880    {
881        match ArgType::from(arg) {
882            ArgType::Optional(opt) => self.require_option_between(opt, span),
883            ArgType::Positional(pos) => self.require_positional_between(pos, span),
884            _ => panic!("impossible code condition"),
885        }
886    }
887
888    /// Checks that there are no more unprocessed arguments that were stored in
889    /// memory.
890    ///
891    /// This function errors if there are any unhandled arguments that were never
892    /// requested during the [Memory] stage.
893    pub fn empty<'a>(&'a mut self) -> Result<()> {
894        self.state.proceed(MemoryState::End);
895        self.try_to_help()?;
896        // check if map is empty, and return the minimum found index.
897        if let Some((prefix, key, _)) = self.capture_bad_flag(self.tokens.len())? {
898            Err(Error::new(
899                self.help.clone(),
900                ErrorKind::UnexpectedArg,
901                ErrorContext::UnexpectedArg(format!("{}{}", prefix, key)),
902                self.options.cap_mode,
903            ))
904        // find first non-none token
905        } else if let Some(t) = self.tokens.iter().find(|p| p.is_some()) {
906            match t {
907                Some(Token::UnattachedArgument(_, word)) => Err(Error::new(
908                    self.help.clone(),
909                    ErrorKind::UnexpectedArg,
910                    ErrorContext::UnexpectedArg(word.to_string()),
911                    self.options.cap_mode,
912                )),
913                Some(Token::Terminator(_)) => Err(Error::new(
914                    self.help.clone(),
915                    ErrorKind::UnexpectedArg,
916                    ErrorContext::UnexpectedArg(symbol::FLAG.to_string()),
917                    self.options.cap_mode,
918                )),
919                _ => panic!("no other tokens types should be left"),
920            }
921        } else {
922            Ok(())
923        }
924    }
925
926    /// Collects the list of arguments that were ignored due to being placed after
927    /// a terminator flag (`--`).
928    ///
929    /// If there are no arguments that were ignored, the result is an empty list.
930    ///
931    /// This function errors if a value is found to be associated with the terminator
932    /// flag.
933    pub fn remainder(&mut self) -> Result<Vec<String>> {
934        self.tokens
935            .iter_mut()
936            .skip_while(|tkn| match tkn {
937                Some(Token::Terminator(_)) => false,
938                _ => true,
939            })
940            .filter_map(|tkn| {
941                match tkn {
942                    // remove the terminator from the stream
943                    Some(Token::Terminator(_)) => {
944                        tkn.take().unwrap();
945                        None
946                    }
947                    Some(Token::Ignore(_, _)) => Some(Ok(tkn.take().unwrap().take_str())),
948                    Some(Token::AttachedArgument(_, _)) => Some(Err(Error::new(
949                        self.help.clone(),
950                        ErrorKind::UnexpectedValue,
951                        ErrorContext::UnexpectedValue(
952                            ArgType::Flag(Flag::new("")),
953                            tkn.take().unwrap().take_str(),
954                        ),
955                        self.options.cap_mode,
956                    ))),
957                    _ => panic!("no other tokens should exist beyond terminator {:?}", tkn),
958                }
959            })
960            .collect()
961    }
962}
963
964// Private API
965
966impl Cli<Memory> {
967    /// Serves the next `Positional` value in the token stream parsed as `T`.
968    ///
969    /// Errors if parsing fails. If the next argument is not a positional, it will
970    /// not move forward in the token stream.
971    fn get_positional<'a, T: FromStr>(&mut self, p: Positional) -> Result<Option<T>>
972    where
973        <T as FromStr>::Err: 'static + std::error::Error,
974    {
975        self.state.proceed(MemoryState::ProcessingPositionals);
976        self.known_args.push(ArgType::Positional(p));
977        self.try_positional()
978    }
979
980    fn get_positional_all<'a, T: FromStr>(&mut self, p: Positional) -> Result<Option<Vec<T>>>
981    where
982        <T as FromStr>::Err: 'static + std::error::Error,
983    {
984        self.state.proceed(MemoryState::ProcessingPositionals);
985        let mut result = Vec::<T>::new();
986        match self.get_positional(p)? {
987            Some(item) => result.push(item),
988            None => return Ok(None),
989        }
990        while let Some(v) = self.try_positional()? {
991            result.push(v);
992        }
993        Ok(Some(result))
994    }
995
996    fn get_positional_until<'a, T: FromStr>(
997        &mut self,
998        p: Positional,
999        limit: usize,
1000    ) -> Result<Option<Vec<T>>>
1001    where
1002        <T as FromStr>::Err: 'static + std::error::Error,
1003    {
1004        self.state.proceed(MemoryState::ProcessingPositionals);
1005        let values = self.get_positional_all::<T>(p)?;
1006        match values {
1007            // verify the size of the vector does not exceed `n`
1008            Some(r) => match r.len() <= limit {
1009                true => Ok(Some(r)),
1010                false => Err(Error::new(
1011                    self.help.clone(),
1012                    ErrorKind::ExceedingMaxCount,
1013                    ErrorContext::ExceededThreshold(self.known_args.pop().unwrap(), r.len(), limit),
1014                    self.options.cap_mode,
1015                )),
1016            },
1017            None => Ok(None),
1018        }
1019    }
1020
1021    fn get_positional_between<'a, T: FromStr, R: RangeBounds<usize>>(
1022        &mut self,
1023        p: Positional,
1024        span: R,
1025    ) -> Result<Option<Vec<T>>>
1026    where
1027        <T as FromStr>::Err: 'static + std::error::Error,
1028    {
1029        self.state.proceed(MemoryState::ProcessingPositionals);
1030        let values = self.get_positional_all::<T>(p)?;
1031        match values {
1032            // verify the size of the vector does not exceed `n`
1033            Some(r) => match span.contains(&r.len()) {
1034                true => Ok(Some(r)),
1035                false => Err(Error::new(
1036                    self.help.clone(),
1037                    ErrorKind::OutsideRange,
1038                    ErrorContext::OutsideRange(
1039                        self.known_args.pop().unwrap(),
1040                        r.len(),
1041                        span.start_bound().cloned(),
1042                        span.end_bound().cloned(),
1043                    ),
1044                    self.options.cap_mode,
1045                )),
1046            },
1047            None => Ok(None),
1048        }
1049    }
1050
1051    /// Forces the next [Positional] to exist from token stream.
1052    ///
1053    /// Errors if parsing fails or if no unattached argument is left in the token stream.
1054    fn require_positional<'a, T: FromStr>(&mut self, p: Positional) -> Result<T>
1055    where
1056        <T as FromStr>::Err: 'static + std::error::Error,
1057    {
1058        self.state.proceed(MemoryState::ProcessingPositionals);
1059        if let Some(value) = self.get_positional(p)? {
1060            Ok(value)
1061        } else {
1062            self.try_to_help()?;
1063            self.empty()?;
1064            Err(Error::new(
1065                self.help.clone(),
1066                ErrorKind::MissingPositional,
1067                ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1068                self.options.cap_mode,
1069            ))
1070        }
1071    }
1072
1073    /// Forces all the next [Positional] to be captured from the token stream.
1074    ///
1075    /// Errors if parsing fails or if zero unattached arguments are left in the token stream to begin.
1076    ///
1077    /// The resulting vector is guaranteed to have `.len() >= 1`.
1078    fn require_positional_all<'a, T: FromStr>(&mut self, p: Positional) -> Result<Vec<T>>
1079    where
1080        <T as FromStr>::Err: 'static + std::error::Error,
1081    {
1082        self.state.proceed(MemoryState::ProcessingPositionals);
1083        let mut result = Vec::<T>::new();
1084        result.push(self.require_positional(p)?);
1085        while let Some(v) = self.try_positional()? {
1086            result.push(v);
1087        }
1088        Ok(result)
1089    }
1090
1091    fn require_positional_until<'a, T: FromStr>(
1092        &mut self,
1093        p: Positional,
1094        limit: usize,
1095    ) -> Result<Vec<T>>
1096    where
1097        <T as FromStr>::Err: 'static + std::error::Error,
1098    {
1099        self.state.proceed(MemoryState::ProcessingPositionals);
1100        let values = self.require_positional_all(p)?;
1101        // verify the size of the vector does not exceed `n`
1102        match values.len() <= limit {
1103            true => Ok(values),
1104            false => Err(Error::new(
1105                self.help.clone(),
1106                ErrorKind::ExceedingMaxCount,
1107                ErrorContext::ExceededThreshold(
1108                    self.known_args.pop().unwrap(),
1109                    values.len(),
1110                    limit,
1111                ),
1112                self.options.cap_mode,
1113            )),
1114        }
1115    }
1116
1117    fn require_positional_between<'a, T: FromStr, R: RangeBounds<usize>>(
1118        &mut self,
1119        p: Positional,
1120        span: R,
1121    ) -> Result<Vec<T>>
1122    where
1123        <T as FromStr>::Err: 'static + std::error::Error,
1124    {
1125        self.state.proceed(MemoryState::ProcessingPositionals);
1126        let values = self.require_positional_all::<T>(p)?;
1127        match span.contains(&values.len()) {
1128            true => Ok(values),
1129            false => Err(Error::new(
1130                self.help.clone(),
1131                ErrorKind::OutsideRange,
1132                ErrorContext::OutsideRange(
1133                    self.known_args.pop().unwrap(),
1134                    values.len(),
1135                    span.start_bound().cloned(),
1136                    span.end_bound().cloned(),
1137                ),
1138                self.options.cap_mode,
1139            )),
1140        }
1141    }
1142
1143    /// Queries for a value of `Optional`.
1144    ///
1145    /// Errors if there are multiple values or if parsing fails.
1146    fn get_option<'a, T: FromStr>(&mut self, o: Optional) -> Result<Option<T>>
1147    where
1148        <T as FromStr>::Err: 'static + std::error::Error,
1149    {
1150        self.state.proceed(MemoryState::ProcessingOptionals);
1151        // collect information on where the flag can be found
1152        let mut locs = self.take_flag_locs(o.get_flag().get_name());
1153        if let Some(c) = o.get_flag().get_switch() {
1154            locs.extend(self.take_switch_locs(c));
1155        }
1156        self.known_args.push(ArgType::Optional(o));
1157        // pull values from where the option flags were found (including switch)
1158        let mut values = self.pull_flag(locs, true);
1159        match values.len() {
1160            1 => {
1161                if let Some(word) = values.pop().unwrap() {
1162                    let result = word.parse::<T>();
1163                    match result {
1164                        Ok(r) => Ok(Some(r)),
1165                        Err(err) => {
1166                            self.try_to_help()?;
1167                            Err(Error::new(
1168                                self.help.clone(),
1169                                ErrorKind::BadType,
1170                                ErrorContext::FailedCast(
1171                                    self.known_args.pop().unwrap(),
1172                                    word,
1173                                    Box::new(err),
1174                                ),
1175                                self.options.cap_mode,
1176                            ))
1177                        }
1178                    }
1179                } else {
1180                    self.try_to_help()?;
1181                    Err(Error::new(
1182                        self.help.clone(),
1183                        ErrorKind::ExpectingValue,
1184                        ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1185                        self.options.cap_mode,
1186                    ))
1187                }
1188            }
1189            0 => Ok(None),
1190            _ => {
1191                self.try_to_help()?;
1192                Err(Error::new(
1193                    self.help.clone(),
1194                    ErrorKind::DuplicateOptions,
1195                    ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1196                    self.options.cap_mode,
1197                ))
1198            }
1199        }
1200    }
1201
1202    /// Queries for all values behind an `Optional`.
1203    ///
1204    /// Errors if a parsing fails from string.
1205    fn get_option_all<'a, T: FromStr>(&mut self, o: Optional) -> Result<Option<Vec<T>>>
1206    where
1207        <T as FromStr>::Err: 'static + std::error::Error,
1208    {
1209        self.state.proceed(MemoryState::ProcessingOptionals);
1210        // collect information on where the flag can be found
1211        let mut locs = self.take_flag_locs(o.get_flag().get_name());
1212        if let Some(c) = o.get_flag().get_switch() {
1213            locs.extend(self.take_switch_locs(c));
1214        }
1215        self.known_args.push(ArgType::Optional(o));
1216        // pull values from where the option flags were found (including switch)
1217        let values = self.pull_flag(locs, true);
1218        if values.is_empty() == true {
1219            return Ok(None);
1220        }
1221        // try to convert each value into the type T
1222        let mut transform = Vec::<T>::with_capacity(values.len());
1223        for val in values {
1224            if let Some(word) = val {
1225                let result = word.parse::<T>();
1226                match result {
1227                    Ok(r) => transform.push(r),
1228                    Err(err) => {
1229                        self.try_to_help()?;
1230                        return Err(Error::new(
1231                            self.help.clone(),
1232                            ErrorKind::BadType,
1233                            ErrorContext::FailedCast(
1234                                self.known_args.pop().unwrap(),
1235                                word,
1236                                Box::new(err),
1237                            ),
1238                            self.options.cap_mode,
1239                        ));
1240                    }
1241                }
1242            } else {
1243                self.try_to_help()?;
1244                return Err(Error::new(
1245                    self.help.clone(),
1246                    ErrorKind::ExpectingValue,
1247                    ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1248                    self.options.cap_mode,
1249                ));
1250            }
1251        }
1252        Ok(Some(transform))
1253    }
1254
1255    /// Queries for up to `n` values behind an `Optional`.
1256    ///
1257    /// Errors if a parsing fails from string or if the number of detected optionals is > n.
1258    fn get_option_until<'a, T: FromStr>(
1259        &mut self,
1260        o: Optional,
1261        limit: usize,
1262    ) -> Result<Option<Vec<T>>>
1263    where
1264        <T as FromStr>::Err: 'static + std::error::Error,
1265    {
1266        self.state.proceed(MemoryState::ProcessingOptionals);
1267        let values = self.get_option_all::<T>(o)?;
1268        match values {
1269            // verify the size of the vector does not exceed `n`
1270            Some(r) => match r.len() <= limit {
1271                true => Ok(Some(r)),
1272                false => Err(Error::new(
1273                    self.help.clone(),
1274                    ErrorKind::ExceedingMaxCount,
1275                    ErrorContext::ExceededThreshold(self.known_args.pop().unwrap(), r.len(), limit),
1276                    self.options.cap_mode,
1277                )),
1278            },
1279            None => Ok(None),
1280        }
1281    }
1282
1283    fn get_option_between<'a, T: FromStr, R: RangeBounds<usize>>(
1284        &mut self,
1285        o: Optional,
1286        span: R,
1287    ) -> Result<Option<Vec<T>>>
1288    where
1289        <T as FromStr>::Err: 'static + std::error::Error,
1290    {
1291        self.state.proceed(MemoryState::ProcessingOptionals);
1292        let values = self.get_option_all::<T>(o)?;
1293        match values {
1294            // verify the size of the vector does not exceed `n`
1295            Some(r) => match span.contains(&r.len()) {
1296                true => Ok(Some(r)),
1297                false => Err(Error::new(
1298                    self.help.clone(),
1299                    ErrorKind::OutsideRange,
1300                    ErrorContext::OutsideRange(
1301                        self.known_args.pop().unwrap(),
1302                        r.len(),
1303                        span.start_bound().cloned(),
1304                        span.end_bound().cloned(),
1305                    ),
1306                    self.options.cap_mode,
1307                )),
1308            },
1309            None => Ok(None),
1310        }
1311    }
1312
1313    /// Queries for an expected value of `Optional`.
1314    fn require_option<'a, T: FromStr>(&mut self, o: Optional) -> Result<T>
1315    where
1316        <T as FromStr>::Err: 'static + std::error::Error,
1317    {
1318        self.state.proceed(MemoryState::ProcessingOptionals);
1319        if let Some(value) = self.get_option(o)? {
1320            Ok(value)
1321        } else {
1322            self.try_to_help()?;
1323            self.empty()?;
1324            Err(Error::new(
1325                self.help.clone(),
1326                ErrorKind::MissingOption,
1327                ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1328                self.options.cap_mode,
1329            ))
1330        }
1331    }
1332
1333    fn require_option_all<'a, T: FromStr>(&mut self, o: Optional) -> Result<Vec<T>>
1334    where
1335        <T as FromStr>::Err: 'static + std::error::Error,
1336    {
1337        self.state.proceed(MemoryState::ProcessingOptionals);
1338        if let Some(value) = self.get_option_all(o)? {
1339            Ok(value)
1340        } else {
1341            self.try_to_help()?;
1342            self.empty()?;
1343            Err(Error::new(
1344                self.help.clone(),
1345                ErrorKind::MissingOption,
1346                ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1347                self.options.cap_mode,
1348            ))
1349        }
1350    }
1351
1352    fn require_option_until<'a, T: FromStr>(&mut self, o: Optional, limit: usize) -> Result<Vec<T>>
1353    where
1354        <T as FromStr>::Err: 'static + std::error::Error,
1355    {
1356        self.state.proceed(MemoryState::ProcessingOptionals);
1357        let values = self.require_option_all(o)?;
1358        // verify the size of the vector does not exceed `n`
1359        match values.len() <= limit {
1360            true => Ok(values),
1361            false => Err(Error::new(
1362                self.help.clone(),
1363                ErrorKind::ExceedingMaxCount,
1364                ErrorContext::ExceededThreshold(
1365                    self.known_args.pop().unwrap(),
1366                    values.len(),
1367                    limit,
1368                ),
1369                self.options.cap_mode,
1370            )),
1371        }
1372    }
1373
1374    fn require_option_between<'a, T: FromStr, R: RangeBounds<usize>>(
1375        &mut self,
1376        o: Optional,
1377        span: R,
1378    ) -> Result<Vec<T>>
1379    where
1380        <T as FromStr>::Err: 'static + std::error::Error,
1381    {
1382        self.state.proceed(MemoryState::ProcessingOptionals);
1383        let values = self.require_option_all::<T>(o)?;
1384        match span.contains(&values.len()) {
1385            true => Ok(values),
1386            false => Err(Error::new(
1387                self.help.clone(),
1388                ErrorKind::OutsideRange,
1389                ErrorContext::OutsideRange(
1390                    self.known_args.pop().unwrap(),
1391                    values.len(),
1392                    span.start_bound().cloned(),
1393                    span.end_bound().cloned(),
1394                ),
1395                self.options.cap_mode,
1396            )),
1397        }
1398    }
1399
1400    /// Queries if a flag was raised once and only once.
1401    ///
1402    /// Errors if the flag has an attached value or was raised multiple times.
1403    fn check_flag<'a>(&mut self, f: Flag) -> Result<bool> {
1404        self.state.proceed(MemoryState::ProcessingFlags);
1405        let occurences = self.check_flag_all(f)?;
1406        match occurences > 1 {
1407            true => {
1408                self.try_to_help()?;
1409                Err(Error::new(
1410                    self.help.clone(),
1411                    ErrorKind::DuplicateOptions,
1412                    ErrorContext::FailedArg(self.known_args.pop().unwrap()),
1413                    self.options.cap_mode,
1414                ))
1415            }
1416            // the flag was either raised once or not at all
1417            false => Ok(occurences == 1),
1418        }
1419    }
1420
1421    /// Queries for the number of times a flag was raised.
1422    ///
1423    /// Errors if the flag has an attached value. Returning a zero indicates the flag was never raised.
1424    fn check_flag_all<'a>(&mut self, f: Flag) -> Result<usize> {
1425        self.state.proceed(MemoryState::ProcessingFlags);
1426        // collect information on where the flag can be found
1427        let mut locs = self.take_flag_locs(f.get_name());
1428        // try to find the switch locations
1429        if let Some(c) = f.get_switch() {
1430            locs.extend(self.take_switch_locs(c));
1431        };
1432        self.known_args.push(ArgType::Flag(f));
1433        let mut occurences = self.pull_flag(locs, false);
1434        // verify there are no values attached to this flag
1435        if let Some(val) = occurences.iter_mut().find(|p| p.is_some()) {
1436            self.try_to_help()?;
1437            return Err(Error::new(
1438                self.help.clone(),
1439                ErrorKind::UnexpectedValue,
1440                ErrorContext::UnexpectedValue(self.known_args.pop().unwrap(), val.take().unwrap()),
1441                self.options.cap_mode,
1442            ));
1443        } else {
1444            let raised = occurences.len() != 0;
1445            // check if the user is asking for help by raising the help flag
1446            if let Some(hp) = &self.help {
1447                if raised == true
1448                    && ArgType::from(hp.get_arg()).into_flag().unwrap().get_name()
1449                        == self
1450                            .known_args
1451                            .last()
1452                            .unwrap()
1453                            .as_flag()
1454                            .unwrap()
1455                            .get_name()
1456                {
1457                    self.asking_for_help = true;
1458                }
1459            }
1460            // return the number of times the flag was raised
1461            Ok(occurences.len())
1462        }
1463    }
1464
1465    /// Queries for the number of times a flag was raised up until `n` times.
1466    ///
1467    /// Errors if the flag has an attached value. Returning a zero indicates the flag was never raised.
1468    fn check_flag_until<'a>(&mut self, f: Flag, limit: usize) -> Result<usize> {
1469        self.state.proceed(MemoryState::ProcessingFlags);
1470        let occurences = self.check_flag_all(f)?;
1471        // verify the size of the vector does not exceed `n`
1472        match occurences <= limit {
1473            true => Ok(occurences),
1474            false => Err(Error::new(
1475                self.help.clone(),
1476                ErrorKind::ExceedingMaxCount,
1477                ErrorContext::ExceededThreshold(self.known_args.pop().unwrap(), occurences, limit),
1478                self.options.cap_mode,
1479            )),
1480        }
1481    }
1482
1483    fn check_flag_between<'a, R: RangeBounds<usize>>(&mut self, f: Flag, span: R) -> Result<usize> {
1484        self.state.proceed(MemoryState::ProcessingFlags);
1485        let occurences = self.check_flag_all(f)?;
1486        // verify the size of the vector does not exceed `n`
1487        match span.contains(&occurences) {
1488            true => Ok(occurences),
1489            false => Err(Error::new(
1490                self.help.clone(),
1491                ErrorKind::OutsideRange,
1492                ErrorContext::OutsideRange(
1493                    self.known_args.pop().unwrap(),
1494                    occurences,
1495                    span.start_bound().cloned(),
1496                    span.end_bound().cloned(),
1497                ),
1498                self.options.cap_mode,
1499            )),
1500        }
1501    }
1502}
1503
1504// Internal methods
1505
1506impl Cli<Memory> {
1507    /// Attempts to extract the next unattached argument to get a positional with valid parsing.
1508    ///
1509    /// Assumes the [Positional] argument is already added as the last element to the `known_args` vector.
1510    fn try_positional<'a, T: FromStr>(&mut self) -> Result<Option<T>>
1511    where
1512        <T as FromStr>::Err: 'static + std::error::Error,
1513    {
1514        match self.next_uarg() {
1515            Some(word) => match word.parse::<T>() {
1516                Ok(r) => Ok(Some(r)),
1517                Err(err) => {
1518                    self.try_to_help()?;
1519                    self.prioritize_suggestion()?;
1520                    Err(Error::new(
1521                        self.help.clone(),
1522                        ErrorKind::BadType,
1523                        ErrorContext::FailedCast(
1524                            self.known_args.pop().unwrap(),
1525                            word,
1526                            Box::new(err),
1527                        ),
1528                        self.options.cap_mode,
1529                    ))
1530                }
1531            },
1532            None => Ok(None),
1533        }
1534    }
1535
1536    /// Transforms the list of `known_args` into a list of the names for every available
1537    /// flag.
1538    ///
1539    /// This method is useful for acquiring a word bank to offer a flag spelling suggestion.
1540    fn known_args_as_flag_names(&self) -> HashSet<&str> {
1541        // note: collect into a `std::collections::HashSet` to avoid dupe
1542        self.known_args
1543            .iter()
1544            .filter_map(|f| match f {
1545                ArgType::Flag(f) => Some(f.get_name()),
1546                ArgType::Optional(o) => Some(o.get_flag().get_name()),
1547                _ => None,
1548            })
1549            .collect()
1550    }
1551
1552    /// Returns the first index where a flag/switch still remains in the token stream.
1553    ///
1554    /// The flag must occur in the token stream before the `breakpoint` index. If
1555    /// the `opt_store` hashmap is empty, it will return none.
1556    fn find_first_flag_left(&self, breakpoint: usize) -> Option<(&str, usize)> {
1557        let mut min_i: Option<(&str, usize)> = None;
1558        let mut opt_it = self
1559            .store
1560            .iter()
1561            .filter(|(_, slot)| slot.is_visited() == false);
1562        while let Some((key, val)) = opt_it.next() {
1563            // check if this flag's index comes before the currently known minimum index
1564            min_i = if *val.first().unwrap() < breakpoint
1565                && (min_i.is_none() || min_i.unwrap().1 > *val.first().unwrap())
1566            {
1567                Some((key.as_ref(), *val.first().unwrap()))
1568            } else {
1569                min_i
1570            };
1571        }
1572        min_i
1573    }
1574
1575    /// Verifies there are no uncaught flags behind a given index.
1576    fn capture_bad_flag<'a>(&self, i: usize) -> Result<Option<(&str, &str, usize)>> {
1577        if let Some((key, val)) = self.find_first_flag_left(i) {
1578            self.try_to_help()?;
1579            // check what type of token it was to determine if it was called with '-' or '--'
1580            if let Some(t) = self.tokens.get(val).unwrap() {
1581                let prefix = match t {
1582                    Token::Switch(_, _) | Token::EmptySwitch(_) => symbol::SWITCH,
1583                    Token::Flag(_) => {
1584                        // try to match it with a valid flag from word bank
1585                        let bank: Vec<&str> = self.known_args_as_flag_names().into_iter().collect();
1586                        if let Some(closest) = if self.options.threshold > 0 {
1587                            seqalin::sel_min_edit_str(key, &bank, self.options.threshold)
1588                        } else {
1589                            None
1590                        } {
1591                            return Err(Error::new(
1592                                self.help.clone(),
1593                                ErrorKind::SuggestArg,
1594                                ErrorContext::SuggestWord(
1595                                    format!("{}{}", symbol::FLAG, key),
1596                                    format!("{}{}", symbol::FLAG, closest),
1597                                ),
1598                                self.options.cap_mode,
1599                            ));
1600                        }
1601                        symbol::FLAG
1602                    }
1603                    _ => panic!("no other tokens are allowed in hashmap"),
1604                };
1605                Ok(Some((prefix, key, val)))
1606            } else {
1607                panic!("this token's values have been removed")
1608            }
1609        } else {
1610            Ok(None)
1611        }
1612    }
1613
1614    /// Returns all locations in the token stream where the flag identifier `tag` is found.
1615    ///
1616    /// Information about Option<Vec<T>> vs. empty Vec<T>: https://users.rust-lang.org/t/space-time-usage-to-construct-vec-t-vs-option-vec-t/35596/6
1617    fn take_flag_locs(&mut self, tag: &str) -> Vec<usize> {
1618        if let Some(slot) = self.store.get_mut(&Tag::Flag(tag.to_owned())) {
1619            slot.visit();
1620            slot.get_indices().to_vec()
1621        } else {
1622            Vec::new()
1623        }
1624    }
1625
1626    /// Returns all locations in the token stream where the switch identifier `c` is found.
1627    fn take_switch_locs(&mut self, c: &char) -> Vec<usize> {
1628        // allocate &str to the stack and not the heap to get from store
1629        let mut arr = [0; 4];
1630        let tag = c.encode_utf8(&mut arr);
1631
1632        if let Some(slot) = self.store.get_mut(&Tag::Switch(tag.to_owned())) {
1633            slot.visit();
1634            slot.get_indices().to_vec()
1635        } else {
1636            Vec::new()
1637        }
1638    }
1639
1640    /// Iterates through the list of tokens to find the first suggestion against a flag to return.
1641    ///
1642    /// Returns ok if cannot make a suggestion.
1643    fn prioritize_suggestion(&self) -> Result<()> {
1644        let mut kv: Vec<(&String, &Vec<usize>)> = self
1645            .store
1646            .iter()
1647            .map(|(tag, slot)| (tag.as_ref(), slot.get_indices()))
1648            .collect::<Vec<(&String, &Vec<usize>)>>();
1649        kv.sort_by(|a, b| a.1.first().unwrap().cmp(b.1.first().unwrap()));
1650        let bank: Vec<&str> = self.known_args_as_flag_names().into_iter().collect();
1651        let r = kv
1652            .iter()
1653            .find_map(|f| match self.tokens.get(*f.1.first().unwrap()).unwrap() {
1654                Some(Token::Flag(_)) => {
1655                    if let Some(word) = if self.options.threshold > 0 {
1656                        seqalin::sel_min_edit_str(f.0, &bank, self.options.threshold)
1657                    } else {
1658                        None
1659                    } {
1660                        Some(Error::new(
1661                            self.help.clone(),
1662                            ErrorKind::SuggestArg,
1663                            ErrorContext::SuggestWord(
1664                                format!("{}{}", symbol::FLAG, f.0),
1665                                format!("{}{}", symbol::FLAG, word),
1666                            ),
1667                            self.options.cap_mode,
1668                        ))
1669                    } else {
1670                        None
1671                    }
1672                }
1673                _ => None,
1674            });
1675        if self.asking_for_help == true {
1676            Ok(())
1677        } else if let Some(e) = r {
1678            Err(e)
1679        } else {
1680            Ok(())
1681        }
1682    }
1683
1684    /// Grabs the flag/switch from the token stream, and collects.
1685    ///
1686    /// If an argument were to follow it will be in the vector.
1687    fn pull_flag(&mut self, locations: Vec<usize>, with_uarg: bool) -> Vec<Option<String>> {
1688        // remove all flag instances located at each index `i` in the vector `locations`
1689        locations
1690            .iter()
1691            .map(|i| {
1692                // remove the flag instance from the token stream
1693                self.tokens.get_mut(*i).unwrap().take();
1694                // check the next position for a value
1695                if let Some(t_next) = self.tokens.get_mut(*i + 1) {
1696                    match t_next {
1697                        Some(Token::AttachedArgument(_, _)) => {
1698                            Some(t_next.take().unwrap().take_str())
1699                        }
1700                        Some(Token::UnattachedArgument(_, _)) => {
1701                            // do not take unattached arguments unless told by parameter
1702                            match with_uarg {
1703                                true => Some(t_next.take().unwrap().take_str()),
1704                                false => None,
1705                            }
1706                        }
1707                        _ => None,
1708                    }
1709                } else {
1710                    None
1711                }
1712            })
1713            .collect()
1714    }
1715
1716    /// Pulls the next `UnattachedArg` token from the token stream.
1717    ///
1718    /// If no more `UnattachedArg` tokens are left, it will return none.
1719    fn next_uarg(&mut self) -> Option<String> {
1720        if let Some(p) = self.tokens.iter_mut().find(|s| match s {
1721            Some(Token::UnattachedArgument(_, _)) | Some(Token::Terminator(_)) => true,
1722            _ => false,
1723        }) {
1724            if let Some(Token::Terminator(_)) = p {
1725                None
1726            } else {
1727                Some(p.take().unwrap().take_str())
1728            }
1729        } else {
1730            None
1731        }
1732    }
1733
1734    /// Checks if help is enabled and is some value.
1735    fn is_help_enabled(&self) -> bool {
1736        // change to does_help_exist()
1737        self.help.is_some()
1738    }
1739
1740    /// Checks if help has been raised and will return its own error for displaying
1741    /// help.
1742    fn try_to_help(&self) -> Result<()> {
1743        if self.options.prioritize_help == true
1744            && self.asking_for_help == true
1745            && self.is_help_enabled() == true
1746        {
1747            Err(Error::new(
1748                self.help.clone(),
1749                ErrorKind::Help,
1750                ErrorContext::Help,
1751                self.options.cap_mode,
1752            ))
1753        } else {
1754            Ok(())
1755        }
1756    }
1757}
1758
1759#[cfg(test)]
1760mod test {
1761    use super::*;
1762    use crate::error::ErrorKind;
1763
1764    /// Helper test fn to write vec of &str as iterator for Cli parameter.
1765    fn args<'a>(args: Vec<&'a str>) -> Box<dyn Iterator<Item = String> + 'a> {
1766        Box::new(args.into_iter().map(|f| f.to_string()).into_iter())
1767    }
1768
1769    #[test]
1770    fn get_all_optionals() {
1771        // option provided multiple times
1772        let mut cli = Cli::new()
1773            .parse(args(vec![
1774                "orbit",
1775                "plan",
1776                "--fileset",
1777                "a",
1778                "--fileset=b",
1779                "--fileset",
1780                "c",
1781            ]))
1782            .save();
1783        let sets: Vec<String> = cli
1784            .get_option_all(Optional::new("fileset"))
1785            .unwrap()
1786            .unwrap();
1787        assert_eq!(sets, vec!["a", "b", "c"]);
1788        // failing case- bad conversion of 'c' to an integer
1789        let mut cli = Cli::new()
1790            .parse(args(vec![
1791                "orbit",
1792                "plan",
1793                "--digit",
1794                "10",
1795                "--digit=9",
1796                "--digit",
1797                "c",
1798            ]))
1799            .save();
1800        assert_eq!(
1801            cli.get_option_all::<i32>(Optional::new("digit")).is_err(),
1802            true
1803        ); // bad conversion
1804           // option provided as valid integers
1805        let mut cli = Cli::new()
1806            .parse(args(vec![
1807                "orbit",
1808                "plan",
1809                "--digit",
1810                "10",
1811                "--digit=9",
1812                "--digit",
1813                "1",
1814            ]))
1815            .save();
1816        let sets: Vec<i32> = cli.get_option_all(Optional::new("digit")).unwrap().unwrap();
1817        assert_eq!(sets, vec![10, 9, 1]);
1818        // option provided once
1819        let mut cli = Cli::new()
1820            .parse(args(vec!["orbit", "plan", "--fileset", "a"]))
1821            .save();
1822        let sets: Vec<String> = cli
1823            .get_option_all(Optional::new("fileset"))
1824            .unwrap()
1825            .unwrap();
1826        assert_eq!(sets, vec!["a"]);
1827        // option not provided
1828        let mut cli = Cli::new().parse(args(vec!["orbit", "plan"])).save();
1829        let sets: Option<Vec<String>> = cli.get_option_all(Optional::new("fileset")).unwrap();
1830        assert_eq!(sets, None);
1831    }
1832
1833    #[test]
1834    fn match_command() {
1835        let mut cli = Cli::new()
1836            .parse(args(vec![
1837                "orbit",
1838                "get",
1839                "rary.gates",
1840                "--instance",
1841                "--component",
1842            ]))
1843            .save();
1844        // successfully matched 'get' command
1845        assert_eq!(
1846            cli.select(&["new", "get", "install", "edit"]).unwrap(),
1847            "get".to_string()
1848        );
1849
1850        let mut cli = Cli::new()
1851            .threshold(4)
1852            .parse(args(vec![
1853                "orbit",
1854                "got",
1855                "rary.gates",
1856                "--instance",
1857                "--component",
1858            ]))
1859            .save();
1860        // suggest 'get' command
1861        assert!(cli.select(&["new", "get", "install", "edit"]).is_err());
1862    }
1863
1864    #[test]
1865    #[should_panic = "requires positional argument"]
1866    fn match_command_no_arg() {
1867        let mut cli = Cli::new()
1868            .parse(args(vec![
1869                "orbit",
1870                "got",
1871                "rary.gates",
1872                "--instance",
1873                "--component",
1874            ]))
1875            .save();
1876        // cli has no positional arguments loaded to report as error... panic
1877        assert!(cli.select(&["new", "get", "install", "edit"]).is_err());
1878    }
1879
1880    #[test]
1881    fn find_first_flag_left() {
1882        let cli = Cli::new()
1883            .parse(args(vec![
1884                "orbit",
1885                "--help",
1886                "new",
1887                "rary.gates",
1888                "--vcs",
1889                "git",
1890            ]))
1891            .save();
1892        assert_eq!(
1893            cli.find_first_flag_left(cli.tokens.len()),
1894            Some(("help", 0))
1895        );
1896
1897        let cli = Cli::new()
1898            .parse(args(vec!["orbit", "new", "rary.gates"]))
1899            .save();
1900        assert_eq!(cli.find_first_flag_left(cli.tokens.len()), None);
1901
1902        let cli = Cli::new()
1903            .parse(args(vec![
1904                "orbit",
1905                "new",
1906                "rary.gates",
1907                "--vcs",
1908                "git",
1909                "--help",
1910            ]))
1911            .save();
1912        assert_eq!(cli.find_first_flag_left(cli.tokens.len()), Some(("vcs", 2)));
1913
1914        let cli = Cli::new()
1915            .parse(args(vec!["orbit", "new", "rary.gates", "-c=git", "--help"]))
1916            .save();
1917        assert_eq!(cli.find_first_flag_left(cli.tokens.len()), Some(("c", 2)));
1918
1919        let cli = Cli::new()
1920            .parse(args(vec!["orbit", "new", "rary.gates", "-c=git", "--help"]))
1921            .save();
1922        assert_eq!(cli.find_first_flag_left(1), None); // check before 'rary.gates' position
1923
1924        let cli = Cli::new()
1925            .parse(args(vec![
1926                "orbit",
1927                "--unknown",
1928                "new",
1929                "rary.gates",
1930                "-c=git",
1931                "--help",
1932            ]))
1933            .save();
1934        assert_eq!(cli.find_first_flag_left(1), Some(("unknown", 0))); // check before 'new' subcommand
1935    }
1936
1937    #[test]
1938    fn processed_all_args() {
1939        let mut cli = Cli::new()
1940            .parse(args(vec![
1941                "orbit",
1942                "--upgrade",
1943                "new",
1944                "rary.gates",
1945                "--vcs",
1946                "git",
1947            ]))
1948            .save();
1949        // tokens are still in token stream
1950        let _ = cli.check_flag(Flag::new("upgrade")).unwrap();
1951        let _: Option<String> = cli.get_option(Optional::new("vcs")).unwrap();
1952        let _: String = cli.require_positional(Positional::new("command")).unwrap();
1953        let _: String = cli.require_positional(Positional::new("ip")).unwrap();
1954        // no more tokens left in stream
1955        assert_eq!(cli.empty().unwrap(), ());
1956
1957        let mut cli = Cli::new()
1958            .parse(args(vec!["orbit", "new", "rary.gates", symbol::FLAG]))
1959            .save();
1960        // removes only valid args/flags/opts
1961        let _ = cli.check_flag(Flag::new("help")).unwrap();
1962        let _: Option<String> = cli.get_option(Optional::new("vcs")).unwrap();
1963        let _: String = cli.require_positional(Positional::new("command")).unwrap();
1964        let _: String = cli.require_positional(Positional::new("ip")).unwrap();
1965        // unexpected '--'
1966        assert!(cli.empty().is_err());
1967
1968        let mut cli = Cli::new()
1969            .parse(args(vec![
1970                "orbit",
1971                "--help",
1972                "new",
1973                "rary.gates",
1974                "--vcs",
1975                "git",
1976            ]))
1977            .save();
1978        // no tokens were removed (help will also raise error)
1979        assert!(cli.empty().is_err());
1980
1981        let mut cli = Cli::new()
1982            .parse(args(vec!["orbit", symbol::FLAG, "some", "extra", "words"]))
1983            .save();
1984        let _: Vec<String> = cli.remainder().unwrap();
1985        // terminator removed as well as its arguments that were ignored
1986        assert_eq!(cli.empty().unwrap(), ());
1987    }
1988
1989    #[test]
1990    fn tokenizer() {
1991        let cli = Cli::new().parse(args(vec![])).save();
1992        assert_eq!(cli.tokens, vec![]);
1993
1994        let cli = Cli::new().parse(args(vec!["orbit"])).save();
1995        assert_eq!(cli.tokens, vec![]);
1996
1997        let cli = Cli::new().parse(args(vec!["orbit", "--help"])).save();
1998        assert_eq!(cli.tokens, vec![Some(Token::Flag(0))]);
1999
2000        let cli = Cli::new().parse(args(vec!["orbit", "--help", "-v"])).save();
2001        assert_eq!(
2002            cli.tokens,
2003            vec![Some(Token::Flag(0)), Some(Token::Switch(1, 'v'))],
2004        );
2005
2006        let cli = Cli::new()
2007            .parse(args(vec!["orbit", "new", "rary.gates"]))
2008            .save();
2009        assert_eq!(
2010            cli.tokens,
2011            vec![
2012                Some(Token::UnattachedArgument(0, "new".to_string())),
2013                Some(Token::UnattachedArgument(1, "rary.gates".to_string())),
2014            ],
2015        );
2016
2017        let cli = Cli::new()
2018            .parse(args(vec!["orbit", "--help", "-vh"]))
2019            .save();
2020        assert_eq!(
2021            cli.tokens,
2022            vec![
2023                Some(Token::Flag(0)),
2024                Some(Token::Switch(1, 'v')),
2025                Some(Token::Switch(1, 'h')),
2026            ],
2027        );
2028
2029        let cli = Cli::new()
2030            .parse(args(vec!["orbit", "--help", "-vhc=10"]))
2031            .save();
2032        assert_eq!(
2033            cli.tokens,
2034            vec![
2035                Some(Token::Flag(0)),
2036                Some(Token::Switch(1, 'v')),
2037                Some(Token::Switch(1, 'h')),
2038                Some(Token::Switch(1, 'c')),
2039                Some(Token::AttachedArgument(1, "10".to_string())),
2040            ],
2041        );
2042
2043        // an attached argument can sneak in behind a terminator
2044        let cli = Cli::new()
2045            .parse(args(vec!["orbit", "--=value", "extra"]))
2046            .save();
2047        assert_eq!(
2048            cli.tokens,
2049            vec![
2050                Some(Token::Terminator(0)),
2051                Some(Token::AttachedArgument(0, "value".to_string())),
2052                Some(Token::Ignore(1, "extra".to_string())),
2053            ]
2054        );
2055
2056        // final boss
2057        let cli = Cli::new().parse(args(vec![
2058            "orbit",
2059            "--help",
2060            "-v",
2061            "new",
2062            "ip",
2063            "--lib",
2064            "--name=rary.gates",
2065            "--help",
2066            "-sci",
2067            symbol::FLAG,
2068            "--map",
2069            "synthesis",
2070            "-jto",
2071        ]));
2072        assert_eq!(
2073            cli.tokens,
2074            vec![
2075                Some(Token::Flag(0)),
2076                Some(Token::Switch(1, 'v')),
2077                Some(Token::UnattachedArgument(2, "new".to_string())),
2078                Some(Token::UnattachedArgument(3, "ip".to_string())),
2079                Some(Token::Flag(4)),
2080                Some(Token::Flag(5)),
2081                Some(Token::AttachedArgument(5, "rary.gates".to_string())),
2082                Some(Token::Flag(6)),
2083                Some(Token::Switch(7, 's')),
2084                Some(Token::Switch(7, 'c')),
2085                Some(Token::Switch(7, 'i')),
2086                Some(Token::Terminator(8)),
2087                Some(Token::Ignore(9, "--map".to_string())),
2088                Some(Token::Ignore(10, "synthesis".to_string())),
2089                Some(Token::Ignore(11, "-jto".to_string())),
2090            ],
2091        );
2092    }
2093
2094    #[test]
2095    fn find_flags_and_switches() {
2096        let mut cli = Cli::new()
2097            .parse(args(vec![
2098                "orbit",
2099                "--help",
2100                "-v",
2101                "new",
2102                "ip",
2103                "--lib",
2104                "--name=rary.gates",
2105                "--help",
2106                "-sci",
2107                "-i",
2108                "--",
2109                "--map",
2110                "synthesis",
2111                "-jto",
2112            ]))
2113            .save();
2114
2115        // detects 0
2116        assert_eq!(cli.take_flag_locs("version"), vec![]);
2117        // detects 1
2118        assert_eq!(cli.take_flag_locs("lib"), vec![4]);
2119        // detects multiple
2120        assert_eq!(cli.take_flag_locs("help"), vec![0, 7]);
2121        // flag was past terminator and marked as ignore
2122        assert_eq!(cli.take_flag_locs("map"), vec![]);
2123        // filters out arguments
2124        assert_eq!(cli.take_flag_locs("rary.gates"), vec![]);
2125
2126        // detects 0
2127        assert_eq!(cli.take_switch_locs(&'q'), vec![]);
2128        // detects 1
2129        assert_eq!(cli.take_switch_locs(&'v'), vec![1]);
2130        // detects multiple
2131        assert_eq!(cli.take_switch_locs(&'i'), vec![10, 11]);
2132        // switch was past terminator and marked as ignore
2133        assert_eq!(cli.take_switch_locs(&'j'), vec![]);
2134    }
2135
2136    #[test]
2137    fn flags_in_map() {
2138        let cli = Cli::new().parse(args(vec![
2139            "orbit",
2140            "--help",
2141            "-v",
2142            "new",
2143            "ip",
2144            "--lib",
2145            "--name=rary.gates",
2146            "--help",
2147            "-sci",
2148            symbol::FLAG,
2149            "--map",
2150            "synthesis",
2151            "-jto",
2152        ]));
2153        let mut store = HashMap::<Tag<String>, Slot>::new();
2154        // store long options
2155        store.insert(
2156            Tag::Flag("help".to_string()),
2157            Slot {
2158                pointers: vec![0, 7],
2159                visited: false,
2160            },
2161        );
2162        store.insert(
2163            Tag::Flag("lib".to_string()),
2164            Slot {
2165                pointers: vec![4],
2166                visited: false,
2167            },
2168        );
2169        store.insert(
2170            Tag::Flag("name".to_string()),
2171            Slot {
2172                pointers: vec![5],
2173                visited: false,
2174            },
2175        );
2176        // stores switches too
2177        store.insert(
2178            Tag::Switch("v".to_string()),
2179            Slot {
2180                pointers: vec![1],
2181                visited: false,
2182            },
2183        );
2184        store.insert(
2185            Tag::Switch("s".to_string()),
2186            Slot {
2187                pointers: vec![8],
2188                visited: false,
2189            },
2190        );
2191        store.insert(
2192            Tag::Switch("c".to_string()),
2193            Slot {
2194                pointers: vec![9],
2195                visited: false,
2196            },
2197        );
2198        store.insert(
2199            Tag::Switch("i".to_string()),
2200            Slot {
2201                pointers: vec![10],
2202                visited: false,
2203            },
2204        );
2205        assert_eq!(cli.store, store);
2206    }
2207
2208    #[test]
2209    fn take_unattached_args() {
2210        let mut cli = Cli::new()
2211            .parse(args(vec![
2212                "orbit",
2213                "--help",
2214                "-v",
2215                "new",
2216                "ip",
2217                "--lib",
2218                "--name=rary.gates",
2219                "--help",
2220                "-scii",
2221                "get",
2222                symbol::FLAG,
2223                "--map",
2224                "synthesis",
2225                "-jto",
2226            ]))
2227            .save();
2228
2229        assert_eq!(cli.next_uarg().unwrap(), "new".to_string());
2230        assert_eq!(cli.next_uarg().unwrap(), "ip".to_string());
2231        assert_eq!(cli.next_uarg().unwrap(), "get".to_string());
2232        assert_eq!(cli.next_uarg(), None);
2233    }
2234
2235    #[test]
2236    fn take_remainder_args() {
2237        let mut cli = Cli::new()
2238            .parse(args(vec![
2239                "orbit",
2240                "--help",
2241                "-v",
2242                "new",
2243                "ip",
2244                "--lib",
2245                "--name=rary.gates",
2246                "--help",
2247                "-scii",
2248                "get",
2249                symbol::FLAG,
2250                "--map",
2251                "synthesis",
2252                "-jto",
2253            ]))
2254            .save();
2255        assert_eq!(cli.remainder().unwrap(), vec!["--map", "synthesis", "-jto"]);
2256        // the items were removed from the token stream
2257        assert_eq!(cli.remainder().unwrap(), Vec::<String>::new());
2258
2259        // an attached argument can sneak in behind a terminator (handle in a result fn)
2260        let mut cli = Cli::new()
2261            .parse(args(vec!["orbit", "--=value", "extra"]))
2262            .save();
2263        assert!(cli.remainder().is_err());
2264
2265        let mut cli = Cli::new().parse(args(vec!["orbit", "--help"])).save();
2266        // the terminator was never found
2267        assert_eq!(cli.remainder().unwrap(), Vec::<String>::new());
2268    }
2269
2270    #[test]
2271    fn pull_values_from_flags() {
2272        let mut cli = Cli::new().parse(args(vec!["orbit", "--help"])).save();
2273        let locs = cli.take_flag_locs("help");
2274        assert_eq!(cli.pull_flag(locs, false), vec![None]);
2275        assert_eq!(cli.tokens.get(0), Some(&None));
2276
2277        let mut cli = Cli::new()
2278            .parse(args(vec![
2279                "orbit",
2280                "--name",
2281                "gates",
2282                "arg",
2283                "--lib",
2284                "new",
2285                "--name=gates2",
2286                "--opt=1",
2287                "--opt",
2288                "--help",
2289            ]))
2290            .save();
2291        let locs = cli.take_flag_locs("lib");
2292        assert_eq!(cli.pull_flag(locs, false), vec![None]);
2293        // token no longer exists
2294        assert_eq!(cli.tokens.get(3), Some(&None));
2295
2296        // gets strings and removes both instances of flag from token stream
2297        let locs = cli.take_flag_locs("name");
2298        assert_eq!(
2299            cli.pull_flag(locs, true),
2300            vec![Some("gates".to_string()), Some("gates2".to_string())]
2301        );
2302        assert_eq!(cli.tokens.get(0), Some(&None));
2303        assert_eq!(cli.tokens.get(5), Some(&None));
2304
2305        let locs = cli.take_flag_locs("opt");
2306        assert_eq!(cli.pull_flag(locs, true), vec![Some("1".to_string()), None]);
2307
2308        // gets switches as well from the store
2309        let mut cli = Cli::new()
2310            .parse(args(vec![
2311                "orbit",
2312                "--name",
2313                "gates",
2314                "-sicn",
2315                "dut",
2316                "new",
2317                "-vl=direct",
2318                "--help",
2319                "-l",
2320                "-m",
2321                "install",
2322            ]))
2323            .save();
2324        let locs = cli.take_switch_locs(&'l');
2325        assert_eq!(
2326            cli.pull_flag(locs, true),
2327            vec![Some("direct".to_string()), None]
2328        );
2329        assert_eq!(cli.tokens.get(9), Some(&None));
2330        assert_eq!(cli.tokens.get(12), Some(&None));
2331        let locs = cli.take_switch_locs(&'s');
2332        assert_eq!(cli.pull_flag(locs, true), vec![None]);
2333        let locs = cli.take_switch_locs(&'v');
2334        assert_eq!(cli.pull_flag(locs, true), vec![None]);
2335        let locs = cli.take_switch_locs(&'i');
2336        assert_eq!(cli.pull_flag(locs, true), vec![None]);
2337        let locs = cli.take_switch_locs(&'c');
2338        assert_eq!(cli.pull_flag(locs, false), vec![None]);
2339        let locs = cli.take_switch_locs(&'m');
2340        assert_eq!(cli.pull_flag(locs, false), vec![None]);
2341    }
2342
2343    #[test]
2344    fn check_flag() {
2345        let mut cli = Cli::new()
2346            .parse(args(vec!["orbit", "--help", "--verbose", "get"]))
2347            .save();
2348        assert_eq!(cli.check_flag(Flag::new("help")).unwrap(), true);
2349        assert_eq!(cli.check_flag(Flag::new("verbose")).unwrap(), true);
2350        assert_eq!(cli.check_flag(Flag::new("version")).unwrap(), false);
2351
2352        let mut cli = Cli::new()
2353            .parse(args(vec!["orbit", "--upgrade", "-u"]))
2354            .save();
2355        assert_eq!(
2356            cli.check_flag(Flag::new("upgrade").switch('u'))
2357                .unwrap_err()
2358                .kind(),
2359            ErrorKind::DuplicateOptions
2360        );
2361
2362        let mut cli = Cli::new()
2363            .parse(args(vec!["orbit", "--verbose", "--verbose", "--version=9"]))
2364            .save();
2365        assert_eq!(
2366            cli.check_flag(Flag::new("verbose")).unwrap_err().kind(),
2367            ErrorKind::DuplicateOptions
2368        );
2369        assert_eq!(
2370            cli.check_flag(Flag::new("version")).unwrap_err().kind(),
2371            ErrorKind::UnexpectedValue
2372        );
2373    }
2374
2375    #[test]
2376    fn check_positional() {
2377        let mut cli = Cli::new()
2378            .parse(args(vec!["orbit", "new", "rary.gates"]))
2379            .save();
2380        assert_eq!(
2381            cli.get_positional::<String>(Positional::new("command"))
2382                .unwrap(),
2383            Some("new".to_string())
2384        );
2385        assert_eq!(
2386            cli.get_positional::<String>(Positional::new("ip")).unwrap(),
2387            Some("rary.gates".to_string())
2388        );
2389        assert_eq!(
2390            cli.get_positional::<i32>(Positional::new("path")).unwrap(),
2391            None
2392        );
2393    }
2394
2395    #[test]
2396    fn check_option() {
2397        let mut cli = Cli::new()
2398            .parse(args(vec!["orbit", "command", "--rate", "10"]))
2399            .save();
2400        assert_eq!(cli.get_option(Optional::new("rate")).unwrap(), Some(10));
2401
2402        let mut cli = Cli::new()
2403            .parse(args(vec![
2404                "orbit", "--flag", "--rate=9", "command", "-r", "14",
2405            ]))
2406            .save();
2407        assert_eq!(
2408            cli.get_option::<i32>(Optional::new("rate").switch('r'))
2409                .unwrap_err()
2410                .kind(),
2411            ErrorKind::DuplicateOptions
2412        );
2413
2414        let mut cli = Cli::new()
2415            .parse(args(vec!["orbit", "--flag", "-r", "14"]))
2416            .save();
2417        assert_eq!(
2418            cli.get_option(Optional::new("rate").switch('r')).unwrap(),
2419            Some(14)
2420        );
2421
2422        let mut cli = Cli::new()
2423            .parse(args(vec!["orbit", "--flag", "--rate", "--verbose"]))
2424            .save();
2425        assert_eq!(
2426            cli.get_option::<i32>(Optional::new("rate"))
2427                .unwrap_err()
2428                .kind(),
2429            ErrorKind::ExpectingValue
2430        );
2431
2432        let mut cli = Cli::new()
2433            .parse(args(vec!["orbit", "--flag", "--rate", "five", "--verbose"]))
2434            .save();
2435        assert!(cli.get_option::<i32>(Optional::new("rate")).is_err());
2436    }
2437
2438    #[test]
2439    fn take_token_str() {
2440        let t = Token::UnattachedArgument(0, "get".to_string());
2441        // consumes token and returns its internal string
2442        assert_eq!(t.take_str(), "get");
2443
2444        let t = Token::AttachedArgument(1, "rary.gates".to_string());
2445        assert_eq!(t.take_str(), "rary.gates");
2446
2447        let t = Token::Ignore(7, "--map".to_string());
2448        assert_eq!(t.take_str(), "--map");
2449    }
2450
2451    #[test]
2452    #[should_panic]
2453    fn take_impossible_token_flag_str() {
2454        let t = Token::Flag(7);
2455        t.take_str();
2456    }
2457
2458    #[test]
2459    #[should_panic]
2460    fn take_impossible_token_switch_str() {
2461        let t = Token::Switch(7, 'h');
2462        t.take_str();
2463    }
2464
2465    #[test]
2466    #[should_panic]
2467    fn take_impossible_token_terminator_str() {
2468        let t = Token::Terminator(9);
2469        t.take_str();
2470    }
2471
2472    #[test]
2473    fn try_help_fail() {
2474        let mut cli = Cli::new().parse(args(vec!["orbit", "--h"])).save();
2475        let locs = cli.take_flag_locs("help");
2476        assert_eq!(locs.len(), 0);
2477        assert_eq!(cli.pull_flag(locs, false), vec![]);
2478    }
2479
2480    #[test]
2481    fn check_option_n() {
2482        let mut cli = Cli::new()
2483            .parse(args(vec!["orbit", "command", "--rate", "10"]))
2484            .save();
2485        assert_eq!(
2486            cli.get_option_until(Optional::new("rate"), 1).unwrap(),
2487            Some(vec![10])
2488        );
2489
2490        let mut cli = Cli::new()
2491            .parse(args(vec!["orbit", "command", "--rate", "10"]))
2492            .save();
2493        assert_eq!(
2494            cli.get_option_until(Optional::new("rate"), 2).unwrap(),
2495            Some(vec![10])
2496        );
2497
2498        let mut cli = Cli::new()
2499            .parse(args(vec![
2500                "orbit", "command", "--rate", "10", "--rate", "4", "--rate", "3",
2501            ]))
2502            .save();
2503        assert_eq!(
2504            cli.get_option_until::<u8>(Optional::new("rate"), 2)
2505                .is_err(),
2506            true
2507        );
2508    }
2509
2510    #[test]
2511    fn check_flag_n() {
2512        let mut cli = Cli::new().parse(args(vec!["orbit"])).save();
2513        assert_eq!(cli.check_flag_until(Flag::new("debug"), 4).unwrap(), 0);
2514
2515        let mut cli = Cli::new().parse(args(vec!["orbit", "--debug"])).save();
2516        assert_eq!(cli.check_flag_until(Flag::new("debug"), 1).unwrap(), 1);
2517
2518        let mut cli = Cli::new()
2519            .parse(args(vec!["orbit", "--debug", "--debug"]))
2520            .save();
2521        assert_eq!(cli.check_flag_until(Flag::new("debug"), 3).unwrap(), 2);
2522
2523        let mut cli = Cli::new()
2524            .parse(args(vec!["orbit", "--debug", "--debug", "--debug"]))
2525            .save();
2526        assert_eq!(cli.check_flag_until(Flag::new("debug"), 3).unwrap(), 3);
2527
2528        let mut cli = Cli::new()
2529            .parse(args(vec![
2530                "orbit", "--debug", "--debug", "--debug", "--debug",
2531            ]))
2532            .save();
2533        assert_eq!(cli.check_flag_until(Flag::new("debug"), 3).is_err(), true);
2534    }
2535
2536    #[test]
2537    fn check_flag_all() {
2538        let mut cli = Cli::new().parse(args(vec!["orbit"])).save();
2539        assert_eq!(cli.check_flag_all(Flag::new("debug")).unwrap(), 0);
2540
2541        let mut cli = Cli::new()
2542            .parse(args(vec!["orbit", "--debug", "--debug", "--debug"]))
2543            .save();
2544        assert_eq!(cli.check_flag_all(Flag::new("debug")).unwrap(), 3);
2545
2546        let mut cli = Cli::new()
2547            .parse(args(vec!["orbit", "--debug", "--debug", "--debug=1"]))
2548            .save();
2549        assert_eq!(cli.check_flag_all(Flag::new("debug")).is_err(), true);
2550    }
2551
2552    #[test]
2553    fn requires_positional_all() {
2554        let mut cli = Cli::new().parse(args(vec!["sum", "10", "20", "30"])).save();
2555        assert_eq!(
2556            cli.require_positional_all::<u8>(Positional::new("digit"))
2557                .unwrap(),
2558            vec![10, 20, 30]
2559        );
2560
2561        let mut cli = Cli::new().parse(args(vec!["sum"])).save();
2562        assert_eq!(
2563            cli.require_positional_all::<u8>(Positional::new("digit"))
2564                .unwrap_err()
2565                .kind(),
2566            ErrorKind::MissingPositional
2567        );
2568
2569        let mut cli = Cli::new()
2570            .parse(args(vec!["sum", "10", "--option", "20", "30"]))
2571            .save();
2572        // note: remember to always check options and flags before positional arguments
2573        assert_eq!(
2574            cli.get_option::<u8>(Optional::new("option")).unwrap(),
2575            Some(20)
2576        );
2577        assert_eq!(
2578            cli.require_positional_all::<u8>(Positional::new("digit"))
2579                .unwrap(),
2580            vec![10, 30]
2581        );
2582
2583        let mut cli = Cli::new().parse(args(vec!["sum", "100"])).save();
2584        assert_eq!(
2585            cli.require_positional_all::<u8>(Positional::new("digit"))
2586                .unwrap(),
2587            vec![100]
2588        );
2589    }
2590
2591    #[test]
2592    fn is_empty_from_parsing() {
2593        let cli = Cli::new().parse(args(vec!["cp"])).save();
2594        assert_eq!(cli.is_empty(), true);
2595
2596        let cli = Cli::new().parse(args(vec!["cp", "echo"])).save();
2597        assert_eq!(cli.is_empty(), false);
2598
2599        let cli = Cli::new().parse(args(vec!["cp", "--", "hello"])).save();
2600        assert_eq!(cli.is_empty(), false);
2601    }
2602}