1use std::path::PathBuf;
2
3use anyhow::Result;
4
5pub fn expand_tilde(path: &str) -> PathBuf {
6 if let Some(stripped) = path.strip_prefix("~/") {
7 if let Some(home) = dirs::home_dir() {
8 return home.join(stripped);
9 }
10 }
11 if path == "~" {
12 if let Some(home) = dirs::home_dir() {
13 return home;
14 }
15 }
16 PathBuf::from(path)
17}
18
19pub fn config_dir() -> PathBuf {
20 dirs::config_dir()
21 .map(|d| d.join("podbox"))
22 .unwrap_or_else(|| PathBuf::from("~/.config/podbox"))
23}
24
25pub fn find_definition() -> Option<PathBuf> {
26 let new_local = PathBuf::from(".podbox.toml");
27 if new_local.exists() {
28 return Some(new_local);
29 }
30
31 let old_local = PathBuf::from(".podmgr.toml");
32 if old_local.exists() {
33 eprintln!(
34 "Warning: '.podmgr.toml' found. Rename it to '.podbox.toml' to silence this warning."
35 );
36 return Some(old_local);
37 }
38
39 let config_dir = config_dir();
40
41 if config_dir.is_dir() {
42 let mut entries: Vec<_> = std::fs::read_dir(&config_dir)
43 .into_iter()
44 .flatten()
45 .filter_map(|e| e.ok())
46 .filter(|e| {
47 e.path()
48 .extension()
49 .map(|ext| ext == "toml")
50 .unwrap_or(false)
51 })
52 .map(|e| e.path())
53 .collect();
54
55 entries.sort();
56 if !entries.is_empty() {
57 if entries.len() > 1 {
58 eprintln!(
59 "Warning: multiple configuration files found in {}. Selecting '{}' alphabetically. Use --config to specify a different file.",
60 config_dir.display(),
61 entries[0].display()
62 );
63 }
64 return Some(entries.remove(0));
65 }
66 }
67
68 None
69}
70
71pub fn list_configs() -> Vec<PathBuf> {
72 let config_dir = config_dir();
73 if !config_dir.is_dir() {
74 return vec![];
75 }
76 let mut entries: Vec<_> = std::fs::read_dir(&config_dir)
77 .into_iter()
78 .flatten()
79 .filter_map(|e| e.ok())
80 .filter(|e| {
81 e.path()
82 .extension()
83 .map(|ext| ext == "toml")
84 .unwrap_or(false)
85 })
86 .map(|e| e.path())
87 .collect();
88 entries.sort();
89 entries
90}
91
92pub fn active_context_path() -> PathBuf {
93 config_dir().join(".active")
94}
95
96pub fn read_active_context() -> Option<String> {
97 let path = active_context_path();
98 let content = std::fs::read_to_string(&path).ok()?;
99 let name = content.trim().to_string();
100 if name.is_empty() {
101 let _ = std::fs::remove_file(&path);
102 return None;
103 }
104 let config_path = config_dir().join(format!("{}.toml", name));
105 if config_path.exists() {
106 Some(name)
107 } else {
108 let _ = std::fs::remove_file(&path);
109 None
110 }
111}
112
113pub fn write_active_context(name: &str) -> Result<()> {
114 let path = active_context_path();
115 std::fs::create_dir_all(path.parent().unwrap())?;
116 std::fs::write(&path, name)?;
117 Ok(())
118}
119
120pub fn clear_active_context() -> Result<()> {
121 let path = active_context_path();
122 if path.exists() {
123 std::fs::remove_file(&path)?;
124 }
125 Ok(())
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_expand_tilde() {
134 let home = dirs::home_dir().unwrap();
135 assert_eq!(expand_tilde("~/foo"), home.join("foo"));
136 assert_eq!(expand_tilde("~"), home.clone());
137 assert_eq!(expand_tilde("/foo/bar"), PathBuf::from("/foo/bar"));
138 }
139}