use std::ffi::OsString;
pub(crate) use crate::arg::*;
use crate::{
error::{Message, MissingItem},
item::Item,
meta_help::Metavar,
parsers::NamedArg,
Error,
};
pub struct Args<'a> {
items: Box<dyn ExactSizeIterator<Item = OsString> + 'a>,
name: Option<String>,
#[cfg(feature = "autocomplete")]
c_rev: Option<usize>,
}
impl Args<'_> {
#[cfg(feature = "autocomplete")]
#[must_use]
pub fn set_comp(mut self, rev: usize) -> Self {
self.c_rev = Some(rev);
self
}
#[must_use]
pub fn set_name(mut self, name: &str) -> Self {
self.name = Some(name.to_owned());
self
}
}
impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
fn from(value: &'static [&'static str; N]) -> Self {
Self {
items: Box::new(value.iter().map(OsString::from)),
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
}
}
}
impl<'a> From<&'a [&'a std::ffi::OsStr]> for Args<'a> {
fn from(value: &'a [&'a std::ffi::OsStr]) -> Self {
Self {
items: Box::new(value.iter().map(OsString::from)),
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
}
}
}
impl<'a> From<&'a [&'a str]> for Args<'a> {
fn from(value: &'a [&'a str]) -> Self {
Self {
items: Box::new(value.iter().map(OsString::from)),
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
}
}
}
impl<'a> From<&'a [String]> for Args<'a> {
fn from(value: &'a [String]) -> Self {
Self {
items: Box::new(value.iter().map(OsString::from)),
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
}
}
}
impl<'a> From<&'a [OsString]> for Args<'a> {
fn from(value: &'a [OsString]) -> Self {
Self {
items: Box::new(value.iter().map(OsString::from)),
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
}
}
}
impl Args<'_> {
#[must_use]
pub fn current_args() -> Self {
let mut value = std::env::args_os();
let name = value.next().and_then(|n| {
let path = std::path::PathBuf::from(n);
let file_name = path.file_name()?;
let s = file_name.to_str()?;
Some(s.to_owned())
});
Self {
items: Box::new(value),
#[cfg(feature = "autocomplete")]
c_rev: None,
name,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(crate) enum ItemState {
Unparsed,
Conflict(usize),
Parsed,
}
impl ItemState {
pub(crate) fn parsed(&self) -> bool {
match self {
ItemState::Unparsed | ItemState::Conflict(_) => false,
ItemState::Parsed => true,
}
}
pub(crate) fn present(&self) -> bool {
match self {
ItemState::Unparsed | ItemState::Conflict(_) => true,
ItemState::Parsed => false,
}
}
}
fn disambiguate_short(
mut os: OsString,
short: String,
short_flags: &[char],
short_args: &[char],
items: &mut Vec<Arg>,
) -> Option<Message> {
let original = items.len();
let mut first_flag = os.clone();
for (ix, c) in short.char_indices() {
let tail_ix = ix + c.len_utf8();
let rest = &short[tail_ix..];
if ix == 0 && rest.is_empty() {
items.push(Arg::Short(c, false, std::mem::take(&mut first_flag)));
return None;
}
match (short_flags.contains(&c), short_args.contains(&c)) {
(true, false) => {
items.push(Arg::Short(c, false, std::mem::take(&mut first_flag)));
}
(false, true) => {
let adjacent_body = !rest.is_empty();
items.push(Arg::Short(c, adjacent_body, std::mem::take(&mut os)));
if adjacent_body {
items.push(Arg::Word(rest.into()));
}
return None;
}
(false, false) => {
items.truncate(original);
items.push(Arg::Word(os));
return None;
}
(true, true) => {
let msg = Message::Ambiguity(items.len(), short);
items.push(Arg::Word(std::mem::take(&mut os)));
return Some(msg);
}
}
}
None
}
pub use inner::State;
mod inner {
use std::{ops::Range, rc::Rc};
use crate::{error::Message, item::Item, Args};
use super::{split_os_argument, Arg, ArgType, ItemState};
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct State {
pub(crate) items: Rc<[Arg]>,
item_state: Vec<ItemState>,
remaining: usize,
#[doc(hidden)]
pub current: Option<usize>,
pub(crate) path: Vec<String>,
#[cfg(feature = "autocomplete")]
comp: Option<crate::complete_gen::Complete>,
scope: Range<usize>,
}
impl State {
pub(crate) fn present(&self, ix: usize) -> Option<bool> {
Some(self.item_state.get(ix)?.present())
}
pub(crate) fn depth(&self) -> usize {
self.path.len()
}
}
pub(crate) struct ArgsIter<'a> {
args: &'a State,
cur: usize,
}
impl State {
#[cfg(feature = "autocomplete")]
pub(crate) fn check_no_pos_ahead(&self) -> bool {
self.comp.as_ref().map_or(false, |c| c.no_pos_ahead)
}
#[cfg(feature = "autocomplete")]
pub(crate) fn set_no_pos_ahead(&mut self) {
if let Some(comp) = &mut self.comp {
comp.no_pos_ahead = true;
}
}
#[allow(clippy::too_many_lines)] pub(crate) fn construct(
args: Args,
short_flags: &[char],
short_args: &[char],
err: &mut Option<Message>,
) -> State {
let mut items = Vec::new();
let mut pos_only = false;
let mut double_dash_marker = None;
#[cfg(feature = "autocomplete")]
let mut comp_scanner = crate::complete_run::ArgScanner {
revision: args.c_rev,
name: args.name.as_deref(),
};
for os in args.items {
if pos_only {
items.push(Arg::PosWord(os));
continue;
}
#[cfg(feature = "autocomplete")]
if comp_scanner.check_next(&os) {
continue;
}
match split_os_argument(&os) {
Some((ArgType::Short, short, None)) => {
if let Some(msg) = super::disambiguate_short(
os,
short,
short_flags,
short_args,
&mut items,
) {
*err = Some(msg);
break;
}
}
Some((ArgType::Short, short, Some(arg))) => {
let mut chars = short.chars();
items.push(Arg::Short(chars.next().unwrap(), true, os));
items.push(arg);
}
Some((ArgType::Long, long, arg)) => {
items.push(Arg::Long(long, arg.is_some(), os));
if let Some(arg) = arg {
items.push(arg);
}
}
None => {
if os == "--" {
double_dash_marker = Some(items.len());
pos_only = true;
}
items.push(if pos_only {
Arg::PosWord(os)
} else {
Arg::Word(os)
});
}
}
}
let mut item_state = vec![ItemState::Unparsed; items.len()];
let mut remaining = items.len();
if let Some(ix) = double_dash_marker {
item_state[ix] = ItemState::Parsed;
remaining -= 1;
#[cfg(feature = "autocomplete")]
if comp_scanner.revision.is_some() && ix == items.len() - 1 {
remaining += 1;
item_state[ix] = ItemState::Unparsed;
}
}
let mut path = Vec::new();
#[cfg(feature = "autocomplete")]
let comp = comp_scanner.done();
if let Some(name) = args.name {
path.push(name);
}
State {
item_state,
remaining,
scope: 0..items.len(),
items: items.into(),
current: None,
path,
#[cfg(feature = "autocomplete")]
comp,
}
}
}
impl<'a> State {
pub(crate) fn items_iter(&'a self) -> ArgsIter<'a> {
ArgsIter {
args: self,
cur: self.scope.start,
}
}
pub(crate) fn remove(&mut self, index: usize) {
if self.scope.contains(&index) && self.item_state[index].present() {
self.current = Some(index);
self.remaining -= 1;
self.item_state[index] = ItemState::Parsed;
}
}
pub(crate) fn pick_winner(&self, other: &Self) -> (bool, Option<usize>) {
for (ix, (me, other)) in self
.item_state
.iter()
.zip(other.item_state.iter())
.enumerate()
{
if me.parsed() ^ other.parsed() {
return (me.parsed(), Some(ix));
}
}
(true, None)
}
pub(crate) fn conflict(&self) -> Option<(usize, usize)> {
let (ix, _item) = self.items_iter().next()?;
if let ItemState::Conflict(other) = self.item_state.get(ix)? {
Some((ix, *other))
} else {
None
}
}
pub(crate) fn save_conflicts(&mut self, loser: &State, win: usize) {
for (winner, loser) in self.item_state.iter_mut().zip(loser.item_state.iter()) {
if winner.present() && loser.parsed() {
*winner = ItemState::Conflict(win);
}
}
}
#[allow(dead_code)]
pub(crate) fn is_empty(&self) -> bool {
self.remaining == 0
}
pub(crate) fn len(&self) -> usize {
self.remaining
}
pub(crate) fn get(&self, ix: usize) -> Option<&Arg> {
if self.scope.contains(&ix) && self.item_state.get(ix)?.present() {
Some(self.items.get(ix)?)
} else {
None
}
}
#[cfg(feature = "autocomplete")]
#[must_use]
pub fn is_comp(&self) -> bool {
self.comp.is_some()
}
pub(crate) fn adjacent_scope(&self, original: &State) -> Option<Range<usize>> {
if self.items.is_empty() {
return None;
}
let start = self.scope().start;
for (mut offset, (this, orig)) in self.item_state[start..]
.iter()
.zip(original.item_state[start..].iter())
.enumerate()
{
offset += start;
if this.present() && orig.present() {
let proposed_scope = start..offset;
return if self.scope() == proposed_scope {
None
} else {
Some(proposed_scope)
};
}
}
None
}
pub(crate) fn adjacently_available_from(&self, start: usize) -> Range<usize> {
let span_size = self
.item_state
.iter()
.copied()
.skip(start)
.take_while(ItemState::present)
.count();
start..start + span_size
}
pub(crate) fn ranges(&'a self, item: &'a Item) -> ArgRangesIter<'a> {
let width = match item {
Item::Any { .. }
| Item::Positional { .. }
| Item::Command { .. }
| Item::Flag { .. } => 1,
Item::Argument { .. } => 2,
};
ArgRangesIter {
args: self,
cur: 0,
width,
}
}
pub(crate) fn scope(&self) -> Range<usize> {
self.scope.clone()
}
pub(crate) fn set_scope(&mut self, scope: Range<usize>) {
self.scope = scope;
self.remaining = self.item_state[self.scope()]
.iter()
.copied()
.filter(ItemState::present)
.count();
}
#[cfg(feature = "autocomplete")]
pub(crate) fn touching_last_remove(&self) -> bool {
self.comp.is_some() && self.items.len() - 1 == self.current.unwrap_or(usize::MAX)
}
#[cfg(feature = "autocomplete")]
pub(crate) fn comp_mut(&mut self) -> Option<&mut crate::complete_gen::Complete> {
self.comp.as_mut()
}
#[cfg(feature = "autocomplete")]
pub(crate) fn comp_ref(&self) -> Option<&crate::complete_gen::Complete> {
self.comp.as_ref()
}
#[cfg(feature = "autocomplete")]
pub(crate) fn swap_comps(&mut self, other: &mut Self) {
std::mem::swap(&mut self.comp, &mut other.comp);
}
}
pub(crate) struct ArgRangesIter<'a> {
args: &'a State,
width: usize,
cur: usize,
}
impl<'a> Iterator for ArgRangesIter<'a> {
type Item = (
usize,
usize,
State,
);
fn next(&mut self) -> Option<Self::Item> {
loop {
let cur = self.cur;
if cur > self.args.scope.end {
return None;
}
self.cur += 1;
if !self.args.present(cur)? {
continue;
}
if cur + self.width > self.args.items.len() {
return None;
}
let mut args = self.args.clone();
args.set_scope(cur..self.args.items.len());
return Some((cur, self.width, args));
}
}
}
impl<'a> Iterator for ArgsIter<'a> {
type Item = (usize, &'a Arg);
fn next(&mut self) -> Option<Self::Item> {
loop {
let ix = self.cur;
if !self.args.scope.contains(&ix) {
return None;
}
self.cur += 1;
if self.args.item_state.get(ix)?.present() {
return Some((ix, &self.args.items[ix]));
}
}
}
}
}
impl State {
#[inline(never)]
#[cfg(feature = "autocomplete")]
pub(crate) fn swap_comps_with(&mut self, comps: &mut Vec<crate::complete_gen::Comp>) {
if let Some(comp) = self.comp_mut() {
comp.swap_comps(comps);
}
}
pub(crate) fn take_flag(&mut self, named: &NamedArg) -> bool {
if let Some((ix, _)) = self
.items_iter()
.find(|arg| named.matches_arg(arg.1, false))
{
self.remove(ix);
true
} else {
false
}
}
pub(crate) fn take_arg(
&mut self,
named: &NamedArg,
adjacent: bool,
metavar: Metavar,
) -> Result<Option<OsString>, Error> {
let (key_ix, _arg) = match self
.items_iter()
.find(|arg| named.matches_arg(arg.1, adjacent))
{
Some(v) => v,
None => return Ok(None),
};
let val_ix = key_ix + 1;
let val = match self.get(val_ix) {
Some(Arg::Word(w) | Arg::ArgWord(w)) => w,
_ => return Err(Error(Message::NoArgument(key_ix, metavar))),
};
let val = val.clone();
self.current = Some(val_ix);
self.remove(key_ix);
self.remove(val_ix);
Ok(Some(val))
}
pub(crate) fn take_positional_word(
&mut self,
metavar: Metavar,
) -> Result<(usize, bool, OsString), Error> {
match self.items_iter().find_map(|(ix, arg)| match arg {
Arg::Word(w) => Some((ix, false, w)),
Arg::PosWord(w) => Some((ix, true, w)),
_ => None,
}) {
Some((ix, strict, w)) => {
let w = w.clone();
self.current = Some(ix);
self.remove(ix);
Ok((ix, strict, w))
}
None => {
let scope = self.scope();
let missing = MissingItem {
item: Item::Positional {
help: None,
metavar,
},
position: scope.start,
scope,
};
Err(Error(Message::Missing(vec![missing])))
}
}
}
pub(crate) fn take_cmd(&mut self, word: &str) -> bool {
if let Some((ix, Arg::Word(w) | Arg::Short(_, _, w) | Arg::Long(_, false, w))) =
self.items_iter().next()
{
if w == word {
self.remove(ix);
self.current = Some(ix);
return true;
}
}
self.current = None;
false
}
#[cfg(test)]
pub(crate) fn peek(&self) -> Option<&Arg> {
self.items_iter().next().map(|x| x.1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::meta_help::Metavar;
use crate::{long, short};
const M: Metavar = Metavar("M");
#[allow(clippy::fallible_impl_from)] impl<const N: usize> From<&'static [&'static str; N]> for State {
fn from(value: &'static [&'static str; N]) -> Self {
let args = Args::from(value);
let mut msg = None;
let res = State::construct(args, &[], &[], &mut msg);
if let Some(err) = &msg {
panic!("Couldn't construct state: {:?}/{:?}", err, res);
}
res
}
}
#[test]
fn long_arg() {
let mut a = State::from(&["--speed", "12"]);
let s = a.take_arg(&long("speed"), false, M).unwrap().unwrap();
assert_eq!(s, "12");
assert!(a.is_empty());
}
#[test]
fn long_flag_and_positional() {
let mut a = State::from(&["--speed", "12"]);
let flag = a.take_flag(&long("speed"));
assert!(flag);
assert!(!a.is_empty());
let s = a.take_positional_word(M).unwrap();
assert_eq!(s.2, "12");
assert!(a.is_empty());
}
#[test]
fn multiple_short_flags() {
let args = Args::from(&["-vvv"]);
let mut err = None;
let mut a = State::construct(args, &['v'], &[], &mut err);
assert!(a.take_flag(&short('v')));
assert!(a.take_flag(&short('v')));
assert!(a.take_flag(&short('v')));
assert!(!a.take_flag(&short('v')));
assert!(a.is_empty());
}
#[test]
fn long_arg_with_equality() {
let mut a = State::from(&["--speed=12"]);
let s = a.take_arg(&long("speed"), false, M).unwrap().unwrap();
assert_eq!(s, "12");
assert!(a.is_empty());
}
#[test]
fn long_arg_with_equality_and_minus() {
let mut a = State::from(&["--speed=-12"]);
let s = a.take_arg(&long("speed"), true, M).unwrap().unwrap();
assert_eq!(s, "-12");
assert!(a.is_empty());
}
#[test]
fn short_arg_with_equality() {
let mut a = State::from(&["-s=12"]);
let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
assert_eq!(s, "12");
assert!(a.is_empty());
}
#[test]
fn short_arg_with_equality_and_minus() {
let mut a = State::from(&["-s=-12"]);
let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
assert_eq!(s, "-12");
assert!(a.is_empty());
}
#[test]
fn short_arg_with_equality_and_minus_is_adjacent() {
let mut a = State::from(&["-s=-12"]);
let s = a.take_arg(&short('s'), true, M).unwrap().unwrap();
assert_eq!(s, "-12");
assert!(a.is_empty());
}
#[test]
fn short_arg_without_equality() {
let mut a = State::from(&["-s", "12"]);
let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
assert_eq!(s, "12");
assert!(a.is_empty());
}
#[test]
fn two_short_flags() {
let mut a = State::from(&["-s", "-v"]);
assert!(a.take_flag(&short('s')));
assert!(a.take_flag(&short('v')));
assert!(a.is_empty());
}
#[test]
fn two_short_flags2() {
let mut a = State::from(&["-s", "-v"]);
assert!(a.take_flag(&short('v')));
assert!(!a.take_flag(&short('v')));
assert!(a.take_flag(&short('s')));
assert!(!a.take_flag(&short('s')));
assert!(a.is_empty());
}
#[test]
fn command_with_flags() {
let mut a = State::from(&["cmd", "-s", "v"]);
assert!(a.take_cmd("cmd"));
let s = a.take_arg(&short('s'), false, M).unwrap().unwrap();
assert_eq!(s, "v");
assert!(a.is_empty());
}
#[test]
fn command_and_positional() {
let mut a = State::from(&["cmd", "pos"]);
assert!(a.take_cmd("cmd"));
let w = a.take_positional_word(M).unwrap();
assert_eq!(w.2, "pos");
assert!(a.is_empty());
}
#[test]
fn positionals_after_double_dash1() {
let mut a = State::from(&["-v", "--", "-x"]);
assert!(a.take_flag(&short('v')));
let w = a.take_positional_word(M).unwrap();
assert_eq!(w.2, "-x");
assert!(a.is_empty());
}
#[test]
fn positionals_after_double_dash2() {
let mut a = State::from(&["-v", "--", "-x"]);
assert!(a.take_flag(&short('v')));
let w = a.take_positional_word(M).unwrap();
assert_eq!(w.2, "-x");
assert!(a.is_empty());
}
#[test]
fn positionals_after_double_dash3() {
let mut a = State::from(&["-v", "12", "--", "-x"]);
let w = a.take_arg(&short('v'), false, M).unwrap().unwrap();
assert_eq!(w, "12");
let w = a.take_positional_word(M).unwrap();
assert_eq!(w.2, "-x");
assert!(a.is_empty());
}
#[test]
fn ambiguity_towards_flag() {
let args = Args::from(&["-abc"]);
let mut err = None;
let mut a = State::construct(args, &['a', 'b', 'c'], &[], &mut err);
assert!(a.take_flag(&short('a')));
assert!(a.take_flag(&short('b')));
assert!(a.take_flag(&short('c')));
}
#[test]
fn ambiguity_towards_argument() {
let args = Args::from(&["-abc"]);
let mut err = None;
let mut a = State::construct(args, &[], &['a'], &mut err);
let r = a.take_arg(&short('a'), false, M).unwrap().unwrap();
assert_eq!(r, "bc");
}
#[test]
fn ambiguity_towards_error() {
let args = Args::from(&["-abc"]);
let mut err = None;
let _a = State::construct(args, &['a', 'b', 'c'], &['a'], &mut err);
assert!(err.is_some());
}
#[test]
fn ambiguity_towards_default() {
let a = State::from(&["-abc"]);
let is_ambig = matches!(a.peek(), Some(Arg::Word(_)));
assert!(is_ambig);
}
}