119625d8cSopenharmony_ci//! Minimal, flexible command-line parser 219625d8cSopenharmony_ci//! 319625d8cSopenharmony_ci//! As opposed to a declarative parser, this processes arguments as a stream of tokens. As lexing 419625d8cSopenharmony_ci//! a command-line is not context-free, we rely on the caller to decide how to interpret the 519625d8cSopenharmony_ci//! arguments. 619625d8cSopenharmony_ci//! 719625d8cSopenharmony_ci//! # Examples 819625d8cSopenharmony_ci//! 919625d8cSopenharmony_ci//! ```rust 1019625d8cSopenharmony_ci//! use std::path::PathBuf; 1119625d8cSopenharmony_ci//! 1219625d8cSopenharmony_ci//! type BoxedError = Box<dyn std::error::Error + Send + Sync>; 1319625d8cSopenharmony_ci//! 1419625d8cSopenharmony_ci//! #[derive(Debug)] 1519625d8cSopenharmony_ci//! struct Args { 1619625d8cSopenharmony_ci//! paths: Vec<PathBuf>, 1719625d8cSopenharmony_ci//! color: Color, 1819625d8cSopenharmony_ci//! verbosity: usize, 1919625d8cSopenharmony_ci//! } 2019625d8cSopenharmony_ci//! 2119625d8cSopenharmony_ci//! #[derive(Debug)] 2219625d8cSopenharmony_ci//! enum Color { 2319625d8cSopenharmony_ci//! Always, 2419625d8cSopenharmony_ci//! Auto, 2519625d8cSopenharmony_ci//! Never, 2619625d8cSopenharmony_ci//! } 2719625d8cSopenharmony_ci//! 2819625d8cSopenharmony_ci//! impl Color { 2919625d8cSopenharmony_ci//! fn parse(s: Option<&clap_lex::RawOsStr>) -> Result<Self, BoxedError> { 3019625d8cSopenharmony_ci//! let s = s.map(|s| s.to_str().ok_or(s)); 3119625d8cSopenharmony_ci//! match s { 3219625d8cSopenharmony_ci//! Some(Ok("always")) | Some(Ok("")) | None => { 3319625d8cSopenharmony_ci//! Ok(Color::Always) 3419625d8cSopenharmony_ci//! } 3519625d8cSopenharmony_ci//! Some(Ok("auto")) => { 3619625d8cSopenharmony_ci//! Ok(Color::Auto) 3719625d8cSopenharmony_ci//! } 3819625d8cSopenharmony_ci//! Some(Ok("never")) => { 3919625d8cSopenharmony_ci//! Ok(Color::Never) 4019625d8cSopenharmony_ci//! } 4119625d8cSopenharmony_ci//! Some(invalid) => { 4219625d8cSopenharmony_ci//! Err(format!("Invalid value for `--color`, {:?}", invalid).into()) 4319625d8cSopenharmony_ci//! } 4419625d8cSopenharmony_ci//! } 4519625d8cSopenharmony_ci//! } 4619625d8cSopenharmony_ci//! } 4719625d8cSopenharmony_ci//! 4819625d8cSopenharmony_ci//! fn parse_args( 4919625d8cSopenharmony_ci//! raw: impl IntoIterator<Item=impl Into<std::ffi::OsString>> 5019625d8cSopenharmony_ci//! ) -> Result<Args, BoxedError> { 5119625d8cSopenharmony_ci//! let mut args = Args { 5219625d8cSopenharmony_ci//! paths: Vec::new(), 5319625d8cSopenharmony_ci//! color: Color::Auto, 5419625d8cSopenharmony_ci//! verbosity: 0, 5519625d8cSopenharmony_ci//! }; 5619625d8cSopenharmony_ci//! 5719625d8cSopenharmony_ci//! let raw = clap_lex::RawArgs::new(raw); 5819625d8cSopenharmony_ci//! let mut cursor = raw.cursor(); 5919625d8cSopenharmony_ci//! raw.next(&mut cursor); // Skip the bin 6019625d8cSopenharmony_ci//! while let Some(arg) = raw.next(&mut cursor) { 6119625d8cSopenharmony_ci//! if arg.is_escape() { 6219625d8cSopenharmony_ci//! args.paths.extend(raw.remaining(&mut cursor).map(PathBuf::from)); 6319625d8cSopenharmony_ci//! } else if arg.is_stdio() { 6419625d8cSopenharmony_ci//! args.paths.push(PathBuf::from("-")); 6519625d8cSopenharmony_ci//! } else if let Some((long, value)) = arg.to_long() { 6619625d8cSopenharmony_ci//! match long { 6719625d8cSopenharmony_ci//! Ok("verbose") => { 6819625d8cSopenharmony_ci//! if let Some(value) = value { 6919625d8cSopenharmony_ci//! return Err(format!("`--verbose` does not take a value, got `{:?}`", value).into()); 7019625d8cSopenharmony_ci//! } 7119625d8cSopenharmony_ci//! args.verbosity += 1; 7219625d8cSopenharmony_ci//! } 7319625d8cSopenharmony_ci//! Ok("color") => { 7419625d8cSopenharmony_ci//! args.color = Color::parse(value)?; 7519625d8cSopenharmony_ci//! } 7619625d8cSopenharmony_ci//! _ => { 7719625d8cSopenharmony_ci//! return Err( 7819625d8cSopenharmony_ci//! format!("Unexpected flag: --{}", arg.display()).into() 7919625d8cSopenharmony_ci//! ); 8019625d8cSopenharmony_ci//! } 8119625d8cSopenharmony_ci//! } 8219625d8cSopenharmony_ci//! } else if let Some(mut shorts) = arg.to_short() { 8319625d8cSopenharmony_ci//! while let Some(short) = shorts.next_flag() { 8419625d8cSopenharmony_ci//! match short { 8519625d8cSopenharmony_ci//! Ok('v') => { 8619625d8cSopenharmony_ci//! args.verbosity += 1; 8719625d8cSopenharmony_ci//! } 8819625d8cSopenharmony_ci//! Ok('c') => { 8919625d8cSopenharmony_ci//! let value = shorts.next_value_os(); 9019625d8cSopenharmony_ci//! args.color = Color::parse(value)?; 9119625d8cSopenharmony_ci//! } 9219625d8cSopenharmony_ci//! Ok(c) => { 9319625d8cSopenharmony_ci//! return Err(format!("Unexpected flag: -{}", c).into()); 9419625d8cSopenharmony_ci//! } 9519625d8cSopenharmony_ci//! Err(e) => { 9619625d8cSopenharmony_ci//! return Err(format!("Unexpected flag: -{}", e.to_str_lossy()).into()); 9719625d8cSopenharmony_ci//! } 9819625d8cSopenharmony_ci//! } 9919625d8cSopenharmony_ci//! } 10019625d8cSopenharmony_ci//! } else { 10119625d8cSopenharmony_ci//! args.paths.push(PathBuf::from(arg.to_value_os().to_os_str().into_owned())); 10219625d8cSopenharmony_ci//! } 10319625d8cSopenharmony_ci//! } 10419625d8cSopenharmony_ci//! 10519625d8cSopenharmony_ci//! Ok(args) 10619625d8cSopenharmony_ci//! } 10719625d8cSopenharmony_ci//! 10819625d8cSopenharmony_ci//! let args = parse_args(["bin", "--hello", "world"]); 10919625d8cSopenharmony_ci//! println!("{:?}", args); 11019625d8cSopenharmony_ci//! ``` 11119625d8cSopenharmony_ci 11219625d8cSopenharmony_ciuse std::ffi::OsStr; 11319625d8cSopenharmony_ciuse std::ffi::OsString; 11419625d8cSopenharmony_ci 11519625d8cSopenharmony_cipub use std::io::SeekFrom; 11619625d8cSopenharmony_ci 11719625d8cSopenharmony_cipub use os_str_bytes::RawOsStr; 11819625d8cSopenharmony_cipub use os_str_bytes::RawOsString; 11919625d8cSopenharmony_ci 12019625d8cSopenharmony_ci/// Command-line arguments 12119625d8cSopenharmony_ci#[derive(Default, Clone, Debug, PartialEq, Eq)] 12219625d8cSopenharmony_cipub struct RawArgs { 12319625d8cSopenharmony_ci items: Vec<OsString>, 12419625d8cSopenharmony_ci} 12519625d8cSopenharmony_ci 12619625d8cSopenharmony_ciimpl RawArgs { 12719625d8cSopenharmony_ci //// Create an argument list to parse 12819625d8cSopenharmony_ci /// 12919625d8cSopenharmony_ci /// **NOTE:** The argument returned will be the current binary. 13019625d8cSopenharmony_ci /// 13119625d8cSopenharmony_ci /// # Example 13219625d8cSopenharmony_ci /// 13319625d8cSopenharmony_ci /// ```rust,no_run 13419625d8cSopenharmony_ci /// # use std::path::PathBuf; 13519625d8cSopenharmony_ci /// let raw = clap_lex::RawArgs::from_args(); 13619625d8cSopenharmony_ci /// let mut cursor = raw.cursor(); 13719625d8cSopenharmony_ci /// let _bin = raw.next_os(&mut cursor); 13819625d8cSopenharmony_ci /// 13919625d8cSopenharmony_ci /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>(); 14019625d8cSopenharmony_ci /// println!("{:?}", paths); 14119625d8cSopenharmony_ci /// ``` 14219625d8cSopenharmony_ci pub fn from_args() -> Self { 14319625d8cSopenharmony_ci Self::new(std::env::args_os()) 14419625d8cSopenharmony_ci } 14519625d8cSopenharmony_ci 14619625d8cSopenharmony_ci //// Create an argument list to parse 14719625d8cSopenharmony_ci /// 14819625d8cSopenharmony_ci /// # Example 14919625d8cSopenharmony_ci /// 15019625d8cSopenharmony_ci /// ```rust,no_run 15119625d8cSopenharmony_ci /// # use std::path::PathBuf; 15219625d8cSopenharmony_ci /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]); 15319625d8cSopenharmony_ci /// let mut cursor = raw.cursor(); 15419625d8cSopenharmony_ci /// let _bin = raw.next_os(&mut cursor); 15519625d8cSopenharmony_ci /// 15619625d8cSopenharmony_ci /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>(); 15719625d8cSopenharmony_ci /// println!("{:?}", paths); 15819625d8cSopenharmony_ci /// ``` 15919625d8cSopenharmony_ci pub fn new(iter: impl IntoIterator<Item = impl Into<std::ffi::OsString>>) -> Self { 16019625d8cSopenharmony_ci let iter = iter.into_iter(); 16119625d8cSopenharmony_ci Self::from(iter) 16219625d8cSopenharmony_ci } 16319625d8cSopenharmony_ci 16419625d8cSopenharmony_ci /// Create a cursor for walking the arguments 16519625d8cSopenharmony_ci /// 16619625d8cSopenharmony_ci /// # Example 16719625d8cSopenharmony_ci /// 16819625d8cSopenharmony_ci /// ```rust,no_run 16919625d8cSopenharmony_ci /// # use std::path::PathBuf; 17019625d8cSopenharmony_ci /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]); 17119625d8cSopenharmony_ci /// let mut cursor = raw.cursor(); 17219625d8cSopenharmony_ci /// let _bin = raw.next_os(&mut cursor); 17319625d8cSopenharmony_ci /// 17419625d8cSopenharmony_ci /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>(); 17519625d8cSopenharmony_ci /// println!("{:?}", paths); 17619625d8cSopenharmony_ci /// ``` 17719625d8cSopenharmony_ci pub fn cursor(&self) -> ArgCursor { 17819625d8cSopenharmony_ci ArgCursor::new() 17919625d8cSopenharmony_ci } 18019625d8cSopenharmony_ci 18119625d8cSopenharmony_ci /// Advance the cursor, returning the next [`ParsedArg`] 18219625d8cSopenharmony_ci pub fn next(&self, cursor: &mut ArgCursor) -> Option<ParsedArg<'_>> { 18319625d8cSopenharmony_ci self.next_os(cursor).map(ParsedArg::new) 18419625d8cSopenharmony_ci } 18519625d8cSopenharmony_ci 18619625d8cSopenharmony_ci /// Advance the cursor, returning a raw argument value. 18719625d8cSopenharmony_ci pub fn next_os(&self, cursor: &mut ArgCursor) -> Option<&OsStr> { 18819625d8cSopenharmony_ci let next = self.items.get(cursor.cursor).map(|s| s.as_os_str()); 18919625d8cSopenharmony_ci cursor.cursor = cursor.cursor.saturating_add(1); 19019625d8cSopenharmony_ci next 19119625d8cSopenharmony_ci } 19219625d8cSopenharmony_ci 19319625d8cSopenharmony_ci /// Return the next [`ParsedArg`] 19419625d8cSopenharmony_ci pub fn peek(&self, cursor: &ArgCursor) -> Option<ParsedArg<'_>> { 19519625d8cSopenharmony_ci self.peek_os(cursor).map(ParsedArg::new) 19619625d8cSopenharmony_ci } 19719625d8cSopenharmony_ci 19819625d8cSopenharmony_ci /// Return a raw argument value. 19919625d8cSopenharmony_ci pub fn peek_os(&self, cursor: &ArgCursor) -> Option<&OsStr> { 20019625d8cSopenharmony_ci self.items.get(cursor.cursor).map(|s| s.as_os_str()) 20119625d8cSopenharmony_ci } 20219625d8cSopenharmony_ci 20319625d8cSopenharmony_ci /// Return all remaining raw arguments, advancing the cursor to the end 20419625d8cSopenharmony_ci /// 20519625d8cSopenharmony_ci /// # Example 20619625d8cSopenharmony_ci /// 20719625d8cSopenharmony_ci /// ```rust,no_run 20819625d8cSopenharmony_ci /// # use std::path::PathBuf; 20919625d8cSopenharmony_ci /// let raw = clap_lex::RawArgs::new(["bin", "foo.txt"]); 21019625d8cSopenharmony_ci /// let mut cursor = raw.cursor(); 21119625d8cSopenharmony_ci /// let _bin = raw.next_os(&mut cursor); 21219625d8cSopenharmony_ci /// 21319625d8cSopenharmony_ci /// let mut paths = raw.remaining(&mut cursor).map(PathBuf::from).collect::<Vec<_>>(); 21419625d8cSopenharmony_ci /// println!("{:?}", paths); 21519625d8cSopenharmony_ci /// ``` 21619625d8cSopenharmony_ci pub fn remaining(&self, cursor: &mut ArgCursor) -> impl Iterator<Item = &OsStr> { 21719625d8cSopenharmony_ci let remaining = self.items[cursor.cursor..].iter().map(|s| s.as_os_str()); 21819625d8cSopenharmony_ci cursor.cursor = self.items.len(); 21919625d8cSopenharmony_ci remaining 22019625d8cSopenharmony_ci } 22119625d8cSopenharmony_ci 22219625d8cSopenharmony_ci /// Adjust the cursor's position 22319625d8cSopenharmony_ci pub fn seek(&self, cursor: &mut ArgCursor, pos: SeekFrom) { 22419625d8cSopenharmony_ci let pos = match pos { 22519625d8cSopenharmony_ci SeekFrom::Start(pos) => pos, 22619625d8cSopenharmony_ci SeekFrom::End(pos) => (self.items.len() as i64).saturating_add(pos).max(0) as u64, 22719625d8cSopenharmony_ci SeekFrom::Current(pos) => (cursor.cursor as i64).saturating_add(pos).max(0) as u64, 22819625d8cSopenharmony_ci }; 22919625d8cSopenharmony_ci let pos = (pos as usize).min(self.items.len()); 23019625d8cSopenharmony_ci cursor.cursor = pos; 23119625d8cSopenharmony_ci } 23219625d8cSopenharmony_ci 23319625d8cSopenharmony_ci /// Inject arguments before the [`RawArgs::next`] 23419625d8cSopenharmony_ci pub fn insert( 23519625d8cSopenharmony_ci &mut self, 23619625d8cSopenharmony_ci cursor: &ArgCursor, 23719625d8cSopenharmony_ci insert_items: impl IntoIterator<Item = impl Into<OsString>>, 23819625d8cSopenharmony_ci ) { 23919625d8cSopenharmony_ci self.items.splice( 24019625d8cSopenharmony_ci cursor.cursor..cursor.cursor, 24119625d8cSopenharmony_ci insert_items.into_iter().map(Into::into), 24219625d8cSopenharmony_ci ); 24319625d8cSopenharmony_ci } 24419625d8cSopenharmony_ci 24519625d8cSopenharmony_ci /// Any remaining args? 24619625d8cSopenharmony_ci pub fn is_end(&self, cursor: &ArgCursor) -> bool { 24719625d8cSopenharmony_ci self.peek_os(cursor).is_none() 24819625d8cSopenharmony_ci } 24919625d8cSopenharmony_ci} 25019625d8cSopenharmony_ci 25119625d8cSopenharmony_ciimpl<I, T> From<I> for RawArgs 25219625d8cSopenharmony_ciwhere 25319625d8cSopenharmony_ci I: Iterator<Item = T>, 25419625d8cSopenharmony_ci T: Into<OsString>, 25519625d8cSopenharmony_ci{ 25619625d8cSopenharmony_ci fn from(val: I) -> Self { 25719625d8cSopenharmony_ci Self { 25819625d8cSopenharmony_ci items: val.map(|x| x.into()).collect(), 25919625d8cSopenharmony_ci } 26019625d8cSopenharmony_ci } 26119625d8cSopenharmony_ci} 26219625d8cSopenharmony_ci 26319625d8cSopenharmony_ci/// Position within [`RawArgs`] 26419625d8cSopenharmony_ci#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] 26519625d8cSopenharmony_cipub struct ArgCursor { 26619625d8cSopenharmony_ci cursor: usize, 26719625d8cSopenharmony_ci} 26819625d8cSopenharmony_ci 26919625d8cSopenharmony_ciimpl ArgCursor { 27019625d8cSopenharmony_ci fn new() -> Self { 27119625d8cSopenharmony_ci Self { cursor: 0 } 27219625d8cSopenharmony_ci } 27319625d8cSopenharmony_ci} 27419625d8cSopenharmony_ci 27519625d8cSopenharmony_ci/// Command-line Argument 27619625d8cSopenharmony_ci#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 27719625d8cSopenharmony_cipub struct ParsedArg<'s> { 27819625d8cSopenharmony_ci inner: std::borrow::Cow<'s, RawOsStr>, 27919625d8cSopenharmony_ci utf8: Option<&'s str>, 28019625d8cSopenharmony_ci} 28119625d8cSopenharmony_ci 28219625d8cSopenharmony_ciimpl<'s> ParsedArg<'s> { 28319625d8cSopenharmony_ci fn new(inner: &'s OsStr) -> Self { 28419625d8cSopenharmony_ci let utf8 = inner.to_str(); 28519625d8cSopenharmony_ci let inner = RawOsStr::new(inner); 28619625d8cSopenharmony_ci Self { inner, utf8 } 28719625d8cSopenharmony_ci } 28819625d8cSopenharmony_ci 28919625d8cSopenharmony_ci /// Argument is length of 0 29019625d8cSopenharmony_ci pub fn is_empty(&self) -> bool { 29119625d8cSopenharmony_ci self.inner.as_ref().is_empty() 29219625d8cSopenharmony_ci } 29319625d8cSopenharmony_ci 29419625d8cSopenharmony_ci /// Does the argument look like a stdio argument (`-`) 29519625d8cSopenharmony_ci pub fn is_stdio(&self) -> bool { 29619625d8cSopenharmony_ci self.inner.as_ref() == "-" 29719625d8cSopenharmony_ci } 29819625d8cSopenharmony_ci 29919625d8cSopenharmony_ci /// Does the argument look like an argument escape (`--`) 30019625d8cSopenharmony_ci pub fn is_escape(&self) -> bool { 30119625d8cSopenharmony_ci self.inner.as_ref() == "--" 30219625d8cSopenharmony_ci } 30319625d8cSopenharmony_ci 30419625d8cSopenharmony_ci /// Does the argument look like a number 30519625d8cSopenharmony_ci pub fn is_number(&self) -> bool { 30619625d8cSopenharmony_ci self.to_value() 30719625d8cSopenharmony_ci .map(|s| s.parse::<f64>().is_ok()) 30819625d8cSopenharmony_ci .unwrap_or_default() 30919625d8cSopenharmony_ci } 31019625d8cSopenharmony_ci 31119625d8cSopenharmony_ci /// Treat as a long-flag 31219625d8cSopenharmony_ci pub fn to_long(&self) -> Option<(Result<&str, &RawOsStr>, Option<&RawOsStr>)> { 31319625d8cSopenharmony_ci if let Some(raw) = self.utf8 { 31419625d8cSopenharmony_ci let remainder = raw.strip_prefix("--")?; 31519625d8cSopenharmony_ci if remainder.is_empty() { 31619625d8cSopenharmony_ci debug_assert!(self.is_escape()); 31719625d8cSopenharmony_ci return None; 31819625d8cSopenharmony_ci } 31919625d8cSopenharmony_ci 32019625d8cSopenharmony_ci let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') { 32119625d8cSopenharmony_ci (p0, Some(p1)) 32219625d8cSopenharmony_ci } else { 32319625d8cSopenharmony_ci (remainder, None) 32419625d8cSopenharmony_ci }; 32519625d8cSopenharmony_ci let flag = Ok(flag); 32619625d8cSopenharmony_ci let value = value.map(RawOsStr::from_str); 32719625d8cSopenharmony_ci Some((flag, value)) 32819625d8cSopenharmony_ci } else { 32919625d8cSopenharmony_ci let raw = self.inner.as_ref(); 33019625d8cSopenharmony_ci let remainder = raw.strip_prefix("--")?; 33119625d8cSopenharmony_ci if remainder.is_empty() { 33219625d8cSopenharmony_ci debug_assert!(self.is_escape()); 33319625d8cSopenharmony_ci return None; 33419625d8cSopenharmony_ci } 33519625d8cSopenharmony_ci 33619625d8cSopenharmony_ci let (flag, value) = if let Some((p0, p1)) = remainder.split_once('=') { 33719625d8cSopenharmony_ci (p0, Some(p1)) 33819625d8cSopenharmony_ci } else { 33919625d8cSopenharmony_ci (remainder, None) 34019625d8cSopenharmony_ci }; 34119625d8cSopenharmony_ci let flag = flag.to_str().ok_or(flag); 34219625d8cSopenharmony_ci Some((flag, value)) 34319625d8cSopenharmony_ci } 34419625d8cSopenharmony_ci } 34519625d8cSopenharmony_ci 34619625d8cSopenharmony_ci /// Can treat as a long-flag 34719625d8cSopenharmony_ci pub fn is_long(&self) -> bool { 34819625d8cSopenharmony_ci self.inner.as_ref().starts_with("--") && !self.is_escape() 34919625d8cSopenharmony_ci } 35019625d8cSopenharmony_ci 35119625d8cSopenharmony_ci /// Treat as a short-flag 35219625d8cSopenharmony_ci pub fn to_short(&self) -> Option<ShortFlags<'_>> { 35319625d8cSopenharmony_ci if let Some(remainder_os) = self.inner.as_ref().strip_prefix('-') { 35419625d8cSopenharmony_ci if remainder_os.starts_with('-') { 35519625d8cSopenharmony_ci None 35619625d8cSopenharmony_ci } else if remainder_os.is_empty() { 35719625d8cSopenharmony_ci debug_assert!(self.is_stdio()); 35819625d8cSopenharmony_ci None 35919625d8cSopenharmony_ci } else { 36019625d8cSopenharmony_ci let remainder = self.utf8.map(|s| &s[1..]); 36119625d8cSopenharmony_ci Some(ShortFlags::new(remainder_os, remainder)) 36219625d8cSopenharmony_ci } 36319625d8cSopenharmony_ci } else { 36419625d8cSopenharmony_ci None 36519625d8cSopenharmony_ci } 36619625d8cSopenharmony_ci } 36719625d8cSopenharmony_ci 36819625d8cSopenharmony_ci /// Can treat as a short-flag 36919625d8cSopenharmony_ci pub fn is_short(&self) -> bool { 37019625d8cSopenharmony_ci self.inner.as_ref().starts_with('-') 37119625d8cSopenharmony_ci && !self.is_stdio() 37219625d8cSopenharmony_ci && !self.inner.as_ref().starts_with("--") 37319625d8cSopenharmony_ci } 37419625d8cSopenharmony_ci 37519625d8cSopenharmony_ci /// Treat as a value 37619625d8cSopenharmony_ci /// 37719625d8cSopenharmony_ci /// **NOTE:** May return a flag or an escape. 37819625d8cSopenharmony_ci pub fn to_value_os(&self) -> &RawOsStr { 37919625d8cSopenharmony_ci self.inner.as_ref() 38019625d8cSopenharmony_ci } 38119625d8cSopenharmony_ci 38219625d8cSopenharmony_ci /// Treat as a value 38319625d8cSopenharmony_ci /// 38419625d8cSopenharmony_ci /// **NOTE:** May return a flag or an escape. 38519625d8cSopenharmony_ci pub fn to_value(&self) -> Result<&str, &RawOsStr> { 38619625d8cSopenharmony_ci self.utf8.ok_or_else(|| self.inner.as_ref()) 38719625d8cSopenharmony_ci } 38819625d8cSopenharmony_ci 38919625d8cSopenharmony_ci /// Safely print an argument that may contain non-UTF8 content 39019625d8cSopenharmony_ci /// 39119625d8cSopenharmony_ci /// This may perform lossy conversion, depending on the platform. If you would like an implementation which escapes the path please use Debug instead. 39219625d8cSopenharmony_ci pub fn display(&self) -> impl std::fmt::Display + '_ { 39319625d8cSopenharmony_ci self.inner.to_str_lossy() 39419625d8cSopenharmony_ci } 39519625d8cSopenharmony_ci} 39619625d8cSopenharmony_ci 39719625d8cSopenharmony_ci/// Walk through short flags within a [`ParsedArg`] 39819625d8cSopenharmony_ci#[derive(Clone, Debug)] 39919625d8cSopenharmony_cipub struct ShortFlags<'s> { 40019625d8cSopenharmony_ci inner: &'s RawOsStr, 40119625d8cSopenharmony_ci utf8_prefix: std::str::CharIndices<'s>, 40219625d8cSopenharmony_ci invalid_suffix: Option<&'s RawOsStr>, 40319625d8cSopenharmony_ci} 40419625d8cSopenharmony_ci 40519625d8cSopenharmony_ciimpl<'s> ShortFlags<'s> { 40619625d8cSopenharmony_ci fn new(inner: &'s RawOsStr, utf8: Option<&'s str>) -> Self { 40719625d8cSopenharmony_ci let (utf8_prefix, invalid_suffix) = if let Some(utf8) = utf8 { 40819625d8cSopenharmony_ci (utf8, None) 40919625d8cSopenharmony_ci } else { 41019625d8cSopenharmony_ci split_nonutf8_once(inner) 41119625d8cSopenharmony_ci }; 41219625d8cSopenharmony_ci let utf8_prefix = utf8_prefix.char_indices(); 41319625d8cSopenharmony_ci Self { 41419625d8cSopenharmony_ci inner, 41519625d8cSopenharmony_ci utf8_prefix, 41619625d8cSopenharmony_ci invalid_suffix, 41719625d8cSopenharmony_ci } 41819625d8cSopenharmony_ci } 41919625d8cSopenharmony_ci 42019625d8cSopenharmony_ci /// Move the iterator forward by `n` short flags 42119625d8cSopenharmony_ci pub fn advance_by(&mut self, n: usize) -> Result<(), usize> { 42219625d8cSopenharmony_ci for i in 0..n { 42319625d8cSopenharmony_ci self.next().ok_or(i)?.map_err(|_| i)?; 42419625d8cSopenharmony_ci } 42519625d8cSopenharmony_ci Ok(()) 42619625d8cSopenharmony_ci } 42719625d8cSopenharmony_ci 42819625d8cSopenharmony_ci /// No short flags left 42919625d8cSopenharmony_ci pub fn is_empty(&self) -> bool { 43019625d8cSopenharmony_ci self.invalid_suffix.is_none() && self.utf8_prefix.as_str().is_empty() 43119625d8cSopenharmony_ci } 43219625d8cSopenharmony_ci 43319625d8cSopenharmony_ci /// Does the short flag look like a number 43419625d8cSopenharmony_ci /// 43519625d8cSopenharmony_ci /// Ideally call this before doing any iterator 43619625d8cSopenharmony_ci pub fn is_number(&self) -> bool { 43719625d8cSopenharmony_ci self.invalid_suffix.is_none() && self.utf8_prefix.as_str().parse::<f64>().is_ok() 43819625d8cSopenharmony_ci } 43919625d8cSopenharmony_ci 44019625d8cSopenharmony_ci /// Advance the iterator, returning the next short flag on success 44119625d8cSopenharmony_ci /// 44219625d8cSopenharmony_ci /// On error, returns the invalid-UTF8 value 44319625d8cSopenharmony_ci pub fn next_flag(&mut self) -> Option<Result<char, &'s RawOsStr>> { 44419625d8cSopenharmony_ci if let Some((_, flag)) = self.utf8_prefix.next() { 44519625d8cSopenharmony_ci return Some(Ok(flag)); 44619625d8cSopenharmony_ci } 44719625d8cSopenharmony_ci 44819625d8cSopenharmony_ci if let Some(suffix) = self.invalid_suffix { 44919625d8cSopenharmony_ci self.invalid_suffix = None; 45019625d8cSopenharmony_ci return Some(Err(suffix)); 45119625d8cSopenharmony_ci } 45219625d8cSopenharmony_ci 45319625d8cSopenharmony_ci None 45419625d8cSopenharmony_ci } 45519625d8cSopenharmony_ci 45619625d8cSopenharmony_ci /// Advance the iterator, returning everything left as a value 45719625d8cSopenharmony_ci pub fn next_value_os(&mut self) -> Option<&'s RawOsStr> { 45819625d8cSopenharmony_ci if let Some((index, _)) = self.utf8_prefix.next() { 45919625d8cSopenharmony_ci self.utf8_prefix = "".char_indices(); 46019625d8cSopenharmony_ci self.invalid_suffix = None; 46119625d8cSopenharmony_ci return Some(&self.inner[index..]); 46219625d8cSopenharmony_ci } 46319625d8cSopenharmony_ci 46419625d8cSopenharmony_ci if let Some(suffix) = self.invalid_suffix { 46519625d8cSopenharmony_ci self.invalid_suffix = None; 46619625d8cSopenharmony_ci return Some(suffix); 46719625d8cSopenharmony_ci } 46819625d8cSopenharmony_ci 46919625d8cSopenharmony_ci None 47019625d8cSopenharmony_ci } 47119625d8cSopenharmony_ci} 47219625d8cSopenharmony_ci 47319625d8cSopenharmony_ciimpl<'s> Iterator for ShortFlags<'s> { 47419625d8cSopenharmony_ci type Item = Result<char, &'s RawOsStr>; 47519625d8cSopenharmony_ci 47619625d8cSopenharmony_ci fn next(&mut self) -> Option<Self::Item> { 47719625d8cSopenharmony_ci self.next_flag() 47819625d8cSopenharmony_ci } 47919625d8cSopenharmony_ci} 48019625d8cSopenharmony_ci 48119625d8cSopenharmony_cifn split_nonutf8_once(b: &RawOsStr) -> (&str, Option<&RawOsStr>) { 48219625d8cSopenharmony_ci match std::str::from_utf8(b.as_raw_bytes()) { 48319625d8cSopenharmony_ci Ok(s) => (s, None), 48419625d8cSopenharmony_ci Err(err) => { 48519625d8cSopenharmony_ci let (valid, after_valid) = b.split_at(err.valid_up_to()); 48619625d8cSopenharmony_ci let valid = std::str::from_utf8(valid.as_raw_bytes()).unwrap(); 48719625d8cSopenharmony_ci (valid, Some(after_valid)) 48819625d8cSopenharmony_ci } 48919625d8cSopenharmony_ci } 49019625d8cSopenharmony_ci} 491