1use std::collections::HashMap;
4use std::path::Path;
5
6use crate::{templates, HookConfig, Result, ShellType};
7
8#[derive(Debug, Clone)]
10pub struct HookGenerator {
11 config: HookConfig,
12}
13
14impl HookGenerator {
15 pub fn new(config: HookConfig) -> Self {
17 Self { config }
18 }
19
20 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 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 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 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 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 fn render_template(&self, template: &str, shell_type: ShellType) -> Result<String> {
106 let mut context = HashMap::new();
107
108 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 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 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 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}