cargo-temp 0.2.4

A CLI tool that allow you to create a temporary new rust project using cargo with already installed dependencies
Documentation
use crate::config::{Config, Depth};
use crate::Dependency;
use anyhow::{ensure, Context, Result};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::{env, fs, process};
use tempfile::TempDir;

pub fn generate_tmp_project(
    worktree_branch: Option<Option<String>>,
    project_name: Option<String>,
    lib: bool,
    git: Option<String>,
    temporary_project_dir: PathBuf,
    git_repo_depth: Option<Depth>,
    vcs: Option<String>,
) -> Result<TempDir> {
    let tmp_dir = {
        let mut builder = tempfile::Builder::new();

        if worktree_branch.is_some() {
            builder.prefix("wk-");
        } else {
            builder.prefix("tmp-");
        }

        builder.tempdir_in(temporary_project_dir)?
    };

    let project_name = project_name.unwrap_or_else(|| {
        tmp_dir
            .path()
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_lowercase()
    });

    if let Some(maybe_branch) = worktree_branch.as_ref() {
        let mut command = process::Command::new("git");
        command.args(["worktree", "add"]);

        match maybe_branch {
            Some(branch) => command.arg(tmp_dir.path()).arg(branch),
            None => command.arg("-d").arg(tmp_dir.path()),
        };

        ensure!(
            command.status().context("Could not start git")?.success(),
            "Cannot create working tree"
        );
    } else if let Some(url) = git {
        let mut command = process::Command::new("git");
        command.arg("clone").arg(url).arg(&tmp_dir.as_ref());

        match git_repo_depth {
            Some(Depth::Active(false)) => {}
            None | Some(Depth::Active(true)) => {
                command.arg("--depth").arg("1");
            }
            Some(Depth::Level(level)) => {
                command.arg("--depth").arg(level.to_string());
            }
        };

        ensure!(
            command.status().context("Could not start git")?.success(),
            "Cannot clone repository"
        );
    } else {
        let mut command = process::Command::new("cargo");
        command
            .current_dir(&tmp_dir)
            .args(["init", "--name", project_name.as_str()]);

        if lib {
            command.arg("--lib");
        }

        if let Some(arg) = vcs {
            command.args(["--vcs", arg.as_str()]);
        }

        ensure!(
            command.status().context("Could not start cargo")?.success(),
            "Cargo command failed"
        );
    }

    Ok(tmp_dir)
}

pub fn add_dependencies_to_project(tmp_dir: &Path, dependencies: &[Dependency]) -> Result<()> {
    let mut toml = fs::OpenOptions::new()
        .append(true)
        .open(tmp_dir.join("Cargo.toml"))?;
    for dependency in dependencies.iter() {
        match dependency {
            Dependency::CrateIo {
                name: s,
                version: v,
            } => match &v {
                Some(version) => writeln!(toml, "{} = \"{}\"", s, version)?,
                None => writeln!(toml, "{} = \"*\"", s)?,
            },
            Dependency::Repository {
                name,
                url,
                branch,
                rev,
            } => {
                write!(toml, "{name} = {{ git = {url:?}", name = name, url = url)?;
                if let Some(branch) = branch {
                    write!(toml, ", branch = {:?}", branch)?;
                }
                if let Some(rev) = rev {
                    write!(toml, ", rev = {:?}", rev)?;
                }
                writeln!(toml, " }}")?;
            }
        }
    }

    Ok(())
}

pub fn generate_delete_file(tmp_dir: &Path) -> Result<PathBuf> {
    let delete_file = tmp_dir.join("TO_DELETE");
    fs::write(
        &delete_file,
        "Delete this file if you want to preserve this project",
    )?;

    Ok(delete_file)
}

pub fn start_shell(config: &Config, tmp_dir: &Path) -> Result<()> {
    let mut shell_process = match config.editor {
        None => process::Command::new(get_shell()),
        Some(ref editor) => {
            let mut ide_process = process::Command::new(editor);
            ide_process
                .args(config.editor_args.iter().flatten())
                .arg(tmp_dir);
            ide_process
        }
    };

    if env::var("CARGO_TARGET_DIR").is_err() {
        if let Some(path) = &config.cargo_target_dir {
            shell_process.env("CARGO_TARGET_DIR", path);
        }
    }

    shell_process
        .current_dir(&tmp_dir)
        .status()
        .context("Cannot start shell")?;

    #[cfg(windows)]
    if config.editor.is_some() {
        unsafe {
            cargo_temp_bindings::Windows::Win32::SystemServices::FreeConsole();
        }
    }

    Ok(())
}

pub fn clean_up(
    delete_file: PathBuf,
    tmp_dir: TempDir,
    worktree_branch: Option<Option<String>>,
) -> Result<()> {
    if !delete_file.exists() {
        println!(
            "Project directory preserved at: {}",
            tmp_dir.into_path().display()
        );
    } else if worktree_branch.is_some() {
        let mut command = process::Command::new("git");
        command
            .args(["worktree", "remove"])
            .arg(&tmp_dir.path())
            .arg("--force");
        ensure!(
            command.status().context("Could not start git")?.success(),
            "Cannot remove working tree"
        );
    }

    Ok(())
}

pub fn get_shell() -> String {
    #[cfg(unix)]
    {
        env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
    }

    #[cfg(windows)]
    {
        env::var("COMSPEC").unwrap_or_else(|_| "cmd".to_string())
    }
}