From 62f030a320baec5dc8f383b62e944a1516816b95 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:25:22 -0400 Subject: [PATCH 1/6] Add global args to contract init cmd --- cmd/soroban-cli/src/commands/contract/init.rs | 109 ++++++++++-------- cmd/soroban-cli/src/commands/contract/mod.rs | 2 +- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index a83504d96..4e915f1cf 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -21,6 +21,8 @@ use std::{ use toml_edit::{Document, TomlError}; use ureq::get; +use crate::{commands::global, print}; + const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; const GITHUB_URL: &str = "https://github.com"; const WITH_EXAMPLE_LONG_HELP_TEXT: &str = @@ -87,73 +89,84 @@ pub enum Error { impl Cmd { #[allow(clippy::unused_self)] - pub fn run(&self) -> Result<(), Error> { - println!("ℹ️ Initializing project at {}", self.project_path); - let project_path = Path::new(&self.project_path); - - init( - project_path, - &self.frontend_template, - &self.with_example, - self.overwrite, - )?; + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let runner = Runner { + args: self.clone(), + print: print::Print::new(global_args.quiet), + }; - Ok(()) + runner.run() } } #[derive(RustEmbed)] #[folder = "src/utils/contract-init-template"] struct TemplateFiles; +struct Runner { + args: Cmd, + print: print::Print, +} -fn init( - project_path: &Path, - frontend_template: &str, - with_examples: &[String], - overwrite: bool, -) -> Result<(), Error> { - // create a project dir, and copy the contents of the base template (contract-init-template) into it - create_dir_all(project_path).map_err(|e| { - eprintln!("Error creating new project directory: {project_path:?}"); - e - })?; - copy_template_files(project_path, overwrite)?; +impl Runner { + fn run(&self) -> Result<(), Error> { + let project_path = Path::new(&self.args.project_path); + self.print + .infoln(format!("Initializing project at {:?}", project_path)); - if !check_internet_connection() { - println!("⚠️ It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); - return Ok(()); + self.init()?; + + Ok(()) } - if !frontend_template.is_empty() { - // create a temp dir for the template repo - let fe_template_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for frontend template"); + fn init(&self) -> Result<(), Error> { + let project_path = Path::new(&self.args.project_path); + let frontend_template = &self.args.frontend_template; + let with_examples = &self.args.with_example; + let overwrite = self.args.overwrite; + + // create a project dir, and copy the contents of the base template (contract-init-template) into it + create_dir_all(project_path).map_err(|e| { + eprintln!("Error creating new project directory: {project_path:?}"); e })?; + copy_template_files(project_path, overwrite)?; - // clone the template repo into the temp dir - clone_repo(frontend_template, fe_template_dir.path())?; + if !check_internet_connection() { + self.print.warnln("It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); + return Ok(()); + } - // copy the frontend template files into the project - copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; - } + if !frontend_template.is_empty() { + // create a temp dir for the template repo + let fe_template_dir = tempfile::tempdir().map_err(|e| { + eprintln!("Error creating temp dir for frontend template"); + e + })?; - // if there are --with-example flags, include the example contracts - if include_example_contracts(with_examples) { - // create an examples temp dir - let examples_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for soroban-examples"); - e - })?; + // clone the template repo into the temp dir + clone_repo(frontend_template, fe_template_dir.path())?; - // clone the soroban-examples repo into the temp dir - clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; + // copy the frontend template files into the project + copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; + } - // copy the example contracts into the project - copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; - } + // if there are --with-example flags, include the example contracts + if include_example_contracts(with_examples) { + // create an examples temp dir + let examples_dir = tempfile::tempdir().map_err(|e| { + eprintln!("Error creating temp dir for soroban-examples"); + e + })?; - Ok(()) + // clone the soroban-examples repo into the temp dir + clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; + + // copy the example contracts into the project + copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; + } + + Ok(()) + } } fn copy_template_files(project_path: &Path, overwrite: bool) -> Result<(), Error> { diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index afd49c6ab..7475721e5 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -135,7 +135,7 @@ impl Cmd { Cmd::Deploy(deploy) => deploy.run(global_args).await?, Cmd::Id(id) => id.run()?, Cmd::Info(info) => info.run().await?, - Cmd::Init(init) => init.run()?, + Cmd::Init(init) => init.run(global_args)?, Cmd::Inspect(inspect) => inspect.run()?, Cmd::Install(install) => install.run(global_args).await?, Cmd::Invoke(invoke) => invoke.run(global_args).await?, From 3d22c95346e0fb733f75080935283bfaf2f546ab Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:36:02 -0400 Subject: [PATCH 2/6] Add plus and over to print system --- cmd/soroban-cli/src/print.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index ca95f652c..b4166f600 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -86,6 +86,8 @@ create_print_functions!(error, errorln, "❌"); create_print_functions!(globe, globeln, "🌎"); create_print_functions!(info, infoln, "ℹ️"); create_print_functions!(link, linkln, "🔗"); +create_print_functions!(over, overln, "🔄"); +create_print_functions!(plus, plusln, "➕"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); create_print_functions!(warn, warnln, "⚠️"); From 57d23d14e03bb82dff31b4a4546d4bd021e0ba4f Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:59:00 -0400 Subject: [PATCH 3/6] Continue to refactor init to use print --- cmd/soroban-cli/src/commands/contract/init.rs | 733 ++++++++++-------- 1 file changed, 400 insertions(+), 333 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 4e915f1cf..1bebb099a 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -120,360 +120,389 @@ impl Runner { fn init(&self) -> Result<(), Error> { let project_path = Path::new(&self.args.project_path); - let frontend_template = &self.args.frontend_template; - let with_examples = &self.args.with_example; - let overwrite = self.args.overwrite; // create a project dir, and copy the contents of the base template (contract-init-template) into it create_dir_all(project_path).map_err(|e| { - eprintln!("Error creating new project directory: {project_path:?}"); + self.print + .errorln("Error creating new project directory: {project_path:?}"); e })?; - copy_template_files(project_path, overwrite)?; + self.copy_template_files()?; - if !check_internet_connection() { + if !self.check_internet_connection() { self.print.warnln("It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); return Ok(()); } - if !frontend_template.is_empty() { + if !&self.args.frontend_template.is_empty() { // create a temp dir for the template repo let fe_template_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for frontend template"); + self.print + .errorln("Error creating temp dir for frontend template"); e })?; // clone the template repo into the temp dir - clone_repo(frontend_template, fe_template_dir.path())?; + self.clone_repo(&self.args.frontend_template, fe_template_dir.path())?; // copy the frontend template files into the project - copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; + self.copy_frontend_files(fe_template_dir.path(), project_path)?; } // if there are --with-example flags, include the example contracts - if include_example_contracts(with_examples) { + if self.include_example_contracts() { // create an examples temp dir let examples_dir = tempfile::tempdir().map_err(|e| { - eprintln!("Error creating temp dir for soroban-examples"); + self.print + .errorln("Error creating temp dir for soroban-examples"); e })?; // clone the soroban-examples repo into the temp dir - clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; + self.clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; // copy the example contracts into the project - copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; + self.copy_example_contracts( + examples_dir.path(), + project_path, + &self.args.with_example, + )?; } Ok(()) } -} -fn copy_template_files(project_path: &Path, overwrite: bool) -> Result<(), Error> { - for item in TemplateFiles::iter() { - let mut to = project_path.join(item.as_ref()); - let exists = file_exists(&to); - if exists && !overwrite { - println!( - "ℹ️ Skipped creating {} as it already exists", - &to.to_string_lossy() - ); - continue; - } - create_dir_all(to.parent().unwrap()).map_err(|e| { - eprintln!("Error creating directory path for: {to:?}"); - e - })?; + fn copy_template_files(&self) -> Result<(), Error> { + let project_path = Path::new(&self.args.project_path); + for item in TemplateFiles::iter() { + let mut to = project_path.join(item.as_ref()); + let exists = self.file_exists(&to); + if exists && !self.args.overwrite { + self.print + .infoln(format!("Skipped creating {to:?} as it already exists")); + continue; + } - let Some(file) = TemplateFiles::get(item.as_ref()) else { - println!("⚠️ Failed to read file: {}", item.as_ref()); - continue; - }; + create_dir_all(to.parent().unwrap()).map_err(|e| { + self.print + .errorln(format!("Error creating directory path for: {to:?}")); + e + })?; - let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { - eprintln!( - "Error converting file contents in {:?} to string", - item.as_ref() - ); - e - })?; + let Some(file) = TemplateFiles::get(item.as_ref()) else { + self.print + .warnln(format!("Failed to read file: {}", item.as_ref())); + continue; + }; + + let file_contents = std::str::from_utf8(file.data.as_ref()).map_err(|e| { + self.print.errorln(format!( + "Error converting file contents in {:?} to string", + item.as_ref() + )); + e + })?; - // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. - let item_path = Path::new(item.as_ref()); - if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { - let item_parent_path = item_path.parent().unwrap(); - to = project_path.join(item_parent_path).join("Cargo.toml"); - } + // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. + let item_path = Path::new(item.as_ref()); + if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { + let item_parent_path = item_path.parent().unwrap(); + to = project_path.join(item_parent_path).join("Cargo.toml"); + } - if exists { - println!("🔄 Overwriting {}", &to.to_string_lossy()); - } else { - println!("➕ Writing {}", &to.to_string_lossy()); + if exists { + self.print.overln(format!("Overwriting {to:?}")); + } else { + self.print.plusln(format!("Writing {to:?}")); + } + write(&to, file_contents).map_err(|e| { + self.print.errorln(format!("Error writing file: {to:?}")); + e + })?; } - write(&to, file_contents).map_err(|e| { - eprintln!("Error writing file: {to:?}"); - e - })?; + Ok(()) } - Ok(()) -} -fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - let contents_to_exclude_from_copy = [ - ".git", - ".github", - "Makefile", - ".vscode", - "target", - "Cargo.lock", - ]; - for entry in read_dir(from).map_err(|e| { - eprintln!("Error reading directory: {from:?}"); - e - })? { - let entry = entry.map_err(|e| { - eprintln!("Error reading entry in directory: {from:?}"); + fn copy_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let contents_to_exclude_from_copy = [ + ".git", + ".github", + "Makefile", + ".vscode", + "target", + "Cargo.lock", + ]; + for entry in read_dir(from).map_err(|e| { + self.print + .errorln(format!("Error reading directory: {from:?}")); e - })?; - let path = entry.path(); - let entry_name = entry.file_name().to_string_lossy().to_string(); - let new_path = to.join(&entry_name); - - if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { - continue; - } - - if path.is_dir() { - create_dir_all(&new_path).map_err(|e| { - eprintln!("Error creating directory: {new_path:?}"); + })? { + let entry = entry.map_err(|e| { + self.print + .errorln(format!("Error reading entry in directory: {from:?}")); e })?; - copy_contents(&path, &new_path, overwrite)?; - } else { - let exists = file_exists(&new_path); - let new_path_str = new_path.to_string_lossy(); - if exists { - let append = - new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); - if append { - append_contents(&path, &new_path)?; - } + let path = entry.path(); + let entry_name = entry.file_name().to_string_lossy().to_string(); + let new_path = to.join(&entry_name); - if overwrite && !append { - println!("🔄 Overwriting {new_path_str}"); + if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { + continue; + } + + if path.is_dir() { + create_dir_all(&new_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {new_path:?}")); + e + })?; + self.copy_contents(&path, &new_path)?; + } else { + let exists = self.file_exists(&new_path); + let new_path_str = new_path.to_string_lossy(); + if exists { + let append = + new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); + if append { + self.append_contents(&path, &new_path)?; + } + + if self.args.overwrite && !append { + self.print.overln(format!("Overwriting {new_path_str}")); + } else { + self.print.infoln(format!( + "Skipped creating {new_path_str} as it already exists" + )); + continue; + } } else { - println!("ℹ️ Skipped creating {new_path_str} as it already exists"); - continue; + self.print.plus(format!("Writing {new_path_str}")); } - } else { - println!("➕ Writing {new_path_str}"); + copy(&path, &new_path).map_err(|e| { + self.print.errorln(format!( + "Error copying from {:?} to {:?}", + path.to_string_lossy(), + new_path + )); + e + })?; } - copy(&path, &new_path).map_err(|e| { - eprintln!( - "Error copying from {:?} to {:?}", - path.to_string_lossy(), - new_path - ); - e - })?; } + + Ok(()) } - Ok(()) -} + fn file_exists(&self, file_path: &Path) -> bool { + metadata(file_path) + .as_ref() + .map(Metadata::is_file) + .unwrap_or(false) + } -fn file_exists(file_path: &Path) -> bool { - metadata(file_path) - .as_ref() - .map(Metadata::is_file) - .unwrap_or(false) -} + fn check_internet_connection(&self) -> bool { + if let Ok(_req) = get(GITHUB_URL).call() { + return true; + } -fn include_example_contracts(contracts: &[String]) -> bool { - !contracts.is_empty() -} + false + } -fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { - let mut prepare = clone::PrepareFetch::new( - from_url, - to_path, - create::Kind::WithWorktree, - create::Options { - destination_must_be_empty: false, - fs_capabilities: None, - }, - open::Options::isolated(), - ) - .map_err(|e| { - eprintln!("Error preparing fetch for {from_url:?}"); - Box::new(e) - })? - .with_shallow(remote::fetch::Shallow::DepthAtRemote( - NonZeroU32::new(1).unwrap(), - )); - - let (mut checkout, _outcome) = prepare - .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| { - eprintln!("Error calling fetch_then_checkout with {from_url:?}"); - Box::new(e) - })?; + fn include_example_contracts(&self) -> bool { + !self.args.with_example.is_empty() + } - let (_repo, _outcome) = checkout - .main_worktree(progress::Discard, &AtomicBool::new(false)) + fn clone_repo(&self, from_url: &str, to_path: &Path) -> Result<(), Error> { + let mut prepare = clone::PrepareFetch::new( + from_url, + to_path, + create::Kind::WithWorktree, + create::Options { + destination_must_be_empty: false, + fs_capabilities: None, + }, + open::Options::isolated(), + ) .map_err(|e| { - eprintln!("Error calling main_worktree for {from_url:?}"); - e - })?; - - Ok(()) -} + self.print + .errorln(format!("Error preparing fetch for {from_url:?}")); + Box::new(e) + })? + .with_shallow(remote::fetch::Shallow::DepthAtRemote( + NonZeroU32::new(1).unwrap(), + )); + + let (mut checkout, _outcome) = prepare + .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print.errorln(format!( + "Error calling fetch_then_checkout with {from_url:?}" + )); + Box::new(e) + })?; -fn copy_example_contracts( - from: &Path, - to: &Path, - contracts: &[String], - overwrite: bool, -) -> Result<(), Error> { - let project_contracts_path = to.join("contracts"); - for contract in contracts { - println!("ℹ️ Initializing example contract: {contract}"); - let contract_as_string = contract.to_string(); - let contract_path = Path::new(&contract_as_string); - let from_contract_path = from.join(contract_path); - let to_contract_path = project_contracts_path.join(contract_path); - create_dir_all(&to_contract_path).map_err(|e| { - eprintln!("Error creating directory: {contract_path:?}"); - e - })?; + let (_repo, _outcome) = checkout + .main_worktree(progress::Discard, &AtomicBool::new(false)) + .map_err(|e| { + self.print + .errorln(format!("Error calling main_worktree for {from_url:?}")); + e + })?; - copy_contents(&from_contract_path, &to_contract_path, overwrite)?; - edit_contract_cargo_file(&to_contract_path)?; + Ok(()) } - Ok(()) -} + fn copy_example_contracts( + &self, + from: &Path, + to: &Path, + contracts: &[String], + ) -> Result<(), Error> { + let project_contracts_path = to.join("contracts"); + for contract in contracts { + self.print + .infoln(format!("Initializing example contract: {contract}")); + let contract_as_string = contract.to_string(); + let contract_path = Path::new(&contract_as_string); + let from_contract_path = from.join(contract_path); + let to_contract_path = project_contracts_path.join(contract_path); + create_dir_all(&to_contract_path).map_err(|e| { + self.print + .errorln(format!("Error creating directory: {contract_path:?}")); + e + })?; -fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { - let cargo_path = contract_path.join("Cargo.toml"); - let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { - eprint!("Error reading Cargo.toml file in: {contract_path:?}"); - e - })?; - - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) - .unwrap() - .replace_all( - cargo_toml_str.as_str(), - "soroban-sdk = { workspace = true }", - ); + self.copy_contents(&from_contract_path, &to_contract_path)?; + self.edit_contract_cargo_file(&to_contract_path)?; + } - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) - .unwrap() - .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); + Ok(()) + } - let mut doc = cargo_toml_str.parse::().map_err(|e| { - eprintln!("Error parsing Cargo.toml file in: {contract_path:?}"); - e - })?; - doc.remove("profile"); + fn edit_contract_cargo_file(&self, contract_path: &Path) -> Result<(), Error> { + let cargo_path = contract_path.join("Cargo.toml"); + let cargo_toml_str = read_to_string(&cargo_path).map_err(|e| { + self.print.errorln(format!( + "Error reading Cargo.toml file in: {contract_path:?}" + )); + e + })?; - write(&cargo_path, doc.to_string()).map_err(|e| { - eprintln!("Error writing to Cargo.toml file in: {contract_path:?}"); - e - })?; + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) + .unwrap() + .replace_all( + cargo_toml_str.as_str(), + "soroban-sdk = { workspace = true }", + ); - Ok(()) -} + let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) + .unwrap() + .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); -fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { - println!("ℹ️ Initializing with frontend template"); - copy_contents(from, to, overwrite)?; - edit_package_json_files(to) -} + let mut doc = cargo_toml_str.parse::().map_err(|e| { + self.print.errorln(format!( + "Error parsing Cargo.toml file in: {contract_path:?}" + )); + e + })?; + doc.remove("profile"); -fn edit_package_json_files(project_path: &Path) -> Result<(), Error> { - let package_name = if let Some(name) = project_path.file_name() { - name.to_owned() - } else { - let current_dir = env::current_dir()?; - let file_name = current_dir - .file_name() - .unwrap_or(OsStr::new("soroban-astro-template")) - .to_os_string(); - file_name - }; + write(&cargo_path, doc.to_string()).map_err(|e| { + self.print.errorln(format!( + "Error writing to Cargo.toml file in: {contract_path:?}" + )); + e + })?; - edit_package_name(project_path, &package_name, "package.json").map_err(|e| { - eprintln!("Error editing package.json file in: {project_path:?}"); - e - })?; - edit_package_name(project_path, &package_name, "package-lock.json") -} + Ok(()) + } -fn edit_package_name( - project_path: &Path, - package_name: &OsStr, - file_name: &str, -) -> Result<(), Error> { - let file_path = project_path.join(file_name); - let file_contents = read_to_string(&file_path)?; + fn copy_frontend_files(&self, from: &Path, to: &Path) -> Result<(), Error> { + self.print.infoln("ℹ️ Initializing with frontend template"); + self.copy_contents(from, to)?; + self.edit_package_json_files(to) + } - let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { - eprintln!("Error parsing package.json file in: {project_path:?}"); - e - })?; + fn edit_package_json_files(&self, project_path: &Path) -> Result<(), Error> { + let package_name = if let Some(name) = project_path.file_name() { + name.to_owned() + } else { + let current_dir = env::current_dir()?; + let file_name = current_dir + .file_name() + .unwrap_or(OsStr::new("soroban-astro-template")) + .to_os_string(); + file_name + }; - doc["name"] = json!(package_name.to_string_lossy()); + self.edit_package_name(project_path, &package_name, "package.json") + .map_err(|e| { + self.print.errorln(format!( + "Error editing package.json file in: {project_path:?}" + )); + e + })?; + self.edit_package_name(project_path, &package_name, "package-lock.json") + } - let formatted_json = to_string_pretty(&doc)?; + fn edit_package_name( + &self, + project_path: &Path, + package_name: &OsStr, + file_name: &str, + ) -> Result<(), Error> { + let file_path = project_path.join(file_name); + let file_contents = read_to_string(&file_path)?; + + let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { + self.print.errorln(format!( + "Error parsing {file_name} file in: {project_path:?}" + )); + e + })?; - write(&file_path, formatted_json)?; + doc["name"] = json!(package_name.to_string_lossy()); - Ok(()) -} + let formatted_json = to_string_pretty(&doc)?; -fn check_internet_connection() -> bool { - if let Ok(_req) = get(GITHUB_URL).call() { - return true; - } + write(&file_path, formatted_json)?; - false -} + Ok(()) + } -// Appends the contents of a file to another file, separated by a delimiter -fn append_contents(from: &Path, to: &Path) -> Result<(), Error> { - let mut from_file = File::open(from)?; - let mut from_content = String::new(); - from_file.read_to_string(&mut from_content)?; + // Appends the contents of a file to another file, separated by a delimiter + fn append_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { + let mut from_file = File::open(from)?; + let mut from_content = String::new(); + from_file.read_to_string(&mut from_content)?; - let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; - let mut to_content = String::new(); - to_file.read_to_string(&mut to_content)?; + let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; + let mut to_content = String::new(); + to_file.read_to_string(&mut to_content)?; - let delimiter = get_merged_file_delimiter(to); - // if the to file already contains the delimiter, we don't need to append the contents again - if to_content.contains(&delimiter) { - return Ok(()); - } + let delimiter = self.get_merged_file_delimiter(to); + // if the to file already contains the delimiter, we don't need to append the contents again + if to_content.contains(&delimiter) { + return Ok(()); + } - to_file.write_all(delimiter.as_bytes())?; - to_file.write_all(from_content.as_bytes())?; + to_file.write_all(delimiter.as_bytes())?; + to_file.write_all(from_content.as_bytes())?; - println!("ℹ️ Merging {} contents", &to.to_string_lossy()); - Ok(()) -} + self.print.infoln(format!("Merging {to:?} contents")); + Ok(()) + } -fn get_merged_file_delimiter(file_path: &Path) -> String { - let comment = if file_path.to_string_lossy().contains("README.md") { - "---\n".to_string() - } else if file_path.to_string_lossy().contains("gitignore") { - "# The following is from the Frontend Template's .gitignore".to_string() - } else { - String::new() - }; + fn get_merged_file_delimiter(&self, file_path: &Path) -> String { + let comment = if file_path.to_string_lossy().contains("README.md") { + "---\n".to_string() + } else if file_path.to_string_lossy().contains("gitignore") { + "# The following is from the Frontend Template's .gitignore".to_string() + } else { + String::new() + }; - format!("\n\n{comment}\n\n").to_string() + format!("\n\n{comment}\n\n").to_string() + } } #[cfg(test)] @@ -495,9 +524,16 @@ mod tests { fn test_init() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -514,9 +550,16 @@ mod tests { fn test_init_including_example_contract() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = ["alloc".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["alloc".to_owned()].to_vec(), + frontend_template: "".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -538,9 +581,16 @@ mod tests { fn test_init_including_multiple_example_contracts() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); - let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - let overwrite = false; - init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["account".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: "".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -563,9 +613,16 @@ mod tests { fn test_init_with_invalid_example_contract() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); - let with_examples = ["invalid_example".to_owned(), "atomic_swap".to_owned()]; - let overwrite = false; - assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: ["invalid_example".to_owned(), "atomic_swap".to_owned()].to_vec(), + frontend_template: "".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + assert!(runner.run().is_err()); temp_dir.close().unwrap(); } @@ -574,15 +631,16 @@ mod tests { fn test_init_with_frontend_template() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -604,28 +662,33 @@ mod tests { fn test_init_with_overwrite() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; // First initialization - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - false, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // Get initial modification times let initial_mod_times = get_mod_times(&project_dir); // Second initialization with overwrite - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - true, // overwrite = true - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: true, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // Get new modification times let new_mod_times = get_mod_times(&project_dir); @@ -660,15 +723,16 @@ mod tests { fn test_init_from_within_an_existing_project() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("./"); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -692,24 +756,28 @@ mod tests { fn test_init_does_not_duplicate_frontend_readme_contents_when_run_more_than_once() { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let with_examples = vec![]; - let overwrite = false; - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); // call init again to make sure the README.md's contents are not duplicated - init( - project_dir.as_path(), - "https://github.com/stellar/soroban-astro-template", - &with_examples, - overwrite, - ) - .unwrap(); + let runner = Runner { + args: Cmd { + project_path: project_dir.to_string_lossy().to_string(), + with_example: vec![], + frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + overwrite: false, + }, + print: print::Print::new(false), + }; + runner.run().unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -756,7 +824,6 @@ mod tests { let cargo_toml_path = contract_dir.as_path().join("Cargo.toml"); let cargo_toml_str = read_to_string(cargo_toml_path.clone()).unwrap(); let doc = cargo_toml_str.parse::().unwrap(); - println!("{cargo_toml_path:?} contents:\n{cargo_toml_str}"); assert!( doc.get("dependencies") .unwrap() From 1d229542b43619c17c4f4630e14b4e66f6a32c01 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:46:52 -0400 Subject: [PATCH 4/6] Clippy --- cmd/soroban-cli/src/commands/contract/init.rs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 1bebb099a..2bed208ca 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -111,8 +111,7 @@ impl Runner { fn run(&self) -> Result<(), Error> { let project_path = Path::new(&self.args.project_path); self.print - .infoln(format!("Initializing project at {:?}", project_path)); - + .infoln(format!("Initializing project at {project_path:?}")); self.init()?; Ok(()) @@ -129,7 +128,7 @@ impl Runner { })?; self.copy_template_files()?; - if !self.check_internet_connection() { + if !Self::check_internet_connection() { self.print.warnln("It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); return Ok(()); } @@ -176,7 +175,7 @@ impl Runner { let project_path = Path::new(&self.args.project_path); for item in TemplateFiles::iter() { let mut to = project_path.join(item.as_ref()); - let exists = self.file_exists(&to); + let exists = Self::file_exists(&to); if exists && !self.args.overwrite { self.print .infoln(format!("Skipped creating {to:?} as it already exists")); @@ -258,7 +257,7 @@ impl Runner { })?; self.copy_contents(&path, &new_path)?; } else { - let exists = self.file_exists(&new_path); + let exists = Self::file_exists(&new_path); let new_path_str = new_path.to_string_lossy(); if exists { let append = @@ -292,14 +291,14 @@ impl Runner { Ok(()) } - fn file_exists(&self, file_path: &Path) -> bool { + fn file_exists(file_path: &Path) -> bool { metadata(file_path) .as_ref() .map(Metadata::is_file) .unwrap_or(false) } - fn check_internet_connection(&self) -> bool { + fn check_internet_connection() -> bool { if let Ok(_req) = get(GITHUB_URL).call() { return true; } @@ -479,7 +478,7 @@ impl Runner { let mut to_content = String::new(); to_file.read_to_string(&mut to_content)?; - let delimiter = self.get_merged_file_delimiter(to); + let delimiter = Self::get_merged_file_delimiter(to); // if the to file already contains the delimiter, we don't need to append the contents again if to_content.contains(&delimiter) { return Ok(()); @@ -492,7 +491,7 @@ impl Runner { Ok(()) } - fn get_merged_file_delimiter(&self, file_path: &Path) -> String { + fn get_merged_file_delimiter(file_path: &Path) -> String { let comment = if file_path.to_string_lossy().contains("README.md") { "---\n".to_string() } else if file_path.to_string_lossy().contains("gitignore") { @@ -528,7 +527,7 @@ mod tests { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), with_example: vec![], - frontend_template: "".to_owned(), + frontend_template: String::new(), overwrite: false, }, print: print::Print::new(false), @@ -554,7 +553,7 @@ mod tests { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), with_example: ["alloc".to_owned()].to_vec(), - frontend_template: "".to_owned(), + frontend_template: String::new(), overwrite: false, }, print: print::Print::new(false), @@ -585,7 +584,7 @@ mod tests { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), with_example: ["account".to_owned(), "atomic_swap".to_owned()].to_vec(), - frontend_template: "".to_owned(), + frontend_template: String::new(), overwrite: false, }, print: print::Print::new(false), @@ -617,7 +616,7 @@ mod tests { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), with_example: ["invalid_example".to_owned(), "atomic_swap".to_owned()].to_vec(), - frontend_template: "".to_owned(), + frontend_template: String::new(), overwrite: false, }, print: print::Print::new(false), From 080378e1b86eb5661fa511499197d619ed78af4a Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:53:11 -0400 Subject: [PATCH 5/6] Simplify init runner --- cmd/soroban-cli/src/commands/contract/init.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 2bed208ca..15c71d99a 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -112,13 +112,6 @@ impl Runner { let project_path = Path::new(&self.args.project_path); self.print .infoln(format!("Initializing project at {project_path:?}")); - self.init()?; - - Ok(()) - } - - fn init(&self) -> Result<(), Error> { - let project_path = Path::new(&self.args.project_path); // create a project dir, and copy the contents of the base template (contract-init-template) into it create_dir_all(project_path).map_err(|e| { From 4da9a8dab1a68f79ca331d5d13f5c40f3d795bd1 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:38:12 -0400 Subject: [PATCH 6/6] Address PR feedback --- cmd/soroban-cli/src/commands/contract/init.rs | 19 +++++++++++-------- cmd/soroban-cli/src/print.rs | 1 - 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 15c71d99a..c70867e23 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -14,7 +14,7 @@ use std::{ }, io::{self, Read, Write}, num::NonZeroU32, - path::Path, + path::{Path, PathBuf}, str, sync::atomic::AtomicBool, }; @@ -109,12 +109,12 @@ struct Runner { impl Runner { fn run(&self) -> Result<(), Error> { - let project_path = Path::new(&self.args.project_path); + let project_path = PathBuf::from(&self.args.project_path); self.print .infoln(format!("Initializing project at {project_path:?}")); // create a project dir, and copy the contents of the base template (contract-init-template) into it - create_dir_all(project_path).map_err(|e| { + create_dir_all(&project_path).map_err(|e| { self.print .errorln("Error creating new project directory: {project_path:?}"); e @@ -126,7 +126,7 @@ impl Runner { return Ok(()); } - if !&self.args.frontend_template.is_empty() { + if !self.args.frontend_template.is_empty() { // create a temp dir for the template repo let fe_template_dir = tempfile::tempdir().map_err(|e| { self.print @@ -138,7 +138,7 @@ impl Runner { self.clone_repo(&self.args.frontend_template, fe_template_dir.path())?; // copy the frontend template files into the project - self.copy_frontend_files(fe_template_dir.path(), project_path)?; + self.copy_frontend_files(fe_template_dir.path(), &project_path)?; } // if there are --with-example flags, include the example contracts @@ -156,7 +156,7 @@ impl Runner { // copy the example contracts into the project self.copy_example_contracts( examples_dir.path(), - project_path, + &project_path, &self.args.with_example, )?; } @@ -203,7 +203,8 @@ impl Runner { } if exists { - self.print.overln(format!("Overwriting {to:?}")); + self.print + .plusln(format!("Writing {to:?} (overwriting existing file)")); } else { self.print.plusln(format!("Writing {to:?}")); } @@ -260,7 +261,9 @@ impl Runner { } if self.args.overwrite && !append { - self.print.overln(format!("Overwriting {new_path_str}")); + self.print.plusln(format!( + "Writing {new_path_str} (overwriting existing file)" + )); } else { self.print.infoln(format!( "Skipped creating {new_path_str} as it already exists" diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index b4166f600..2afa5ec03 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -86,7 +86,6 @@ create_print_functions!(error, errorln, "❌"); create_print_functions!(globe, globeln, "🌎"); create_print_functions!(info, infoln, "ℹ️"); create_print_functions!(link, linkln, "🔗"); -create_print_functions!(over, overln, "🔄"); create_print_functions!(plus, plusln, "➕"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎");