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