Skip to content

Commit 4ffa681

Browse files
committed
feat: added macro for auto registration
1 parent de698d2 commit 4ffa681

9 files changed

Lines changed: 703 additions & 0 deletions

File tree

rust/hercules-macros/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "hercules-macros"
3+
description = "Attribute macros for the hercules action SDK"
4+
version.workspace = true
5+
edition.workspace = true
6+
license.workspace = true
7+
repository.workspace = true
8+
9+
[lib]
10+
proc-macro = true
11+
12+
[dependencies]
13+
syn = { version = "2", features = ["full", "extra-traits"] }
14+
quote = "1"
15+
proc-macro2 = "1"
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
use syn::ItemStruct;
4+
5+
use crate::parse::{hercules_path, translation_vec, AttrArgs};
6+
7+
pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
8+
let item_struct: ItemStruct = syn::parse2(item)?;
9+
let args = AttrArgs::parse(attr)?;
10+
let hercules = hercules_path();
11+
12+
let identifier = args.required_string("identifier")?;
13+
let name = translation_vec(&args.translations("name")?, &hercules);
14+
let display_message = translation_vec(&args.translations("display_message")?, &hercules);
15+
let alias = translation_vec(&args.translations("alias")?, &hercules);
16+
let generic_keys: Vec<String> = args.string_list("generic_keys")?;
17+
18+
let ident = &item_struct.ident;
19+
Ok(quote! {
20+
#item_struct
21+
22+
impl #hercules::DataType for #ident {
23+
fn meta() -> #hercules::DataTypeMeta {
24+
#hercules::DataTypeMeta {
25+
identifier: #identifier.to_string(),
26+
name: #name,
27+
display_message: #display_message,
28+
alias: #alias,
29+
generic_keys: vec![#(#generic_keys.to_string()),*],
30+
}
31+
}
32+
}
33+
34+
#hercules::inventory::submit! {
35+
#hercules::Registration(|action| {
36+
action.register_data_type::<#ident>()
37+
.unwrap_or_else(|e| panic!("failed to register data type {}: {e}", stringify!(#ident)));
38+
})
39+
}
40+
})
41+
}

