unc_abi_client/
lib.rs

1use std::fs::File;
2use std::io::Write;
3use std::path::PathBuf;
4use std::{env, fs};
5
6use anyhow::{anyhow, Result};
7use convert_case::{Case, Casing};
8use quote::format_ident;
9
10use unc_abi_client_impl::{generate_abi_client, read_abi};
11pub use unc_abi_client_macros::generate;
12
13/// Configuration options for ABI code generation.
14#[derive(Default)]
15pub struct Generator {
16    pub out_dir: Option<PathBuf>,
17    abis: Vec<(PathBuf, Option<String>)>,
18}
19
20impl Generator {
21    pub fn new(out_dir: PathBuf) -> Self {
22        Generator {
23            out_dir: Some(out_dir),
24            abis: vec![],
25        }
26    }
27
28    pub fn file(mut self, path: impl Into<PathBuf>) -> Self {
29        self.abis.push((path.into(), None));
30        self
31    }
32
33    pub fn file_with_name(mut self, path: impl Into<PathBuf>, name: String) -> Self {
34        self.abis.push((path.into(), Some(name)));
35        self
36    }
37
38    pub fn generate(self) -> Result<()> {
39        let target: PathBuf = self.out_dir.clone().map(Ok).unwrap_or_else(|| {
40            env::var_os("OUT_DIR")
41                .ok_or_else(|| anyhow!("OUT_DIR environment variable is not set"))
42                .map(Into::into)
43        })?;
44        fs::create_dir_all(&target)?;
45
46        for (abi_path, name) in self.abis {
47            let abi_path_no_ext = abi_path.with_extension("");
48            let abi_filename = abi_path_no_ext
49                .file_name()
50                .ok_or_else(|| anyhow!("{:?} is not a valid ABI path", &abi_path))?;
51            let rust_path = target.join(abi_filename).with_extension("rs");
52            let unc_abi = read_abi(&abi_path);
53
54            let contract_name = name
55                .as_ref()
56                .map(|n| format_ident!("{}", n))
57                .or_else(|| {
58                    unc_abi
59                        .metadata
60                        .name
61                        .clone()
62                        .map(|n| format_ident!("{}Client", n.to_case(Case::UpperCamel)))
63                })
64                .ok_or_else(|| {
65                    anyhow!(
66                        "ABI file '{}' does not contain a contract name. Please supply the name via `file_with_name`.",
67                        abi_path.display()
68                    )
69                })?;
70
71            let token_stream = generate_abi_client(unc_abi, contract_name);
72            let syntax_tree = syn::parse_file(&token_stream.to_string()).unwrap();
73            let formatted = prettyplease::unparse(&syntax_tree);
74
75            let mut rust_file = File::create(rust_path)?;
76            write!(rust_file, "{}", formatted)?;
77        }
78
79        Ok(())
80    }
81}