Construct clap commands from handlers too
This commit is contained in:
parent
a73ababe02
commit
a527b22515
3 changed files with 88 additions and 0 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 {}`
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue