#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(clippy::should_implement_trait)]
#![allow(clippy::map_clone)]
use std::{ffi::OsString, fmt::Display, str::FromStr};
pub struct Parser {
source: Box<dyn Iterator<Item = OsString> + 'static>,
shorts: Option<(Vec<u8>, usize)>,
#[cfg(windows)]
shorts_utf16: Option<(Vec<u16>, usize)>,
long: Option<String>,
long_value: Option<OsString>,
last_option: LastOption,
finished_opts: bool,
bin_name: Option<OsString>,
}
impl std::fmt::Debug for Parser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut f = f.debug_struct("Parser");
f.field("source", &"<iterator>")
.field("shorts", &self.shorts);
#[cfg(windows)]
f.field("shorts_utf16", &self.shorts_utf16);
f.field("long", &self.long)
.field("long_value", &self.long_value)
.field("last_option", &self.last_option)
.field("finished_opts", &self.finished_opts)
.finish()
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum LastOption {
None,
Short(char),
Long,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Arg<'a> {
Short(char),
Long(&'a str),
Value(OsString),
}
impl Parser {
pub fn next(&mut self) -> Result<Option<Arg<'_>>, Error> {
self.check_state();
if let Some(value) = self.long_value.take() {
return Err(Error::UnexpectedValue {
option: self.long.clone(),
value,
});
}
if let Some((ref arg, ref mut pos)) = self.shorts {
match first_codepoint(&arg[*pos..]) {
Ok(None) => {
self.shorts = None;
}
Ok(Some(ch)) => {
*pos += ch.len_utf8();
self.last_option = LastOption::Short(ch);
return Ok(Some(Arg::Short(ch)));
}
Err(_) => {
*pos += 1;
self.last_option = LastOption::Short('�');
return Ok(Some(Arg::Short('�')));
}
}
}
#[cfg(windows)]
{
if let Some((ref arg, ref mut pos)) = self.shorts_utf16 {
match first_utf16_codepoint(&arg[*pos..]) {
Ok(None) => {
self.shorts_utf16 = None;
}
Ok(Some(ch)) => {
*pos += ch.len_utf16();
self.last_option = LastOption::Short(ch);
return Ok(Some(Arg::Short(ch)));
}
Err(_) => {
*pos += 1;
self.last_option = LastOption::Short('�');
return Ok(Some(Arg::Short('�')));
}
}
}
}
let arg = match self.source.next() {
Some(arg) => arg,
None => return Ok(None),
};
if self.finished_opts {
return Ok(Some(Arg::Value(arg)));
}
if arg == "--" {
self.finished_opts = true;
return self.next();
}
#[cfg(any(unix, target_os = "wasi"))]
{
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::{OsStrExt, OsStringExt};
let bytes = arg.as_bytes();
if bytes.starts_with(b"--") {
let option = if let Some(ind) = bytes.iter().position(|&b| b == b'=') {
self.long_value = Some(OsString::from_vec(bytes[ind + 1..].into()));
String::from_utf8_lossy(&bytes[..ind]).into()
} else {
match arg.into_string() {
Ok(text) => text,
Err(arg) => arg.to_string_lossy().into_owned(),
}
};
Ok(Some(self.set_long(option)))
} else if bytes.len() > 1 && bytes[0] == b'-' {
self.shorts = Some((arg.into_vec(), 1));
self.next()
} else {
Ok(Some(Arg::Value(arg)))
}
}
#[cfg(not(any(unix, target_os = "wasi")))]
{
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let mut bytes = arg.encode_wide();
const DASH: u16 = b'-' as u16;
match (bytes.next(), bytes.next()) {
(Some(DASH), Some(_)) => {
}
_ => {
return Ok(Some(Arg::Value(arg)));
}
}
}
let arg = match arg.into_string() {
Ok(arg) => arg,
Err(arg) => {
#[cfg(windows)]
{
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let arg: Vec<u16> = arg.encode_wide().collect();
const DASH: u16 = b'-' as u16;
const EQ: u16 = b'=' as u16;
if arg.starts_with(&[DASH, DASH]) {
if let Some(ind) = arg.iter().position(|&u| u == EQ) {
self.long_value = Some(OsString::from_wide(&arg[ind + 1..]));
let long = self.set_long(String::from_utf16_lossy(&arg[..ind]));
return Ok(Some(long));
} else {
let long = self.set_long(String::from_utf16_lossy(&arg));
return Ok(Some(long));
}
} else {
assert!(arg.starts_with(&[DASH]));
assert!(arg.len() > 1);
self.shorts_utf16 = Some((arg, 1));
return self.next();
}
};
#[cfg(not(windows))]
{
let text = arg.to_string_lossy();
if text.starts_with('-') {
text.into_owned()
} else {
return Ok(Some(Arg::Value(arg)));
}
}
}
};
if arg.starts_with("--") {
let mut parts = arg.splitn(2, '=');
if let (Some(option), Some(value)) = (parts.next(), parts.next()) {
self.long_value = Some(value.into());
Ok(Some(self.set_long(option.into())))
} else {
Ok(Some(self.set_long(arg)))
}
} else if arg.starts_with('-') && arg != "-" {
self.shorts = Some((arg.into(), 1));
self.next()
} else {
Ok(Some(Arg::Value(arg.into())))
}
}
}
pub fn value(&mut self) -> Result<OsString, Error> {
self.check_state();
if let Some(value) = self.optional_value() {
return Ok(value);
}
if let Some(value) = self.source.next() {
return Ok(value);
}
let option = match self.last_option {
LastOption::None => None,
LastOption::Short(ch) => Some(format!("-{}", ch)),
LastOption::Long => self.long.clone(),
};
Err(Error::MissingValue { option })
}
pub fn bin_name(&self) -> Option<&str> {
self.bin_name.as_ref().and_then(|s| s.to_str())
}
fn optional_value(&mut self) -> Option<OsString> {
if let Some(value) = self.long_value.take() {
return Some(value);
}
if let Some((arg, pos)) = self.shorts.take() {
if pos < arg.len() {
#[cfg(any(unix, target_os = "wasi"))]
{
#[cfg(unix)]
use std::os::unix::ffi::OsStringExt;
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::OsStringExt;
return Some(OsString::from_vec(arg[pos..].into()));
}
#[cfg(not(any(unix, target_os = "wasi")))]
{
let arg = String::from_utf8(arg[pos..].into())
.expect("short option args on exotic platforms must be unicode");
return Some(arg.into());
}
}
}
#[cfg(windows)]
{
if let Some((arg, pos)) = self.shorts_utf16.take() {
if pos < arg.len() {
use std::os::windows::ffi::OsStringExt;
return Some(OsString::from_wide(&arg[pos..]));
}
}
}
None
}
pub fn from_env() -> Parser {
let mut source = std::env::args_os();
let bin_name = source.next();
Parser {
source: Box::new(source),
shorts: None,
#[cfg(windows)]
shorts_utf16: None,
long: None,
long_value: None,
last_option: LastOption::None,
finished_opts: false,
bin_name,
}
}
pub fn from_args<I>(args: I) -> Parser
where
I: IntoIterator + 'static,
I::Item: Into<OsString>,
{
Parser {
source: Box::new(args.into_iter().map(Into::into)),
shorts: None,
#[cfg(windows)]
shorts_utf16: None,
long: None,
long_value: None,
last_option: LastOption::None,
finished_opts: false,
bin_name: None,
}
}
fn set_long(&mut self, value: String) -> Arg {
self.last_option = LastOption::Long;
self.long = None;
Arg::Long(&self.long.get_or_insert(value)[2..])
}
fn check_state(&self) {
if let Some((ref arg, pos)) = self.shorts {
assert!(pos <= arg.len());
assert!(arg.len() > 1);
if pos > 1 {
assert!(self.last_option != LastOption::None);
assert!(self.last_option != LastOption::Long);
}
assert!(self.long_value.is_none());
}
#[cfg(windows)]
{
if let Some((ref arg, pos)) = self.shorts_utf16 {
assert!(pos <= arg.len());
assert!(arg.len() > 1);
if pos > 1 {
assert!(self.last_option != LastOption::None);
assert!(self.last_option != LastOption::Long);
}
assert!(self.shorts.is_none());
assert!(self.long_value.is_none());
}
}
match self.last_option {
LastOption::None => {
assert!(self.long.is_none());
assert!(self.long_value.is_none());
}
LastOption::Short(_) => {
assert!(self.long_value.is_none());
}
LastOption::Long => {
assert!(self.long.is_some());
}
}
if self.long_value.is_some() {
assert!(self.long.is_some());
assert!(self.last_option == LastOption::Long);
}
}
}
impl<'a> Arg<'a> {
pub fn unexpected(self) -> Error {
match self {
Arg::Short(short) => Error::UnexpectedOption(format!("-{}", short)),
Arg::Long(long) => Error::UnexpectedOption(format!("--{}", long)),
Arg::Value(value) => Error::UnexpectedArgument(value),
}
}
}
pub enum Error {
MissingValue {
option: Option<String>,
},
UnexpectedOption(String),
UnexpectedArgument(OsString),
UnexpectedValue {
option: Option<String>,
value: OsString,
},
ParsingFailed {
value: String,
error: Box<dyn std::error::Error + Send + Sync + 'static>,
},
NonUnicodeValue(OsString),
Custom(Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use crate::Error::*;
match self {
MissingValue { option: None } => write!(f, "missing argument at end of command"),
MissingValue {
option: Some(option),
} => {
write!(f, "missing argument for option '{}'", option)
}
UnexpectedOption(option) => write!(f, "invalid option '{}'", option),
UnexpectedArgument(value) => write!(f, "unexpected argument {:?}", value),
UnexpectedValue {
option: Some(option),
value,
} => {
write!(
f,
"unexpected argument for option '{}': {:?}",
option, value
)
}
UnexpectedValue {
option: None,
value,
} => {
write!(f, "unexpected argument for option: {:?}", value)
}
NonUnicodeValue(value) => write!(f, "argument is invalid unicode: {:?}", value),
ParsingFailed { value, error } => {
write!(f, "cannot parse argument {:?}: {}", value, error)
}
Custom(err) => write!(f, "{}", err),
}
}
}
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::ParsingFailed { error, .. } | Error::Custom(error) => Some(error.as_ref()),
_ => None,
}
}
}
impl From<String> for Error {
fn from(msg: String) -> Self {
Error::Custom(msg.into())
}
}
impl<'a> From<&'a str> for Error {
fn from(msg: &'a str) -> Self {
Error::Custom(msg.into())
}
}
impl From<OsString> for Error {
fn from(arg: OsString) -> Self {
Error::NonUnicodeValue(arg)
}
}
mod private {
pub trait Sealed {}
impl Sealed for std::ffi::OsString {}
}
pub trait ValueExt: private::Sealed {
fn parse<T: FromStr>(&self) -> Result<T, Error>
where
T::Err: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
fn parse_with<F, T, E>(&self, func: F) -> Result<T, Error>
where
F: FnOnce(&str) -> Result<T, E>,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>;
}
impl ValueExt for OsString {
fn parse<T: FromStr>(&self) -> Result<T, Error>
where
T::Err: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
self.parse_with(FromStr::from_str)
}
fn parse_with<F, T, E>(&self, func: F) -> Result<T, Error>
where
F: FnOnce(&str) -> Result<T, E>,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
match self.to_str() {
Some(text) => match func(text) {
Ok(value) => Ok(value),
Err(err) => Err(Error::ParsingFailed {
value: text.to_owned(),
error: err.into(),
}),
},
None => Err(Error::NonUnicodeValue(self.into())),
}
}
}
pub mod prelude {
pub use super::Arg::*;
pub use super::ValueExt;
}
fn first_codepoint(bytes: &[u8]) -> Result<Option<char>, u8> {
let bytes = bytes.get(..4).unwrap_or(bytes);
let text = match std::str::from_utf8(bytes) {
Ok(text) => text,
Err(err) if err.valid_up_to() > 0 => {
std::str::from_utf8(&bytes[..err.valid_up_to()]).unwrap()
}
Err(_) => return Err(bytes[0]),
};
Ok(text.chars().next())
}
#[cfg(windows)]
fn first_utf16_codepoint(units: &[u16]) -> Result<Option<char>, u16> {
match std::char::decode_utf16(units.iter().map(|ch| *ch)).next() {
Some(Ok(ch)) => Ok(Some(ch)),
Some(Err(_)) => Err(units[0]),
None => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::prelude::*;
use super::*;
fn parse(args: &'static str) -> Parser {
Parser::from_args(args.split_whitespace().map(bad_string))
}
macro_rules! assert_matches {
($expression: expr, $pattern: pat) => {
match $expression {
$pattern => true,
_ => panic!(
"{:?} does not match {:?}",
stringify!($expression),
stringify!($pattern)
),
}
};
}
#[test]
fn test_basic() -> Result<(), Error> {
let mut p = parse("-n 10 foo - -- baz -qux");
assert_eq!(p.next()?.unwrap(), Short('n'));
assert_eq!(p.value()?.parse::<i32>()?, 10);
assert_eq!(p.next()?.unwrap(), Value("foo".into()));
assert_eq!(p.next()?.unwrap(), Value("-".into()));
assert_eq!(p.next()?.unwrap(), Value("baz".into()));
assert_eq!(p.next()?.unwrap(), Value("-qux".into()));
assert_eq!(p.next()?, None);
assert_eq!(p.next()?, None);
assert_eq!(p.next()?, None);
Ok(())
}
#[test]
fn test_combined() -> Result<(), Error> {
let mut p = parse("-abc -fvalue -xfvalue");
assert_eq!(p.next()?.unwrap(), Short('a'));
assert_eq!(p.next()?.unwrap(), Short('b'));
assert_eq!(p.next()?.unwrap(), Short('c'));
assert_eq!(p.next()?.unwrap(), Short('f'));
assert_eq!(p.value()?, "value");
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.next()?.unwrap(), Short('f'));
assert_eq!(p.value()?, "value");
assert_eq!(p.next()?, None);
Ok(())
}
#[test]
fn test_long() -> Result<(), Error> {
let mut p = parse("--foo --bar=qux --foobar=qux=baz");
assert_eq!(p.next()?.unwrap(), Long("foo"));
assert_eq!(p.next()?.unwrap(), Long("bar"));
assert_eq!(p.value()?, "qux");
assert_eq!(p.next()?.unwrap(), Long("foobar"));
match p.next().unwrap_err() {
Error::UnexpectedValue {
option: Some(option),
value,
} => {
assert_eq!(option, "--foobar");
assert_eq!(value, "qux=baz");
}
_ => panic!(),
}
assert_eq!(p.next()?, None);
Ok(())
}
#[test]
fn test_dash_args() -> Result<(), Error> {
let mut p = parse("-x -- -y");
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.next()?.unwrap(), Value("-y".into()));
assert_eq!(p.next()?, None);
let mut p = parse("-x -- -y");
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.value()?, "--");
assert_eq!(p.next()?.unwrap(), Short('y'));
assert_eq!(p.next()?, None);
let mut p = parse("-x - -y");
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.next()?.unwrap(), Value("-".into()));
assert_eq!(p.next()?.unwrap(), Short('y'));
assert_eq!(p.next()?, None);
let mut p = parse("-x-y");
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.next()?.unwrap(), Short('-'));
assert_eq!(p.next()?.unwrap(), Short('y'));
assert_eq!(p.next()?, None);
Ok(())
}
#[test]
fn test_missing_value() -> Result<(), Error> {
let mut p = parse("-o");
assert_eq!(p.next()?.unwrap(), Short('o'));
match p.value() {
Err(Error::MissingValue {
option: Some(option),
}) => assert_eq!(option, "-o"),
_ => panic!(),
}
let mut q = parse("--out");
assert_eq!(q.next()?.unwrap(), Long("out"));
match q.value() {
Err(Error::MissingValue {
option: Some(option),
}) => assert_eq!(option, "--out"),
_ => panic!(),
}
let mut r = parse("");
assert_matches!(r.value(), Err(Error::MissingValue { option: None }));
Ok(())
}
#[test]
fn test_weird_args() -> Result<(), Error> {
let mut p = Parser::from_args(&[
"", "--=", "--=3", "-", "-x", "--", "-", "-x", "--", "", "-", "-x",
]);
assert_eq!(p.next()?.unwrap(), Value(OsString::from("")));
assert_eq!(p.next()?.unwrap(), Long(""));
assert_eq!(p.value()?, OsString::from(""));
assert_eq!(p.next()?.unwrap(), Long(""));
assert_eq!(p.value()?, OsString::from("3"));
assert_eq!(p.next()?.unwrap(), Value(OsString::from("-")));
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.value()?, OsString::from("--"));
assert_eq!(p.next()?.unwrap(), Value(OsString::from("-")));
assert_eq!(p.next()?.unwrap(), Short('x'));
assert_eq!(p.next()?.unwrap(), Value(OsString::from("")));
assert_eq!(p.next()?.unwrap(), Value(OsString::from("-")));
assert_eq!(p.next()?.unwrap(), Value(OsString::from("-x")));
assert_eq!(p.next()?, None);
#[cfg(any(unix, target_os = "wasi", windows))]
{
let mut q = parse("--=@");
assert_eq!(q.next()?.unwrap(), Long(""));
assert_eq!(q.value()?, bad_string("@"));
assert_eq!(q.next()?, None);
}
let mut r = parse("");
assert_eq!(r.next()?, None);
Ok(())
}
#[test]
fn test_unicode() -> Result<(), Error> {
let mut p = parse("-aµ --µ=10 µ --foo=µ");
assert_eq!(p.next()?.unwrap(), Short('a'));
assert_eq!(p.next()?.unwrap(), Short('µ'));
assert_eq!(p.next()?.unwrap(), Long("µ"));
assert_eq!(p.value()?, "10");
assert_eq!(p.next()?.unwrap(), Value("µ".into()));
assert_eq!(p.next()?.unwrap(), Long("foo"));
assert_eq!(p.value()?, "µ");
Ok(())
}
#[cfg(any(unix, target_os = "wasi", windows))]
#[test]
fn test_mixed_invalid() -> Result<(), Error> {
let mut p = parse("--foo=@@@");
assert_eq!(p.next()?.unwrap(), Long("foo"));
assert_eq!(p.value()?, bad_string("@@@"));
let mut q = parse("-💣@@@");
assert_eq!(q.next()?.unwrap(), Short('💣'));
assert_eq!(q.value()?, bad_string("@@@"));
let mut r = parse("-f@@@");
assert_eq!(r.next()?.unwrap(), Short('f'));
assert_eq!(r.next()?.unwrap(), Short('�'));
assert_eq!(r.next()?.unwrap(), Short('�'));
assert_eq!(r.next()?.unwrap(), Short('�'));
assert_eq!(r.next()?, None);
let mut s = parse("--foo=bar=@@@");
assert_eq!(s.next()?.unwrap(), Long("foo"));
assert_eq!(s.value()?, bad_string("bar=@@@"));
Ok(())
}
#[cfg(any(unix, target_os = "wasi", windows))]
#[test]
fn test_separate_invalid() -> Result<(), Error> {
let mut p = parse("--foo @@@");
assert_eq!(p.next()?.unwrap(), Long("foo"));
assert_eq!(p.value()?, bad_string("@@@"));
Ok(())
}
#[cfg(any(unix, target_os = "wasi", windows))]
#[test]
fn test_invalid_long_option() -> Result<(), Error> {
let mut p = parse("--@=10");
assert_eq!(p.next()?.unwrap(), Long("�"));
assert_eq!(p.value().unwrap(), OsString::from("10"));
assert_eq!(p.next()?, None);
let mut q = parse("--@");
assert_eq!(q.next()?.unwrap(), Long("�"));
assert_eq!(q.next()?, None);
Ok(())
}
#[test]
fn test_value_ext() -> Result<(), Error> {
let s = OsString::from("-10");
assert_eq!(s.parse::<i32>()?, -10);
assert_eq!(
s.parse_with(|s| match s {
"-10" => Ok(0),
_ => Err("bad"),
})?,
0,
);
match s.parse::<u32>() {
Err(Error::ParsingFailed { value, .. }) => assert_eq!(value, "-10"),
_ => panic!(),
}
match s.parse_with(|s| match s {
"11" => Ok(0_i32),
_ => Err("bad"),
}) {
Err(Error::ParsingFailed { value, .. }) => assert_eq!(value, "-10"),
_ => panic!(),
}
assert_eq!(s.into_string()?, "-10");
Ok(())
}
#[cfg(any(unix, target_os = "wasi", windows))]
#[test]
fn test_value_ext_invalid() -> Result<(), Error> {
let s = bad_string("foo@");
assert_matches!(s.parse::<i32>(), Err(Error::NonUnicodeValue(_)));
assert_matches!(
s.parse_with(<f32 as FromStr>::from_str),
Err(Error::NonUnicodeValue(_))
);
assert_matches!(
s.into_string().map_err(Error::from),
Err(Error::NonUnicodeValue(_))
);
Ok(())
}
#[test]
fn test_first_codepoint() {
assert_eq!(first_codepoint(b"foo").unwrap(), Some('f'));
assert_eq!(first_codepoint(b"").unwrap(), None);
assert_eq!(first_codepoint(b"f\xFF\xFF").unwrap(), Some('f'));
assert_eq!(first_codepoint(b"\xC2\xB5bar").unwrap(), Some('µ'));
first_codepoint(b"\xFF").unwrap_err();
assert_eq!(first_codepoint(b"foo\xC2\xB5").unwrap(), Some('f'));
}
fn bad_string(text: &str) -> OsString {
#[cfg(any(unix, target_os = "wasi"))]
{
#[cfg(unix)]
use std::os::unix::ffi::OsStringExt;
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::OsStringExt;
let mut text = text.as_bytes().to_vec();
for ch in &mut text {
if *ch == b'@' {
*ch = b'\xFF';
}
}
OsString::from_vec(text)
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStringExt;
let mut out = Vec::new();
for ch in text.chars() {
if ch == '@' {
out.push(0xD800);
} else {
let mut buf = [0; 2];
out.extend(&*ch.encode_utf16(&mut buf));
}
}
OsString::from_wide(&out)
}
#[cfg(not(any(unix, target_os = "wasi", windows)))]
{
if text.contains('@') {
unimplemented!("Don't know how to create invalid OsStrings on this platform");
}
text.into()
}
}
#[test]
fn basic_fuzz() {
#[cfg(any(windows, unix, target_os = "wasi"))]
const VOCABULARY: &[&str] = &[
"", "-", "--", "a", "-a", "-aa", "@", "-@", "-a@", "-@a", "--a", "--@", "--a=a",
"--a=", "--a=@", "--@=a", "--=", "--=@", "--=a", "-@@",
];
#[cfg(not(any(windows, unix, target_os = "wasi")))]
const VOCABULARY: &[&str] = &[
"", "-", "--", "a", "-a", "-aa", "--a", "--a=a", "--a=", "--=", "--=a",
];
let vocabulary: Vec<OsString> = VOCABULARY.iter().map(|&s| bad_string(s)).collect();
let mut permutations = vec![vec![]];
for _ in 0..3 {
let mut new = Vec::new();
for old in permutations {
for word in &vocabulary {
let mut extended = old.clone();
extended.push(word);
new.push(extended);
}
}
permutations = new;
for permutation in &permutations {
println!("{:?}", permutation);
let permutation: Vec<OsString> = permutation.iter().map(|&s| s.clone()).collect();
let p = Parser::from_args(permutation.into_iter());
exhaust(p, 0);
}
}
}
fn exhaust(parser: Parser, depth: u16) {
if depth > 100 {
panic!("Stuck in loop");
}
let (mut a, mut b) = dup_parser(parser);
match a.next() {
Ok(None) => (),
_ => exhaust(a, depth + 1),
}
match b.value() {
Err(_) => (),
_ => exhaust(b, depth + 1),
}
}
fn dup_parser(mut parser: Parser) -> (Parser, Parser) {
let args: Vec<OsString> = parser.source.collect();
parser.source = Box::new(args.clone().into_iter());
let new = Parser {
source: Box::new(args.into_iter()),
shorts: parser.shorts.clone(),
#[cfg(windows)]
shorts_utf16: parser.shorts_utf16.clone(),
long: parser.long.clone(),
long_value: parser.long_value.clone(),
last_option: parser.last_option,
finished_opts: parser.finished_opts,
bin_name: parser.bin_name.clone(),
};
(parser, new)
}
}