diff --git a/src/lib.rs b/src/lib.rs index 694bcdf..6fdfd42 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)] diff --git a/src/positional.rs b/src/positional.rs new file mode 100644 index 0000000..b3af7e2 --- /dev/null +++ b/src/positional.rs @@ -0,0 +1,400 @@ +//! Parsing of positional arguments. +//! +//! The signature for parsing positional arguments is one of [`&'static str`], +//! [`Opt`], [`Many`] 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![OsString::from("one")]).unwrap(); +//! assert_eq!(a, OsString::from("one")); +//! assert_eq!(b, None); +//! +//! let (a, b) = ("FILE1", Opt("FILE2")).unpack(vec![OsString::from("one"), OsString::from("two")]).unwrap(); +//! assert_eq!(a, OsString::from("one")); +//! assert_eq!(b, Some(OsString::from("two"))); +//! ``` +//! +//! Here are a few examples: +//! +//! ```ignore +//! () // no arguments +//! "FOO" // one required argument with output `OsString` +//! Opt("FOO") // one optional argument with output `Option` +//! Many("FOO") // one or more arguments with output `Vec` +//! Opt(Many("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", Opt(Many("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 +//! (Many("FOO"), Many("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::ffi::OsString; + +/// A required argument +type Req = &'static str; + +/// Makes it's argument optional +pub struct Opt(pub T); + +/// 1 or more arguments +pub struct Many(pub Req); + +/// Unpack a `Vec` into the output type +/// +/// See the [module documentation](crate::positional) for more information. +pub trait Unpack { + type Output: ToOptional; + fn unpack(&self, operands: Vec) -> Result; +} + +impl Unpack for () { + type Output = (); + + fn unpack(&self, operands: Vec) -> Result { + assert_empty(operands) + } +} + +impl Unpack for (T,) { + type Output = T::Output; + + fn unpack(&self, operands: Vec) -> Result { + self.0.unpack(operands) + } +} + +impl Unpack for Req { + type Output = OsString; + + fn unpack(&self, mut operands: Vec) -> Result { + let arg = pop_front(self, &mut operands)?; + assert_empty(operands)?; + Ok(arg) + } +} + +impl Unpack for Opt +where + T::Output: ToOptional, +{ + type Output = ::Out; + + fn unpack(&self, operands: Vec) -> Result { + Ok(if operands.is_empty() { + ::none() + } else { + self.0.unpack(operands)?.some() + }) + } +} + +impl Unpack for Many { + type Output = Vec; + + fn unpack(&self, operands: Vec) -> Result { + if operands.is_empty() { + return Err(Error { + exit_code: 1, + kind: ErrorKind::MissingPositionalArguments(vec![self.0.into()]), + }); + } + Ok(operands) + } +} + +impl Unpack for (Req, T) { + type Output = (OsString, T::Output); + + fn unpack(&self, mut operands: Vec) -> Result { + let arg = pop_front(self.0, &mut operands)?; + let rest = self.1.unpack(operands)?; + Ok((arg, rest)) + } +} + +impl Unpack for (Req, Req, T) { + type Output = (OsString, OsString, T::Output); + + fn unpack(&self, mut operands: Vec) -> Result { + 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 = ( as Unpack>::Output, ::Output); + + fn unpack(&self, mut operands: Vec) -> Result { + let arg = pop_back(self.1, &mut operands)?; + let rest = self.0.unpack(operands)?; + Ok((rest, arg)) + } +} + +impl Unpack for (Many, Req) { + type Output = (::Output, ::Output); + + fn unpack(&self, mut operands: Vec) -> Result { + 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.into()]), + }); + } + 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.into()]), + }) +} + +fn assert_empty(mut operands: Vec) -> Result<(), Error> { + if let Some(arg) = operands.pop() { + return Err(Error { + exit_code: 1, + kind: ErrorKind::UnexpectedArgument(arg), + }); + } + Ok(()) +} + +pub trait ToOptional { + type Out: ToOptional; + fn some(self) -> Self::Out; + fn none() -> Self::Out; +} + +impl ToOptional for OsString { + type Out = Option; + fn some(self) -> Self::Out { + Some(self) + } + fn none() -> Self::Out { + None + } +} + +impl ToOptional for () { + type Out = Option; + fn some(self) -> Self::Out { + Some(self) + } + fn none() -> Self::Out { + None + } +} + +impl ToOptional for Vec { + type Out = Self; + fn some(self) -> Self::Out { + self + } + fn none() -> Self::Out { + Vec::new() + } +} + +impl ToOptional for (T1, T2) { + type Out = Option; + fn some(self) -> Self::Out { + Some(self) + } + fn none() -> Self::Out { + None + } +} + +impl ToOptional for (T1, T2, T3) { + type Out = Option; + fn some(self) -> Self::Out { + Some(self) + } + fn none() -> Self::Out { + None + } +} + +impl ToOptional for Option { + type Out = Self; + fn some(self) -> Self::Out { + self + } + fn none() -> Self::Out { + None + } +} + +#[cfg(test)] +mod test { + use super::{Many, Opt, Unpack}; + use std::ffi::OsString; + + macro_rules! a { + ($e:expr, $t:ty) => { + let _: Result<$t, _> = $e.unpack(Vec::new()); + }; + } + + #[track_caller] + fn assert_ok(signature: &U, expected: U::Output, operands: [&str; N]) + where + U::Output: Eq + std::fmt::Debug, + { + let operands = operands.into_iter().map(Into::into).collect(); + assert_eq!(signature.unpack(operands).unwrap(), expected); + } + + #[track_caller] + fn assert_err(signature: &impl Unpack, operands: [&str; N]) { + let operands = operands.into_iter().map(Into::into).collect(); + assert!(signature.unpack(operands).is_err()); + } + + #[test] + fn compile_tests() { + // The five basic ones + a!((), ()); + a!("FOO", OsString); + a!(Opt("FOO"), Option); + a!(Many("FOO"), Vec); + a!(Opt(Many("FOO")), Vec); + + // Start building some tuples + a!(("FOO", "BAR"), (OsString, OsString)); + a!(("FOO", Opt("BAR")), (OsString, Option)); + a!(("FOO", Many("BAR")), (OsString, Vec)); + a!(("FOO", Opt(Many("BAR"))), (OsString, Vec)); + + // The other way around! + a!((Opt("FOO"), "BAR"), (Option, OsString)); + a!((Many("FOO"), "BAR"), (Vec, OsString)); + a!((Opt(Many("FOO")), "BAR"), (Vec, OsString)); + + // Longer tuples! + a!(("FOO", "BAR", "BAZ"), (OsString, OsString, OsString)); + a!( + ("FOO", "BAR", Opt("BAZ")), + (OsString, OsString, Option) + ); + a!( + ("FOO", "BAR", Many("BAZ")), + (OsString, OsString, Vec) + ); + a!( + ("FOO", "BAR", Opt(Many("BAZ"))), + (OsString, OsString, Vec) + ); + + // seq [FIRST [INCREMENT]] LAST + a!( + (Opt(("FIRST", Opt("INCREMENT"))), "LAST"), + (Option<(OsString, Option)>, OsString) + ); + + // mknod NAME TYPE [MAJOR MINOR] + a!( + ("NAME", "TYPE", Opt(("MAJOR", "MINOR"))), + (OsString, OsString, Option<(OsString, OsString)>) + ); + + // chroot + a!( + ("NEWROOT", Opt(("COMMAND", Opt(Many("ARG"))))), + (OsString, Option<(OsString, Vec)>) + ); + } + + #[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".into(), ["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".into()), ["foo"]); + assert_err(&s, ["foo", "bar"]); + assert_err(&s, ["foo", "bar", "baz"]); + } + + #[test] + fn many() { + let s = Many("FOO"); + assert_err(&s, []); + assert_ok(&s, vec!["foo".into()], ["foo"]); + assert_ok(&s, vec!["foo".into(), "bar".into()], ["foo", "bar"]); + assert_ok( + &s, + vec!["foo".into(), "bar".into(), "baz".into()], + ["foo", "bar", "baz"], + ); + } + + #[test] + fn opt_many() { + let s = Opt(Many("FOO")); + assert_ok(&s, vec![], []); + assert_ok(&s, vec!["foo".into()], ["foo"]); + assert_ok(&s, vec!["foo".into(), "bar".into()], ["foo", "bar"]); + assert_ok( + &s, + vec!["foo".into(), "bar".into(), "baz".into()], + ["foo", "bar", "baz"], + ); + } + + #[test] + fn req_req() { + let s = ("FOO", "BAR"); + assert_err(&s, []); + assert_err(&s, ["foo"]); + assert_ok(&s, ("foo".into(), "bar".into()), ["foo", "bar"]); + assert_err(&s, ["foo", "bar", "baz"]); + } +}