You've already forked uutils-args
mirror of
https://github.com/uutils/uutils-args.git
synced 2026-06-10 16:13:08 -07:00
Merge pull request #82 from tertsdiepraam/new-positional
New positional argument system
This commit is contained in:
@@ -13,6 +13,7 @@ pub struct ArgumentsAttr {
|
||||
pub file: Option<String>,
|
||||
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(())
|
||||
|
||||
+15
-1
@@ -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::<Vec<OsString>>();
|
||||
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 },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -28,7 +28,7 @@ pub enum ErrorKind {
|
||||
UnexpectedOption(String, Vec<String>),
|
||||
|
||||
/// 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<lexopt::Error> 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 }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
mod error;
|
||||
pub mod internal;
|
||||
pub mod positional;
|
||||
mod value;
|
||||
|
||||
#[cfg(doc)]
|
||||
@@ -64,6 +65,7 @@ pub enum Argument<T: Arguments> {
|
||||
Help,
|
||||
Version,
|
||||
Positional(OsString),
|
||||
MultiPositional(Vec<OsString>),
|
||||
Custom(T),
|
||||
}
|
||||
|
||||
@@ -142,6 +144,9 @@ impl<T: Arguments> ArgumentIter<T> {
|
||||
Argument::Positional(arg) => {
|
||||
self.positional_arguments.push(arg);
|
||||
}
|
||||
Argument::MultiPositional(args) => {
|
||||
self.positional_arguments.extend(args);
|
||||
}
|
||||
Argument::Custom(arg) => return Ok(Some(arg)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>` into the corresponding
|
||||
//! [`Unpack::Output<T>`] 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<T>`:
|
||||
//! 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<OsString>`
|
||||
//! Many1("FOO") // one or more arguments with output `Vec<OsString>`
|
||||
//! Many0("FOO") // zero or more arguments with output `Vec<OsString>`
|
||||
//! ("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<T>(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<T>;
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error>;
|
||||
}
|
||||
|
||||
impl Unpack for () {
|
||||
type Output<T> = ();
|
||||
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
assert_empty(operands)
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unpack> Unpack for (U,) {
|
||||
type Output<T> = U::Output<T>;
|
||||
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
self.0.unpack(operands)
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpack for Req {
|
||||
type Output<T> = T;
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
let arg = pop_front(self, &mut operands)?;
|
||||
assert_empty(operands)?;
|
||||
Ok(arg)
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unpack> Unpack for Opt<U> {
|
||||
type Output<T> = Option<U::Output<T>>;
|
||||
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
Ok(if operands.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.0.unpack(operands)?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpack for Many0 {
|
||||
type Output<T> = Vec<T>;
|
||||
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
Ok(operands)
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpack for Many1 {
|
||||
type Output<T> = Vec<T>;
|
||||
|
||||
fn unpack<T: Debug>(&self, operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
if operands.is_empty() {
|
||||
return Err(Error {
|
||||
exit_code: 1,
|
||||
kind: ErrorKind::MissingPositionalArguments(vec![self.0.into()]),
|
||||
});
|
||||
}
|
||||
Ok(operands)
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unpack> Unpack for (Req, U) {
|
||||
type Output<T> = (T, U::Output<T>);
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
let arg = pop_front(self.0, &mut operands)?;
|
||||
let rest = self.1.unpack(operands)?;
|
||||
Ok((arg, rest))
|
||||
}
|
||||
}
|
||||
|
||||
impl<U: Unpack> Unpack for (Req, Req, U) {
|
||||
type Output<T> = (T, T, U::Output<T>);
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, 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<U: Unpack> Unpack for (Opt<U>, Req) {
|
||||
type Output<T> = (Option<<U as Unpack>::Output<T>>, T);
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, 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<T> = (Vec<T>, T);
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, 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<T> = (Vec<T>, T);
|
||||
|
||||
fn unpack<T: Debug>(&self, mut operands: Vec<T>) -> Result<Self::Output<T>, Error> {
|
||||
let arg = pop_back(self.1, &mut operands)?;
|
||||
let rest = self.0.unpack(operands)?;
|
||||
Ok((rest, arg))
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_front<T: Debug>(name: &str, operands: &mut Vec<T>) -> Result<T, Error> {
|
||||
if operands.is_empty() {
|
||||
return Err(Error {
|
||||
exit_code: 1,
|
||||
kind: ErrorKind::MissingPositionalArguments(vec![name.to_string()]),
|
||||
});
|
||||
}
|
||||
Ok(operands.remove(0))
|
||||
}
|
||||
|
||||
fn pop_back<T: Debug>(name: &str, operands: &mut Vec<T>) -> Result<T, Error> {
|
||||
operands.pop().ok_or_else(|| Error {
|
||||
exit_code: 1,
|
||||
kind: ErrorKind::MissingPositionalArguments(vec![name.to_string()]),
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_empty<T: Debug>(mut operands: Vec<T>) -> 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<const N: usize>(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"]);
|
||||
}
|
||||
}
|
||||
+27
-23
@@ -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<Arg> for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<I>(args: I) -> Result<(Settings, Option<OsString>), uutils_args::Error>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<OsString>,
|
||||
{
|
||||
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"))
|
||||
);
|
||||
}
|
||||
|
||||
+20
-12
@@ -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<Arg> 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<Settings, uutils_args::Error> {
|
||||
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"]);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
+33
-14
@@ -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<Arg> for Settings {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse<I>(args: I) -> Result<(Settings, Option<OsString>), uutils_args::Error>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Into<OsString>,
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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<Arg> 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")]);
|
||||
}
|
||||
Reference in New Issue
Block a user