leankg 0.7.0

Lightweight Knowledge Graph for AI-Assisted Development
Documentation
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Registry {
    pub version: u32,
    pub repos: HashMap<String, RepoEntry>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepoEntry {
    pub path: String,
    pub last_indexed: Option<String>,
    pub element_count: Option<usize>,
}

impl Default for Registry {
    fn default() -> Self {
        Self {
            version: 1,
            repos: HashMap::new(),
        }
    }
}

impl Registry {
    pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
        let path = Self::registry_path()?;
        if !path.exists() {
            return Ok(Registry::default());
        }
        let content = fs::read_to_string(&path)?;
        let registry: Registry = serde_json::from_str(&content)?;
        Ok(registry)
    }

    pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
        let path = Self::registry_path()?;
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        let content = serde_json::to_string_pretty(self)?;
        fs::write(&path, content)?;
        Ok(())
    }

    pub fn register(
        &mut self,
        name: String,
        path: String,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let abs_path = if PathBuf::from(&path).is_absolute() {
            path.clone()
        } else {
            std::env::current_dir()
                .map(|p| p.join(&path).to_string_lossy().to_string())
                .unwrap_or(path)
        };

        let entry = RepoEntry {
            path: abs_path,
            last_indexed: None,
            element_count: None,
        };
        self.repos.insert(name, entry);
        self.save()?;
        Ok(())
    }

    pub fn unregister(&mut self, name: &str) -> Result<(), Box<dyn std::error::Error>> {
        self.repos.remove(name);
        self.save()?;
        Ok(())
    }

    pub fn get_repo(&self, name: &str) -> Option<&RepoEntry> {
        self.repos.get(name)
    }

    pub fn list_repos(&self) -> Vec<(&String, &RepoEntry)> {
        self.repos.iter().collect()
    }

    pub fn update_last_indexed(
        &mut self,
        name: &str,
        timestamp: String,
        element_count: usize,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if let Some(entry) = self.repos.get_mut(name) {
            entry.last_indexed = Some(timestamp);
            entry.element_count = Some(element_count);
            self.save()?;
        }
        Ok(())
    }

    fn registry_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
        let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
        Ok(PathBuf::from(home).join(".leankg").join("registry.json"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_registry_default() {
        let registry = Registry::default();
        assert_eq!(registry.version, 1);
        assert!(registry.repos.is_empty());
    }

    #[test]
    fn test_registry_register_and_unregister() {
        let temp_dir = TempDir::new().unwrap();
        let registry_path = temp_dir.path().join("registry.json");

        std::env::set_var("HOME", temp_dir.path());

        let mut registry = Registry::default();
        registry
            .register("test-repo".to_string(), "/path/to/test".to_string())
            .unwrap();

        assert!(registry.repos.contains_key("test-repo"));
        assert_eq!(
            registry.get_repo("test-repo").unwrap().path,
            "/path/to/test"
        );

        registry.unregister("test-repo").unwrap();
        assert!(!registry.repos.contains_key("test-repo"));

        std::env::remove_var("HOME");
    }
}