1use anyhow::{anyhow, Result};
2use convert_case::{Case, Casing};
3use near_sdk_abi_impl::{generate_ext, read_abi};
4use quote::{format_ident, quote};
5use std::fs::File;
6use std::io::Write;
7use std::path::PathBuf;
8use std::{env, fs};
9
10pub use near_sdk_abi_macros::near_abi_ext;
11
12pub struct AbiFile {
13 pub path: PathBuf,
15 pub contract_name: Option<String>,
18 pub mod_name: Option<String>,
21}
22
23impl AbiFile {
24 pub fn new(path: impl Into<PathBuf>) -> Self {
25 AbiFile {
26 path: path.into(),
27 contract_name: None,
28 mod_name: None,
29 }
30 }
31}
32
33#[derive(Default)]
35pub struct Generator {
36 out_dir: Option<PathBuf>,
37 abis: Vec<AbiFile>,
38}
39
40impl Generator {
41 pub fn new(out_dir: PathBuf) -> Self {
42 Generator {
43 out_dir: Some(out_dir),
44 abis: vec![],
45 }
46 }
47
48 pub fn file(mut self, abi_file: AbiFile) -> Self {
49 self.abis.push(abi_file);
50 self
51 }
52
53 pub fn generate(self) -> Result<()> {
54 let target: PathBuf = self.out_dir.map(Ok).unwrap_or_else(|| {
55 env::var_os("OUT_DIR")
56 .ok_or_else(|| anyhow!("OUT_DIR environment variable is not set"))
57 .map(Into::into)
58 })?;
59 fs::create_dir_all(&target)?;
60
61 for AbiFile {
62 path,
63 contract_name,
64 mod_name,
65 } in self.abis
66 {
67 let abi_path_no_ext = path.with_extension("");
68 let abi_filename = abi_path_no_ext
69 .file_name()
70 .ok_or_else(|| anyhow!("{:?} is not a valid ABI path", path.display()))?;
71 let rust_path = target.join(abi_filename).with_extension("rs");
72
73 let near_abi = read_abi(&path);
74
75 let contract_name = contract_name
76 .as_ref()
77 .map(|n| format_ident!("{}", n))
78 .or_else(|| {
79 near_abi
80 .metadata
81 .name
82 .clone()
83 .map(|n| format_ident!("Ext{}", n.to_case(Case::UpperCamel)))
84 })
85 .ok_or_else(|| {
86 anyhow!(
87 "ABI file '{}' does not contain a contract name. Please supply the name via `file_with_name`.",
88 path.display()
89 )
90 })?;
91
92 let token_stream = generate_ext(
93 near_abi,
94 contract_name,
95 mod_name.map(|n| format_ident!("{}", n)),
96 );
97 let token_stream = quote! {
98 #![allow(unused_imports)]
99 use serde::{Deserialize, Serialize};
100 #token_stream
101 };
102 let syntax_tree = syn::parse_file(&token_stream.to_string()).unwrap();
103 let formatted = prettyplease::unparse(&syntax_tree);
104
105 let mut rust_file = File::create(rust_path)?;
106 write!(rust_file, "{}", formatted)?;
107 }
108
109 Ok(())
110 }
111}