diff --git a/derive/src/attributes.rs b/derive/src/attributes.rs index fa068b4..a517f14 100644 --- a/derive/src/attributes.rs +++ b/derive/src/attributes.rs @@ -13,6 +13,7 @@ pub struct ArgumentsAttr { pub file: Option, pub exit_code: i32, pub parse_echo_style: bool, + pub options_first: bool, } impl Default for ArgumentsAttr { @@ -23,6 +24,7 @@ impl Default for ArgumentsAttr { file: None, exit_code: 1, parse_echo_style: false, + options_first: false, } } } @@ -55,6 +57,9 @@ impl ArgumentsAttr { "parse_echo_style" => { args.parse_echo_style = true; } + "options_first" => { + args.options_first = true; + } _ => return Err(meta.error("unrecognized argument for arguments attribute")), }; Ok(()) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 3b2fbdd..5e8d7f4 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -67,6 +67,20 @@ pub fn arguments(input: TokenStream) -> TokenStream { quote!(parser.next()?) }; + // If options_first is set and we find the first positional argument, we + // immediately return all of them. + let positional = if arguments_attr.options_first { + quote!( + // Unwrap is fine because this is called when we have just parsed a + // value and therefore are not partially within an option. + let mut values = parser.raw_args().unwrap().collect::>(); + values.insert(0, value); + Ok(Some(::uutils_args::Argument::MultiPositional(values))) + ) + } else { + quote!(Ok(Some(::uutils_args::Argument::Positional(value)))) + }; + let expanded = quote!( impl #impl_generics Arguments for #name #ty_generics #where_clause { const EXIT_CODE: i32 = #exit_code; @@ -91,7 +105,7 @@ pub fn arguments(input: TokenStream) -> TokenStream { match arg { lexopt::Arg::Short(short) => { #short }, lexopt::Arg::Long(long) => { #long }, - lexopt::Arg::Value(value) => { Ok(Some(::uutils_args::Argument::Positional(value))) }, + lexopt::Arg::Value(value) => { #positional }, } } diff --git a/src/error.rs b/src/error.rs index ff7a6f4..0da36e5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,7 +28,7 @@ pub enum ErrorKind { UnexpectedOption(String, Vec), /// No more positional arguments were expected, but one was given anyway. - UnexpectedArgument(OsString), + UnexpectedArgument(String), /// A value was passed to an option that didn't expect a value. UnexpectedValue { @@ -99,7 +99,7 @@ impl Display for ErrorKind { Ok(()) } ErrorKind::UnexpectedArgument(arg) => { - write!(f, "Found an invalid argument '{}'.", arg.to_string_lossy()) + write!(f, "Found an invalid argument '{}'.", arg) } ErrorKind::UnexpectedValue { option, value } => { write!( @@ -144,7 +144,9 @@ impl From for ErrorKind { match other { lexopt::Error::MissingValue { option } => Self::MissingValue { option }, lexopt::Error::UnexpectedOption(s) => Self::UnexpectedOption(s, Vec::new()), - lexopt::Error::UnexpectedArgument(s) => Self::UnexpectedArgument(s), + lexopt::Error::UnexpectedArgument(s) => { + Self::UnexpectedArgument(s.to_string_lossy().to_string()) + } lexopt::Error::UnexpectedValue { option, value } => { Self::UnexpectedValue { option, value } } diff --git a/src/lib.rs b/src/lib.rs index 694bcdf..5164734 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod error; pub mod internal; +pub mod positional; mod value; #[cfg(doc)] @@ -64,6 +65,7 @@ pub enum Argument { Help, Version, Positional(OsString), + MultiPositional(Vec), Custom(T), } @@ -142,6 +144,9 @@ impl ArgumentIter { Argument::Positional(arg) => { self.positional_arguments.push(arg); } + Argument::MultiPositional(args) => { + self.positional_arguments.extend(args); + } Argument::Custom(arg) => return Ok(Some(arg)), } } diff --git a/src/positional.rs b/src/positional.rs new file mode 100644 index 0000000..b8b0640 --- /dev/null +++ b/src/positional.rs @@ -0,0 +1,361 @@ +//! Parsing of positional arguments. +//! +//! The signature for parsing positional arguments is one of `&'static str`, +//! [`Opt`], [`Many0`], [`Many1`] or a tuple of those. The [`Unpack::unpack`] +//! method of these types parses a `Vec` into the corresponding +//! [`Unpack::Output`] type. +//! +//! For example: +//! ``` +//! use std::ffi::OsString; +//! use uutils_args::positional::{Opt, Unpack}; +//! +//! let (a, b) = ("FILE1", Opt("FILE2")).unpack(vec!["one"]).unwrap(); +//! assert_eq!(a, "one"); +//! assert_eq!(b, None); +//! +//! let (a, b) = ("FILE1", Opt("FILE2")).unpack(vec!["one", "two"]).unwrap(); +//! assert_eq!(a, "one"); +//! assert_eq!(b, Some("two")); +//! +//! // It works for any `Vec`: +//! let (a, b) = ("FILE1", Opt("FILE2")).unpack(vec![1, 2]).unwrap(); +//! assert_eq!(a, 1); +//! assert_eq!(b, Some(2)); +//! ``` +//! +//! Here are a few examples: +//! +//! ```ignore +//! () // no arguments +//! "FOO" // one required argument with output `OsString` +//! Opt("FOO") // one optional argument with output `Option` +//! Many1("FOO") // one or more arguments with output `Vec` +//! Many0("FOO") // zero or more arguments with output `Vec` +//! ("FOO", "FOO") // two required arguments with output (`OsString`, `OsString`) +//! ``` +//! +//! This allows for the construction of complex signatures. The signature +//! +//! ```ignore +//! ("FOO", Many0("BAR")) +//! ``` +//! +//! specifies that there is first a required argument "FOO" and any number of +//! values for "BAR". +//! +//! However, not all combinations are supported by design. For example, the +//! signature +//! +//! ```ignore +//! (Many0("FOO"), Many0("BAR")) +//! ``` +//! +//! does not make sense, because it's unclear where the positional arguments +//! should go. The supported tuples implement [`Unpack`]. + +use crate::error::{Error, ErrorKind}; +use std::fmt::Debug; + +/// A required argument +type Req = &'static str; + +/// Makes it's argument optional +pub struct Opt(pub T); + +/// 1 or more arguments +pub struct Many1(pub Req); + +/// 0 or more arguments +pub struct Many0(pub Req); + +/// Unpack a `Vec` into the output type +/// +/// See the [module documentation](crate::positional) for more information. +pub trait Unpack { + type Output; + fn unpack(&self, operands: Vec) -> Result, Error>; +} + +impl Unpack for () { + type Output = (); + + fn unpack(&self, operands: Vec) -> Result, Error> { + assert_empty(operands) + } +} + +impl Unpack for (U,) { + type Output = U::Output; + + fn unpack(&self, operands: Vec) -> Result, Error> { + self.0.unpack(operands) + } +} + +impl Unpack for Req { + type Output = T; + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg = pop_front(self, &mut operands)?; + assert_empty(operands)?; + Ok(arg) + } +} + +impl Unpack for Opt { + type Output = Option>; + + fn unpack(&self, operands: Vec) -> Result, Error> { + Ok(if operands.is_empty() { + None + } else { + Some(self.0.unpack(operands)?) + }) + } +} + +impl Unpack for Many0 { + type Output = Vec; + + fn unpack(&self, operands: Vec) -> Result, Error> { + Ok(operands) + } +} + +impl Unpack for Many1 { + type Output = Vec; + + fn unpack(&self, operands: Vec) -> Result, Error> { + if operands.is_empty() { + return Err(Error { + exit_code: 1, + kind: ErrorKind::MissingPositionalArguments(vec![self.0.into()]), + }); + } + Ok(operands) + } +} + +impl Unpack for (Req, U) { + type Output = (T, U::Output); + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg = pop_front(self.0, &mut operands)?; + let rest = self.1.unpack(operands)?; + Ok((arg, rest)) + } +} + +impl Unpack for (Req, Req, U) { + type Output = (T, T, U::Output); + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg1 = pop_front(self.0, &mut operands)?; + let arg2 = pop_front(self.1, &mut operands)?; + let rest = self.2.unpack(operands)?; + Ok((arg1, arg2, rest)) + } +} + +impl Unpack for (Opt, Req) { + type Output = (Option<::Output>, T); + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg = pop_back(self.1, &mut operands)?; + let rest = self.0.unpack(operands)?; + Ok((rest, arg)) + } +} + +impl Unpack for (Many0, Req) { + type Output = (Vec, T); + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg = pop_back(self.1, &mut operands)?; + let rest = self.0.unpack(operands)?; + Ok((rest, arg)) + } +} + +impl Unpack for (Many1, Req) { + type Output = (Vec, T); + + fn unpack(&self, mut operands: Vec) -> Result, Error> { + let arg = pop_back(self.1, &mut operands)?; + let rest = self.0.unpack(operands)?; + Ok((rest, arg)) + } +} + +fn pop_front(name: &str, operands: &mut Vec) -> Result { + if operands.is_empty() { + return Err(Error { + exit_code: 1, + kind: ErrorKind::MissingPositionalArguments(vec![name.to_string()]), + }); + } + Ok(operands.remove(0)) +} + +fn pop_back(name: &str, operands: &mut Vec) -> Result { + operands.pop().ok_or_else(|| Error { + exit_code: 1, + kind: ErrorKind::MissingPositionalArguments(vec![name.to_string()]), + }) +} + +fn assert_empty(mut operands: Vec) -> Result<(), Error> { + if let Some(arg) = operands.pop() { + return Err(Error { + exit_code: 1, + kind: ErrorKind::UnexpectedArgument(format!("{:?}", arg)), + }); + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::{Many0, Many1, Opt, Unpack}; + + macro_rules! a { + ($e:expr, $t:ty) => { + let _: Result<$t, _> = $e.unpack(Vec::<&str>::new()); + }; + } + + #[track_caller] + fn assert_ok<'a, U: Unpack, const N: usize>( + signature: &U, + expected: U::Output<&'a str>, + operands: [&'a str; N], + ) where + U::Output<&'a str>: Eq + std::fmt::Debug, + { + assert_eq!(signature.unpack(Vec::from(operands)).unwrap(), expected); + } + + #[track_caller] + fn assert_err(signature: &impl Unpack, operands: [&str; N]) { + let operands = Vec::from(operands); + assert!(signature.unpack(operands).is_err()); + } + + #[test] + fn compile_tests() { + // The five basic ones + a!((), ()); + a!("FOO", &str); + a!(Opt("FOO"), Option<&str>); + a!(Many0("FOO"), Vec<&str>); + a!(Many1("FOO"), Vec<&str>); + + // Start building some tuples + a!(("FOO", "BAR"), (&str, &str)); + a!(("FOO", Opt("BAR")), (&str, Option<&str>)); + a!(("FOO", Many0("BAR")), (&str, Vec<&str>)); + a!(("FOO", Many1("BAR")), (&str, Vec<&str>)); + + // The other way around! + a!((Opt("FOO"), "BAR"), (Option<&str>, &str)); + a!((Many0("FOO"), "BAR"), (Vec<&str>, &str)); + a!((Many1("FOO"), "BAR"), (Vec<&str>, &str)); + + // Longer tuples! + a!(("FOO", "BAR", "BAZ"), (&str, &str, &str)); + a!(("FOO", "BAR", Opt("BAZ")), (&str, &str, Option<&str>)); + a!(("FOO", "BAR", Many0("BAZ")), (&str, &str, Vec<&str>)); + a!(("FOO", "BAR", Many1("BAZ")), (&str, &str, Vec<&str>)); + + // seq [FIRST [INCREMENT]] LAST + a!( + (Opt(("FIRST", Opt("INCREMENT"))), "LAST"), + (Option<(&str, Option<&str>)>, &str) + ); + + // mknod NAME TYPE [MAJOR MINOR] + a!( + ("NAME", "TYPE", Opt(("MAJOR", "MINOR"))), + (&str, &str, Option<(&str, &str)>) + ); + + // chroot + a!( + ("NEWROOT", Opt(("COMMAND", Many0("ARG")))), + (&str, Option<(&str, Vec<&str>)>) + ); + } + + #[test] + fn unit() { + assert_ok(&(), (), []); + assert_err(&(), ["foo"]); + assert_err(&(), ["foo", "bar"]); + } + + #[test] + fn required() { + let s = "FOO"; + assert_err(&s, []); + assert_ok(&s, "foo", ["foo"]); + assert_err(&s, ["foo", "bar"]); + assert_err(&s, ["foo", "bar", "baz"]); + } + + #[test] + fn optional() { + let s = Opt("FOO"); + assert_ok(&s, None, []); + assert_ok(&s, Some("foo"), ["foo"]); + assert_err(&s, ["foo", "bar"]); + assert_err(&s, ["foo", "bar", "baz"]); + } + + #[test] + fn many1() { + let s = Many1("FOO"); + assert_err(&s, []); + assert_ok(&s, vec!["foo"], ["foo"]); + assert_ok(&s, vec!["foo", "bar"], ["foo", "bar"]); + assert_ok(&s, vec!["foo", "bar", "baz"], ["foo", "bar", "baz"]); + } + + #[test] + fn many0() { + let s = Many0("FOO"); + assert_ok(&s, vec![], []); + assert_ok(&s, vec!["foo"], ["foo"]); + assert_ok(&s, vec!["foo", "bar"], ["foo", "bar"]); + assert_ok(&s, vec!["foo", "bar", "baz"], ["foo", "bar", "baz"]); + } + + #[test] + fn req_req() { + let s = ("FOO", "BAR"); + assert_err(&s, []); + assert_err(&s, ["foo"]); + assert_ok(&s, ("foo", "bar"), ["foo", "bar"]); + assert_err(&s, ["foo", "bar", "baz"]); + } + + #[test] + fn seq() { + let s = (Opt(("FIRST", Opt("INCREMENT"))), "LAST"); + assert_err(&s, []); + assert_ok(&s, (None, "1"), ["1"]); + assert_ok(&s, (Some(("1", None)), "2"), ["1", "2"]); + assert_ok(&s, (Some(("1", Some("2"))), "3"), ["1", "2", "3"]); + assert_err(&s, ["1", "2", "3", "4"]); + } + + #[test] + fn mknod() { + let s = ("NAME", "TYPE", Opt(("MAJOR", "MINOR"))); + assert_err(&s, []); + assert_err(&s, ["1"]); + assert_ok(&s, ("1", "2", None), ["1", "2"]); + assert_err(&s, ["1", "2", "3"]); + assert_ok(&s, ("1", "2", Some(("3", "4"))), ["1", "2", "3", "4"]); + } +} diff --git a/tests/coreutils/base32.rs b/tests/coreutils/base32.rs index de32e74..24ce251 100644 --- a/tests/coreutils/base32.rs +++ b/tests/coreutils/base32.rs @@ -1,4 +1,9 @@ -use uutils_args::{Arguments, Options}; +use std::ffi::OsString; + +use uutils_args::{ + positional::{Opt, Unpack}, + Arguments, Options, +}; #[derive(Clone, Arguments)] enum Arg { @@ -39,30 +44,29 @@ impl Options for Settings { } } +fn parse(args: I) -> Result<(Settings, Option), uutils_args::Error> +where + I: IntoIterator, + I::Item: Into, +{ + let (s, ops) = Settings::default().parse(args)?; + let file = Opt("FILE").unpack(ops)?; + Ok((s, file)) +} + #[test] fn wrap() { + assert_eq!(parse(["base32"]).unwrap().0.wrap, Some(76)); + assert_eq!(parse(["base32", "-w0"]).unwrap().0.wrap, None); + assert_eq!(parse(["base32", "-w100"]).unwrap().0.wrap, Some(100)); + assert_eq!(parse(["base32", "--wrap=100"]).unwrap().0.wrap, Some(100)); +} + +#[test] +fn file() { + assert_eq!(parse(["base32"]).unwrap().1, None); assert_eq!( - Settings::default().parse(["base32"]).unwrap().0.wrap, - Some(76) - ); - assert_eq!( - Settings::default().parse(["base32", "-w0"]).unwrap().0.wrap, - None - ); - assert_eq!( - Settings::default() - .parse(["base32", "-w100"]) - .unwrap() - .0 - .wrap, - Some(100) - ); - assert_eq!( - Settings::default() - .parse(["base32", "--wrap=100"]) - .unwrap() - .0 - .wrap, - Some(100) + parse(["base32", "file"]).unwrap().1, + Some(OsString::from("file")) ); } diff --git a/tests/coreutils/basename.rs b/tests/coreutils/basename.rs index 7f0716e..9324fe3 100644 --- a/tests/coreutils/basename.rs +++ b/tests/coreutils/basename.rs @@ -1,6 +1,9 @@ use std::ffi::OsString; -use uutils_args::{Arguments, Options}; +use uutils_args::{ + positional::{Many1, Unpack}, + Arguments, Options, +}; #[derive(Clone, Arguments)] enum Arg { @@ -35,19 +38,24 @@ impl Options for Settings { } } -fn parse(args: &[&str]) -> Settings { - let (mut settings, operands) = Settings::default().parse(args).unwrap(); - settings.names = operands; - if !settings.multiple { - assert_eq!(settings.names.len(), 2); - settings.suffix = settings.names.pop().unwrap(); +fn parse(args: &[&str]) -> Result { + let (mut settings, operands) = Settings::default().parse(args)?; + + if settings.multiple { + let names = Many1("FILE").unpack(operands)?; + settings.names = names; + } else { + let (names, suffix) = ("FILE", "SUFFIX").unpack(operands)?; + settings.names = vec![names]; + settings.suffix = suffix; } - settings + + Ok(settings) } #[test] fn name_and_suffix() { - let settings = parse(&["basename", "foobar", "bar"]); + let settings = parse(&["basename", "foobar", "bar"]).unwrap(); assert!(!settings.zero); assert_eq!(settings.names, vec!["foobar"]); assert_eq!(settings.suffix, "bar"); @@ -55,7 +63,7 @@ fn name_and_suffix() { #[test] fn zero_name_and_suffix() { - let settings = parse(&["basename", "-z", "foobar", "bar"]); + let settings = parse(&["basename", "-z", "foobar", "bar"]).unwrap(); assert!(settings.zero); assert_eq!(settings.names, vec!["foobar"]); assert_eq!(settings.suffix, "bar"); @@ -63,7 +71,7 @@ fn zero_name_and_suffix() { #[test] fn all_and_names() { - let settings = parse(&["basename", "-a", "foobar", "bar"]); + let settings = parse(&["basename", "-a", "foobar", "bar"]).unwrap(); assert!(settings.multiple); assert!(!settings.zero); assert_eq!(settings.names, vec!["foobar", "bar"]); @@ -72,7 +80,7 @@ fn all_and_names() { #[test] fn option_like_names() { - let settings = parse(&["basename", "-a", "--", "-a", "-z", "--suffix=SUFFIX"]); + let settings = parse(&["basename", "-a", "--", "-a", "-z", "--suffix=SUFFIX"]).unwrap(); assert!(settings.multiple); assert!(!settings.zero); assert_eq!(settings.names, vec!["-a", "-z", "--suffix=SUFFIX"]); diff --git a/tests/coreutils/ls.rs b/tests/coreutils/ls.rs index 681e83c..9673926 100644 --- a/tests/coreutils/ls.rs +++ b/tests/coreutils/ls.rs @@ -38,7 +38,7 @@ impl When { Self::Always => true, Self::Never => false, // Should be atty::is(atty::Stream::Stdout), but I don't want to - // pull that depenency in just for this test. + // pull that dependency in just for this test. Self::Auto => true, } } diff --git a/tests/coreutils/mktemp.rs b/tests/coreutils/mktemp.rs index 1319709..2410f7d 100644 --- a/tests/coreutils/mktemp.rs +++ b/tests/coreutils/mktemp.rs @@ -1,6 +1,12 @@ -use std::path::{Path, PathBuf}; +use std::{ + ffi::OsString, + path::{Path, PathBuf}, +}; -use uutils_args::{Arguments, Options}; +use uutils_args::{ + positional::{Opt, Unpack}, + Arguments, Options, +}; #[derive(Clone, Arguments)] enum Arg { @@ -46,39 +52,52 @@ impl Options for Settings { } } +fn parse(args: I) -> Result<(Settings, Option), uutils_args::Error> +where + I: IntoIterator, + I::Item: Into, +{ + let (s, ops) = Settings::default().parse(args)?; + let file = Opt("FILE").unpack(ops)?; + Ok((s, file)) +} + #[test] fn suffix() { - let (s, _operands) = Settings::default() - .parse(["mktemp", "--suffix=hello"]) - .unwrap(); + let (s, _template) = parse(["mktemp", "--suffix=hello"]).unwrap(); assert_eq!(s.suffix.unwrap(), "hello"); - let (s, _operands) = Settings::default().parse(["mktemp", "--suffix="]).unwrap(); + let (s, _template) = parse(["mktemp", "--suffix="]).unwrap(); assert_eq!(s.suffix.unwrap(), ""); - let (s, _operands) = Settings::default().parse(["mktemp", "--suffix="]).unwrap(); + let (s, _template) = parse(["mktemp", "--suffix="]).unwrap(); assert_eq!(s.suffix.unwrap(), ""); - let (s, _operands) = Settings::default().parse(["mktemp"]).unwrap(); + let (s, _template) = parse(["mktemp"]).unwrap(); assert_eq!(s.suffix, None); } #[test] fn tmpdir() { - let (s, _operands) = Settings::default().parse(["mktemp", "--tmpdir"]).unwrap(); + let (s, _template) = parse(["mktemp", "--tmpdir"]).unwrap(); assert_eq!(s.tmp_dir.unwrap(), Path::new(".")); - let (s, _operands) = Settings::default().parse(["mktemp", "--tmpdir="]).unwrap(); + let (s, _template) = parse(["mktemp", "--tmpdir="]).unwrap(); assert_eq!(s.tmp_dir.unwrap(), Path::new("")); - let (s, _operands) = Settings::default().parse(["mktemp", "-p", "foo"]).unwrap(); + let (s, _template) = parse(["mktemp", "-p", "foo"]).unwrap(); assert_eq!(s.tmp_dir.unwrap(), Path::new("foo")); - let (s, _operands) = Settings::default().parse(["mktemp", "-pfoo"]).unwrap(); + let (s, _template) = parse(["mktemp", "-pfoo"]).unwrap(); assert_eq!(s.tmp_dir.unwrap(), Path::new("foo")); - let (s, _operands) = Settings::default().parse(["mktemp", "-p", ""]).unwrap(); + let (s, _template) = parse(["mktemp", "-p", ""]).unwrap(); assert_eq!(s.tmp_dir.unwrap(), Path::new("")); - assert!(Settings::default().parse(["mktemp", "-p"]).is_err()); + assert!(parse(["mktemp", "-p"]).is_err()); +} + +#[test] +fn too_many_arguments() { + assert!(parse(["mktemp", "foo", "bar"]).is_err()); } diff --git a/tests/options_first.rs b/tests/options_first.rs new file mode 100644 index 0000000..ee9bc73 --- /dev/null +++ b/tests/options_first.rs @@ -0,0 +1,62 @@ +use std::ffi::OsString; + +use uutils_args::{Arguments, Options}; + +#[test] +fn timeout_like() { + // The timeout coreutil has -v and a command argument + #[derive(Arguments)] + #[arguments(options_first)] + enum Arg { + #[arg("-v", "--verbose")] + Verbose, + } + + #[derive(Default)] + struct Settings { + verbose: bool, + } + + impl Options for Settings { + fn apply(&mut self, arg: Arg) { + match arg { + Arg::Verbose => self.verbose = true, + } + } + } + + let (settings, command) = Settings::default() + .parse(["timeout", "-v", "10", "foo", "-v"]) + .unwrap(); + + assert!(settings.verbose); + assert_eq!( + command, + vec![ + OsString::from("10"), + OsString::from("foo"), + OsString::from("-v") + ] + ); + + let (settings, command) = Settings::default() + .parse(["timeout", "10", "foo", "-v"]) + .unwrap(); + + assert!(!settings.verbose); + assert_eq!( + command, + vec![ + OsString::from("10"), + OsString::from("foo"), + OsString::from("-v") + ] + ); + + let (settings, command) = Settings::default() + .parse(["timeout", "--", "10", "-v"]) + .unwrap(); + + assert!(!settings.verbose); + assert_eq!(command, vec![OsString::from("10"), OsString::from("-v")]); +}