devist 0.6.0

Project bootstrap CLI for AI-assisted development. Spin up new projects from templates, manage backends, and keep your codebase comprehensible.
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;

use crate::paths;

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Registry {
    #[serde(default)]
    pub projects: Vec<Project>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
    pub name: String,
    pub path: PathBuf,
    pub template: String,
    pub created_at: String,
}

impl Registry {
    pub fn load() -> Result<Self> {
        let path = paths::registry_file()?;
        if !path.exists() {
            return Ok(Registry::default());
        }
        let text = fs::read_to_string(&path)
            .with_context(|| format!("Failed to read {}", path.display()))?;
        let reg: Registry =
            toml::from_str(&text).with_context(|| format!("Failed to parse {}", path.display()))?;
        Ok(reg)
    }

    pub fn save(&self) -> Result<()> {
        let path = paths::registry_file()?;
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        let text = toml::to_string_pretty(self)?;
        fs::write(&path, text)?;
        Ok(())
    }

    pub fn add(&mut self, project: Project) {
        if let Some(slot) = self.projects.iter_mut().find(|p| p.name == project.name) {
            *slot = project;
        } else {
            self.projects.push(project);
        }
    }

    pub fn remove(&mut self, name: &str) -> bool {
        let before = self.projects.len();
        self.projects.retain(|p| p.name != name);
        before != self.projects.len()
    }

    #[allow(dead_code)]
    pub fn find(&self, name: &str) -> Option<&Project> {
        self.projects.iter().find(|p| p.name == name)
    }
}