Skip to content

Commit

Permalink
feat(macros): add support for macro register (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
fu050409 authored Sep 24, 2024
1 parent 73dd542 commit 17f7257
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 74 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"aionbot",
"chronos",
"Deque",
"Hasher",
"onebot",
"serde"
]
Expand Down
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/aionbot-adapter-onebot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ edition = "2021"

[dependencies]
aionbot-core = { version = "0.1.0", path = "../aionbot-core" }
anyhow = "1.0.89"
onebot_v11 = "0.1.5"
14 changes: 14 additions & 0 deletions crates/aionbot-adapter-onebot/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
use anyhow::Result;

pub extern crate aionbot_core;

pub trait Adapter {
fn reply(&self, message: &str) -> impl std::future::Future<Output = Result<()>> + Send;
}

impl Adapter for aionbot_core::event::Event {
async fn reply(&self, message: &str) -> Result<()> {
let _ = message;
// let ws = onebot_v11::connect::ws_reverse::ReverseWsConnect::new(config);
unimplemented!()
}
}
18 changes: 0 additions & 18 deletions crates/aionbot-core/src/core.rs

This file was deleted.

8 changes: 4 additions & 4 deletions crates/aionbot-core/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use crate::{router::Router, types::Callback};

#[derive(Clone)]
pub struct Entry {
id: String,
priority: i8,
router: Arc<Box<dyn Router>>,
callback: Arc<Callback>,
pub id: &'static str,
pub priority: i8,
pub router: Arc<Box<dyn Router>>,
pub callback: Arc<Callback>,
}

