cstats-hook 0.1.1

Hook generation for cstats shell integration
Documentation
//! Hook generator implementation

use std::collections::HashMap;
use std::path::Path;

use crate::{templates, HookConfig, Result, ShellType};

/// Hook generator for creating shell integration scripts
#[derive(Debug, Clone)]
pub struct HookGenerator {
    config: HookConfig,
}

impl HookGenerator {
    /// Create a new hook generator with the given configuration
    pub fn new(config: HookConfig) -> Self {
        Self { config }
    }

    /// Create a hook generator with default configuration for the specified shell
    pub fn for_shell(shell_type: ShellType) -> Self {
        let config = HookConfig {
            shell_type: shell_type.to_string(),
            default_source: Some(shell_type.to_string()),
            ..Default::default()
        };
        Self::new(config)
    }

    /// Generate a hook script for the configured shell
    pub fn generate(&self) -> Result<String> {
        let shell_type: ShellType = self.config.shell_type.parse()?;

        let template = match shell_type {
            ShellType::Bash => templates::BASH_TEMPLATE,
            ShellType::Zsh => templates::ZSH_TEMPLATE,
            ShellType::Fish => templates::FISH_TEMPLATE,
            ShellType::PowerShell => templates::POWERSHELL_TEMPLATE,
        };

        self.render_template(template, shell_type)
    }

    /// Generate and write a hook script to a file
    pub async fn generate_to_file(&self, output_path: &Path) -> Result<()> {
        let content = self.generate()?;
        tokio::fs::write(output_path, content).await?;
        Ok(())
    }

    /// Get the recommended installation path for the hook
    pub fn get_install_path(&self) -> Result<String> {
        let shell_type: ShellType = self.config.shell_type.parse()?;

        let path = match shell_type {
            ShellType::Bash => "~/.bashrc",
            ShellType::Zsh => "~/.zshrc",
            ShellType::Fish => "~/.config/fish/config.fish",
            ShellType::PowerShell => "$PROFILE",
        };

        Ok(path.to_string())
    }

    /// Get installation instructions for the hook
    pub fn get_install_instructions(&self) -> Result<String> {
        let shell_type: ShellType = self.config.shell_type.parse()?;
        let install_path = self.get_install_path()?;

        let instructions = match shell_type {
            ShellType::Bash | ShellType::Zsh => {
                format!(
                    "Add the generated hook to your {} file:\n\n\
                    echo '# Load cstats hook' >> {}\n\
                    cat hook.{} >> {}\n\n\
                    Then restart your shell or run: source {}",
                    shell_type, install_path, shell_type, install_path, install_path
                )
            }
            ShellType::Fish => {
                format!(
                    "Add the generated hook to your Fish configuration:\n\n\
                    mkdir -p ~/.config/fish\n\
                    echo '# Load cstats hook' >> {}\n\
                    cat hook.fish >> {}\n\n\
                    Then restart your shell.",
                    install_path, install_path
                )
            }
            ShellType::PowerShell => "Add the generated hook to your PowerShell profile:\n\n\
                # Create profile if it doesn't exist\n\
                if (!(Test-Path -Path $PROFILE)) {\n\
                    New-Item -ItemType File -Path $PROFILE -Force\n\
                }\n\n\
                # Add hook content to profile\n\
                Add-Content -Path $PROFILE -Value (Get-Content hook.ps1)\n\n\
                Then restart PowerShell."
                .to_string(),
        };

        Ok(instructions)
    }

    /// Render a template with the current configuration
    fn render_template(&self, template: &str, shell_type: ShellType) -> Result<String> {
        let mut context = HashMap::new();

        // Basic context variables
        context.insert("shell_type", shell_type.to_string());
        context.insert(
            "default_source",
            self.config
                .default_source
                .as_deref()
                .unwrap_or(&shell_type.to_string())
                .to_string(),
        );
        context.insert(
            "cstats_path",
            self.config
                .cstats_path
                .as_deref()
                .unwrap_or("cstats")
                .to_string(),
        );
        context.insert("auto_collect_all", self.config.auto_collect_all.to_string());

        // Auto commands list
        let auto_commands = if self.config.auto_commands.is_empty() {
            "# No auto commands configured".to_string()
        } else {
            self.config
                .auto_commands
                .iter()
                .map(|cmd| {
                    format!(
                        "# alias {}='{} collect --source {{}} --command {}'",
                        cmd,
                        context.get("cstats_path").unwrap(),
                        cmd
                    )
                })
                .collect::<Vec<_>>()
                .join("\n")
        };
        context.insert("auto_commands", auto_commands);

        // Environment variables
        let env_vars = if self.config.env_vars.is_empty() {
            "# No environment variables configured".to_string()
        } else {
            self.config
                .env_vars
                .iter()
                .map(|(k, v)| match shell_type {
                    ShellType::Fish => format!("set -gx {} {}", k, v),
                    ShellType::PowerShell => format!("$env:{} = '{}'", k, v),
                    _ => format!("export {}={}", k, v),
                })
                .collect::<Vec<_>>()
                .join("\n")
        };
        context.insert("env_vars", env_vars);

        // Simple template replacement
        let mut result = template.to_string();
        for (key, value) in context {
            let placeholder = format!("{{{{{}}}}}", key);
            result = result.replace(&placeholder, &value);
        }

        Ok(result)
    }
}

impl Default for HookGenerator {
    fn default() -> Self {
        Self::for_shell(ShellType::Bash)
    }
}