teloxide icon indicating copy to clipboard operation
teloxide copied to clipboard

Feature Request: implement parsing of arguments to commands separated by some arbitrary separator

Open Hirrolot opened this issue 3 years ago • 6 comments

I think that it would be nice if we had something like LinearArgs for BotCommand as a type that holds a sequence of arguments separated by some string.

Related to https://github.com/teloxide/teloxide/issues/395.

Design

Since a separator is always known at compile-time and needs to be abstracted away, probably const generics could do the trick: LinearArgs<"my-separator">.

Hirrolot avatar Jun 15 '21 05:06 Hirrolot

At the moment const generics only work with primitives:

error: `&'static str` is forbidden as the type of a const generic parameter
 --> src/lib.rs:1:19
  |
1 | struct T<const S: &'static str>;
  |                   ^^^^^^^^^^^^
  |
  = note: the only supported types are integers, `bool` and `char`
  = help: more complex types are supported with `#![feature(const_generics)]`

[playground]

WaffleLapkin avatar Jun 15 '21 07:06 WaffleLapkin

Frozen until string literals support for const generics.

Hirrolot avatar Jun 15 '21 07:06 Hirrolot

We can add crutches like the macro attributes inside macro:

#[derive(BotCommand)]
enum Command {
  Foo,
  #[command(linear_args_separator = "my-separator")]
  Bar(LinearArgs)
}

which expands into:

impl BotCommand for Command {
  fn parse(text: &str) -> Result<Self, ParseError> {
    let (cmd, args) = /* initial parse */;
    match cmd {
      "bar" => {
        let args = LinearArgs::parse("my-separator", args)?;
        return Ok(Command::Bar(args));
      } 
      /* another branches */
    } 
  }
}

p0lunin avatar Jun 17 '21 17:06 p0lunin

Or we can just use the separator which defined at the enum level (whitespace by default, can be override by #[command(separator = "my-separator")] like here)

p0lunin avatar Jun 17 '21 17:06 p0lunin

@p0lunin, the first approach with #[command(linear_args_separator = "my-separator")] is, as you've mentioned, a workaround to generally be avoided. The second approach is limited: what if a user wants to use a different separator for their LinearArgs and the rest of the arguments?

As for me, waiting till the const generics support for string literals is more sane, since the issue is not that critical.

Hirrolot avatar Jun 20 '21 04:06 Hirrolot

We've discussed this feature today and come to a conclusion that waiting for &str-const-generics is not the best idea and we should implement the feature in another way. Also I think we should change the name, LinearArgs is not really helpful, I propose Separated, but there may be other options.

The way we've decided to implement the feature is to use a generic argument implementing a trait as a replacement for the const-generic argument:

pub struct Separated<T = String, S: for<'a> Separator<'a> = Whitespace>(pub Vec<T>);

impl<T: FromStr, S: for<'a> Separator<'a>> FromStr for Separated<T, S> { ... }

pub trait Separator<'a> {
    type Iter: Iterator<Item = &'a str>;

    fn split(s: &'a str) -> Self::Iter;
}

impl<'a> Separator<'a> for Whitespace { ... }
impl<'a> Separator<'a> for Space { ... }
// etc

WaffleLapkin avatar Jul 25 '22 10:07 WaffleLapkin