bendis 0.5.12

A patch tool for Bender to work better in HERIS project
mod commands;
mod converter;
mod utils;

use anyhow::Result;
use clap::{CommandFactory, Parser, Subcommand};
use utils::config::BendisConfig;
use utils::welcome;

const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Parser)]
#[command(name = "bendis")]
#[command(about = "A wrapper and patch tool for Bender to work better in Heris project", long_about = None)]
#[command(version = VERSION)]
#[command(after_help = "Hardening:\n  bendis update --hard    Prepare and activate RTL through AegisRTL\n\nOther Bender commands are passed through by Bendis.")]
struct Cli {
    #[command(subcommand)]
    command: Option<Commands>,

    /// Pass all other arguments to bender
    #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
    args: Vec<String>,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize bendis project structure
    Init,
    /// Update dependencies with URL conversion
    Update {
        /// Prepare and activate RTL through AegisRTL after updating dependencies
        #[arg(long)]
        hard: bool,
    },
    /// Edit bendis configuration file
    Config,
}

fn main() -> Result<()> {
    // Parse first so help and version do not depend on user configuration.
    let cli = Cli::parse();

    let mut config = BendisConfig::load()?;
    let is_first_run = config.first_run == 1;
    let current_version = VERSION;
    let stored_version = config.version.as_str();
    let is_version_changed = !stored_version.is_empty() && stored_version != current_version;

    // Determine if we should show welcome message
    let should_show_welcome = is_first_run || is_version_changed;

    // Show welcome message for first run or version update (non-version commands)
    if should_show_welcome {
        if is_version_changed {
            welcome::show_welcome_with_version(Some(stored_version));
        } else {
            welcome::show_welcome();
        }
        config.first_run = 0;
        config.version = current_version.to_string();
        config.save()?;
        welcome::show_separator();
    }

    match cli.command {
        Some(Commands::Init) => {
            commands::init::run()?;
        }
        Some(Commands::Update { hard }) => {
            commands::update::run(hard)?;
        }
        Some(Commands::Config) => {
            commands::config::run()?;
        }
        None => {
            // Pass through to bender
            if !cli.args.is_empty() {
                commands::passthrough::run(&cli.args)?;
            } else {
                Cli::command().print_help()?;
                println!();
            }
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{Cli, Commands};
    use clap::{CommandFactory, Parser};

    #[test]
    fn update_accepts_hard_flag() {
        let cli = Cli::try_parse_from(["bendis", "update", "--hard"])
            .expect("update --hard should be accepted");

        assert!(matches!(cli.command, Some(Commands::Update { hard: true })));
    }

    #[test]
    fn root_help_mentions_hard_update_command() {
        let help = Cli::command().render_long_help().to_string();

        assert!(help.contains("bendis update --hard"));
    }

    #[test]
    fn update_help_describes_hard_flag() {
        let command = Cli::command();
        let update = command
            .find_subcommand("update")
            .expect("update subcommand should exist");
        let help = update.clone().render_long_help().to_string();

        assert!(help.contains("--hard"));
        assert!(help.contains("AegisRTL"));
    }
}