thag_rs 0.2.0

A versatile cross-platform playground and REPL for Rust snippets, expressions and programs. Accepts a script file or dynamic options.
/*[toml]
[dependencies]
thag_styling = { version = "0.2, thag-auto", features = ["inquire_theming"] }
*/

/// Fast download of `thag_rs` demo directory (starter kit) with subdirectories.
/// Git `sparse-checkout` approach suggested and written by `ChatGPT`, local directory handling assisted by `Claude`.
///
/// `thag_styling` included
//# Purpose: Prototype for `thag_get_demo_dir`.
//# Categories: crates, prototype, technique, tools
use inquire::{self, set_global_render_config};
use std::error::Error;
use std::fs;
use std::io::Write as _;
use std::path::PathBuf;
use std::process::Command;
use thag_styling::{
    auto_help, file_navigator, help_system::check_help_and_exit, styling::Styleable,
    themed_inquire_config,
};

file_navigator! {}

struct TempCleanupGuard(PathBuf);

impl Drop for TempCleanupGuard {
    fn drop(&mut self) {
        if self.0.exists() {
            let _ = fs::remove_dir_all(&self.0);
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    // Check for help first - automatically extracts from source comments
    let help = auto_help!();
    check_help_and_exit(&help);

    set_global_render_config(themed_inquire_config());

    // 1) Select a target parent directory
    let mut navigator = FileNavigator::new();

    let (dest_dir, demo_dest) = loop {
        println!("Select where you want to save the new `demo` directory");
        let Ok(dest_dir) = select_directory(&mut navigator, false) else {
            println!("\nNo directory selected. Exiting.\n");
            return Ok(());
        };
        // // `select_directory` already handles this.
        // fs::create_dir_all(&dest_dir)?;

        // Check if demo already exists in destination
        let demo_dest = dest_dir.join("demo");
        if demo_dest.exists() {
            println!(
                "\n{}\n",
                format!("Destination already has subdirectory {}", "demo").emphasis()
            );
            continue;
        }
        break (dest_dir, demo_dest);
    };

    // We'll do a temporary clone as a sibling of the destination demo directory
    let temp_clone = dest_dir.join("temp_thag_rs_clone");
    let _cleanup_guard = TempCleanupGuard(temp_clone.clone());

    // 2) Sparse clone into temporary dir
    let status = Command::new("git")
        .args([
            "clone",
            "--depth",
            "1",
            "--branch",
            "main",
            "--filter=blob:none",
            "--sparse",
            "https://github.com/durbanlegend/thag_rs.git",
            temp_clone.to_str().unwrap(),
        ])
        .status()?;
    if !status.success() {
        return Err("git clone failed".into());
    }

    // 3) Set sparse-checkout to include demo/
    let status = Command::new("git")
        .current_dir(&temp_clone)
        .args(["sparse-checkout", "set", "--no-cone", "demo"])
        .status()?;
    if !status.success() {
        return Err("git sparse-checkout failed".into());
    }

    // 4) Exclude demo/proc_macros/target/
    let sparse_file = temp_clone.join(".git/info/sparse-checkout");
    let mut file = fs::OpenOptions::new().append(true).open(&sparse_file)?;
    file.write_all(b"!demo/proc_macros/target/\n")?;

    // 5) Apply updated sparse patterns
    let status = Command::new("git")
        .current_dir(&temp_clone)
        .args(["read-tree", "-mu", "HEAD"])
        .status()?;
    if !status.success() {
        return Err("git read-tree failed".into());
    }

    // 6) Remove .git to leave plain files
    let status = Command::new("rm")
        .args(["-rf", ".git"])
        .current_dir(&temp_clone)
        .status()?;
    if !status.success() {
        return Err("failed to remove .git".into());
    }

    // 7) Move demo/ directory to the user-specified destination
    let demo_src = temp_clone.join("demo");
    fs::rename(&demo_src, &demo_dest)?;

    // 8) Remove the temporary clone directory
    fs::remove_dir_all(&temp_clone)?;

    println!("✅ demo/ downloaded to: {}", demo_dest.display());
    Ok(())
}