loam_cli/commands/
init.rs1use clap::Parser;
2use rust_embed::{EmbeddedFile, RustEmbed};
3use soroban_cli::commands::contract::init as soroban_init;
4use std::{
5 fs::{self, create_dir_all, metadata, read_to_string, remove_dir_all, write, Metadata},
6 io,
7 path::{Path, PathBuf},
8};
9use toml_edit::{DocumentMut, TomlError};
10
11const FRONTEND_TEMPLATE: &str = "https://github.com/loambuild/frontend";
12
13#[derive(RustEmbed)]
14#[folder = "./src/examples/soroban/core"]
15struct ExampleCore;
16
17#[derive(RustEmbed)]
18#[folder = "./src/examples/soroban/status_message"]
19struct ExampleStatusMessage;
20
21#[derive(Parser, Debug, Clone)]
23pub struct Cmd {
24 pub project_path: PathBuf,
26 #[arg(default_value = "loam-example")]
28 pub name: String,
29}
30#[derive(thiserror::Error, Debug)]
32pub enum Error {
33 #[error("Io error: {0}")]
34 IoError(#[from] io::Error),
35 #[error("Soroban init error: {0}")]
36 SorobanInitError(#[from] soroban_init::Error),
37 #[error("Failed to convert bytes to string: {0}")]
38 ConverBytesToStringErr(#[from] std::str::Utf8Error),
39 #[error("Failed to parse toml file: {0}")]
40 TomlParseError(#[from] TomlError),
41}
42
43impl Cmd {
44 #[allow(clippy::unused_self)]
53 pub fn run(&self) -> Result<(), Error> {
54 soroban_init::Cmd {
58 project_path: self.project_path.to_string_lossy().to_string(),
59 name: self.name.clone(),
60 with_example: None,
61 frontend_template: Some(FRONTEND_TEMPLATE.to_string()),
62 overwrite: true,
63 }
64 .run(&soroban_cli::commands::global::Args::default())?;
65
66 remove_dir_all(self.project_path.join("contracts/hello_world/")).map_err(|e| {
68 eprintln!("Error removing directory");
69 e
70 })?;
71
72 copy_example_contracts(&self.project_path)?;
73 rename_cargo_toml_remove(&self.project_path, "core")?;
74 rename_cargo_toml_remove(&self.project_path, "status_message")?;
75 update_workspace_cargo_toml(&self.project_path.join("Cargo.toml"))?;
76 Ok(())
77 }
78}
79
80fn update_workspace_cargo_toml(cargo_path: &Path) -> Result<(), Error> {
82 let cargo_toml_str = read_to_string(cargo_path).map_err(|e| {
83 eprintln!("Error reading Cargo.toml file in: {cargo_path:?}");
84 e
85 })?;
86
87 let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#)
88 .unwrap()
89 .replace_all(
90 cargo_toml_str.as_str(),
91 r#"loam-sdk = "0.6.12"
92loam-subcontract-core = "0.7.5""#,
93 );
94
95 let doc = cargo_toml_str.parse::<DocumentMut>().map_err(|e| {
96 eprintln!("Error parsing Cargo.toml file in: {cargo_path:?}");
97 e
98 })?;
99
100 write(cargo_path, doc.to_string()).map_err(|e| {
101 eprintln!("Error writing to Cargo.toml file in: {cargo_path:?}");
102 e
103 })?;
104
105 Ok(())
106}
107
108fn copy_example_contracts(to: &Path) -> Result<(), Error> {
109 for item in ExampleCore::iter() {
110 copy_file(
111 &to.join("contracts/core"),
112 item.as_ref(),
113 ExampleCore::get(&item),
114 )?;
115 }
116 for item in ExampleStatusMessage::iter() {
117 copy_file(
118 &to.join("contracts/status_message"),
119 item.as_ref(),
120 ExampleStatusMessage::get(&item),
121 )?;
122 }
123
124 Ok(())
125}
126
127fn copy_file(
128 example_path: &Path,
129 filename: &str,
130 embedded_file: Option<EmbeddedFile>,
131) -> Result<(), Error> {
132 let to = example_path.join(filename);
133 if file_exists(&to) {
134 println!(
135 "ℹ️ Skipped creating {} as it already exists",
136 &to.to_string_lossy()
137 );
138 return Ok(());
139 }
140 create_dir_all(to.parent().expect("invalid path")).map_err(|e| {
141 eprintln!("Error creating directory path for: {to:?}");
142 e
143 })?;
144
145 let Some(embedded_file) = embedded_file else {
146 println!("⚠️ Failed to read file: {filename}");
147 return Ok(());
148 };
149
150 let file_contents = std::str::from_utf8(embedded_file.data.as_ref()).map_err(|e| {
151 eprintln!("Error converting file contents in {filename:?} to string",);
152 e
153 })?;
154
155 println!("➕ Writing {}", &to.to_string_lossy());
156 write(&to, file_contents).map_err(|e| {
157 eprintln!("Error writing file: {to:?}");
158 e
159 })?;
160 Ok(())
161}
162
163fn file_exists(file_path: &Path) -> bool {
165 metadata(file_path)
166 .as_ref()
167 .map(Metadata::is_file)
168 .unwrap_or(false)
169}
170
171fn rename_cargo_toml_remove(project: &Path, name: &str) -> Result<(), Error> {
172 let from = project.join(format!("contracts/{name}/Cargo.toml.remove"));
173 let to = from.with_extension("");
174 println!("Renaming to {from:?} to {to:?}");
175 fs::rename(from, to)?;
176 Ok(())
177}