1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Project {
10 pub path: String,
12 pub description: String,
14}
15
16#[derive(Debug, Serialize, Deserialize)]
18pub struct Registry {
19 pub projects: HashMap<String, Project>,
20 pub default_project: Option<String>,
22}
23
24impl Registry {
25 pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
27 let content = fs::read_to_string(&path)
28 .with_context(|| format!("Failed to read registry file: {:?}", path.as_ref()))?;
29
30 serde_yaml::from_str(&content)
31 .with_context(|| format!("Failed to parse registry file: {:?}", path.as_ref()))
32 }
33
34 pub fn get_project(&self, name: &str) -> Option<&Project> {
36 self.projects.get(name)
37 }
38
39 pub fn list_projects(&self) -> Vec<&str> {
41 self.projects.keys().map(|k| k.as_str()).collect()
42 }
43
44 pub fn register_project(&mut self, name: String, path: String, description: String) {
46 let project = Project { path, description };
47
48 self.projects.insert(name, project);
49 }
50
51 pub fn set_default_project(&mut self, name: &str) -> Result<()> {
53 if !self.projects.contains_key(name) {
54 anyhow::bail!("Project '{}' not found in registry", name);
55 }
56
57 self.default_project = Some(name.to_string());
59
60 Ok(())
61 }
62
63 pub fn clear_default_project(&mut self) {
65 self.default_project = None;
66 }
67
68 pub fn get_default_project(&self) -> Option<(&str, &Project)> {
70 if let Some(default_name) = &self.default_project {
71 if let Some(project) = self.projects.get(default_name) {
72 return Some((default_name, project));
73 }
74 }
75 None
76 }
77
78 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
80 let content = serde_yaml::to_string(&self)?;
81
82 if let Some(parent) = path.as_ref().parent() {
84 fs::create_dir_all(parent)?;
85 }
86
87 fs::write(&path, content)
88 .with_context(|| format!("Failed to write registry to {:?}", path.as_ref()))?;
89
90 Ok(())
91 }
92
93 pub fn create_default<P: AsRef<Path>>(path: P) -> Result<()> {
95 if path.as_ref().exists() {
96 return Ok(());
97 }
98
99 let mut projects = HashMap::new();
100 projects.insert(
101 "default".to_string(),
102 Project {
103 path: "requirements.yaml".to_string(),
104 description: "Default project".to_string(),
105 },
106 );
107
108 let registry = Registry {
109 projects,
110 default_project: None,
111 };
112 let content = serde_yaml::to_string(®istry)?;
113
114 if let Some(parent) = path.as_ref().parent() {
116 fs::create_dir_all(parent)?;
117 }
118
119 fs::write(&path, content)
120 .with_context(|| format!("Failed to write default registry to {:?}", path.as_ref()))?;
121
122 Ok(())
123 }
124}
125
126pub fn get_registry_path() -> Result<PathBuf> {
128 if let Ok(path) = std::env::var("AIDA_REGISTRY_PATH") {
130 return Ok(PathBuf::from(path));
131 }
132 if let Ok(path) = std::env::var("REQ_REGISTRY_PATH") {
133 return Ok(PathBuf::from(path));
134 }
135
136 let home_dir = dirs::home_dir().context("Failed to determine home directory")?;
137
138 let new_config = home_dir.join(".aida.config");
140 let legacy_config = home_dir.join(".requirements.config");
141
142 if new_config.exists() {
143 Ok(new_config)
144 } else if legacy_config.exists() {
145 Ok(legacy_config)
147 } else {
148 Ok(new_config)
150 }
151}
152
153pub fn get_config_dir() -> Result<PathBuf> {
155 let config_dir = dirs::config_dir()
156 .context("Failed to determine config directory")?
157 .join("aida");
158 Ok(config_dir)
159}
160
161pub fn get_templates_dir() -> Result<PathBuf> {
163 Ok(get_config_dir()?.join("templates"))
164}