Repository: cargo-generate/cargo-generate
Pull request #625 (2022-06-06 00:10:14)

Override project_dir when using `generate()`
Hello there!

I made this PR to add the `dest` argument to the `generate` function.

This argument override the destination of the project if we give a path to the `generate` function.
If we give `None`, the behaviour stay the same as expected.

This is a breaking change btw, if you have suggestions to avoid that?

sassman: But one more note on the idea; I think this is worth to introduce as a CLI parameter, where folks can pass a different `--base-path` than `$CWD`, so if that parameter is omitted it is set to CWD. 
By doing so users can provide it via CLI params, and it would be encapsulated in `Args` so the function signature also would not have to change.
yozhgoor: I replaced the `dest` argument from `generate()` by a `--destination` parameter. 
It conflict with `--init` so you can choose between a subfolder, the current directory or a given path. 

Patch:
diff --git a/src/args.rs b/src/args.rs
index 425bd5b..cbbc06c 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -119,9 +119,13 @@ pub struct Args {
     pub define: Vec<String>,
 
     /// Generate the template directly into the current dir. No subfolder will be created and no vcs is initialized.
-    #[clap(long)]
+    #[clap(long, conflicts_with = "destination")]
     pub init: bool,
 
+    /// Generate the template directly at the given path.
+    #[clap(long, conflicts_with = "init")]
+    pub destination: Option<PathBuf>,
+
     /// Will enforce a fresh git init on the generated project
     #[clap(long)]
     pub force_git_init: bool,
diff --git a/src/lib.rs b/src/lib.rs
index 7b4b861..cd5c244 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -350,33 +350,40 @@ fn locate_template_file(
     }
 }
 
+/// Resolves the project dir.
+///
+/// if `args.init == true` it returns the path of `$CWD` and if let some `args.destination`,
+/// it returns the given path.
 fn resolve_project_dir(name: &ProjectName, args: &Args) -> Result<PathBuf> {
     if args.init {
         let cwd = env::current_dir()?;
         return Ok(cwd);
     }
 
-    let dir_name = if args.force {
-        name.raw()
-    } else {
+    let base_path = args
+        .destination
+        .as_ref()
+        .map(|p| p.to_path_buf())
+        .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| ".".into()));
+
+    let dir_name = args.force.then(|| name.raw()).unwrap_or_else(|| {
         rename_warning(name);
         name.kebab_case()
-    };
-    let project_dir = env::current_dir()
-        .unwrap_or_else(|_e| ".".into())
-        .join(&dir_name);
+    });
+
+    let project_dir = base_path.join(&dir_name);
 
     if project_dir.exists() {
-        Err(anyhow!(
+        bail!(
             "{} {}",
             emoji::ERROR,
             style("Target directory already exists, aborting!")
                 .bold()
                 .red()
-        ))
-    } else {
-        Ok(project_dir)
+        );
     }
+
+    Ok(project_dir)
 }
 
 fn expand_template(


Test patch:
diff --git a/tests/integration/basics.rs b/tests/integration/basics.rs
index bffe035..b970495 100644
--- a/tests/integration/basics.rs
+++ b/tests/integration/basics.rs
@@ -390,6 +390,40 @@ version = "0.1.0"
     Ok(())
 }
 
+#[test]
+fn it_can_generate_at_given_path() -> anyhow::Result<()> {
+    let template = tmp_dir()
+        .file(
+            "Cargo.toml",
+            r#"[package]
+name = "{{project-name}}"
+description = "A wonderful project"
+version = "0.1.0"
+"#,
+        )
+        .init_git()
+        .build();
+    let dir = tmp_dir().build();
+    let dest = dir.path().join("destination");
+    fs::create_dir(&dest).expect("can create directory");
+    binary()
+        .arg("generate")
+        .arg("--git")
+        .arg(template.path())
+        .arg("--name")
+        .arg("my-proj")
+        .arg("--destination")
+        .arg(&dest)
+        .current_dir(&dir.path())
+        .assert()
+        .success()
+        .stdout(predicates::str::contains("Done!").from_utf8());
+    assert!(dir
+        .read("destination/my-proj/Cargo.toml")
+        .contains("my-proj"));
+    Ok(())
+}
+
 #[test]
 fn it_refuses_to_overwrite_files() -> anyhow::Result<()> {
     let template = tmp_dir()
diff --git a/tests/integration/library.rs b/tests/integration/library.rs
index 5b197ae..b8fa1de 100644
--- a/tests/integration/library.rs
+++ b/tests/integration/library.rs
@@ -36,6 +36,7 @@ version = "0.1.0"
         ssh_identity: None,
         define: vec![],
         init: false,
+        destination: None,
         force_git_init: false,
         allow_commands: false,
     };