Construct clap commands from handlers too

This commit is contained in:
Joscha 2024-12-29 13:59:27 +01:00
parent 30640b393a
commit 0941605ad0
3 changed files with 88 additions and 0 deletions

View file

@ -4,6 +4,7 @@ use euphoxide::api::Message;
use euphoxide_bot::{
basic::FromHandler,
botrulez::{FullHelp, Ping, ShortHelp},
clap::FromClapHandler,
CommandExt, Commands, Context, Propagate,
};
use euphoxide_client::MultiClient;
@ -24,6 +25,21 @@ async fn pyramid(_arg: &str, msg: &Message, ctx: &Context) -> euphoxide::Result<
Ok(Propagate::No)
}
#[derive(clap::Parser)]
struct AddArgs {
lhs: i64,
rhs: i64,
}
async fn add(args: AddArgs, msg: &Message, ctx: &Context) -> euphoxide::Result<Propagate> {
let result = args.lhs + args.rhs;
ctx.reply_only(msg.id, format!("{} + {} = {result}", args.lhs, args.rhs))
.await?;
Ok(Propagate::No)
}
#[tokio::main]
async fn main() {
let (event_tx, mut event_rx) = mpsc::channel(10);
@ -48,6 +64,13 @@ async fn main() {
.with_description("build a pyramid")
.general("pyramid"),
)
.then(
FromClapHandler::new(add)
.clap()
.with_info()
.with_description("add two numbers")
.general("add"),
)
.build();
let clients = MultiClient::new(event_tx);

View file

@ -148,6 +148,11 @@ pub trait CommandExt: Sized {
fn specific(self, name: impl ToString) -> Specific<Self> {
Specific::new(name, self)
}
#[cfg(feature = "clap")]
fn clap(self) -> clap::Clap<Self> {
clap::Clap(self)
}
}
// Sadly this doesn't work: `impl<E, C: Command<E>> CommandExt for C {}`

View file

@ -1,5 +1,7 @@
//! [`clap`]-based commands.
use std::{future::Future, marker::PhantomData};
use async_trait::async_trait;
use clap::{CommandFactory, Parser};
use euphoxide::api::Message;
@ -136,6 +138,64 @@ where
}
}
// TODO Simplify all this once AsyncFn becomes stable
pub trait ClapHandlerFn<'a0, 'a1, A, E>:
Fn(A, &'a0 Message, &'a1 Context<E>) -> Self::Future
where
E: 'a1,
{
type Future: Future<Output = Result<Propagate, E>> + Send;
}
impl<'a0, 'a1, A, E, F, Fut> ClapHandlerFn<'a0, 'a1, A, E> for F
where
E: 'a1,
F: Fn(A, &'a0 Message, &'a1 Context<E>) -> Fut + ?Sized,
Fut: Future<Output = Result<Propagate, E>> + Send,
{
type Future = Fut;
}
pub struct FromClapHandler<A, F> {
_a: PhantomData<A>,
pub handler: F,
}
impl<A, F> FromClapHandler<A, F> {
// Artificially constrained so we don't accidentally choose an incorrect A.
// Relying on type inference of A can result in unknown type errors even
// though we know what A should be based on F.
pub fn new<'a0, 'a1, E, Fut>(handler: F) -> Self
where
F: Fn(A, &'a0 Message, &'a1 Context<E>) -> Fut,
E: 'a1,
{
Self {
_a: PhantomData,
handler,
}
}
}
#[async_trait]
impl<A, E, F> ClapCommand<E> for FromClapHandler<A, F>
where
F: for<'a0, 'a1> ClapHandlerFn<'a0, 'a1, A, E> + Sync,
A: Send + Sync + 'static,
{
type Args = A;
async fn execute(
&self,
args: Self::Args,
msg: &Message,
ctx: &Context<E>,
) -> Result<Propagate, E> {
(self.handler)(args, msg, ctx).await
}
}
#[cfg(test)]
mod test {
use super::parse_quoted_args;