bpaf/
args.rs

1use std::ffi::OsString;
2
3pub(crate) use crate::arg::*;
4use crate::{
5    error::{Message, MissingItem},
6    item::Item,
7    meta_help::Metavar,
8    parsers::NamedArg,
9    Error,
10};
11
12/// All currently present command line parameters with some extra metainfo
13///
14/// Use it for unit tests and manual parsing. For production use you would want to replace the
15/// program name with [`set_name`](Args::set_name), but for tests passing a slice of strings to
16/// [`run_inner`](crate::OptionParser::run_inner) is usually more convenient.
17///
18///
19/// The easiest way to create `Args` is by using its `From` instance.
20/// ```rust
21/// # use bpaf::*;
22/// let parser = short('f')
23///     .switch()
24///     .to_options();
25/// let value = parser
26///     .run_inner(Args::from(&["-f"]))
27///     .unwrap();
28/// assert!(value);
29///
30/// // this also works
31/// let value = parser.run_inner(&["-f"])
32///     .unwrap();
33/// assert!(value);
34/// ```
35pub struct Args<'a> {
36    items: Box<dyn ExactSizeIterator<Item = OsString> + 'a>,
37    name: Option<String>,
38    #[cfg(feature = "autocomplete")]
39    c_rev: Option<usize>,
40}
41
42impl Args<'_> {
43    /// Enable completions with custom output revision style
44    ///
45    /// Use revision 0 if you want to test completion mechanism
46    ///
47    /// ```rust
48    /// # use bpaf::*;
49    /// let parser = short('f').switch().to_options();
50    /// // ask bpaf to produce more input from "-", for
51    /// // suggesting new items use "" at the end
52    /// let r = parser.run_inner(Args::from(&["-"])
53    ///     .set_comp(0))
54    ///     .unwrap_err()
55    ///     .unwrap_stdout();
56    /// assert_eq!(r, "-f");
57    /// ```
58    ///
59    /// Note to self: shell passes "" as a parameter in situations like foo `--bar TAB`, bpaf
60    /// completion stubs adopt this conventions add pass it along. This is needed so completer can
61    /// tell the difference between `--bar` being completed or an argument to it in the example
62    /// above.
63    #[cfg(feature = "autocomplete")]
64    #[must_use]
65    pub fn set_comp(mut self, rev: usize) -> Self {
66        self.c_rev = Some(rev);
67        self
68    }
69
70    /// Add an application name for args created from custom input
71    /// ```rust
72    /// # use bpaf::*;
73    /// let parser = short('f').switch().to_options();
74    /// let r = parser
75    ///     .run_inner(Args::from(&["--help"]).set_name("my_app"))
76    ///     .unwrap_err()
77    ///     .unwrap_stdout();
78    /// # drop(r);
79    /// ```
80    #[must_use]
81    pub fn set_name(mut self, name: &str) -> Self {
82        self.name = Some(name.to_owned());
83        self
84    }
85}
86
87impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
88    fn from(value: &'static [&'static str; N]) -> Self {
89        Self {
90            items: Box::new(value.iter().map(OsString::from)),
91            #[cfg(feature = "autocomplete")]
92            c_rev: None,
93            name: None,
94        }
95    }
96}
97
98impl<'a> From<&'a [&'a std::ffi::OsStr]> for Args<'a> {
99    fn from(value: &'a [&'a std::ffi::OsStr]) -> Self {
100        Self {
101            items: Box::new(value.iter().map(OsString::from)),
102            #[cfg(feature = "autocomplete")]
103            c_rev: None,
104            name: None,
105        }
106    }
107}
108
109impl<'a> From<&'a [&'a str]> for Args<'a> {
110    fn from(value: &'a [&'a str]) -> Self {
111        Self {
112            items: Box::new(value.iter().map(OsString::from)),
113            #[cfg(feature = "autocomplete")]
114            c_rev: None,
115            name: None,
116        }
117    }
118}
119
120impl<'a> From<&'a [String]> for Args<'a> {
121    fn from(value: &'a [String]) -> Self {
122        Self {
123            items: Box::new(value.iter().map(OsString::from)),
124            #[cfg(feature = "autocomplete")]
125            c_rev: None,
126            name: None,
127        }
128    }
129}
130
131impl<'a> From<&'a [OsString]> for Args<'a> {
132    fn from(value: &'a [OsString]) -> Self {
133        Self {
134            items: Box::new(value.iter().map(OsString::from)),
135            #[cfg(feature = "autocomplete")]
136            c_rev: None,
137            name: None,
138        }
139    }
140}
141
142impl Args<'_> {
143    /// Get a list of command line arguments from OS
144    #[must_use]
145    pub fn current_args() -> Self {
146        let mut value = std::env::args_os();
147        let name = value.next().and_then(|n| {
148            let path = std::path::PathBuf::from(n);
149            let file_name = path.file_name()?;
150            let s = file_name.to_str()?;
151            Some(s.to_owned())
152        });
153        Self {
154            items: Box::new(value),
155            #[cfg(feature = "autocomplete")]
156            c_rev: None,
157            name,
158        }
159    }
160}
161
162/// Shows which branch of [`ParseOrElse`] parsed the argument
163#[derive(Debug, Clone, Copy, Eq, PartialEq)]
164pub(crate) enum ItemState {
165    /// Value is yet to be parsed
166    Unparsed,
167    /// Both branches succeeded, first parser was taken in favor of the second one
168    Conflict(usize),
169    /// Value was parsed
170    Parsed,
171}
172
173impl ItemState {
174    pub(crate) fn parsed(&self) -> bool {
175        match self {
176            ItemState::Unparsed | ItemState::Conflict(_) => false,
177            ItemState::Parsed => true,
178        }
179    }
180    pub(crate) fn present(&self) -> bool {
181        match self {
182            ItemState::Unparsed | ItemState::Conflict(_) => true,
183            ItemState::Parsed => false,
184        }
185    }
186}
187
188fn disambiguate_short(
189    mut os: OsString,
190    short: String,
191    short_flags: &[char],
192    short_args: &[char],
193    items: &mut Vec<Arg>,
194) -> Option<Message> {
195    // block can start with 0 or more short flags
196    // followed by zero or one short argument, possibly with a body
197
198    // keep the old length around so we can trimp items to it and push a Arg::Word
199    // if we decide to give up
200    let original = items.len();
201
202    // first flag contains the original os string for error message and anywhere purposes
203    let mut first_flag = os.clone();
204
205    for (ix, c) in short.char_indices() {
206        let tail_ix = ix + c.len_utf8();
207        let rest = &short[tail_ix..];
208
209        // shortcircuit single character short options
210        if ix == 0 && rest.is_empty() {
211            items.push(Arg::Short(c, false, std::mem::take(&mut first_flag)));
212            return None;
213        }
214        match (short_flags.contains(&c), short_args.contains(&c)) {
215            // short name that can be flag
216            (true, false) => {
217                items.push(Arg::Short(c, false, std::mem::take(&mut first_flag)));
218            }
219
220            // short name that can be argument
221            (false, true) => {
222                let adjacent_body = !rest.is_empty();
223                items.push(Arg::Short(c, adjacent_body, std::mem::take(&mut os)));
224                if adjacent_body {
225                    items.push(Arg::Word(rest.into()));
226                }
227                return None;
228            }
229
230            // neither is valid and there's more than one character. fallback to using it as a Word
231            (false, false) => {
232                items.truncate(original);
233                items.push(Arg::Word(os));
234                return None;
235            }
236
237            // ambiguity, this is bad
238            (true, true) => {
239                let msg = Message::Ambiguity(items.len(), short);
240                items.push(Arg::Word(std::mem::take(&mut os)));
241                return Some(msg);
242            }
243        }
244    }
245    None
246}
247
248pub use inner::State;
249/// Hides [`State`] internal implementation
250mod inner {
251    use std::{ops::Range, rc::Rc};
252
253    use crate::{error::Message, item::Item, Args};
254
255    use super::{split_os_argument, Arg, ArgType, ItemState};
256    #[derive(Clone, Debug)]
257    #[doc(hidden)]
258    pub struct State {
259        /// list of all available command line arguments, in `Rc` for cheap cloning
260        pub(crate) items: Rc<[Arg]>,
261
262        item_state: Vec<ItemState>,
263
264        /// performance optimization mostly - tracks removed item and gives cheap is_empty and len
265        remaining: usize,
266
267        #[doc(hidden)]
268        /// Used to render an error message for [`parse`][crate::Parser::parse]
269        /// contains an index of a currently consumed item if we are parsing a single
270        /// item
271        pub current: Option<usize>,
272
273        /// path to current command, "deeper" parser should win in or_else branches
274        pub(crate) path: Vec<String>,
275
276        #[cfg(feature = "autocomplete")]
277        comp: Option<crate::complete_gen::Complete>,
278
279        //        /// A way to customize behavior for --help and error handling
280        //        pub(crate) improve_error: super::Improve,
281        /// Describes scope current parser will be consuming elements from. Usually it will be
282        /// considering the whole sequence of (unconsumed) arguments, but for "adjacent"
283        /// scope starts on the right of the first consumed item and might end before the end
284        /// of the list, similarly for "commands"
285        scope: Range<usize>,
286    }
287
288    impl State {
289        /// Check if item at ixth position is still present (was not parsed)
290        pub(crate) fn present(&self, ix: usize) -> Option<bool> {
291            Some(self.item_state.get(ix)?.present())
292        }
293
294        pub(crate) fn depth(&self) -> usize {
295            self.path.len()
296        }
297    }
298
299    pub(crate) struct ArgsIter<'a> {
300        args: &'a State,
301        cur: usize,
302    }
303
304    impl State {
305        #[cfg(feature = "autocomplete")]
306        pub(crate) fn check_no_pos_ahead(&self) -> bool {
307            self.comp.as_ref().map_or(false, |c| c.no_pos_ahead)
308        }
309
310        #[cfg(feature = "autocomplete")]
311        pub(crate) fn set_no_pos_ahead(&mut self) {
312            if let Some(comp) = &mut self.comp {
313                comp.no_pos_ahead = true;
314            }
315        }
316
317        #[allow(clippy::too_many_lines)] // it's relatively simple.
318        pub(crate) fn construct(
319            args: Args,
320            short_flags: &[char],
321            short_args: &[char],
322            err: &mut Option<Message>,
323        ) -> State {
324            let mut items = Vec::new();
325            let mut pos_only = false;
326            let mut double_dash_marker = None;
327
328            #[cfg(feature = "autocomplete")]
329            let mut comp_scanner = crate::complete_run::ArgScanner {
330                revision: args.c_rev,
331                name: args.name.as_deref(),
332            };
333
334            for os in args.items {
335                if pos_only {
336                    items.push(Arg::PosWord(os));
337                    continue;
338                }
339
340                #[cfg(feature = "autocomplete")]
341                if comp_scanner.check_next(&os) {
342                    continue;
343                }
344
345                match split_os_argument(&os) {
346                    // -f and -fbar, but also -vvvvv
347                    Some((ArgType::Short, short, None)) => {
348                        if let Some(msg) = super::disambiguate_short(
349                            os,
350                            short,
351                            short_flags,
352                            short_args,
353                            &mut items,
354                        ) {
355                            *err = Some(msg);
356                            break;
357                        }
358                    }
359                    Some((ArgType::Short, short, Some(arg))) => {
360                        let mut chars = short.chars();
361                        items.push(Arg::Short(chars.next().unwrap(), true, os));
362                        items.push(arg);
363                    }
364                    // --key and --key=val
365                    Some((ArgType::Long, long, arg)) => {
366                        items.push(Arg::Long(long, arg.is_some(), os));
367                        if let Some(arg) = arg {
368                            items.push(arg);
369                        }
370                    }
371                    // something that is not a short or long flag, keep them as positionals
372                    // handle "--" specifically as "end of flags" marker
373                    None => {
374                        if os == "--" {
375                            double_dash_marker = Some(items.len());
376                            pos_only = true;
377                        }
378                        items.push(if pos_only {
379                            Arg::PosWord(os)
380                        } else {
381                            Arg::Word(os)
382                        });
383                    }
384                }
385            }
386
387            let mut item_state = vec![ItemState::Unparsed; items.len()];
388            let mut remaining = items.len();
389            if let Some(ix) = double_dash_marker {
390                item_state[ix] = ItemState::Parsed;
391                remaining -= 1;
392
393                #[cfg(feature = "autocomplete")]
394                if comp_scanner.revision.is_some() && ix == items.len() - 1 {
395                    remaining += 1;
396                    item_state[ix] = ItemState::Unparsed;
397                }
398            }
399
400            let mut path = Vec::new();
401
402            #[cfg(feature = "autocomplete")]
403            let comp = comp_scanner.done();
404
405            if let Some(name) = args.name {
406                path.push(name);
407            }
408            State {
409                item_state,
410                remaining,
411                scope: 0..items.len(),
412                items: items.into(),
413                current: None,
414                path,
415                #[cfg(feature = "autocomplete")]
416                comp,
417            }
418        }
419    }
420
421    impl<'a> State {
422        /// creates iterator over remaining elements
423        pub(crate) fn items_iter(&'a self) -> ArgsIter<'a> {
424            ArgsIter {
425                args: self,
426                cur: self.scope.start,
427            }
428        }
429
430        pub(crate) fn remove(&mut self, index: usize) {
431            if self.scope.contains(&index) && self.item_state[index].present() {
432                self.current = Some(index);
433                self.remaining -= 1;
434                self.item_state[index] = ItemState::Parsed;
435            }
436        }
437
438        pub(crate) fn pick_winner(&self, other: &Self) -> (bool, Option<usize>) {
439            for (ix, (me, other)) in self
440                .item_state
441                .iter()
442                .zip(other.item_state.iter())
443                .enumerate()
444            {
445                if me.parsed() ^ other.parsed() {
446                    return (me.parsed(), Some(ix));
447                }
448            }
449            (true, None)
450        }
451
452        /// find first saved conflict
453        pub(crate) fn conflict(&self) -> Option<(usize, usize)> {
454            let (ix, _item) = self.items_iter().next()?;
455            if let ItemState::Conflict(other) = self.item_state.get(ix)? {
456                Some((ix, *other))
457            } else {
458                None
459            }
460        }
461
462        pub(crate) fn save_conflicts(&mut self, loser: &State, win: usize) {
463            for (winner, loser) in self.item_state.iter_mut().zip(loser.item_state.iter()) {
464                if winner.present() && loser.parsed() {
465                    *winner = ItemState::Conflict(win);
466                }
467            }
468        }
469
470        #[allow(dead_code)]
471        // it is in use when autocomplete is enabled
472        pub(crate) fn is_empty(&self) -> bool {
473            self.remaining == 0
474        }
475
476        pub(crate) fn len(&self) -> usize {
477            self.remaining
478        }
479
480        /// Get an argument from a scope that was not consumed yet
481        pub(crate) fn get(&self, ix: usize) -> Option<&Arg> {
482            if self.scope.contains(&ix) && self.item_state.get(ix)?.present() {
483                Some(self.items.get(ix)?)
484            } else {
485                None
486            }
487        }
488
489        #[cfg(feature = "autocomplete")]
490        /// Check if parser performs autocompletion
491        ///
492        /// used by construct macro
493        #[must_use]
494        pub fn is_comp(&self) -> bool {
495            self.comp.is_some()
496        }
497
498        /// Narrow down scope of &self to adjacently consumed values compared to original.
499        pub(crate) fn adjacent_scope(&self, original: &State) -> Option<Range<usize>> {
500            if self.items.is_empty() {
501                return None;
502            }
503
504            // starting at the beginning of the scope look for the first mismatch
505            let start = self.scope().start;
506            for (mut offset, (this, orig)) in self.item_state[start..]
507                .iter()
508                .zip(original.item_state[start..].iter())
509                .enumerate()
510            {
511                offset += start;
512                // once there's a mismatch we have the scope we are looking for:
513                // all the adjacent items consumed in this. It doesn't make sense to remove it if
514                // it matches the original scope though...
515                if this.present() && orig.present() {
516                    let proposed_scope = start..offset;
517                    return if self.scope() == proposed_scope {
518                        None
519                    } else {
520                        Some(proposed_scope)
521                    };
522                }
523            }
524            None
525        }
526
527        /// Get a scope for an adjacently available block of item starting at start
528        pub(crate) fn adjacently_available_from(&self, start: usize) -> Range<usize> {
529            let span_size = self
530                .item_state
531                .iter()
532                .copied()
533                .skip(start)
534                .take_while(ItemState::present)
535                .count();
536            start..start + span_size
537        }
538
539        pub(crate) fn ranges(&'a self, item: &'a Item) -> ArgRangesIter<'a> {
540            let width = match item {
541                Item::Any { .. }
542                | Item::Positional { .. }
543                | Item::Command { .. }
544                | Item::Flag { .. } => 1,
545                Item::Argument { .. } => 2,
546            };
547            ArgRangesIter {
548                args: self,
549                cur: 0,
550                width,
551            }
552        }
553
554        pub(crate) fn scope(&self) -> Range<usize> {
555            self.scope.clone()
556        }
557
558        /// Mark everything outside of `range` as removed
559        pub(crate) fn set_scope(&mut self, scope: Range<usize>) {
560            self.scope = scope;
561            self.remaining = self.item_state[self.scope()]
562                .iter()
563                .copied()
564                .filter(ItemState::present)
565                .count();
566        }
567
568        #[cfg(feature = "autocomplete")]
569        /// check if bpaf tries to complete last consumed element
570        pub(crate) fn touching_last_remove(&self) -> bool {
571            self.comp.is_some() && self.items.len() - 1 == self.current.unwrap_or(usize::MAX)
572        }
573
574        #[cfg(feature = "autocomplete")]
575        pub(crate) fn comp_mut(&mut self) -> Option<&mut crate::complete_gen::Complete> {
576            self.comp.as_mut()
577        }
578
579        #[cfg(feature = "autocomplete")]
580        pub(crate) fn comp_ref(&self) -> Option<&crate::complete_gen::Complete> {
581            self.comp.as_ref()
582        }
583
584        #[cfg(feature = "autocomplete")]
585        pub(crate) fn swap_comps(&mut self, other: &mut Self) {
586            std::mem::swap(&mut self.comp, &mut other.comp);
587        }
588    }
589
590    pub(crate) struct ArgRangesIter<'a> {
591        args: &'a State,
592        width: usize,
593        cur: usize,
594    }
595    impl<'a> Iterator for ArgRangesIter<'a> {
596        type Item = (
597            /* start offset */ usize,
598            /* width of the first item */ usize,
599            State,
600        );
601
602        fn next(&mut self) -> Option<Self::Item> {
603            loop {
604                let cur = self.cur;
605                if cur > self.args.scope.end {
606                    return None;
607                }
608                self.cur += 1;
609                if !self.args.present(cur)? {
610                    continue;
611                }
612                if cur + self.width > self.args.items.len() {
613                    return None;
614                }
615                // It should be possible to optimize this code a bit
616                // by checking if first item can possibly
617                let mut args = self.args.clone();
618                args.set_scope(cur..self.args.items.len());
619                return Some((cur, self.width, args));
620            }
621        }
622    }
623
624    impl<'a> Iterator for ArgsIter<'a> {
625        type Item = (usize, &'a Arg);
626
627        fn next(&mut self) -> Option<Self::Item> {
628            loop {
629                let ix = self.cur;
630                if !self.args.scope.contains(&ix) {
631                    return None;
632                }
633                self.cur += 1;
634                if self.args.item_state.get(ix)?.present() {
635                    return Some((ix, &self.args.items[ix]));
636                }
637            }
638        }
639    }
640}
641
642impl State {
643    #[inline(never)]
644    #[cfg(feature = "autocomplete")]
645    pub(crate) fn swap_comps_with(&mut self, comps: &mut Vec<crate::complete_gen::Comp>) {
646        if let Some(comp) = self.comp_mut() {
647            comp.swap_comps(comps);
648        }
649    }
650
651    /// Get a short or long flag: `-f` / `--flag`
652    ///
653    /// Returns false if value isn't present
654    pub(crate) fn take_flag(&mut self, named: &NamedArg) -> bool {
655        if let Some((ix, _)) = self
656            .items_iter()
657            .find(|arg| named.matches_arg(arg.1, false))
658        {
659            self.remove(ix);
660            true
661        } else {
662            false
663        }
664    }
665
666    /// get a short or long arguments
667    ///
668    /// Returns Ok(None) if flag isn't present
669    /// Returns Err if flag is present but value is either missing or strange.
670    pub(crate) fn take_arg(
671        &mut self,
672        named: &NamedArg,
673        adjacent: bool,
674        metavar: Metavar,
675    ) -> Result<Option<OsString>, Error> {
676        let (key_ix, _arg) = match self
677            .items_iter()
678            .find(|arg| named.matches_arg(arg.1, adjacent))
679        {
680            Some(v) => v,
681            None => return Ok(None),
682        };
683
684        let val_ix = key_ix + 1;
685        let val = match self.get(val_ix) {
686            Some(Arg::Word(w) | Arg::ArgWord(w)) => w,
687            _ => return Err(Error(Message::NoArgument(key_ix, metavar))),
688        };
689        let val = val.clone();
690        self.current = Some(val_ix);
691        self.remove(key_ix);
692        self.remove(val_ix);
693        Ok(Some(val))
694    }
695
696    /// gets first positional argument present
697    ///
698    /// returns Ok(None) if input is empty
699    /// returns Err if first positional argument is a flag
700    pub(crate) fn take_positional_word(
701        &mut self,
702        metavar: Metavar,
703    ) -> Result<(usize, bool, OsString), Error> {
704        match self.items_iter().find_map(|(ix, arg)| match arg {
705            Arg::Word(w) => Some((ix, false, w)),
706            Arg::PosWord(w) => Some((ix, true, w)),
707            _ => None,
708        }) {
709            Some((ix, strict, w)) => {
710                let w = w.clone();
711                self.current = Some(ix);
712                self.remove(ix);
713                Ok((ix, strict, w))
714            }
715            None => {
716                let scope = self.scope();
717                let missing = MissingItem {
718                    item: Item::Positional {
719                        help: None,
720                        metavar,
721                    },
722                    position: scope.start,
723                    scope,
724                };
725                Err(Error(Message::Missing(vec![missing])))
726            }
727        }
728    }
729
730    /// take a static string argument from the first present argument
731    pub(crate) fn take_cmd(&mut self, word: &str) -> bool {
732        if let Some((ix, Arg::Word(w) | Arg::Short(_, _, w) | Arg::Long(_, false, w))) =
733            self.items_iter().next()
734        {
735            if w == word {
736                self.remove(ix);
737                self.current = Some(ix);
738                return true;
739            }
740        }
741        self.current = None;
742        false
743    }
744
745    #[cfg(test)]
746    pub(crate) fn peek(&self) -> Option<&Arg> {
747        self.items_iter().next().map(|x| x.1)
748    }
749}
750
751#[cfg(test)]
752mod tests {
753    use super::*;
754    use crate::meta_help::Metavar;
755    use crate::{long, short};
756    const M: Metavar = Metavar("M");
757
758    #[allow(clippy::fallible_impl_from)] // this is for tests only, panic is okay
759    impl<const N: usize> From<&'static [&'static str; N]> for State {
760        fn from(value: &'static [&'static str; N]) -> Self {
761            let args = Args::from(value);
762            let mut msg = None;
763            let res = State::construct(args, &[], &[], &mut msg);
764            if let Some(err) = &msg {
765                panic!("Couldn't construct state: {:?}/{:?}", err, res);
766            }
767            res
768        }
769    }
770
771    #[test]
772    fn long_arg() {
773        let mut a = State::from(&["--speed", "12"]);
774        let s = a.take_arg(&long("speed"), false, M).unwrap().unwrap();
775        assert_eq!(s, "12");
776        assert!(a.is_empty());
777    }
778    #[test]
779    fn long_flag_and_positional() {
780        let mut a = State::from(&["--speed", "12"]);
781        let flag = a.take_flag(&long("speed"));
782        assert!(flag);
783        assert!(!a.is_empty());
784        let s = a.take_positional_word(M).unwrap();
785        assert_eq!(s.2, "12");
786        assert!(a.is_empty());
787    }
788
789    #[test]
790    fn multiple_short_flags() {
791        let args = Args::from(&["-vvv"]);
792        let mut err = None;
793        let mut a = State::construct(args, &['v'], &[], &mut err);
794        assert!(a.take_flag(&short('v')));
795        assert!(a.take_flag(&short('v')));
796        assert!(a.take_flag(&short('v')));
797        assert!(!a.take_flag(&short('v')));
798        assert!(a.is_empty());
799    }
800
801    #[test]
802    fn long_arg_with_equality() {
803        let mut a = State::from(&["--speed=12"]);
804        let s = a.take_arg(&long("speed"), false, M).unwrap().unwrap();
805        assert_eq!(s, "12");
806        assert!(a.is_empty());
807    }
808
809    #[test]
810    fn long_arg_with_equality_and_minus() {
811        let mut a = State::from(&["--speed=-12"]);
812        let s = a.take_arg(&long("speed"), true, M).unwrap().unwrap();
813        assert_eq!(s, "-12");
814        assert!(a.is_empty());
815    }
816
817    #[test]
818    fn short_arg_with_equality() {
819        let mut a = State::from(&["-s=12"]);
820        let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
821        assert_eq!(s, "12");
822        assert!(a.is_empty());
823    }
824
825    #[test]
826    fn short_arg_with_equality_and_minus() {
827        let mut a = State::from(&["-s=-12"]);
828        let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
829        assert_eq!(s, "-12");
830        assert!(a.is_empty());
831    }
832
833    #[test]
834    fn short_arg_with_equality_and_minus_is_adjacent() {
835        let mut a = State::from(&["-s=-12"]);
836        let s = a.take_arg(&short('s'), true, M).unwrap().unwrap();
837        assert_eq!(s, "-12");
838        assert!(a.is_empty());
839    }
840
841    #[test]
842    fn short_arg_without_equality() {
843        let mut a = State::from(&["-s", "12"]);
844        let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
845        assert_eq!(s, "12");
846        assert!(a.is_empty());
847    }
848
849    #[test]
850    fn two_short_flags() {
851        let mut a = State::from(&["-s", "-v"]);
852        assert!(a.take_flag(&short('s')));
853        assert!(a.take_flag(&short('v')));
854        assert!(a.is_empty());
855    }
856
857    #[test]
858    fn two_short_flags2() {
859        let mut a = State::from(&["-s", "-v"]);
860        assert!(a.take_flag(&short('v')));
861        assert!(!a.take_flag(&short('v')));
862        assert!(a.take_flag(&short('s')));
863        assert!(!a.take_flag(&short('s')));
864        assert!(a.is_empty());
865    }
866
867    #[test]
868    fn command_with_flags() {
869        let mut a = State::from(&["cmd", "-s", "v"]);
870        assert!(a.take_cmd("cmd"));
871        let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
872        assert_eq!(s, "v");
873        assert!(a.is_empty());
874    }
875
876    #[test]
877    fn command_and_positional() {
878        let mut a = State::from(&["cmd", "pos"]);
879        assert!(a.take_cmd("cmd"));
880        let w = a.take_positional_word(M).unwrap();
881        assert_eq!(w.2, "pos");
882        assert!(a.is_empty());
883    }
884
885    #[test]
886    fn positionals_after_double_dash1() {
887        let mut a = State::from(&["-v", "--", "-x"]);
888        assert!(a.take_flag(&short('v')));
889        let w = a.take_positional_word(M).unwrap();
890        assert_eq!(w.2, "-x");
891        assert!(a.is_empty());
892    }
893
894    #[test]
895    fn positionals_after_double_dash2() {
896        let mut a = State::from(&["-v", "--", "-x"]);
897        assert!(a.take_flag(&short('v')));
898        let w = a.take_positional_word(M).unwrap();
899        assert_eq!(w.2, "-x");
900        assert!(a.is_empty());
901    }
902
903    #[test]
904    fn positionals_after_double_dash3() {
905        let mut a = State::from(&["-v", "12", "--", "-x"]);
906        let w = a.take_arg(&short('v'), false, M).unwrap().unwrap();
907        assert_eq!(w, "12");
908        let w = a.take_positional_word(M).unwrap();
909        assert_eq!(w.2, "-x");
910        assert!(a.is_empty());
911    }
912
913    #[test]
914    fn ambiguity_towards_flag() {
915        let args = Args::from(&["-abc"]);
916        let mut err = None;
917        let mut a = State::construct(args, &['a', 'b', 'c'], &[], &mut err);
918
919        assert!(a.take_flag(&short('a')));
920        assert!(a.take_flag(&short('b')));
921        assert!(a.take_flag(&short('c')));
922    }
923
924    #[test]
925    fn ambiguity_towards_argument() {
926        let args = Args::from(&["-abc"]);
927        let mut err = None;
928        let mut a = State::construct(args, &[], &['a'], &mut err);
929
930        let r = a.take_arg(&short('a'), false, M).unwrap().unwrap();
931        assert_eq!(r, "bc");
932    }
933
934    #[test]
935    fn ambiguity_towards_error() {
936        let args = Args::from(&["-abc"]);
937        let mut err = None;
938        let _a = State::construct(args, &['a', 'b', 'c'], &['a'], &mut err);
939        assert!(err.is_some());
940    }
941
942    #[test]
943    fn ambiguity_towards_default() {
944        // AKA unresolved
945        let a = State::from(&["-abc"]);
946        let is_ambig = matches!(a.peek(), Some(Arg::Word(_)));
947        assert!(is_ambig);
948    }
949}