rust/hercules-macros/src/event.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::quote;
3+
use syn::ItemStruct;
4+
5+
use crate::items::setting_tokens;
6+
use crate::parse::{
7+
hercules_path, optional_string, optional_translation_vec, take_repeated, AttrArgs,
8+
};
9+
10+
pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
11+
let mut item_struct: ItemStruct = syn::parse2(item)?;
12+
let args = AttrArgs::parse(attr)?;
13+
let hercules = hercules_path();
14+
15+
let base = args.path("base")?.ok_or_else(|| {
16+
syn::Error::new(
17+
Span::call_site(),
18+
"missing required `base = SomeRuntimeEvent`",
19+
)
20+
})?;
21+
22+
let settings = take_repeated(&mut item_struct, "setting")?
23+
.iter()
24+
.map(|s| setting_tokens(s, &hercules))
25+
.collect::<syn::Result<Vec<_>>>()?;
26+
27+
let identifier = optional_string(args.string("identifier"))?;
28+
let signature = optional_string(args.string("signature"))?;
29+
let name = optional_translation_vec(&args, "name", &hercules)?;
30+
let description = optional_translation_vec(&args, "description", &hercules)?;
31+
let documentation = optional_translation_vec(&args, "documentation", &hercules)?;
32+
let display_message = optional_translation_vec(&args, "display_message", &hercules)?;
33+
let alias = optional_translation_vec(&args, "alias", &hercules)?;
34+
let display_icon = optional_string(args.string("display_icon"))?;
35+
let editable = if args.flag("editable") {
36+
quote!(Some(true))
37+
} else {
38+
quote!(None)
39+
};
40+
41+
let ident = &item_struct.ident;
42+
Ok(quote! {
43+
#item_struct
44+
45+
impl #hercules::Event for #ident {
46+
type Base = #base;
47+
48+
fn meta() -> #hercules::EventMeta {
49+
#hercules::EventMeta {
50+
identifier: #identifier,
51+
signature: #signature,
52+
settings: vec![#(#settings),*],
53+
editable: #editable,
54+
name: #name,
55+
description: #description,
56+
documentation: #documentation,
57+
display_message: #display_message,
58+
alias: #alias,
59+
display_icon: #display_icon,
60+
}
61+
}
62+
}
63+
64+
#hercules::inventory::submit! {
65+
#hercules::Registration(|action| {
66+
action.register_event::<#ident>()
67+
.unwrap_or_else(|e| panic!("failed to register event {}: {e}", stringify!(#ident)));
68+
})
69+
}
70+
})
71+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use proc_macro2::{Span, TokenStream};
2+
use quote::quote;
3+
use syn::ItemStruct;
4+
5+
use crate::items::parameter_tokens;
6+
use crate::parse::{
7+
hercules_path, optional_string, optional_translation_vec, take_repeated, AttrArgs,
8+
};
9+
10+
pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
11+
let mut item_struct: ItemStruct = syn::parse2(item)?;
12+
let args = AttrArgs::parse(attr)?;
13+
let hercules = hercules_path();
14+
15+
let base = args.path("base")?.ok_or_else(|| {
16+
syn::Error::new(
17+
Span::call_site(),
18+
"missing required `base = SomeRuntimeFunction`",
19+
)
20+
})?;
21+
22+
let parameters = take_repeated(&mut item_struct, "parameter")?
23+
.iter()
24+
.map(|p| parameter_tokens(p, &hercules))
25+
.collect::<syn::Result<Vec<_>>>()?;
26+
27+
let identifier = optional_string(args.string("identifier"))?;
28+
let signature = optional_string(args.string("signature"))?;
29+
let name = optional_translation_vec(&args, "name", &hercules)?;
30+
let description = optional_translation_vec(&args, "description", &hercules)?;
31+
let documentation = optional_translation_vec(&args, "documentation", &hercules)?;
32+
let deprecation_message = optional_translation_vec(&args, "deprecation_message", &hercules)?;
33+
let display_message = optional_translation_vec(&args, "display_message", &hercules)?;
34+
let alias = optional_translation_vec(&args, "alias", &hercules)?;
35+
let display_icon = optional_string(args.string("display_icon"))?;
36+
let design = optional_string(args.string("design"))?;
37+
// Presence-only: this overrides `throws_error` to `true`; to leave it
38+
// unset (inherit the base), simply omit the flag.
39+
let throws_error = if args.flag("throws_error") {
40+
quote!(Some(true))
41+
} else {
42+
quote!(None)
43+
};
44+
45+
let ident = &item_struct.ident;
46+
Ok(quote! {
47+
#item_struct
48+
49+
impl #hercules::Function for #ident {
50+
type Base = #base;
51+
52+
fn meta() -> #hercules::FunctionMeta {
53+
#hercules::FunctionMeta {
54+
identifier: #identifier,
55+
signature: #signature,
56+
name: #name,
57+
description: #description,
58+
documentation: #documentation,
59+
deprecation_message: #deprecation_message,
60+
display_message: #display_message,
61+
alias: #alias,
62+
display_icon: #display_icon,
63+
design: #design,
64+
throws_error: #throws_error,
65+
parameters: vec![#(#parameters),*],
66+
}
67+
}
68+
}
69+
70+
// `Function` carries no instance/state (dispatch always resolves
71+
// through `Base`), so unlike `runtime_function` this needs no
72+
// `manual` escape hatch — it's always safe to auto-register.
73+
#hercules::inventory::submit! {
74+
#hercules::Registration(|action| {
75+
action.register_function::<#ident>()
76+
.unwrap_or_else(|e| panic!("failed to register function {}: {e}", stringify!(#ident)));
77+
})
78+
}
79+
})
80+
}

