From 07bdff2de5b07d154ed3de505fd2f9c958f759d5 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 11:01:16 -0400 Subject: [PATCH 1/7] adds overwrite option to init --- cmd/soroban-cli/src/commands/contract/init.rs | 100 +++++++++++++----- 1 file changed, 71 insertions(+), 29 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 21b9d48bb..ac7e1e346 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -47,6 +47,9 @@ pub struct Cmd { long_help = "An optional flag to pass in a url for a frontend template repository." )] pub frontend_template: String, + + #[arg(short, long, long_help = "Overwrite all existing files.")] + pub overwrite: bool, } fn possible_example_values() -> ValueParser { @@ -89,7 +92,12 @@ impl Cmd { println!("ℹ️ Initializing project at {}", self.project_path); let project_path = Path::new(&self.project_path); - init(project_path, &self.frontend_template, &self.with_example)?; + init( + project_path, + &self.frontend_template, + &self.with_example, + self.overwrite, + )?; Ok(()) } @@ -103,13 +111,14 @@ 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)?; + copy_template_files(project_path, overwrite)?; 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."); @@ -127,7 +136,7 @@ fn init( clone_repo(frontend_template, fe_template_dir.path())?; // copy the frontend template files into the project - copy_frontend_files(fe_template_dir.path(), project_path)?; + copy_frontend_files(fe_template_dir.path(), project_path, overwrite)?; } // if there are --with-example flags, include the example contracts @@ -142,22 +151,25 @@ fn init( 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)?; + copy_example_contracts(examples_dir.path(), project_path, with_examples, overwrite)?; } Ok(()) } -fn copy_template_files(project_path: &Path) -> Result<(), Error> { +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 mut exists = false; if file_exists(&to) { - println!( - "ℹ️ Skipped creating {} as it already exists", - &to.to_string_lossy() - ); - continue; + exists = true; + if !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:?}"); @@ -184,7 +196,11 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { to = project_path.join(item_parent_path).join("Cargo.toml"); } - println!("➕ Writing {}", &to.to_string_lossy()); + if exists { + println!("🔄 Overwriting {}", &to.to_string_lossy()); + } else { + println!("➕ Writing {}", &to.to_string_lossy()); + } write(&to, file_contents).map_err(|e| { eprintln!("Error writing file: {to:?}"); e @@ -193,7 +209,7 @@ fn copy_template_files(project_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { +fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { let contents_to_exclude_from_copy = [ ".git", ".github", @@ -223,24 +239,34 @@ fn copy_contents(from: &Path, to: &Path) -> Result<(), Error> { eprintln!("Error creating directory: {new_path:?}"); e })?; - copy_contents(&path, &new_path)?; + copy_contents(&path, &new_path, overwrite)?; } else { + let mut exists = false; if file_exists(&new_path) { + exists = true; + let mut appended = false; if new_path.to_string_lossy().contains(".gitignore") { + appended = true; append_contents(&path, &new_path)?; } if new_path.to_string_lossy().contains("README.md") { + appended = true; append_contents(&path, &new_path)?; } - println!( - "ℹ️ Skipped creating {} as it already exists", - &new_path.to_string_lossy() - ); - continue; + if overwrite && !appended { + println!("🔄 Overwriting {}", &new_path.to_string_lossy()); + } else { + println!( + "ℹ️ Skipped creating {} as it already exists", + &new_path.to_string_lossy() + ); + continue; + } + } + if !exists { + println!("➕ Writing {}", &new_path.to_string_lossy()); } - - println!("➕ Writing {}", &new_path.to_string_lossy()); copy(&path, &new_path).map_err(|e| { eprintln!( "Error copying from {:?} to {:?}", @@ -302,7 +328,12 @@ fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Result<(), Error> { +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}"); @@ -315,7 +346,7 @@ fn copy_example_contracts(from: &Path, to: &Path, contracts: &[String]) -> Resul e })?; - copy_contents(&from_contract_path, &to_contract_path)?; + copy_contents(&from_contract_path, &to_contract_path, overwrite)?; edit_contract_cargo_file(&to_contract_path)?; } @@ -354,9 +385,9 @@ fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { Ok(()) } -fn copy_frontend_files(from: &Path, to: &Path) -> Result<(), Error> { +fn copy_frontend_files(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { println!("ℹ️ Initializing with frontend template"); - copy_contents(from, to)?; + copy_contents(from, to, overwrite)?; edit_package_json_files(to) } @@ -458,7 +489,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = vec![]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -476,7 +508,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); let with_examples = ["alloc".to_owned()]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -499,7 +532,8 @@ mod tests { let temp_dir = tempfile::tempdir().unwrap(); let project_dir = temp_dir.path().join("project"); let with_examples = ["account".to_owned(), "atomic_swap".to_owned()]; - init(project_dir.as_path(), "", &with_examples).unwrap(); + let overwrite = false; + init(project_dir.as_path(), "", &with_examples, overwrite).unwrap(); assert_base_template_files_exist(&project_dir); assert_default_hello_world_contract_files_exist(&project_dir); @@ -523,7 +557,8 @@ mod tests { 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()]; - assert!(init(project_dir.as_path(), "", &with_examples,).is_err()); + let overwrite = false; + assert!(init(project_dir.as_path(), "", &with_examples, overwrite).is_err()); temp_dir.close().unwrap(); } @@ -533,10 +568,12 @@ mod tests { 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(); @@ -561,10 +598,12 @@ mod tests { 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(); @@ -591,10 +630,12 @@ mod tests { 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(); @@ -603,6 +644,7 @@ mod tests { project_dir.as_path(), "https://github.com/stellar/soroban-astro-template", &with_examples, + overwrite, ) .unwrap(); From 14afce47185d2593104850a6258396ecfa9b55c0 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 11:39:32 -0400 Subject: [PATCH 2/7] add test case and update docs --- Cargo.lock | 1 + FULL_HELP_DOCS.md | 1 + cmd/soroban-cli/Cargo.toml | 1 + cmd/soroban-cli/src/commands/contract/init.rs | 64 ++++++++++++++++++- 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4d70268b9..edab8bb9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4732,6 +4732,7 @@ dependencies = [ "tracing-subscriber", "ulid", "ureq", + "walkdir", "wasm-opt", "wasmparser 0.90.0", "which", diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 8dea0e36b..167549cf1 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -424,6 +424,7 @@ Initialize a Soroban project with an example contract * `-f`, `--frontend-template ` — An optional flag to pass in a url for a frontend template repository. Default value: `` +* `-o`, `--overwrite` — Overwrite all existing files. diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 3b0b90153..6d40ddd90 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -130,3 +130,4 @@ ureq = { version = "2.9.1", features = ["json"] } assert_cmd = "2.0.4" assert_fs = "1.0.7" predicates = "2.1.5" +walkdir = "2.5.0" diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index ac7e1e346..0428fac0a 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -478,7 +478,13 @@ fn get_merged_file_delimiter(file_path: &Path) -> String { #[cfg(test)] mod tests { use itertools::Itertools; - use std::fs::{self, read_to_string}; + use std::{ + collections::HashMap, + fs::{self, read_to_string}, + path::PathBuf, + time::SystemTime, + }; + use walkdir::WalkDir; use super::*; @@ -593,6 +599,62 @@ mod tests { temp_dir.close().unwrap(); } + #[test] + 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(); + + // 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(); + + // Get new modification times + let new_mod_times = get_mod_times(&project_dir); + + // Compare modification times + for (path, initial_time) in initial_mod_times { + let new_time = new_mod_times.get(&path).expect("File should still exist"); + assert!( + new_time > &initial_time, + "File {} should have a later modification time", + path.display() + ); + } + + temp_dir.close().unwrap(); + } + + fn get_mod_times(dir: &Path) -> HashMap { + let mut mod_times = HashMap::new(); + for entry in WalkDir::new(dir) { + let entry = entry.unwrap(); + if entry.file_type().is_file() { + let path = entry.path().to_owned(); + let metadata = fs::metadata(&path).unwrap(); + mod_times.insert(path, metadata.modified().unwrap()); + } + } + mod_times + } + #[test] fn test_init_from_within_an_existing_project() { let temp_dir = tempfile::tempdir().unwrap(); From 2e9dc77babca626b4223db7924e33fcc75efa41b Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 11:55:44 -0400 Subject: [PATCH 3/7] cleanup logic --- cmd/soroban-cli/src/commands/contract/init.rs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 0428fac0a..427837ccd 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -160,16 +160,13 @@ fn init( 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 mut exists = false; - if file_exists(&to) { - exists = true; - if !overwrite { - println!( - "ℹ️ Skipped creating {} as it already exists", - &to.to_string_lossy() - ); - continue; - } + 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:?}"); @@ -241,9 +238,8 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { })?; copy_contents(&path, &new_path, overwrite)?; } else { - let mut exists = false; - if file_exists(&new_path) { - exists = true; + let exists = file_exists(&new_path); + if exists { let mut appended = false; if new_path.to_string_lossy().contains(".gitignore") { appended = true; From e93da7afc2570ae33749468ccc2f6a9b02b19267 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 11:57:51 -0400 Subject: [PATCH 4/7] simplify --- cmd/soroban-cli/src/commands/contract/init.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 427837ccd..f5582a8b8 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -259,8 +259,7 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { ); continue; } - } - if !exists { + } else { println!("➕ Writing {}", &new_path.to_string_lossy()); } copy(&path, &new_path).map_err(|e| { From 5f163fe10c722fdcbe74e17a8b83189a75e182f5 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 13:06:56 -0400 Subject: [PATCH 5/7] Update cmd/soroban-cli/src/commands/contract/init.rs simplify appended check Co-authored-by: Willem Wyndham --- cmd/soroban-cli/src/commands/contract/init.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index f5582a8b8..9f5441d2e 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -240,13 +240,9 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { } else { let exists = file_exists(&new_path); if exists { - let mut appended = false; - if new_path.to_string_lossy().contains(".gitignore") { - appended = true; - append_contents(&path, &new_path)?; - } - if new_path.to_string_lossy().contains("README.md") { - appended = true; + let new_path_str = new_path.to_string_lossy(); + let append = new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); + if append { append_contents(&path, &new_path)?; } From 98ea14b15a3f5e9b0f40b89aa3d0e883e77d1140 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 13:07:12 -0400 Subject: [PATCH 6/7] Update cmd/soroban-cli/src/commands/contract/init.rs simplified appended check Co-authored-by: Willem Wyndham --- cmd/soroban-cli/src/commands/contract/init.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 9f5441d2e..2547e0f47 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -246,17 +246,16 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { append_contents(&path, &new_path)?; } - if overwrite && !appended { - println!("🔄 Overwriting {}", &new_path.to_string_lossy()); + if overwrite && !append { + println!("🔄 Overwriting {new_path_str}"); } else { println!( - "ℹ️ Skipped creating {} as it already exists", - &new_path.to_string_lossy() + "ℹ️ Skipped creating {new_path_str} as it already exists" ); continue; } } else { - println!("➕ Writing {}", &new_path.to_string_lossy()); + println!("➕ Writing {new_path_str}"); } copy(&path, &new_path).map_err(|e| { eprintln!( From 192a9020b11151e8616df1fbcad5ddbd73be6151 Mon Sep 17 00:00:00 2001 From: Blaine Heffron Date: Tue, 23 Jul 2024 13:24:03 -0400 Subject: [PATCH 7/7] fix scope issue --- cmd/soroban-cli/src/commands/contract/init.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index 2547e0f47..26bf5816a 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -239,9 +239,10 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { 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 new_path_str = new_path.to_string_lossy(); - let append = new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); + let append = + new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); if append { append_contents(&path, &new_path)?; } @@ -249,9 +250,7 @@ fn copy_contents(from: &Path, to: &Path, overwrite: bool) -> Result<(), Error> { if overwrite && !append { println!("🔄 Overwriting {new_path_str}"); } else { - println!( - "ℹ️ Skipped creating {new_path_str} as it already exists" - ); + println!("ℹ️ Skipped creating {new_path_str} as it already exists"); continue; } } else {