impl Entry {
Expand Down
4 changes: 2 additions & 2 deletions crates/aionbot-core/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ impl Handler {
pub async fn input(&self, event: &Arc<Event>) -> Result<()> {
let mut queue = self.matches(event);
while let Some(entry) = queue.pop() {
entry.get_handler()(event).await?;
entry.get_handler()(event.clone()).await?;
}
Ok(())
}

#[inline]
pub fn matches(&self, event: &Arc<Event>) -> EventQueue<Entry> {
let mut queue = EventQueue::new();

for entry in self.entries.iter() {
if entry.get_router().matches(event) {
queue.push(entry.get_priority(), entry.clone());
Expand Down
2 changes: 1 addition & 1 deletion crates/aionbot-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod core;
pub mod entry;
pub mod event;
pub mod handler;
pub mod prelude;
pub mod queue;
pub mod router;
pub mod types;
4 changes: 4 additions & 0 deletions crates/aionbot-core/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub use crate::entry::Entry;
pub use crate::event::Event;
pub use crate::router::*;
pub use crate::types::*;
10 changes: 0 additions & 10 deletions crates/aionbot-core/src/queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,6 @@ impl<T: Eq + Hash + Clone> EventQueue<T> {
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}

pub fn generate(&mut self) -> impl Iterator<Item = Option<T>> + '_ {
std::iter::from_fn(move || {
if !self.is_empty() {
Some(self.pop())
} else {
None
}
})
}
}

impl<T: Eq + Hash + Clone> Default for EventQueue<T> {
Expand Down
83 changes: 45 additions & 38 deletions crates/aionbot-core/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,60 @@ impl Router for str {
}
}

impl Router for &str {
fn matches(&self, event: &Event) -> bool {
self == &event.plain_data.to_string()
}
}

impl Router for String {
fn matches(&self, message: &Event) -> bool {
self == &message.plain_data.to_string()
fn matches(&self, event: &Event) -> bool {
self == &event.plain_data.to_string()
}
}

// pub struct ExactMatchRouter {
// pattern: String,
// ignore_spaces: bool,
// }
pub struct ExactMatchRouter {
pattern: String,
ignore_spaces: bool,
}

// impl ExactMatchRouter {
// pub fn new(pattern: &str, ignore_spaces: bool) -> Self {
// Self {
// pattern: pattern.to_string(),
// ignore_spaces,
// }
// }
// }
impl ExactMatchRouter {
pub fn new(pattern: &str, ignore_spaces: bool) -> Self {
Self {
pattern: pattern.to_string(),
ignore_spaces,
}
}
}

// impl Router for ExactMatchRouter {
// fn matches(&self, message: &str) -> bool {
// if self.ignore_spaces {
// message.replace(" ", "") == self.pattern.replace(" ", "")
// } else {
// message == self.pattern
// }
// }
// }
impl Router for ExactMatchRouter {
fn matches(&self, event: &Event) -> bool {
let message = event.plain_data.to_string();
if self.ignore_spaces {
message.replace(" ", "") == self.pattern.replace(" ", "")
} else {
message == self.pattern
}
}
}

// pub struct RegexRouter {
// pattern: regex::Regex,
// }
pub struct RegexRouter {
pattern: regex::Regex,
}

// impl RegexRouter {
// pub fn new(pattern: &str) -> Self {
// Self {
// pattern: regex::Regex::new(pattern).unwrap(),
// }
// }
// }
impl RegexRouter {
pub fn new(pattern: &str) -> Self {
Self {
pattern: regex::Regex::new(pattern).unwrap(),
}
}
}

// impl Router for RegexRouter {
// fn matches(&self, message: &str) -> bool {
// self.pattern.is_match(message)
// }
// }
impl Router for RegexRouter {
fn matches(&self, event: &Event) -> bool {
self.pattern.is_match(&event.plain_data.to_string())
}
}

// pub struct StartsWithRouter {
// pattern: String,
Expand Down
5 changes: 4 additions & 1 deletion crates/aionbot-core/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::sync::Arc;

use anyhow::Result;
use futures::future::BoxFuture;

use crate::event::Event;

pub type Callback = fn(&Event) -> BoxFuture<'static, Result<String>>;
pub type HandlerCallback = BoxFuture<'static, Result<()>>;
pub type Callback = fn(Arc<Event>) -> HandlerCallback;
12 changes: 12 additions & 0 deletions crates/aionbot-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "aionbot-macros"
version = "0.1.0"
edition = "2021"

[dependencies]
proc-macro2 = "1.0.86"
quote = "1.0.37"
syn = { version = "2.0.77", features = ["full"] }

[lib]
proc-macro = true
105 changes: 105 additions & 0 deletions crates/aionbot-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::hash::{DefaultHasher, Hash, Hasher};

use proc_macro::TokenStream;
use quote::quote;
use syn::{meta::ParseNestedMeta, parse_macro_input, Result};

struct HandlerArgs {
priority: syn::LitInt,
router: Option<syn::Expr>,
}

impl Default for HandlerArgs {
fn default() -> Self {
Self {
priority: syn::LitInt::new("0", proc_macro2::Span::call_site()),
router: None,
}
}
}

impl HandlerArgs {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if let Some(ident) = meta.path.get_ident() {
match ident.to_string().as_str() {
"router" => {
self.router = Some(meta.value()?.parse()?);
Ok(())
}
"priority" => {
self.priority = meta.value()?.parse()?;
Ok(())
}
_ => Err(meta.error("msg")),
}
} else {
Err(meta.error("msg"))
}
}

fn is_empty(&self) -> bool {
self.router.is_none()
}
}

#[proc_macro_attribute]
pub fn register(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as syn::ItemFn);

let mut attrs = HandlerArgs::default();
let parser = syn::meta::parser(|meta| attrs.parse(meta));
parse_macro_input!(attr with parser);

let vis = &input.vis;
match vis {
syn::Visibility::Public(_) => {}
_ => {
return TokenStream::from(
quote! { compile_error!("Only public functions can be registered"); },
)
}
}

let origin_ident = &input.sig.ident;
let mut fn_name = input.sig.ident.to_string();
let mut hasher = DefaultHasher::new();
fn_name.hash(&mut hasher);
let hash_id = hasher.finish().to_string();
fn_name.extend("_".chars().chain(hash_id.chars()));

let fn_name_ident = syn::Ident::new(&fn_name, input.sig.ident.span());

let fn_args = &input.sig.inputs;
let fn_body = &input.block;

let default_router = quote! { "default" }.into();
let default_router = parse_macro_input!(default_router as syn::Expr);
let router = attrs.router.as_ref().unwrap_or(&default_router);

if attrs.is_empty() {
return TokenStream::from(
quote! { compile_error!("Missing `#[register(router = \"...\")]` attribute"); },
);
};

let expanded = quote! {
use std::sync::*;
use std::cell::*;
use aionbot_core::prelude::*;

pub fn #fn_name_ident(#fn_args) -> HandlerCallback {
Box::pin(async move { #fn_body })
}

pub fn #origin_ident() -> Entry {
Entry {
id: #hash_id,
priority: 0,
router: Arc::new(Box::new(#router)),
callback: Arc::new(#fn_name_ident),
}
}
};

TokenStream::from(expanded)
}
10 changes: 10 additions & 0 deletions crates/aionbot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "aionbot"
version = "0.1.0"
edition = "2021"

[dependencies]
aionbot-adapter-onebot = { version = "0.1.0", path = "../aionbot-adapter-onebot" }
aionbot-core = { version = "0.1.0", path = "../aionbot-core" }
aionbot-macros = { version = "0.1.0", path = "../aionbot-macros" }
tokio = { version = "1.40.0", features = ["full"] }
Loading

0 comments on commit 17f7257

Please sign in to comment.