diff --git a/meli/src/command.rs b/meli/src/command.rs index 039ba2a1..43745c81 100644 --- a/meli/src/command.rs +++ b/meli/src/command.rs @@ -656,9 +656,15 @@ mod tests { assert_eq!( parse_command(b"set foo").unwrap_err().to_string(), BadValue { - inner: "Bad argument for `set`. Accepted arguments are [seen, unseen, plain, \ - threaded, compact, conversations]." - .into(), + inner: "foo".into(), + suggestions: Some(&[ + "seen", + "unseen", + "plain", + "threaded", + "compact", + "conversations" + ]) } .to_string(), ); diff --git a/meli/src/command/error.rs b/meli/src/command/error.rs index 942e2dbd..121251ad 100644 --- a/meli/src/command/error.rs +++ b/meli/src/command/error.rs @@ -29,6 +29,7 @@ pub enum CommandError { }, BadValue { inner: Cow<'static, str>, + suggestions: Option<&'static [&'static str]>, }, WrongNumberOfArguments { too_many: bool, @@ -63,7 +64,25 @@ impl std::fmt::Display for CommandError { Self::Parsing { inner, kind: _ } => { write!(fmt, "Could not parse command: {}", inner) } - Self::BadValue { inner } => { + Self::BadValue { + inner, + suggestions: Some(suggs), + } => { + write!(fmt, "Bad value/argument: {}. Possible values are: ", inner)?; + let len = suggs.len(); + for (i, val) in suggs.iter().enumerate() { + if i == len.saturating_sub(1) { + write!(fmt, "{}", val)?; + } else { + write!(fmt, "{}, ", val)?; + } + } + write!(fmt, "") + } + Self::BadValue { + inner, + suggestions: None, + } => { write!(fmt, "Bad value/argument: {}", inner) } Self::WrongNumberOfArguments { @@ -121,3 +140,28 @@ impl std::fmt::Display for CommandError { } impl std::error::Error for CommandError {} + +#[cfg(test)] +mod tests { + use super::CommandError; + + #[test] + fn test_command_error_display() { + assert_eq!( + &CommandError::BadValue { + inner: "foo".into(), + suggestions: Some(&[ + "seen", + "unseen", + "plain", + "threaded", + "compact", + "conversations" + ]) + } + .to_string(), + "Bad value/argument: foo. Possible values are: seen, unseen, plain, threaded, \ + compact, conversations" + ); + } +} diff --git a/meli/src/command/parser.rs b/meli/src/command/parser.rs index a7a96067..a0b52b8b 100644 --- a/meli/src/command/parser.rs +++ b/meli/src/command/parser.rs @@ -24,22 +24,37 @@ use super::*; use crate::command::{argcheck::*, error::*}; +const FLAG_SUGGESTIONS: &[&str] = &[ + "passed", + "replied", + "seen or read", + "junk or trash or trashed", + "draft", + "flagged", +]; + macro_rules! command_err { - (nom $b:expr, $input: expr, $msg:literal) => {{ + (nom $b:expr, $input: expr, $msg:expr, $suggs:expr) => {{ let evaluated: IResult<&'_ [u8], _> = { $b }; match evaluated { Err(_) => { - let err = CommandError::BadValue { inner: $msg.into() }; + let err = CommandError::BadValue { + inner: $msg.into(), + suggestions: $suggs, + }; return Ok(($input, Err(err))); } Ok(v) => v, } }}; - ($b:expr, $input: expr, $msg:literal) => {{ + ($b:expr, $input: expr, $msg:expr, $suggs:expr) => {{ let evaluated = { $b }; match evaluated { Err(_) => { - let err = CommandError::BadValue { inner: $msg.into() }; + let err = CommandError::BadValue { + inner: $msg.into(), + suggestions: $suggs, + }; return Ok(($input, Err(err))); } Ok(v) => v, @@ -206,7 +221,7 @@ pub fn parse_command(input: &[u8]) -> Result { /// assert_eq!( /// &parsed.unwrap_err().to_string(), /// "Bad value/argument: xunk is not a valid flag name. Possible values are: passed, replied, \ -/// seen or read, junk or trash or trashed, draft and flagged." +/// seen or read, junk or trash or trashed, draft, flagged" /// ); /// ``` pub fn flag<'a>(input: &'a [u8]) -> IResult<&'a [u8], Result> { @@ -243,12 +258,8 @@ pub fn flag<'a>(input: &'a [u8]) -> IResult<&'a [u8], Result(input: &'a [u8]) -> IResult<&'a [u8], Result IResult<&[u8], Result> { let (input, _) = is_a(" ")(input)?; arg_chk!(inc check, input); let (input, ret) = command_err!(nom - alt((map(tag("seen"), |_| Listing(SetSeen)), map(tag("unseen"), |_| Listing(SetUnseen))))(input), + alt(( + map(tag("seen"), |_| Listing(SetSeen)), + map(tag("unseen"), |_| Listing(SetUnseen) + )))(input), input, - "Bad argument for `set`. Accepted arguments are [seen, unseen, plain, threaded, compact, conversations]."); + String::from_utf8_lossy(input.trim()).to_string(), + Some(&["seen", "unseen", "plain", "threaded", "compact", "conversations"])); arg_chk!(finish check, input); let (input, _) = eof(input)?; Ok((input, Ok(ret))) @@ -408,7 +419,8 @@ pub fn goto(input: &[u8]) -> IResult<&[u8], Result> { let (input, nth) = command_err!(nom usize_c(input), input, - "Argument must be an integer."); + "Argument must be an integer.", + None); arg_chk!(finish check, input); let (input, _) = eof(input)?; Ok((input, Ok(Action::ViewMailbox(nth)))) @@ -581,7 +593,8 @@ pub fn mailto(input: &[u8]) -> IResult<&[u8], Result> { let (input, val) = command_err!( parser(val.as_bytes()), val.as_bytes(), - "Could not parse mailto value. If the value is valid, please report this bug." + "Could not parse mailto value. If the value is valid, please report this bug.", + None ); Ok((input, Ok(Compose(Mailto(val))))) } @@ -958,7 +971,8 @@ pub fn toggle(input: &[u8]) -> IResult<&[u8], Result> { return Ok(( input, Err(CommandError::BadValue { - inner: "Valid toggle values are thread_snooze, mouse, sign, encrypt.".into(), + inner: String::from_utf8_lossy(input).to_string().into(), + suggestions: Some(&["thread_snooze", "mouse", "sign", "encrypt"]), }), )); }