1use crate::{Result, VxError};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::env;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct VenvConfig {
13 pub name: String,
14 pub tools: HashMap<String, String>, pub path_entries: Vec<PathBuf>,
16 pub env_vars: HashMap<String, String>,
17}
18
19pub struct VenvManager {
21 venvs_dir: PathBuf,
22}
23
24impl VenvManager {
25 pub fn new() -> Result<Self> {
26 let venvs_dir = if let Ok(vx_home) = env::var("VX_HOME") {
28 PathBuf::from(vx_home).join("venvs")
29 } else if let Some(config_dir) = dirs::config_dir() {
30 config_dir.join("vx").join("venvs")
31 } else {
32 dirs::home_dir()
33 .unwrap_or_else(|| std::env::current_dir().unwrap())
34 .join(".vx")
35 .join("venvs")
36 };
37
38 std::fs::create_dir_all(&venvs_dir).map_err(|e| VxError::Other {
40 message: format!("Failed to create venvs directory: {}", e),
41 })?;
42
43 Ok(Self { venvs_dir })
44 }
45
46 pub fn create(&self, name: &str, tools: &[(String, String)]) -> Result<()> {
48 let venv_dir = self.venvs_dir.join(name);
49
50 if venv_dir.exists() {
51 return Err(VxError::Other {
52 message: format!("Virtual environment '{}' already exists", name),
53 });
54 }
55
56 std::fs::create_dir_all(&venv_dir).map_err(|e| VxError::Other {
58 message: format!("Failed to create venv directory: {}", e),
59 })?;
60 std::fs::create_dir_all(venv_dir.join("bin")).map_err(|e| VxError::Other {
61 message: format!("Failed to create bin directory: {}", e),
62 })?;
63 std::fs::create_dir_all(venv_dir.join("config")).map_err(|e| VxError::Other {
64 message: format!("Failed to create config directory: {}", e),
65 })?;
66
67 let mut tool_versions = HashMap::new();
69 for (tool, version) in tools {
70 tool_versions.insert(tool.clone(), version.clone());
71 }
72
73 let venv_config = VenvConfig {
74 name: name.to_string(),
75 tools: tool_versions,
76 path_entries: vec![venv_dir.join("bin")],
77 env_vars: HashMap::new(),
78 };
79
80 self.save_venv_config(&venv_config)?;
82
83 for (tool, version) in tools {
85 self.install_tool_for_venv(name, tool, version)?;
86 }
87
88 Ok(())
89 }
90
91 pub fn activate(&self, name: &str) -> Result<String> {
93 let venv_config = self.load_venv_config(name)?;
94
95 let mut commands = Vec::new();
97
98 commands.push(format!("export VX_VENV={name}"));
100
101 for path_entry in &venv_config.path_entries {
103 commands.push(format!("export PATH={}:$PATH", path_entry.display()));
104 }
105
106 for (key, value) in &venv_config.env_vars {
108 commands.push(format!("export {key}={value}"));
109 }
110
111 commands.push(format!("export PS1=\"(vx:{name}) $PS1\""));
113
114 Ok(commands.join("\n"))
115 }
116
117 pub fn deactivate() -> String {
119 [
120 "unset VX_VENV",
121 "# Restore original PATH (implementation needed)",
122 "# Restore original PS1 (implementation needed)",
123 ]
124 .join("\n")
125 }
126
127 pub fn list(&self) -> Result<Vec<String>> {
129 let mut venvs = Vec::new();
130
131 if self.venvs_dir.exists() {
132 for entry in std::fs::read_dir(&self.venvs_dir).map_err(|e| VxError::Other {
133 message: format!("Failed to read venvs directory: {}", e),
134 })? {
135 let entry = entry.map_err(|e| VxError::Other {
136 message: format!("Failed to read directory entry: {}", e),
137 })?;
138 if entry
139 .file_type()
140 .map_err(|e| VxError::Other {
141 message: format!("Failed to get file type: {}", e),
142 })?
143 .is_dir()
144 {
145 if let Some(name) = entry.file_name().to_str() {
146 venvs.push(name.to_string());
147 }
148 }
149 }
150 }
151
152 venvs.sort();
153 Ok(venvs)
154 }
155
156 pub fn remove(&self, name: &str) -> Result<()> {
158 let venv_dir = self.venvs_dir.join(name);
159
160 if !venv_dir.exists() {
161 return Err(VxError::Other {
162 message: format!("Virtual environment '{}' does not exist", name),
163 });
164 }
165
166 std::fs::remove_dir_all(&venv_dir).map_err(|e| VxError::Other {
167 message: format!("Failed to remove venv directory: {}", e),
168 })?;
169 Ok(())
170 }
171
172 pub fn current() -> Option<String> {
174 env::var("VX_VENV").ok()
175 }
176
177 pub fn is_active() -> bool {
179 env::var("VX_VENV").is_ok()
180 }
181
182 fn install_tool_for_venv(&self, _venv_name: &str, _tool: &str, _version: &str) -> Result<()> {
184 Ok(())
186 }
187
188 fn save_venv_config(&self, config: &VenvConfig) -> Result<()> {
190 let config_path = self
191 .venvs_dir
192 .join(&config.name)
193 .join("config")
194 .join("venv.toml");
195 let toml_content = toml::to_string_pretty(config).map_err(|e| VxError::Other {
196 message: format!("Failed to serialize venv config: {}", e),
197 })?;
198 std::fs::write(config_path, toml_content).map_err(|e| VxError::Other {
199 message: format!("Failed to write venv config: {}", e),
200 })?;
201 Ok(())
202 }
203
204 fn load_venv_config(&self, name: &str) -> Result<VenvConfig> {
206 let config_path = self.venvs_dir.join(name).join("config").join("venv.toml");
207
208 if !config_path.exists() {
209 return Err(VxError::Other {
210 message: format!("Virtual environment '{}' configuration not found", name),
211 });
212 }
213
214 let content = std::fs::read_to_string(config_path).map_err(|e| VxError::Other {
215 message: format!("Failed to read venv config: {}", e),
216 })?;
217 let config: VenvConfig = toml::from_str(&content).map_err(|e| VxError::Other {
218 message: format!("Failed to parse venv config: {}", e),
219 })?;
220 Ok(config)
221 }
222}
223
224impl Default for VenvManager {
225 fn default() -> Self {
226 Self::new().expect("Failed to create VenvManager")
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_venv_manager_creation() {
236 let manager = VenvManager::new();
237 assert!(manager.is_ok());
238 }
239
240 #[test]
241 fn test_current_venv() {
242 assert!(!VenvManager::is_active());
244 }
245}