typhoon_program_id_macro/
lib.rs1use {
2 cargo_manifest::Manifest,
3 heck::ToUpperCamelCase,
4 proc_macro::TokenStream,
5 proc_macro2::Span,
6 quote::{format_ident, quote, ToTokens},
7 std::env::var,
8 syn::{parse::Parse, parse_macro_input, Ident, LitStr},
9};
10
11#[proc_macro]
12pub fn program_id(item: TokenStream) -> TokenStream {
13 parse_macro_input!(item as ProgramId)
14 .to_token_stream()
15 .into()
16}
17
18struct ProgramId {
19 pub name: Ident,
20 pub id: String,
21}
22
23impl Parse for ProgramId {
24 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
25 let id: LitStr = input.parse()?;
26 let name = generate_name()?;
27
28 Ok(ProgramId {
29 id: id.value(),
30 name,
31 })
32 }
33}
34
35impl ToTokens for ProgramId {
36 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
37 let id = &self.id;
38 let name = &self.name;
39
40 quote! {
41 declare_id!(#id);
42
43 pub struct #name;
44
45 impl ProgramId for #name {
46 const ID: Address = crate::ID;
47 }
48 }
49 .to_tokens(tokens);
50 }
51}
52
53fn get_cargo_toml() -> syn::Result<String> {
54 let crate_dir = var("CARGO_MANIFEST_DIR")
55 .map_err(|_| syn::Error::new(Span::call_site(), "Not in valid rust project."))?;
56
57 Ok(format!("{crate_dir}/Cargo.toml"))
58}
59
60fn generate_name() -> syn::Result<Ident> {
61 let cargo_toml = get_cargo_toml()?;
62 let manifest = Manifest::from_path(cargo_toml)
63 .map_err(|_| syn::Error::new(Span::call_site(), "Invalid Cargo.toml"))?;
64 let package_section = manifest.package.ok_or(syn::Error::new(
65 Span::call_site(),
66 "Invalid package section",
67 ))?;
68
69 Ok(format_ident!(
70 "{}Program",
71 package_section.name.to_upper_camel_case()
72 ))
73}