Skip to main content

patina/
session.rs

1/// Session management for Patina projects
2///
3/// This module provides project discovery functionality for Patina.
4/// The original pattern staging system (add/commit workflow with session.json)
5/// has been replaced by the git-aware navigation system that automatically
6/// tracks patterns through their git lifecycle.
7///
8/// The SessionManager now focuses on essential project location services
9/// used by various Patina commands.
10use anyhow::Result;
11use std::path::PathBuf;
12
13/// Manages session context for Patina projects
14pub struct SessionManager;
15
16impl SessionManager {
17    /// Find the root of a Patina project by walking up the directory tree
18    /// looking for a .patina directory.
19    ///
20    /// This is used by commands like `patina navigate` and `patina doctor`
21    /// to locate the project configuration and layer directories.
22    ///
23    /// # Returns
24    /// - `Ok(PathBuf)` - The absolute path to the project root
25    /// - `Err` - If not in a Patina project directory
26    ///
27    /// # Example
28    /// ```no_run
29    /// # use anyhow::Result;
30    /// use patina::session::SessionManager;
31    ///
32    /// # fn main() -> Result<()> {
33    /// let project_root = SessionManager::find_project_root()?;
34    /// let layer_path = project_root.join("layer");
35    /// # Ok(())
36    /// # }
37    /// ```
38    pub fn find_project_root() -> Result<PathBuf> {
39        let mut current = std::env::current_dir()?;
40
41        loop {
42            if current.join(".patina").exists() {
43                return Ok(current);
44            }
45
46            if let Some(parent) = current.parent() {
47                current = parent.to_path_buf();
48            } else {
49                anyhow::bail!("Not in a Patina project directory");
50            }
51        }
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use std::fs;
59    use tempfile::TempDir;
60
61    #[test]
62    fn test_find_project_root_from_subdirectory() {
63        // Create a temporary project structure
64        let temp_dir = TempDir::new().unwrap();
65        let patina_dir = temp_dir.path().join(".patina");
66        fs::create_dir(&patina_dir).unwrap();
67
68        // Create a subdirectory and navigate to it
69        let sub_dir = temp_dir.path().join("src").join("commands");
70        fs::create_dir_all(&sub_dir).unwrap();
71
72        // Use a guard to ensure directory is restored even on panic
73        struct DirGuard {
74            original: Option<PathBuf>,
75        }
76        impl Drop for DirGuard {
77            fn drop(&mut self) {
78                if let Some(ref path) = self.original {
79                    let _ = std::env::set_current_dir(path);
80                }
81            }
82        }
83
84        let _guard = DirGuard {
85            original: std::env::current_dir().ok(),
86        };
87
88        std::env::set_current_dir(&sub_dir).unwrap();
89
90        // Should find the project root from the subdirectory
91        let found_root = SessionManager::find_project_root().unwrap();
92        assert_eq!(
93            found_root.canonicalize().unwrap(),
94            temp_dir.path().canonicalize().unwrap()
95        );
96    }
97
98    #[test]
99    fn test_find_project_root_not_in_project() {
100        // Create a directory without .patina
101        let temp_dir = TempDir::new().unwrap();
102
103        // Use a guard to ensure directory is restored even on panic
104        struct DirGuard {
105            original: Option<PathBuf>,
106        }
107        impl Drop for DirGuard {
108            fn drop(&mut self) {
109                if let Some(ref path) = self.original {
110                    let _ = std::env::set_current_dir(path);
111                }
112            }
113        }
114
115        let _guard = DirGuard {
116            original: std::env::current_dir().ok(),
117        };
118
119        std::env::set_current_dir(temp_dir.path()).unwrap();
120
121        // Should return an error when not in a Patina project
122        let result = SessionManager::find_project_root();
123        assert!(result.is_err());
124        assert!(result
125            .unwrap_err()
126            .to_string()
127            .contains("Not in a Patina project directory"));
128    }
129
130    #[test]
131    fn test_find_project_root_at_root() {
132        // Create a project at the root level
133        let temp_dir = TempDir::new().unwrap();
134        let patina_dir = temp_dir.path().join(".patina");
135        fs::create_dir(&patina_dir).unwrap();
136
137        // Use a guard to ensure directory is restored even on panic
138        struct DirGuard {
139            original: Option<PathBuf>,
140        }
141        impl Drop for DirGuard {
142            fn drop(&mut self) {
143                if let Some(ref path) = self.original {
144                    let _ = std::env::set_current_dir(path);
145                }
146            }
147        }
148
149        let _guard = DirGuard {
150            original: std::env::current_dir().ok(),
151        };
152
153        std::env::set_current_dir(temp_dir.path()).unwrap();
154
155        // Should find the project root when already at root
156        let found_root = SessionManager::find_project_root().unwrap();
157        assert_eq!(
158            found_root.canonicalize().unwrap(),
159            temp_dir.path().canonicalize().unwrap()
160        );
161    }
162}