Skip to main content

worktree_io/ttl/
registry.rs

1use std::path::{Path, PathBuf};
2use std::time::SystemTime;
3
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6
7/// A workspace entry stored in the registry.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct WorkspaceRecord {
10    /// Absolute path to the worktree directory.
11    pub path: PathBuf,
12    /// When this workspace was first created.
13    #[serde(with = "humantime_serde")]
14    pub created_at: SystemTime,
15}
16
17/// Persistent registry of all known workspaces and their creation timestamps.
18#[derive(Debug, Clone, Serialize, Deserialize, Default)]
19#[serde(default)]
20pub struct WorkspaceRegistry {
21    /// Registered workspaces.
22    #[serde(rename = "workspace")]
23    pub workspace: Vec<WorkspaceRecord>,
24}
25
26impl WorkspaceRegistry {
27    /// Return the path to the workspace registry file
28    /// (`~/.config/worktree/workspaces.toml`).
29    ///
30    /// # Errors
31    ///
32    /// Returns an error if the home directory cannot be determined.
33    pub fn path() -> Result<PathBuf> {
34        let home = dirs::home_dir().context("Could not determine home directory")?;
35        Ok(home
36            .join(".config")
37            .join("worktree")
38            .join("workspaces.toml"))
39    }
40
41    fn load_from(path: &Path) -> Result<Self> {
42        if !path.exists() {
43            return Ok(Self::default());
44        }
45        let content = std::fs::read_to_string(path)
46            .context(format!("Failed to read registry from {}", path.display()))?;
47        toml::from_str(&content).context(format!("Failed to parse registry at {}", path.display()))
48    }
49
50    fn write_to(&self, path: &Path) -> Result<()> {
51        let parent = path.parent().context("registry path has no parent")?;
52        std::fs::create_dir_all(parent)
53            .context(format!("Failed to create config dir {}", parent.display()))?;
54        let content = toml::to_string(self).context("Failed to serialize workspace registry")?;
55        std::fs::write(path, content)
56            .context(format!("Failed to write registry to {}", path.display()))
57    }
58
59    /// Load the registry from disk; returns an empty registry when absent.
60    ///
61    /// # Errors
62    ///
63    /// Returns an error if the file cannot be read or parsed.
64    pub fn load() -> Result<Self> {
65        Self::load_from(&Self::path()?)
66    }
67
68    /// Persist the registry to disk.
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if the directory cannot be created or the file cannot
73    /// be written.
74    pub fn save(&self) -> Result<()> {
75        self.write_to(&Self::path()?)
76    }
77
78    /// Register a workspace path with the current timestamp.
79    ///
80    /// Idempotent: if `path` is already present, the existing entry is left
81    /// unchanged.
82    pub fn register(&mut self, path: PathBuf) {
83        if !self.workspace.iter().any(|r| r.path == path) {
84            self.workspace.push(WorkspaceRecord {
85                path,
86                created_at: SystemTime::now(),
87            });
88        }
89    }
90}
91
92#[cfg(test)]
93#[path = "registry_tests.rs"]
94mod registry_tests;