rust/hercules-macros/src/items.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//! Token builders for the two repeatable child items:
2+
//! `#[parameter(...)]` (functions) and `#[setting(...)]` (events).
3+
4+
use proc_macro2::TokenStream;
5+
use quote::quote;
6+
7+
use crate::parse::{translation_vec, AttrArgs};
8+
9+
pub fn parameter_tokens(args: &AttrArgs, hercules: &TokenStream) -> syn::Result<TokenStream> {
10+
let runtime_name = args.required_string("runtime_name")?;
11+
let runtime_definition_name = match args.string("runtime_definition_name")? {
12+
Some(s) => quote!(Some(#s.to_string())),
13+
None => quote!(None),
14+
};
15+
let name = translation_vec(&args.translations("name")?, hercules);
16+
let description = translation_vec(&args.translations("description")?, hercules);
17+
let documentation = translation_vec(&args.translations("documentation")?, hercules);
18+
let default_value = match args.value_tokens("default_value")? {
19+
Some(v) => quote!(Some(#hercules::serde_json::json!(#v))),
20+
None => quote!(None),
21+
};
22+
let hidden = args.flag("hidden");
23+
let optional = args.flag("optional");
24+
25+
Ok(quote! {
26+
#hercules::ParameterMeta {
27+
runtime_name: #runtime_name.to_string(),
28+
runtime_definition_name: #runtime_definition_name,
29+
name: #name,
30+
description: #description,
31+
documentation: #documentation,
32+
default_value: #default_value,
33+
hidden: #hidden,
34+
optional: #optional,
35+
}
36+
})
37+
}
38+
39+
pub fn setting_tokens(args: &AttrArgs, hercules: &TokenStream) -> syn::Result<TokenStream> {
40+
let identifier = args.required_string("identifier")?;
41+
let unique = match args.string("unique")?.as_deref() {
42+
Some("project") => quote!(#hercules::UniquenessScope::Project),
43+
_ => quote!(#hercules::UniquenessScope::None),
44+
};
45+
let linked: Vec<String> = args.string_list("linked_data_type_identifiers")?;
46+
let name = translation_vec(&args.translations("name")?, hercules);
47+
let description = translation_vec(&args.translations("description")?, hercules);
48+
let default_value = match args.value_tokens("default_value")? {
49+
Some(v) => quote!(Some(#hercules::serde_json::json!(#v))),
50+
None => quote!(None),
51+
};
52+
let hidden = args.flag("hidden");
53+
let optional = args.flag("optional");
54+
55+
Ok(quote! {
56+
#hercules::EventSettingMeta {
57+
identifier: #identifier.to_string(),
58+
unique: #unique,
59+
linked_data_type_identifiers: vec![#(#linked.to_string()),*],
60+
default_value: #default_value,
61+
name: #name,
62+
description: #description,
63+
hidden: #hidden,
64+
optional: #optional,
65+
}
66+
})
67+
}

rust/hercules-macros/src/lib.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//! Attribute macros that replace the TS SDK's `reflect-metadata` decorators
2+
//! (`@Identifier`, `@Signature`, `@Parameter`, ...) with compile-time code
3+
//! generation: each macro parses its arguments and any repeated
4+
//! `#[parameter(...)]`/`#[setting(...)]` helper attributes on the annotated
5+
//! struct, then emits a `meta()` associated function implementing the
6+
//! corresponding `hercules` trait. No runtime reflection, no metadata
7+
//! registry — the definition is just a value the macro builds once, at
8+
//! compile time.
9+
10+
mod data_type;
11+
mod event;
12+
mod function;
13+
mod items;
14+
mod parse;
15+
mod runtime_event;
16+
mod runtime_function;
17+
18+
use proc_macro::TokenStream;
19+
20+
fn run(
21+
f: impl FnOnce(
22+
proc_macro2::TokenStream,
23+
proc_macro2::TokenStream,
24+
) -> syn::Result<proc_macro2::TokenStream>,
25+
attr: TokenStream,
26+
item: TokenStream,
27+
) -> TokenStream {
28+
f(attr.into(), item.into())
29+
.unwrap_or_else(syn::Error::into_compile_error)
30+
.into()
31+
}
32+
33+
/// Declares a function Aquila can invoke. See the crate-level docs on
34+
/// `hercules::RuntimeFunction` for the full attribute grammar.
35+
#[proc_macro_attribute]
36+
pub fn runtime_function(attr: TokenStream, item: TokenStream) -> TokenStream {
37+
run(runtime_function::expand, attr, item)
38+
}
39+
40+
/// Declares a public [`hercules::Function`] that overrides metadata on top
41+
/// of a `base = SomeRuntimeFunction`.
42+
#[proc_macro_attribute]
43+
pub fn function(attr: TokenStream, item: TokenStream) -> TokenStream {
44+
run(function::expand, attr, item)
45+
}
46+
47+
/// Declares a data type derived from the struct's `schemars::JsonSchema` impl.
48+
#[proc_macro_attribute]
49+
pub fn data_type(attr: TokenStream, item: TokenStream) -> TokenStream {
50+
run(data_type::expand, attr, item)
51+
}
52+
53+
/// Declares an internal event/flow-type an action can fire.
54+
#[proc_macro_attribute]
55+
pub fn runtime_event(attr: TokenStream, item: TokenStream) -> TokenStream {
56+
run(runtime_event::expand, attr, item)
57+
}
58+
59+
/// Declares a public [`hercules::Event`] that overrides metadata on top of a
60+
/// `base = SomeRuntimeEvent`.
61+
#[proc_macro_attribute]
62+
pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
63+
run(event::expand, attr, item)
64+
}

0 commit comments

Comments
 (0)