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