Skip to content

Commit d1a489d

Browse files
extend command_handler macro
now supports aliases and restricted commands, captures docs
1 parent 3ebc174 commit d1a489d

4 files changed

Lines changed: 108 additions & 24 deletions

File tree

doc/command-handlers.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@ Handlers are identified by the `command_handler` attribute macro.
1414

1515
## The command_handler macro
1616

17-
The `command_handler` attribute macro has two possible forms, allowing for
18-
multiple command dispatchers to exist.
17+
The `command_handler` attribute macro can take several arguments:
1918

20-
The single-argument form - e.g. `#[command_handler("CAP")]` defines a handler
21-
for the 'default' global dispatcher, which is used to look up client protocol
22-
commands.
19+
```rs
20+
#[command_handler("PRIMARY", "ALIAS", "ALIAS2", in("PARENT"), restricted)]
21+
```
2322

24-
The two-argument form - e.g. `#[command_handler("CERT", in("NS"))]` puts the
25-
handler into a named secondary dispatcher, in this case `"NS"`. This form is
23+
The first argument defines the primary name of the command. Any further strings
24+
given will be used as aliases for the command.
25+
26+
If an argument is given of the form `in("PARENT")`, the command handler will be
27+
put into a named secondary dispatcher, in this case `"PARENT"`. This form is
2628
used to define handlers for services commands, and may have other uses in the
27-
future.
29+
future. If this argument is not given, the handler is added to the 'default'
30+
global dispatcher, which is used to look up client protocol commands.
31+
32+
If the `restricted` keyword is added, the command will be marked as for operators
33+
and will not be shown in `HELP` output to users.
2834

2935
## Async handlers
3036

sable_ircd/src/command/dispatcher.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::hash_map;
2+
13
use super::{plumbing::Command, *};
24

35
/// Type alias for a boxed command context
@@ -7,17 +9,21 @@ pub type BoxCommand<'cmd> = Box<dyn Command + 'cmd>;
79
/// attribute macro
810
pub type CommandHandlerWrapper = for<'a> fn(BoxCommand<'a>) -> Option<AsyncHandler<'a>>;
911

12+
#[derive(Clone)]
1013
/// A command handler registration. Constructed by the `command_handler` attribute macro.
1114
pub struct CommandRegistration {
1215
pub(super) command: &'static str,
16+
pub(super) aliases: &'static [&'static str],
1317
pub(super) dispatcher: Option<&'static str>,
1418
pub(super) handler: CommandHandlerWrapper,
19+
pub(super) restricted: bool,
20+
pub(super) docs: &'static [&'static str],
1521
}
1622

1723
/// A command dispatcher. Collects registered command handlers and allows lookup by
1824
/// command name.
1925
pub struct CommandDispatcher {
20-
handlers: HashMap<String, CommandHandlerWrapper>,
26+
commands: HashMap<String, CommandRegistration>,
2127
}
2228

2329
inventory::collect!(CommandRegistration);
@@ -42,11 +48,14 @@ impl CommandDispatcher {
4248

4349
for reg in inventory::iter::<CommandRegistration> {
4450
if reg.dispatcher == category_name {
45-
map.insert(reg.command.to_ascii_uppercase(), reg.handler);
51+
map.insert(reg.command.to_ascii_uppercase(), reg.clone());
52+
for alias in reg.aliases {
53+
map.insert(alias.to_ascii_uppercase(), reg.clone());
54+
}
4655
}
4756
}
4857

49-
Self { handlers: map }
58+
Self { commands: map }
5059
}
5160

5261
/// Look up and execute the handler function for to a given command.
@@ -59,12 +68,20 @@ impl CommandDispatcher {
5968
) -> Option<AsyncHandler<'cmd>> {
6069
let command: BoxCommand<'cmd> = Box::new(command);
6170

62-
match self.handlers.get(&command.command().to_ascii_uppercase()) {
63-
Some(handler) => handler(command),
71+
match self.commands.get(&command.command().to_ascii_uppercase()) {
72+
Some(cmd) => (cmd.handler)(command),
6473
None => {
6574
command.notify_error(CommandError::CommandNotFound(command.command().to_owned()));
6675
None
6776
}
6877
}
6978
}
79+
80+
pub fn get_command(&self, command: &str) -> Option<&CommandRegistration> {
81+
self.commands.get(&command.to_ascii_uppercase())
82+
}
83+
84+
pub fn iter_commands(&self) -> hash_map::Iter<'_, String, CommandRegistration> {
85+
self.commands.iter()
86+
}
7087
}

sable_ircd/src/server/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tokio::{
2121
};
2222

2323
use std::{
24-
collections::VecDeque,
24+
collections::{hash_map, VecDeque},
2525
sync::{Arc, Weak},
2626
time::Duration,
2727
};
@@ -116,6 +116,16 @@ impl ClientServer {
116116
self.node.name()
117117
}
118118

