cstats_hook/
generator.rs

1//! Hook generator implementation
2
3use std::collections::HashMap;
4use std::path::Path;
5
6use crate::{templates, HookConfig, Result, ShellType};
7
8/// Hook generator for creating shell integration scripts
9#[derive(Debug, Clone)]
10pub struct HookGenerator {
11    config: HookConfig,
12}
13
14impl HookGenerator {
15    /// Create a new hook generator with the given configuration
16    pub fn new(config: HookConfig) -> Self {
17        Self { config }
18    }
19
20    /// Create a hook generator with default configuration for the specified shell
21    pub fn for_shell(shell_type: ShellType) -> Self {
22        let config = HookConfig {
23            shell_type: shell_type.to_string(),
24            default_source: Some(shell_type.to_string()),
25            ..Default::default()
26        };
27        Self::new(config)
28    }
29
30    /// Generate a hook script for the configured shell
31    pub fn generate(&self) -> Result<String> {
32        let shell_type: ShellType = self.config.shell_type.parse()?;
33
34        let template = match shell_type {
35            ShellType::Bash => templates::BASH_TEMPLATE,
36            ShellType::Zsh => templates::ZSH_TEMPLATE,
37            ShellType::Fish => templates::FISH_TEMPLATE,
38            ShellType::PowerShell => templates::POWERSHELL_TEMPLATE,
39        };
40
41        self.render_template(template, shell_type)
42    }
43
44    /// Generate and write a hook script to a file
45    pub async fn generate_to_file(&self, output_path: &Path) -> Result<()> {
46        let content = self.generate()?;
47        tokio::fs::write(output_path, content).await?;
48        Ok(())
49    }
50
51    /// Get the recommended installation path for the hook
52    pub fn get_install_path(&self) -> Result<String> {
53        let shell_type: ShellType = self.config.shell_type.parse()?;
54
55        let path = match shell_type {
56            ShellType::Bash => "~/.bashrc",
57            ShellType::Zsh => "~/.zshrc",
58            ShellType::Fish => "~/.config/fish/config.fish",
59            ShellType::PowerShell => "$PROFILE",
60        };
61
62        Ok(path.to_string())
63    }
64
65    /// Get installation instructions for the hook
66    pub fn get_install_instructions(&self) -> Result<String> {
67        let shell_type: ShellType = self.config.shell_type.parse()?;
68        let install_path = self.get_install_path()?;
69
70        let instructions = match shell_type {
71            ShellType::Bash | ShellType::Zsh => {
72                format!(
73                    "Add the generated hook to your {} file:\n\n\
74                    echo '# Load cstats hook' >> {}\n\
75                    cat hook.{} >> {}\n\n\
76                    Then restart your shell or run: source {}",
77                    shell_type, install_path, shell_type, install_path, install_path
78                )
79            }
80            ShellType::Fish => {
81                format!(
82                    "Add the generated hook to your Fish configuration:\n\n\
83                    mkdir -p ~/.config/fish\n\
84                    echo '# Load cstats hook' >> {}\n\
85                    cat hook.fish >> {}\n\n\
86                    Then restart your shell.",
87                    install_path, install_path
88                )
89            }
90            ShellType::PowerShell => "Add the generated hook to your PowerShell profile:\n\n\
91                # Create profile if it doesn't exist\n\
92                if (!(Test-Path -Path $PROFILE)) {\n\
93                    New-Item -ItemType File -Path $PROFILE -Force\n\
94                }\n\n\
95                # Add hook content to profile\n\
96                Add-Content -Path $PROFILE -Value (Get-Content hook.ps1)\n\n\
97                Then restart PowerShell."
98                .to_string(),
99        };
100
101        Ok(instructions)
102    }
103
104    /// Render a template with the current configuration
105    fn render_template(&self, template: &str, shell_type: ShellType) -> Result<String> {
106        let mut context = HashMap::new();
107
108        // Basic context variables
109        context.insert("shell_type", shell_type.to_string());
110        context.insert(
111            "default_source",
112            self.config
113                .default_source
114                .as_deref()
115                .unwrap_or(&shell_type.to_string())
116                .to_string(),
117        );
118        context.insert(
119            "cstats_path",
120            self.config
121                .cstats_path
122                .as_deref()
123                .unwrap_or("cstats")
124                .to_string(),
125        );
126        context.insert("auto_collect_all", self.config.auto_collect_all.to_string());
127
128        // Auto commands list
129        let auto_commands = if self.config.auto_commands.is_empty() {
130            "# No auto commands configured".to_string()
131        } else {
132            self.config
133                .auto_commands
134                .iter()
135                .map(|cmd| {
136                    format!(
137                        "# alias {}='{} collect --source {{}} --command {}'",
138                        cmd,
139                        context.get("cstats_path").unwrap(),
140                        cmd
141                    )
142                })
143                .collect::<Vec<_>>()
144                .join("\n")
145        };
146        context.insert("auto_commands", auto_commands);
147
148        // Environment variables
149        let env_vars = if self.config.env_vars.is_empty() {
150            "# No environment variables configured".to_string()
151        } else {
152            self.config
153                .env_vars
154                .iter()
155                .map(|(k, v)| match shell_type {
156                    ShellType::Fish => format!("set -gx {} {}", k, v),
157                    ShellType::PowerShell => format!("$env:{} = '{}'", k, v),
158                    _ => format!("export {}={}", k, v),
159                })
160                .collect::<Vec<_>>()
161                .join("\n")
162        };
163        context.insert("env_vars", env_vars);
164
165        // Simple template replacement
166        let mut result = template.to_string();
167        for (key, value) in context {
168            let placeholder = format!("{{{{{}}}}}", key);
169            result = result.replace(&placeholder, &value);
170        }
171
172        Ok(result)
173    }
174}
175
176impl Default for HookGenerator {
177    fn default() -> Self {
178        Self::for_shell(ShellType::Bash)
179    }
180}