Construct clap commands from handlers too

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

View file

@ -4,6 +4,7 @@ use euphoxide::api::Message;
use euphoxide_bot::{ use euphoxide_bot::{
basic::FromHandler, basic::FromHandler,
botrulez::{FullHelp, Ping, ShortHelp}, botrulez::{FullHelp, Ping, ShortHelp},
clap::FromClapHandler,
CommandExt, Commands, Context, Propagate, CommandExt, Commands, Context, Propagate,
}; };
use euphoxide_client::MultiClient; use euphoxide_client::MultiClient;
@ -24,6 +25,21 @@ async fn pyramid(_arg: &str, msg: &Message, ctx: &Context) -> euphoxide::Result<
Ok(Propagate::No) 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] #[tokio::main]
async fn main() { async fn main() {
let (event_tx, mut event_rx) = mpsc::channel(10); let (event_tx, mut event_rx) = mpsc::channel(10);
@ -48,6 +64,13 @@ async fn main() {
.with_description("build a pyramid") .with_description("build a pyramid")
.general("pyramid"), .general("pyramid"),
) )
.then(
FromClapHandler::new(add)
.clap()
.described()
.with_description("add two numbers")
.general("add"),
)
.build(); .build();
let clients = MultiClient::new(event_tx); let clients = MultiClient::new(event_tx);

View file

@ -148,6 +148,11 @@ pub trait CommandExt: Sized {
fn specific(self, name: impl ToString) -> Specific<Self> { fn specific(self, name: impl ToString) -> Specific<Self> {
Specific::new(name, 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 {}` // Sadly this doesn't work: `impl<E, C: Command<E>> CommandExt for C {}`

View file

@ -1,5 +1,7 @@
//! [`clap`]-based commands. //! [`clap`]-based commands.
use std::{future::Future, marker::PhantomData};
use async_trait::async_trait; use async_trait::async_trait;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use euphoxide::api::Message; 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)] #[cfg(test)]
mod test { mod test {
use super::parse_quoted_args; use super::parse_quoted_args;