119+
/// Get a command from the server's dispatcher
120+
pub fn get_command(&self, cmd: &str) -> Option<&CommandRegistration> {
121+
self.command_dispatcher.get_command(cmd)
122+
}
123+
124+
/// Get a command from the server's dispatcher
125+
pub fn iter_commands(&self) -> hash_map::Iter<'_, String, CommandRegistration> {
126+
self.command_dispatcher.iter_commands()
127+
}
128+
119129
/// Submit a command action to process in the next loop iteration.
120130
#[tracing::instrument(skip(self))]
121131
pub fn add_action(&self, act: CommandAction) {

sable_macros/src/command_handler.rs

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,67 @@ use super::*;
22

33
use quote::{quote, quote_spanned};
44
use syn::{
5-
parenthesized, parse::Parse, parse_macro_input, token::In, Ident, ItemFn, LitStr, Token,
5+
parenthesized, parse::Parse, parse_macro_input, token::In, Attribute, Ident, ItemFn, LitStr,
6+
Meta, MetaNameValue, Token,
67
};
78

89
struct CommandHandlerAttr {
910
command_name: LitStr,
11+
aliases: Vec<LitStr>,
1012
dispatcher: Option<LitStr>,
13+
restricted: bool,
1114
}
1215

1316
impl Parse for CommandHandlerAttr {
1417
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1518
let command_name = input.parse()?;
16-
let dispatcher = if input.parse::<Token![,]>().is_ok() {
17-
let content;
18-
input.parse::<In>()?;
19-
let _paren = parenthesized!(content in input);
20-
Some(content.parse()?)
21-
} else {
22-
None
23-
};
19+
let mut aliases = vec![];
20+
let mut dispatcher = None;
21+
let mut restricted = false;
22+
while input.peek(Token![,]) {
23+
if !input.peek2(LitStr) {
24+
break;
25+
}
26+
let _ = input.parse::<Token![,]>();
27+
aliases.push(input.parse()?);
28+
}
29+
while input.peek(Token![,]) {
30+
let _ = input.parse::<Token![,]>()?;
31+
if input.peek(In) {
32+
let content;
33+
input.parse::<In>()?;
34+
let _paren = parenthesized!(content in input);
35+
dispatcher = Some(content.parse()?);
36+
} else if input.peek(Ident) {
37+
if input.parse::<Ident>()? == "restricted" {
38+
restricted = true;
39+
}
40+
}
41+
}
2442
Ok(Self {
2543
command_name,
44+
aliases,
2645
dispatcher,
46+
restricted,
2747
})
2848
}
2949
}
3050

51+
pub fn command_docs(attrs: &[Attribute]) -> Vec<String> {
52+
attrs
53+
.iter()
54+
.filter(|a| a.path.is_ident("doc"))
55+
.filter_map(|a| match a.parse_meta() {
56+
Ok(Meta::NameValue(MetaNameValue {
57+
lit: syn::Lit::Str(s),
58+
..
59+
})) => Some(s.value()),
60+
_ => None,
61+
})
62+
.map(|s| s.strip_prefix(' ').unwrap_or(&s).trim_end().to_owned())
63+
.collect()
64+
}
65+
3166
pub fn command_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
3267
let input = parse_macro_input!(attr as CommandHandlerAttr);
3368
let item = parse_macro_input!(item as ItemFn);
@@ -43,11 +78,24 @@ pub fn command_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
4378
}
4479
}
4580

81+
let aliases = input.aliases;
82+
for alias in &aliases {
83+
for c in alias.value().chars() {
84+
if !c.is_ascii_uppercase() {
85+
return quote_spanned!(command_name.span()=> compile_error!("Command aliases should be uppercase")).into();
86+
}
87+
}
88+
}
89+
4690
let dispatcher = match input.dispatcher {
4791
Some(name) => quote!( Some( #name ) ),
4892
None => quote!(None),
4993
};
5094

95+
let restricted = input.restricted;
96+
97+
let docs = command_docs(&item.attrs);
98+
5199
let body = if asyncness.is_none() {
52100
quote!(
53101
if let Err(e) = crate::command::plumbing::call_handler(ctx.as_ref(), &super::#name, ctx.args())
@@ -90,8 +138,11 @@ pub fn command_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
90138

91139
inventory::submit!(crate::command::CommandRegistration {
92140
command: #command_name,
141+
aliases: &[ #(#aliases),* ],
93142
dispatcher: #dispatcher,
94-
handler: call_proxy
143+
handler: call_proxy,
144+
restricted: #restricted,
145+
docs: &[ #(#docs),* ],
95146
});
96147
}
97148
).into()

0 commit comments

Comments
 (0)