diff options
Diffstat (limited to 'src/pathinfo.rs')
-rw-r--r-- | src/pathinfo.rs | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/pathinfo.rs b/src/pathinfo.rs new file mode 100644 index 0000000..b0c3be4 --- /dev/null +++ b/src/pathinfo.rs | |||
@@ -0,0 +1,246 @@ | |||
1 | use std::{ | ||
2 | fmt::Display, | ||
3 | path::PathBuf, | ||
4 | }; | ||
5 | |||
6 | use serde::{Deserialize, Serialize}; | ||
7 | |||
8 | use crate::{ | ||
9 | backup::BackupId, | ||
10 | config::Config, | ||
11 | error::{Error, Result}, | ||
12 | }; | ||
13 | |||
14 | #[derive(Debug, Clone, Serialize, Deserialize)] | ||
15 | pub struct PathInfo { | ||
16 | pub modified: bool, | ||
17 | pub is_file: bool, | ||
18 | rel_location: String, | ||
19 | location_root: LocationRoot, | ||
20 | last_modified: BackupId, | ||
21 | children: Vec<PathInfo> | ||
22 | } | ||
23 | |||
24 | impl PathInfo { | ||
25 | pub fn from_path(config: &Config, path: &str) -> Result<Self> { | ||
26 | let locations = Self::parse_location(path, config)?; | ||
27 | |||
28 | Ok(Self::handle_dir(config, &locations.0, &locations.1)?) | ||
29 | } | ||
30 | |||
31 | fn handle_dir( | ||
32 | config: &Config, | ||
33 | rel_location: &str, | ||
34 | location_root: &LocationRoot, | ||
35 | ) -> Result<Self> { | ||
36 | println!("Handling {rel_location}"); | ||
37 | let path = Self::get_abs_path(&location_root.to_string(), rel_location); | ||
38 | Ok(if path.is_dir() { | ||
39 | let mut modified = false; | ||
40 | let mut children: Vec<PathInfo> = Vec::new(); | ||
41 | |||
42 | let paths = std::fs::read_dir(path).unwrap(); | ||
43 | for path in paths { | ||
44 | let pathstr = path.unwrap().path().to_string_lossy().to_string(); | ||
45 | let root = format!("{}/", location_root.to_string()); | ||
46 | let Some(rl) = pathstr.split_once(&root) else { | ||
47 | panic!("HUH"); | ||
48 | }; | ||
49 | let handle = Self::handle_dir(config, rl.1, location_root)?; | ||
50 | if handle.modified { | ||
51 | modified = true; | ||
52 | }; | ||
53 | children.push(handle); | ||
54 | } | ||
55 | Self { | ||
56 | modified, | ||
57 | is_file: false, | ||
58 | rel_location: rel_location.to_string(), | ||
59 | location_root: location_root.clone(), | ||
60 | last_modified: "".to_string(), | ||
61 | children | ||
62 | } | ||
63 | } else { | ||
64 | Self::from_file(rel_location, location_root.clone())? | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | fn from_file(rel_location: &str, location_root: LocationRoot) -> Result<Self> { | ||
69 | println!("From file {rel_location}"); | ||
70 | |||
71 | let modified = false; | ||
72 | |||
73 | Ok(Self { | ||
74 | rel_location: rel_location.to_string(), | ||
75 | location_root, | ||
76 | modified, | ||
77 | last_modified: "".to_string(), | ||
78 | is_file: true, | ||
79 | children: Vec::new() | ||
80 | }) | ||
81 | } | ||
82 | |||
83 | pub fn get_absolute_path(&self) -> PathBuf { | ||
84 | Self::get_abs_path(&self.location_root.to_string(), &self.rel_location) | ||
85 | } | ||
86 | |||
87 | fn get_abs_path(location_root: &str, rel_location: &str) -> PathBuf { | ||
88 | let path = format!("{}/{}", location_root, rel_location); | ||
89 | PathBuf::from(path) | ||
90 | } | ||
91 | |||
92 | fn parse_location(value: &str, config: &Config) -> Result<(String, LocationRoot)> { | ||
93 | let Some(split) = value.split_once('/') else { | ||
94 | return Err(Error::InvalidDirectory(value.to_string())); | ||
95 | }; | ||
96 | if split.0.starts_with('~') { | ||
97 | if config.user.len() != 1 { | ||
98 | return Err(Error::MultiUser); | ||
99 | } | ||
100 | return Ok(( | ||
101 | split.1.to_string(), | ||
102 | LocationRoot::User(config.user[0].clone()), | ||
103 | )); | ||
104 | }; | ||
105 | Ok(( | ||
106 | split.1.to_string(), | ||
107 | LocationRoot::from_op_str(split.0, config)?, | ||
108 | )) | ||
109 | } | ||
110 | } | ||
111 | |||
112 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
113 | pub enum LocationRoot { | ||
114 | User(String), | ||
115 | Custom(String), | ||
116 | SystemSettings, | ||
117 | Root, | ||
118 | } | ||
119 | |||
120 | impl Display for LocationRoot { | ||
121 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
122 | match self { | ||
123 | LocationRoot::User(user) => write!(f, "/home/{user}"), | ||
124 | LocationRoot::Custom(loc) => write!(f, "{loc}"), | ||
125 | LocationRoot::SystemSettings => write!(f, "/etc"), | ||
126 | LocationRoot::Root => write!(f, "/"), | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | impl LocationRoot { | ||
132 | fn from_op_str(value: &str, config: &Config) -> Result<Self> { | ||
133 | let split_str = value.split_once(':'); | ||
134 | let Some(split_op) = split_str else { | ||
135 | return Err(Error::NoIndex); | ||
136 | }; | ||
137 | match split_op.0 { | ||
138 | "u" => Ok(Self::User(split_op.1.to_string())), | ||
139 | "s" => Ok(Self::SystemSettings), | ||
140 | "r" => Ok(Self::Root), | ||
141 | "c" => Ok(Self::Custom( | ||
142 | config | ||
143 | .custom_directories | ||
144 | .get(split_op.1) | ||
145 | .ok_or_else(|| Error::CustomDirectory(split_op.1.to_string()))? | ||
146 | .to_string(), | ||
147 | )), | ||
148 | _ => Err(Error::InvalidIndex(split_op.0.to_string())), | ||
149 | } | ||
150 | } | ||
151 | } | ||
152 | |||
153 | #[cfg(test)] | ||
154 | mod tests { | ||
155 | use crate::{ | ||
156 | config::Config, | ||
157 | error::{Error, Result}, | ||
158 | pathinfo::PathInfo, | ||
159 | }; | ||
160 | |||
161 | use super::LocationRoot; | ||
162 | |||
163 | #[test] | ||
164 | fn from_op_str() -> Result<()> { | ||
165 | let mut config = Config::default(); | ||
166 | config | ||
167 | .custom_directories | ||
168 | .insert("test".to_string(), "/usr/local/test".to_string()); | ||
169 | |||
170 | let mut values: Vec<(&str, Result<LocationRoot>)> = Vec::new(); | ||
171 | values.push(("u:test", Ok(LocationRoot::User("test".to_string())))); | ||
172 | values.push(("s:", Ok(LocationRoot::SystemSettings))); | ||
173 | values.push(("r:", Ok(LocationRoot::Root))); | ||
174 | values.push(( | ||
175 | "c:test", | ||
176 | Ok(LocationRoot::Custom("/usr/local/test".to_string())), | ||
177 | )); | ||
178 | values.push(("c:rest", Err(Error::CustomDirectory("rest".to_string())))); | ||
179 | values.push(("t:test/", Err(Error::InvalidIndex("t".to_string())))); | ||
180 | values.push(( | ||
181 | "test:test/usr", | ||
182 | Err(Error::InvalidIndex("test".to_string())), | ||
183 | )); | ||
184 | values.push(("/usr/local/test", Err(Error::NoIndex))); | ||
185 | values.push(("c/usr/local/test", Err(Error::NoIndex))); | ||
186 | |||
187 | for value in values { | ||
188 | print!("Testing {value:?}"); | ||
189 | assert_eq!(LocationRoot::from_op_str(value.0, &config), value.1); | ||
190 | println!("\rTesting {value:?} ✓"); | ||
191 | } | ||
192 | |||
193 | Ok(()) | ||
194 | } | ||
195 | |||
196 | #[test] | ||
197 | fn parse_location() -> Result<()> { | ||
198 | let mut config = Config::default(); | ||
199 | config.user.push("test".to_string()); | ||
200 | config | ||
201 | .custom_directories | ||
202 | .insert("test".to_string(), "/usr/local/test".to_string()); | ||
203 | |||
204 | let mut values: Vec<(&str, Result<(String, LocationRoot)>)> = Vec::new(); | ||
205 | values.push(( | ||
206 | "~/.config/nvim", | ||
207 | Ok(( | ||
208 | ".config/nvim".to_string(), | ||
209 | LocationRoot::User("test".to_string()), | ||
210 | )), | ||
211 | )); | ||
212 | values.push(( | ||
213 | "u:test/.config/nvim", | ||
214 | Ok(( | ||
215 | ".config/nvim".to_string(), | ||
216 | LocationRoot::User("test".to_string()), | ||
217 | )), | ||
218 | )); | ||
219 | values.push(( | ||
220 | "r:/.config/nvim", | ||
221 | Ok((".config/nvim".to_string(), LocationRoot::Root)), | ||
222 | )); | ||
223 | values.push(( | ||
224 | "r:/.config/nvim", | ||
225 | Ok((".config/nvim".to_string(), LocationRoot::Root)), | ||
226 | )); | ||
227 | values.push(( | ||
228 | "s:/.config/nvim", | ||
229 | Ok((".config/nvim".to_string(), LocationRoot::SystemSettings)), | ||
230 | )); | ||
231 | values.push(( | ||
232 | "c:test/.config/nvim", | ||
233 | Ok(( | ||
234 | ".config/nvim".to_string(), | ||
235 | LocationRoot::Custom("/usr/local/test".to_string()), | ||
236 | )), | ||
237 | )); | ||
238 | |||
239 | for value in values { | ||
240 | print!("Testing {value:?}"); | ||
241 | assert_eq!(PathInfo::parse_location(&value.0, &config), value.1); | ||
242 | println!("\rTesting {value:?} ✓"); | ||
243 | } | ||
244 | Ok(()) | ||
245 | } | ||
246 | } | ||