1use std::path::PathBuf;
9
10pub fn config_dir() -> Option<PathBuf> {
12 dirs::config_dir().map(|d| d.join("perspt"))
13}
14
15pub fn config_file() -> Option<PathBuf> {
17 config_dir().map(|d| d.join("config.toml"))
18}
19
20pub fn policy_dir() -> Option<PathBuf> {
22 config_dir().map(|d| d.join("rules"))
23}
24
25pub fn data_dir() -> Option<PathBuf> {
27 dirs::data_local_dir().map(|d| d.join("perspt"))
28}
29
30pub fn database_path() -> Option<PathBuf> {
32 data_dir().map(|d| d.join("perspt.db"))
33}
34
35pub fn history_file() -> Option<PathBuf> {
37 data_dir().map(|d| d.join("history.txt"))
38}
39
40pub fn legacy_dir() -> Option<PathBuf> {
42 dirs::home_dir()
43 .map(|h| h.join(".perspt"))
44 .filter(|p| p.is_dir())
45}
46
47pub fn check_legacy_migration() {
52 let Some(legacy) = legacy_dir() else {
53 return;
54 };
55
56 let legacy_config = legacy.join("config.toml");
57 let legacy_rules = legacy.join("rules");
58
59 let new_config = config_file();
60 let new_rules = policy_dir();
61
62 if legacy_config.exists() {
63 let target = new_config
64 .as_deref()
65 .map(|p| p.display().to_string())
66 .unwrap_or_else(|| "<config_dir>/perspt/config.toml".into());
67 if new_config.as_ref().is_none_or(|p| !p.exists()) {
68 log::warn!(
69 "Legacy config found at {}. Consider moving it to {}",
70 legacy_config.display(),
71 target
72 );
73 }
74 }
75
76 if legacy_rules.is_dir() {
77 let target = new_rules
78 .as_deref()
79 .map(|p| p.display().to_string())
80 .unwrap_or_else(|| "<config_dir>/perspt/rules/".into());
81 if new_rules.as_ref().is_none_or(|p| !p.is_dir()) {
82 log::warn!(
83 "Legacy rules found at {}. Consider moving them to {}",
84 legacy_rules.display(),
85 target
86 );
87 }
88 }
89}
90
91pub fn resolve_config_file() -> Option<PathBuf> {
94 if let Some(path) = config_file() {
96 if path.exists() {
97 return Some(path);
98 }
99 }
100 legacy_dir()
102 .map(|d| d.join("config.toml"))
103 .filter(|p| p.exists())
104}
105
106pub fn resolve_policy_dir() -> Option<PathBuf> {
109 if let Some(path) = policy_dir() {
111 if path.is_dir() {
112 return Some(path);
113 }
114 }
115 legacy_dir().map(|d| d.join("rules")).filter(|p| p.is_dir())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn config_dir_ends_with_perspt() {
125 if let Some(dir) = config_dir() {
126 assert!(dir.ends_with("perspt"));
127 }
128 }
129
130 #[test]
131 fn config_file_ends_with_config_toml() {
132 if let Some(path) = config_file() {
133 assert_eq!(path.file_name().unwrap(), "config.toml");
134 }
135 }
136
137 #[test]
138 fn policy_dir_ends_with_rules() {
139 if let Some(dir) = policy_dir() {
140 assert!(dir.ends_with("rules"));
141 }
142 }
143
144 #[test]
145 fn data_dir_ends_with_perspt() {
146 if let Some(dir) = data_dir() {
147 assert!(dir.ends_with("perspt"));
148 }
149 }
150
151 #[test]
152 fn database_path_ends_with_db() {
153 if let Some(path) = database_path() {
154 assert_eq!(path.file_name().unwrap(), "perspt.db");
155 }
156 }
157
158 #[test]
159 fn history_file_ends_with_history_txt() {
160 if let Some(path) = history_file() {
161 assert_eq!(path.file_name().unwrap(), "history.txt");
162 }
163 }
164}