makectl 0.2.0

Generate and manage targets in your Makefiles
use std::fs;
use std::path::Path;

use anyhow::{bail, Result};
use colored::Colorize;
use inquire::{Confirm, MultiSelect, Select};

use crate::cli::Language;
use crate::templates::TemplateRegistry;

pub fn execute(file: &str, lang: Option<Language>, force: bool, interactive: bool) -> Result<()> {
    if Path::new(file).exists() && !force {
        bail!(
            "Makefile already exists at {} (use --force to overwrite)",
            file
        );
    }

    let registry = TemplateRegistry::new();

    if interactive {
        return execute_interactive(file, &registry);
    }

    let mut content = String::new();
    content.push_str("# Generated by makectl\n");
    content.push_str("# https://github.com/rochacbruno/makectl\n\n");
    content.push_str(".DEFAULT_GOAL := help\n\n");

    let help = registry.get("generic/help").unwrap();
    content.push_str(&format!(
        "# MAKECTL MANAGED START help generic/help\n{}\n# MAKECTL MANAGED END help\n",
        help.content
    ));

    if let Some(ref lang) = lang {
        let lang_str = lang.as_str();
        let templates = registry.list_by_language(lang_str);
        for t in &templates {
            if t.id == "generic/help" {
                continue;
            }
            let target_name = &t.name;
            content.push_str(&format!(
                "\n# MAKECTL MANAGED START {} {}\n{}\n# MAKECTL MANAGED END {}\n",
                target_name, t.id, t.content, target_name
            ));
        }

        if lang_str != "generic" {
            let generic_templates = registry.list_by_language("generic");
            for t in &generic_templates {
                if t.id == "generic/help" {
                    continue;
                }
                let target_name = &t.name;
                content.push_str(&format!(
                    "\n# MAKECTL MANAGED START {} {}\n{}\n# MAKECTL MANAGED END {}\n",
                    target_name, t.id, t.content, target_name
                ));
            }
        }
    }

    fs::write(file, &content)?;

    println!("{} {}", "Created".green().bold(), file);
    if let Some(ref lang) = lang {
        println!(
            "  Included {} templates and generic utilities",
            lang.as_str()
        );
    }
    println!(
        "  Run {} to see available targets",
        "make help".cyan().bold()
    );

    Ok(())
}

fn execute_interactive(file: &str, registry: &TemplateRegistry) -> Result<()> {
    let lang_options = vec!["Python", "Rust", "Go", "Node", "Generic", "None (minimal)"];
    let lang_choice = Select::new("Project language?", lang_options).prompt()?;

    let lang_str = match lang_choice {
        "Python" => Some("python"),
        "Rust" => Some("rust"),
        "Go" => Some("go"),
        "Node" => Some("node"),
        "Generic" => Some("generic"),
        _ => None,
    };

    let mut candidates: Vec<&crate::templates::Template> = Vec::new();
    if let Some(lang) = lang_str {
        candidates.extend(registry.list_by_language(lang));
        if lang != "generic" {
            candidates.extend(registry.list_by_language("generic"));
        }
    } else {
        candidates.extend(registry.list_by_language("generic"));
    }

    let display_items: Vec<String> = candidates
        .iter()
        .map(|t| format!("{} - {}", t.id, t.description))
        .collect();

    let defaults: Vec<usize> = (0..display_items.len()).collect();

    let selected = MultiSelect::new("Select templates to include:", display_items)
        .with_default(&defaults)
        .prompt()?;

    let selected_ids: Vec<&str> = selected
        .iter()
        .filter_map(|s| s.split(" - ").next())
        .collect();

    let include_default_goal = Confirm::new("Set .DEFAULT_GOAL := help?")
        .with_default(true)
        .prompt()?;

    let mut content = String::new();
    content.push_str("# Generated by makectl\n");
    content.push_str("# https://github.com/rochacbruno/makectl\n\n");

    if include_default_goal {
        content.push_str(".DEFAULT_GOAL := help\n\n");
    }

    for id in &selected_ids {
        if let Some(t) = registry.get(id) {
            let target_name = &t.name;
            content.push_str(&format!(
                "# MAKECTL MANAGED START {} {}\n{}\n# MAKECTL MANAGED END {}\n\n",
                target_name, t.id, t.content, target_name
            ));
        }
    }

    fs::write(file, &content)?;

    println!("{} {}", "Created".green().bold(), file);
    println!("  Included {} template(s)", selected_ids.len());
    println!(
        "  Run {} to see available targets",
        "make help".cyan().bold()
    );

    Ok(())
}