multiversx_sc_meta_lib/contract/generate_snippets/
snippet_crate_gen.rs

1use colored::Colorize;
2use std::{
3    fs::{self, File, OpenOptions},
4    io::Write,
5    path::{Path, PathBuf},
6};
7
8use crate::version_history;
9
10static SNIPPETS_SOURCE_FILE_NAME: &str = "interactor_main.rs";
11static LIB_SOURCE_FILE_NAME: &str = "interact.rs";
12static SC_CONFIG_FILE_NAME: &str = "sc-config.toml";
13static CONFIG_TOML_PATH: &str = "config.toml";
14static CONFIG_SOURCE_FILE_NAME: &str = "config.rs";
15static INTERACTOR_CS_TEST_FILE_NAME: &str = "interact_cs_tests.rs";
16static INTERACTOR_TEST_FILE_NAME: &str = "interact_tests.rs";
17
18pub(crate) fn create_snippets_folder(snippets_folder_path: &PathBuf) {
19    let _ = fs::create_dir(snippets_folder_path);
20}
21
22pub(crate) fn create_snippets_gitignore(snippets_folder_path: &Path, overwrite: bool) {
23    let gitignore_path = snippets_folder_path.join(".gitignore");
24    let mut file = if overwrite {
25        File::create(&gitignore_path).unwrap()
26    } else {
27        match File::options()
28            .create_new(true)
29            .write(true)
30            .open(&gitignore_path)
31        {
32            Ok(f) => f,
33            Err(_) => return,
34        }
35    };
36
37    writeln!(
38        &mut file,
39        "# Pem files are used for interactions, but shouldn't be committed
40*.pem"
41    )
42    .unwrap();
43}
44
45pub(crate) fn create_snippets_cargo_toml(
46    snippets_folder_path: &Path,
47    contract_crate_name: &str,
48    overwrite: bool,
49) {
50    let contract_deps = contract_crate_name.replace("_", "-");
51    let cargo_toml_path = snippets_folder_path.join("Cargo.toml");
52    let mut file = if overwrite {
53        File::create(&cargo_toml_path).unwrap()
54    } else {
55        match File::options()
56            .create_new(true)
57            .write(true)
58            .open(&cargo_toml_path)
59        {
60            Ok(f) => f,
61            Err(_) => return,
62        }
63    };
64
65    let last_release_version = &version_history::LAST_VERSION;
66
67    writeln!(
68        &mut file,
69        r#"[package]
70name = "rust-interact"
71version = "0.0.0"
72authors = ["you"]
73edition = "2024"
74publish = false
75
76[[bin]]
77name = "rust-interact"
78path = "src/{SNIPPETS_SOURCE_FILE_NAME}"
79
80[lib]
81path = "src/{LIB_SOURCE_FILE_NAME}"
82
83[dependencies.{contract_deps}]
84path = ".."
85
86[dependencies.multiversx-sc-snippets]
87version = "{last_release_version}"
88
89[dependencies.multiversx-sc]
90version = "{last_release_version}"
91
92[dependencies]
93clap = {{ version = "4.4.7", features = ["derive"] }}
94serde = {{ version = "1.0", features = ["derive"] }}
95toml = "0.9"
96
97[features]
98chain-simulator-tests = []
99"#
100    )
101    .unwrap();
102}
103
104pub(crate) fn create_src_folder(snippets_folder_path: &Path) {
105    // returns error if folder already exists, so we ignore the result
106    let src_folder_path = snippets_folder_path.join("src");
107    let _ = fs::create_dir(src_folder_path);
108}
109
110#[must_use]
111pub(crate) fn create_and_get_lib_file(snippets_folder_path: &Path, overwrite: bool) -> File {
112    let lib_path = snippets_folder_path.join("src").join(LIB_SOURCE_FILE_NAME);
113    if overwrite {
114        File::create(&lib_path).unwrap()
115    } else {
116        match File::options().create_new(true).write(true).open(&lib_path) {
117            Ok(f) => f,
118            Err(_) => {
119                println!(
120                    "{}",
121                    format!(
122                        "{lib_path:#?} file already exists, --overwrite option was not provided",
123                    )
124                    .yellow()
125                );
126                File::options().write(true).open(&lib_path).unwrap()
127            }
128        }
129    }
130}
131
132pub(crate) fn create_main_file(snippets_folder_path: &Path, contract_crate_name: &str) {
133    let lib_path = snippets_folder_path
134        .join("src")
135        .join(SNIPPETS_SOURCE_FILE_NAME);
136
137    let mut file = File::create(lib_path).unwrap();
138
139    writeln!(
140        &mut file,
141        r#"
142use multiversx_sc_snippets::imports::*;
143use rust_interact::{contract_crate_name}_cli;
144
145#[tokio::main]
146async fn main() {{
147    {contract_crate_name}_cli().await;
148}}  
149"#
150    )
151    .unwrap();
152}
153
154pub(crate) fn create_test_folder_and_get_files(snippets_folder_path: &Path) -> (File, File) {
155    let folder_path = snippets_folder_path.join("tests");
156
157    if !Path::new(&folder_path).exists() {
158        fs::create_dir_all(&folder_path).expect("Failed to create tests directory");
159    }
160
161    let interactor_file_path = folder_path.join(INTERACTOR_TEST_FILE_NAME);
162    let interactor_cs_file_path = folder_path.join(INTERACTOR_CS_TEST_FILE_NAME);
163
164    let interactor_file =
165        File::create(interactor_file_path).expect("Failed to create interact_tests.rs file");
166    let interactor_cs_file =
167        File::create(interactor_cs_file_path).expect("Failed to create interact_cs_tests.rs file");
168
169    (interactor_file, interactor_cs_file)
170}
171
172pub(crate) fn create_sc_config_file(overwrite: bool, contract_crate_name: &str) {
173    let sc_config_path = Path::new("..").join(SC_CONFIG_FILE_NAME);
174    let proxy_name = format!("{}_proxy.rs", contract_crate_name.replace("-", "_"),);
175
176    // check if the file should be overwritten or if it already exists
177    let mut file = if overwrite || !sc_config_path.exists() {
178        File::create(sc_config_path).unwrap()
179    } else {
180        // file already exists
181        let mut file = OpenOptions::new()
182            .read(true)
183            .append(true)
184            .open(&sc_config_path)
185            .unwrap();
186
187        if file_contains_proxy_path(&sc_config_path, &proxy_name).unwrap_or(false) {
188            return;
189        }
190
191        writeln!(&mut file).unwrap();
192
193        file
194    };
195
196    // will be deserialized into a PathBuf, which normalizes the path depending on the platform
197    // when deserializing from toml, backwards slashes are not allowed
198    let full_proxy_entry = r#"[[proxy]]
199path = "interactor/src"#;
200
201    // write full proxy toml entry to the file
202    writeln!(&mut file, "{full_proxy_entry}/{proxy_name}\"").unwrap();
203}
204
205pub(crate) fn create_config_toml_file(snippets_folder_path: &Path) {
206    let config_path = snippets_folder_path.join(CONFIG_TOML_PATH);
207    let mut file = File::create(config_path).unwrap();
208
209    writeln!(
210        &mut file,
211        r#"# chain_type = 'simulator'
212# gateway_uri = 'http://localhost:8085'
213
214chain_type = 'real'
215gateway_uri = 'https://devnet-gateway.multiversx.com'"#
216    )
217    .unwrap();
218}
219
220pub(crate) fn create_config_rust_file(snippets_folder_path: &Path) -> File {
221    let lib_path = snippets_folder_path
222        .join("src")
223        .join(CONFIG_SOURCE_FILE_NAME);
224
225    File::create(lib_path).unwrap()
226}
227
228fn file_contains_proxy_path(file_path: &PathBuf, proxy_name: &str) -> std::io::Result<bool> {
229    let file_content = fs::read_to_string(file_path)?;
230
231    let proxy_path = Path::new("interactor").join("src").join(proxy_name);
232    let proxy_entry = format!("path = \"{}\"", &proxy_path.to_string_lossy());
233
234    Ok(file_content.contains(&proxy_entry))
235}