opensass 0.0.6

🧩 A CLI to interact with the OpenSASS ecosystem.
Documentation
use crate::utils::fs::{copy_relevant_files, update_cargo_toml, update_pub_file};
use crates_io_api::SyncClient;
use flate2::read::GzDecoder;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::blocking::get;
use std::{fs, io::Cursor, path::Path};
use tar::Archive;
use tempfile::tempdir;
use tracing::info;

pub fn run_add(crate_name: &str, feature: &str, no_cum: bool) -> anyhow::Result<()> {
    let client = SyncClient::new(
        "opensass (via crates_io_api)",
        std::time::Duration::from_millis(1000),
    )?;

    let spinner = ProgressBar::new_spinner();
    spinner.set_style(
        ProgressStyle::default_spinner()
            .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ")
            .template("{spinner:.cyan} {msg}")
            .expect("valid spinner"),
    );

    spinner.set_message(format!("🔍 Fetching crate: {crate_name}"));
    let crate_info = client.get_crate(crate_name)?;
    let latest_version = &crate_info.versions[0].num;
    spinner.set_message(format!("📦 Found latest version: {latest_version}"));

    let dir = tempdir()?;
    let url = format!(
        "https://crates.io/api/v1/crates/{}/{}/download",
        crate_name, latest_version
    );

    spinner.set_message("⬇️ Downloading crate...");
    let response = get(&url)?;
    let bytes = response.bytes()?;

    spinner.set_message("📦 Unpacking crate...");
    let tar = GzDecoder::new(Cursor::new(bytes));
    let mut archive = Archive::new(tar);
    archive.unpack(&dir)?;

    let extracted_path = fs::read_dir(&dir)?
        .filter_map(Result::ok)
        .find(|entry| entry.path().is_dir())
        .map(|entry| entry.path())
        .ok_or_else(|| anyhow::anyhow!("Failed to locate unpacked crate folder"))?;

    let source_path = extracted_path.join("src");
    let current_project_src = Path::new("src");

    let new_crate_name = crate_name.replace('-', "_");

    spinner.set_message("📁 Copying relevant source files...");
    let _ = copy_relevant_files(
        &source_path,
        current_project_src,
        &new_crate_name,
        feature,
        no_cum,
    )?;

    spinner.set_message("🧩 Updating lib.rs...");
    update_pub_file(
        current_project_src.join("lib.rs"),
        &[new_crate_name.to_string()],
    )?;

    spinner.set_message("🛠️ Updating Cargo.toml...");
    update_cargo_toml(
        extracted_path.join("Cargo.toml"),
        Path::new("Cargo.toml"),
        feature,
    )?;

    spinner.finish_with_message("✅ Component added successfully!");

    info!(
        "Component `{}` with feature `{}` added to project",
        crate_name, feature
    );
    Ok(())
}