Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:07:15.844Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

derive_action.rs

212 lines · 7.4 KB · rust
1use crate::register_action::generate_register_action;
2use proc_macro::TokenStream;
3use proc_macro2::Ident;
4use quote::quote;
5use syn::{Data, DeriveInput, LitStr, Token, parse::ParseStream};
6
7pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
8    let input = syn::parse_macro_input!(input as DeriveInput);
9
10    let struct_name = &input.ident;
11    let mut name_argument = None;
12    let mut deprecated_aliases = Vec::new();
13    let mut no_json = false;
14    let mut no_register = false;
15    let mut namespace = None;
16    let mut deprecated = None;
17    let mut doc_str: Option<String> = None;
18
19    /*
20    *
21    * #[action()]
22    * Struct Foo {
23    *  bar: bool // is bar considered an attribute
24    }
25    */
26    for attr in &input.attrs {
27        if attr.path().is_ident("action") {
28            attr.parse_nested_meta(|meta| {
29                if meta.path.is_ident("name") {
30                    if name_argument.is_some() {
31                        return Err(meta.error("'name' argument specified multiple times"));
32                    }
33                    meta.input.parse::<Token![=]>()?;
34                    let lit: LitStr = meta.input.parse()?;
35                    name_argument = Some(lit.value());
36                } else if meta.path.is_ident("namespace") {
37                    if namespace.is_some() {
38                        return Err(meta.error("'namespace' argument specified multiple times"));
39                    }
40                    meta.input.parse::<Token![=]>()?;
41                    let ident: Ident = meta.input.parse()?;
42                    namespace = Some(ident.to_string());
43                } else if meta.path.is_ident("no_json") {
44                    if no_json {
45                        return Err(meta.error("'no_json' argument specified multiple times"));
46                    }
47                    no_json = true;
48                } else if meta.path.is_ident("no_register") {
49                    if no_register {
50                        return Err(meta.error("'no_register' argument specified multiple times"));
51                    }
52                    no_register = true;
53                } else if meta.path.is_ident("deprecated_aliases") {
54                    if !deprecated_aliases.is_empty() {
55                        return Err(
56                            meta.error("'deprecated_aliases' argument specified multiple times")
57                        );
58                    }
59                    meta.input.parse::<Token![=]>()?;
60                    // Parse array of string literals
61                    let content;
62                    syn::bracketed!(content in meta.input);
63                    let aliases = content.parse_terminated(
64                        |input: ParseStream| input.parse::<LitStr>(),
65                        Token![,],
66                    )?;
67                    deprecated_aliases.extend(aliases.into_iter().map(|lit| lit.value()));
68                } else if meta.path.is_ident("deprecated") {
69                    if deprecated.is_some() {
70                        return Err(meta.error("'deprecated' argument specified multiple times"));
71                    }
72                    meta.input.parse::<Token![=]>()?;
73                    let lit: LitStr = meta.input.parse()?;
74                    deprecated = Some(lit.value());
75                } else {
76                    return Err(meta.error(format!(
77                        "'{:?}' argument not recognized, expected \
78                        'namespace', 'no_json', 'no_register, 'deprecated_aliases', or 'deprecated'",
79                        meta.path
80                    )));
81                }
82                Ok(())
83            })
84            .unwrap_or_else(|e| panic!("in #[action] attribute: {}", e));
85        } else if attr.path().is_ident("doc") {
86            use syn::{Expr::Lit, ExprLit, Lit::Str, Meta, MetaNameValue};
87            if let Meta::NameValue(MetaNameValue {
88                value:
89                    Lit(ExprLit {
90                        lit: Str(ref lit_str),
91                        ..
92                    }),
93                ..
94            }) = attr.meta
95            {
96                let doc = lit_str.value();
97                let doc_str = doc_str.get_or_insert_default();
98                doc_str.push_str(doc.trim());
99                doc_str.push('\n');
100            }
101        }
102    }
103
104    let name = name_argument.unwrap_or_else(|| struct_name.to_string());
105
106    if name.contains("::") {
107        panic!(
108            "in #[action] attribute: `name = \"{name}\"` must not contain `::`, \
109            also specify `namespace` instead"
110        );
111    }
112
113    let full_name = if let Some(namespace) = namespace {
114        format!("{namespace}::{name}")
115    } else {
116        name
117    };
118
119    let is_unit_struct = matches!(&input.data, Data::Struct(data) if data.fields.is_empty());
120
121    let build_fn_body = if no_json {
122        let error_msg = format!("{} cannot be built from JSON", full_name);
123        quote! { Err(gpui::private::anyhow::anyhow!(#error_msg)) }
124    } else if is_unit_struct {
125        quote! { Ok(Box::new(Self)) }
126    } else {
127        quote! { Ok(Box::new(gpui::private::serde_json::from_value::<Self>(_value)?)) }
128    };
129
130    let json_schema_fn_body = if no_json || is_unit_struct {
131        quote! { None }
132    } else {
133        quote! { Some(<Self as gpui::private::schemars::JsonSchema>::json_schema(_generator)) }
134    };
135
136    let deprecated_aliases_fn_body = if deprecated_aliases.is_empty() {
137        quote! { &[] }
138    } else {
139        let aliases = deprecated_aliases.iter();
140        quote! { &[#(#aliases),*] }
141    };
142
143    let deprecation_fn_body = if let Some(message) = deprecated {
144        quote! { Some(#message) }
145    } else {
146        quote! { None }
147    };
148
149    let documentation_fn_body = if let Some(doc) = doc_str {
150        let doc = doc.trim();
151        quote! { Some(#doc) }
152    } else {
153        quote! { None }
154    };
155
156    let registration = if no_register {
157        quote! {}
158    } else {
159        generate_register_action(struct_name)
160    };
161
162    TokenStream::from(quote! {
163        #registration
164
165        impl gpui::Action for #struct_name {
166            fn name(&self) -> &'static str {
167                #full_name
168            }
169
170            fn name_for_type() -> &'static str
171            where
172                Self: Sized
173            {
174                #full_name
175            }
176
177            fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
178                action
179                    .as_any()
180                    .downcast_ref::<Self>()
181                    .map_or(false, |a| self == a)
182            }
183
184            fn boxed_clone(&self) -> Box<dyn gpui::Action> {
185                Box::new(self.clone())
186            }
187
188            fn build(_value: gpui::private::serde_json::Value) -> gpui::Result<Box<dyn gpui::Action>> {
189                #build_fn_body
190            }
191
192            fn action_json_schema(
193                _generator: &mut gpui::private::schemars::SchemaGenerator,
194            ) -> Option<gpui::private::schemars::Schema> {
195                #json_schema_fn_body
196            }
197
198            fn deprecated_aliases() -> &'static [&'static str] {
199                #deprecated_aliases_fn_body
200            }
201
202            fn deprecation_message() -> Option<&'static str> {
203                #deprecation_fn_body
204            }
205
206            fn documentation() -> Option<&'static str> {
207                #documentation_fn_body
208            }
209        }
210    })
211}
212
Served at tenant.openagents/omega Member data and write actions are omitted.