cargo-auto 2026.306.1319

Automation tasks coded in Rust language for the workflow of Rust projects
Documentation
// template_new_cli_mod.rs

//! Template for new_cli.
//!
//! The template is downloaded from codeberg:  
//! <https://codeberg.org/automation-tasks-rs/cargo_auto_template_new_cli/releases/vx.x.x/download/template.tar.gz>

use crate::{pos, utils_mod::OptionLogNone, ResultLogError, GREEN, RED, RESET, YELLOW};

/// Creates a new Rust project from template.
pub fn new_cli(remote_repo: Option<String>) -> anyhow::Result<()> {
    let Some(remote_repo) = remote_repo else {
        println!("{RED}Error: First argument 'remote_repo' is missing: `cargo auto new_cli remote_repo`{RESET}");
        println!("{RED}Example: cargo auto new_cli codeberg.org/bestia-dev-work-in-progress/hello_world{RESET}");
        return Ok(());
    };
    let mut splitted = remote_repo.split("/");
    let repo_domain = splitted.next().log_msg(pos!(), "repo_domain missing")?;
    let owner_or_organization = splitted.next().log_msg(pos!(), "owner_or_organization missing")?;
    let rust_project_name = splitted.next().log_msg(pos!(), "rust_project_name missing")?;

    copy_to_files(repo_domain, owner_or_organization, rust_project_name).log(pos!())?;

    println!();
    println!("  {YELLOW}The command `cargo auto new_cli` generated the directory `{rust_project_name}`.{RESET}");
    println!("  {YELLOW}You can open this new Rust project in VSCode:{RESET}",);
    println!("{GREEN}code {rust_project_name}{RESET}");
    println!("  {YELLOW}Then build inside the VSCode terminal with:{RESET}");
    println!("{GREEN}cargo auto build{RESET}");
    println!("  {YELLOW}and follow the detailed instructions.{RESET}");
    Ok(())
}

/// Copy the Rust project into a compressed file.  
fn copy_to_files(repo_domain: &str, owner_or_organization: &str, rust_project_name: &str) -> anyhow::Result<()> {
    let folder_path = std::path::Path::new(rust_project_name);
    if folder_path.exists() {
        anyhow::bail!("{RED}Error: Folder {rust_project_name} already exists! {RESET}");
    }
    std::fs::create_dir_all(folder_path).log(pos!())?;

    // download latest template.tar.gz
    println!("  {YELLOW}Downloading template.tar.gz...{RESET}");
    let file_to_download = "template.tar.gz";
    let path = "./template.tar.gz";

    // I need to get the latest version from codeberg.org.
    // When requesting for the latest release it responds with a redirect with the actual version number.
    let url = "https://codeberg.org/automation-tasks-rs/cargo_auto_template_new_cli/releases/latest";
    let custom_policy = reqwest::redirect::Policy::custom(|attempt| attempt.stop());
    let reqwest_client = reqwest::blocking::ClientBuilder::new()
        .redirect(custom_policy)
        .build()
        .log(pos!())?;
    let http_response = reqwest_client.get(url).send().log(pos!())?;
    let http_location = http_response
        .headers()
        .get(reqwest::header::LOCATION)
        .log_msg(pos!(), "header Location None")?;
    let http_location = http_location.to_str().log(pos!())?;
    // /automation-tasks-rs/cargo_auto_template_new_cli/releases/tag/v0.0.23
    // use the redirect location to create the url for download the latest release file.
    // https://codeberg.org/automation-tasks-rs/cargo_auto_template_new_cli/releases/tag/v0.0.23
    let http_location = http_location.replace("/tag/", "/download/");
    let url = format!("https://codeberg.org{http_location}/{file_to_download}");
    // https://codeberg.org/automation-tasks-rs/cargo_auto_template_new_cli/releases/download/v0.0.23/template.tar.gz

    let reqwest_client = reqwest::blocking::Client::new();
    let http_response = reqwest_client.get(url).send();
    if let Ok(body) = http_response {
        let body = body.bytes().log(pos!())?;
        // Get the content of the response
        std::fs::write(path, &body)
            .or_else(|err| anyhow::bail!("Download failed for {file_to_download} {err}"))
            .log(pos!())?;
    } else {
        anyhow::bail!("Error while retrieving data: {:#?}", http_response.err());
    }

    // decompress into folder_path
    let tar_gz = std::fs::File::open(path).log(pos!())?;
    let tar = flate2::read::GzDecoder::new(tar_gz);
    let mut archive = tar::Archive::new(tar);
    archive.unpack(folder_path).log(pos!())?;
    std::fs::remove_file(path).log(pos!())?;

    // replace placeholders inside text files
    for entry in walkdir::WalkDir::new(folder_path).into_iter().filter_map(Result::ok) {
        if entry.file_type().is_file() {
            // template has only valid utf8 files
            println!("replace: {}", entry.path().to_string_lossy());
            let mut content = std::fs::read_to_string(entry.path()).log(pos!())?;
            content = content.replace(
                "codeberg.org/automation-tasks-rs/cargo_auto_template_new_cli",
                &format!("{repo_domain}/{owner_or_organization}/{rust_project_name}"),
            );
            content = content.replace(
                "automation-tasks-rs/cargo_auto_template_new_cli",
                &format!("{owner_or_organization}/{rust_project_name}"),
            );
            content = content.replace("cargo_auto_template_new_cli", rust_project_name);
            content = content.replace("CARGO_AUTO_TEMPLATE_NEW_CLI", &rust_project_name.to_uppercase());

            std::fs::write(entry.path(), content).log(pos!())?;
        }
    }
    // renaming files is tricky and must be traverse  in reverse.
    let mut traverse_reverse: Vec<walkdir::DirEntry> = walkdir::WalkDir::new(folder_path).into_iter().filter_map(Result::ok).collect();
    traverse_reverse.reverse();
    for entry in traverse_reverse.iter() {
        if entry.file_name() == "cargo_auto_template_new_cli" {
            println!("rename: {}", entry.path().to_string_lossy());
            use anyhow::Context;
            std::fs::rename(
                entry.path(),
                entry.path().parent().context("parent is None")?.join(rust_project_name),
            )
            .log(pos!())?;
        }
    }
    Ok(())
}