1use std::{
2 collections::{BTreeMap, HashSet},
3 env, fs,
4 path::PathBuf,
5};
6
7use darling::{util::PathList, FromMeta};
8use proc_macro2::{Ident, TokenStream};
9use quote::{format_ident, quote};
10
11use crate::{
12 generate_accounts, generate_events, generate_ix_handlers, generate_ix_structs,
13 generate_typedefs, GEN_VERSION,
14};
15
16#[derive(Default, FromMeta)]
17pub struct GeneratorOptions {
18 pub idl_path: String,
20 pub skip: Option<PathList>,
22 pub zero_copy: Option<PathList>,
24 pub packed: Option<PathList>,
26}
27
28fn path_list_to_string(list: Option<&PathList>) -> HashSet<String> {
29 list.map(|el| {
30 el.iter()
31 .map(|el| el.get_ident().unwrap().to_string())
32 .collect()
33 })
34 .unwrap_or_default()
35}
36
37impl GeneratorOptions {
38 pub fn to_generator(&self) -> Generator {
39 let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
40 let path = PathBuf::from(cargo_manifest_dir).join(&self.idl_path);
41 let idl_contents = fs::read_to_string(&path).unwrap();
42 let idl: anchor_lang_idl_spec::Idl = serde_json::from_str(&idl_contents).unwrap();
43
44 let skip = path_list_to_string(self.skip.as_ref());
45 let zero_copy = path_list_to_string(self.zero_copy.as_ref());
46 let packed = path_list_to_string(self.packed.as_ref());
47
48 let all_type_names = idl
49 .accounts
50 .iter()
51 .map(|a| a.name.clone())
52 .chain(idl.types.iter().map(|t| t.name.clone()))
53 .collect::<HashSet<_>>();
54
55 let mut struct_opts: BTreeMap<String, StructOpts> = BTreeMap::new();
56 all_type_names.iter().for_each(|name| {
57 struct_opts.insert(
58 name.to_string(),
59 StructOpts {
60 skip: skip.contains(name),
61 zero_copy: zero_copy.contains(name),
62 packed: packed.contains(name),
63 },
64 );
65 });
66
67 Generator { idl, struct_opts }
68 }
69}
70
71#[derive(Clone, Copy, Debug, Default)]
72pub struct StructOpts {
73 pub skip: bool,
74 pub packed: bool,
75 pub zero_copy: bool,
76}
77
78pub struct Generator {
79 pub idl: anchor_lang_idl_spec::Idl,
80 pub struct_opts: BTreeMap<String, StructOpts>,
81}
82
83impl Generator {
84 pub fn generate_cpi_interface(&self) -> TokenStream {
85 let idl = &self.idl;
86 let program_name: Ident = format_ident!("{}", idl.metadata.name);
87
88 let accounts = generate_accounts(&idl.types, &idl.accounts, &self.struct_opts);
89 let events = generate_events(&idl.events, &idl.types, &self.struct_opts);
90 let typedefs = generate_typedefs(&idl.types, &self.struct_opts);
91 let ix_handlers = generate_ix_handlers(&idl.instructions);
92 let ix_structs = generate_ix_structs(&idl.instructions);
93
94 let docs = format!(
95 " Anchor CPI crate generated from {} v{} using [anchor-gen](https://crates.io/crates/anchor-gen) v{}.",
96 &idl.metadata.name,
97 &idl.metadata.version,
98 &GEN_VERSION.unwrap_or("unknown")
99 );
100
101 let address = idl.address.clone();
102
103 quote! {
104 use anchor_lang::prelude::*;
105
106 declare_id!(#address);
107
108 pub mod typedefs {
109 use super::*;
111 #typedefs
112 }
113
114 pub mod state {
115 use super::*;
117 #accounts
118 }
119
120 pub mod events {
121 use super::*;
123 #events
124 }
125
126 pub mod ix_accounts {
127 use super::*;
129 #ix_structs
130 }
131
132 use ix_accounts::*;
133 pub use state::*;
134 pub use typedefs::*;
135
136 #[program]
137 pub mod #program_name {
138 #![doc = #docs]
139
140 use super::*;
141 #ix_handlers
142 }
143 }
144 }
145}