Skip to main content

aida_core/
registry.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7/// Represents a project in the registry
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Project {
10    /// Path to the requirements file
11    pub path: String,
12    /// Description of the project
13    pub description: String,
14}
15
16/// Registry of all projects
17#[derive(Debug, Serialize, Deserialize)]
18pub struct Registry {
19    pub projects: HashMap<String, Project>,
20    /// Optional default project name
21    pub default_project: Option<String>,
22}
23
24impl Registry {
25    /// Loads the registry from the provided path
26    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    /// Gets a project by name
35    pub fn get_project(&self, name: &str) -> Option<&Project> {
36        self.projects.get(name)
37    }
38
39    /// Lists all project names
40    pub fn list_projects(&self) -> Vec<&str> {
41        self.projects.keys().map(|k| k.as_str()).collect()
42    }
43
44    /// Registers a new project or updates an existing one
45    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    /// Sets a project as the default
52    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        // Update the default project name
58        self.default_project = Some(name.to_string());
59
60        Ok(())
61    }
62
63    /// Clears the default project setting
64    pub fn clear_default_project(&mut self) {
65        self.default_project = None;
66    }
67
68    /// Gets the default project if set
69    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    /// Save the registry to the specified path
79    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
80        let content = serde_yaml::to_string(&self)?;
81
82        // Ensure parent directories exist
83        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    /// Creates a default registry file if it doesn't exist
94    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(&registry)?;
113
114        // Ensure parent directories exist
115        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
126/// Gets the path to the registry file
127pub fn get_registry_path() -> Result<PathBuf> {
128    // Check if AIDA_REGISTRY_PATH environment variable is set (also support legacy REQ_REGISTRY_PATH)
129    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    // Check for new config file first, fall back to legacy if it exists
139    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        // Use legacy config if it exists and new one doesn't
146        Ok(legacy_config)
147    } else {
148        // Default to new config location for new installations
149        Ok(new_config)
150    }
151}
152
153/// Gets the path to the AIDA config directory (~/.config/aida/)
154pub 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
161/// Gets the path to the templates directory (~/.config/aida/templates/)
162pub fn get_templates_dir() -> Result<PathBuf> {
163    Ok(get_config_dir()?.join("templates"))
164}