Opinionated Android 15+ Linux Terminal Setup
android linux command-line-tools
at main 369 lines 13 kB view raw
1use owo_colors::OwoColorize; 2use std::{collections::HashMap, fmt}; 3 4use crate::config::{Configuration, OhMyPosh, SshConfig}; 5 6#[derive(Debug)] 7pub enum Diff { 8 Added(String, String, String), // Parent, child, value 9 Removed(String, String, String), // Parent, child, value 10 Changed(String, String, String, String), // Parent, child, old value, new value 11 Nested(String, Vec<Diff>), // Parent field, nested differences 12} 13 14impl fmt::Display for Diff { 15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 match self { 17 Diff::Added(parent, child, value) => { 18 if child.is_empty() { 19 write!(f, "+ {}: {}", parent.green(), value.green()) 20 } else { 21 write!( 22 f, 23 "+ {}:\n + {}: {}", 24 parent.green(), 25 child.green(), 26 value.green() 27 ) 28 } 29 } 30 Diff::Removed(parent, child, value) => { 31 if child.is_empty() { 32 write!(f, "- {}: {}", parent.magenta(), value.magenta()) 33 } else { 34 write!( 35 f, 36 "- {}:\n - {}: {}", 37 parent.magenta(), 38 child.magenta(), 39 value.magenta() 40 ) 41 } 42 } 43 Diff::Changed(parent, child, old_value, new_value) => { 44 if child.is_empty() { 45 write!( 46 f, 47 "- {}: {}\n+ {}: {}", 48 parent.magenta(), 49 old_value.magenta(), 50 parent.green(), 51 new_value.green() 52 ) 53 } else { 54 write!( 55 f, 56 "- {}:\n - {}: {}\n+ {}:\n + {}: {}", 57 parent.magenta(), 58 child.magenta(), 59 old_value.magenta(), 60 parent.green(), 61 child.green(), 62 new_value.green() 63 ) 64 } 65 } 66 Diff::Nested(parent, diffs) => { 67 write!(f, "{}:", parent)?; 68 for diff in diffs { 69 write!(f, "\n {}", diff)?; 70 } 71 Ok(()) 72 } 73 } 74 } 75} 76 77fn compare_hashmap( 78 parent: &str, 79 old: &Option<HashMap<String, String>>, 80 new: &Option<HashMap<String, String>>, 81) -> Vec<Diff> { 82 let mut diffs = Vec::new(); 83 match (old, new) { 84 (None, Some(new_map)) => { 85 for (key, value) in new_map { 86 diffs.push(Diff::Added(parent.to_string(), key.clone(), value.clone())); 87 } 88 } 89 (Some(old_map), None) => { 90 for (key, value) in old_map { 91 diffs.push(Diff::Removed( 92 parent.to_string(), 93 key.clone(), 94 value.clone(), 95 )); 96 } 97 } 98 (Some(old_map), Some(new_map)) => { 99 for (key, old_value) in old_map { 100 match new_map.get(key) { 101 None => diffs.push(Diff::Removed( 102 parent.to_string(), 103 key.clone(), 104 old_value.clone(), 105 )), 106 Some(new_value) if new_value != old_value => { 107 diffs.push(Diff::Changed( 108 parent.to_string(), 109 key.clone(), 110 old_value.clone(), 111 new_value.clone(), 112 )); 113 } 114 _ => {} 115 } 116 } 117 for (key, new_value) in new_map { 118 if !old_map.contains_key(key) { 119 diffs.push(Diff::Added( 120 parent.to_string(), 121 key.clone(), 122 new_value.clone(), 123 )); 124 } 125 } 126 } 127 (None, None) => {} 128 } 129 diffs 130} 131 132fn compare_vec(parent: &str, old: &Option<Vec<String>>, new: &Option<Vec<String>>) -> Vec<Diff> { 133 let mut diffs = Vec::new(); 134 match (old, new) { 135 (None, Some(new_vec)) => { 136 for item in new_vec { 137 diffs.push(Diff::Added( 138 parent.to_string(), 139 "".to_string(), 140 item.clone(), 141 )); 142 } 143 } 144 (Some(old_vec), None) => { 145 for item in old_vec { 146 diffs.push(Diff::Removed( 147 parent.to_string(), 148 "".to_string(), 149 item.clone(), 150 )); 151 } 152 } 153 (Some(old_vec), Some(new_vec)) => { 154 let old_set: std::collections::HashSet<_> = old_vec.iter().collect(); 155 let new_set: std::collections::HashSet<_> = new_vec.iter().collect(); 156 for item in new_set.difference(&old_set) { 157 diffs.push(Diff::Added( 158 parent.to_string(), 159 "".to_string(), 160 item.to_string(), 161 )); 162 } 163 for item in old_set.difference(&new_set) { 164 diffs.push(Diff::Removed( 165 parent.to_string(), 166 "".to_string(), 167 item.to_string(), 168 )); 169 } 170 } 171 (None, None) => {} 172 } 173 diffs 174} 175 176fn compare_bool(parent: &str, old: &Option<bool>, new: &Option<bool>) -> Vec<Diff> { 177 match (old, new) { 178 (None, Some(new_val)) => vec![Diff::Added( 179 parent.to_string(), 180 "".to_string(), 181 new_val.to_string(), 182 )], 183 (Some(old_val), None) => vec![Diff::Removed( 184 parent.to_string(), 185 "".to_string(), 186 old_val.to_string(), 187 )], 188 (Some(old_val), Some(new_val)) if old_val != new_val => { 189 vec![Diff::Changed( 190 parent.to_string(), 191 "".to_string(), 192 old_val.to_string(), 193 new_val.to_string(), 194 )] 195 } 196 _ => vec![], 197 } 198} 199 200fn compare_oh_my_posh(old: &Option<OhMyPosh>, new: &Option<OhMyPosh>) -> Vec<Diff> { 201 let mut diffs = Vec::new(); 202 match (old, new) { 203 (None, Some(new_omp)) => { 204 if let Some(theme) = &new_omp.theme { 205 diffs.push(Diff::Added( 206 "oh_my_posh".to_string(), 207 "theme".to_string(), 208 theme.clone(), 209 )); 210 } 211 } 212 (Some(old_omp), None) => { 213 if let Some(theme) = &old_omp.theme { 214 diffs.push(Diff::Removed( 215 "oh_my_posh".to_string(), 216 "theme".to_string(), 217 theme.clone(), 218 )); 219 } 220 } 221 (Some(old_omp), Some(new_omp)) => match (&old_omp.theme, &new_omp.theme) { 222 (None, Some(new_theme)) => { 223 diffs.push(Diff::Added( 224 "oh_my_posh".to_string(), 225 "theme".to_string(), 226 new_theme.clone(), 227 )); 228 } 229 (Some(old_theme), None) => { 230 diffs.push(Diff::Removed( 231 "oh_my_posh".to_string(), 232 "theme".to_string(), 233 old_theme.clone(), 234 )); 235 } 236 (Some(old_theme), Some(new_theme)) if old_theme != new_theme => { 237 diffs.push(Diff::Changed( 238 "oh_my_posh".to_string(), 239 "theme".to_string(), 240 old_theme.clone(), 241 new_theme.clone(), 242 )); 243 } 244 _ => {} 245 }, 246 (None, None) => {} 247 } 248 if !diffs.is_empty() { 249 vec![Diff::Nested("oh_my_posh".to_string(), diffs)] 250 } else { 251 vec![] 252 } 253} 254 255fn compare_ssh_config(old: &Option<SshConfig>, new: &Option<SshConfig>) -> Vec<Diff> { 256 let mut diffs = Vec::new(); 257 match (old, new) { 258 (None, Some(new_ssh)) => { 259 if let Some(port) = new_ssh.port { 260 diffs.push(Diff::Added( 261 "ssh".to_string(), 262 "port".to_string(), 263 port.to_string(), 264 )); 265 } 266 if let Some(keys) = &new_ssh.authorized_keys { 267 for key in keys { 268 diffs.push(Diff::Added( 269 "ssh".to_string(), 270 "authorized_keys".to_string(), 271 key.clone(), 272 )); 273 } 274 } 275 } 276 (Some(old_ssh), None) => { 277 if let Some(port) = old_ssh.port { 278 diffs.push(Diff::Removed( 279 "ssh".to_string(), 280 "port".to_string(), 281 port.to_string(), 282 )); 283 } 284 if let Some(keys) = &old_ssh.authorized_keys { 285 for key in keys { 286 diffs.push(Diff::Removed( 287 "ssh".to_string(), 288 "authorized_keys".to_string(), 289 key.clone(), 290 )); 291 } 292 } 293 } 294 (Some(old_ssh), Some(new_ssh)) => { 295 match (old_ssh.port, new_ssh.port) { 296 (None, Some(new_port)) => { 297 diffs.push(Diff::Added( 298 "ssh".to_string(), 299 "port".to_string(), 300 new_port.to_string(), 301 )); 302 } 303 (Some(old_port), None) => { 304 diffs.push(Diff::Removed( 305 "ssh".to_string(), 306 "port".to_string(), 307 old_port.to_string(), 308 )); 309 } 310 (Some(old_port), Some(new_port)) if old_port != new_port => { 311 diffs.push(Diff::Changed( 312 "ssh".to_string(), 313 "port".to_string(), 314 old_port.to_string(), 315 new_port.to_string(), 316 )); 317 } 318 _ => {} 319 } 320 let key_diffs = compare_vec("ssh", &old_ssh.authorized_keys, &new_ssh.authorized_keys); 321 diffs.extend(key_diffs.into_iter().map(|diff| match diff { 322 Diff::Added(_, _, value) => { 323 Diff::Added("ssh".to_string(), "authorized_keys".to_string(), value) 324 } 325 Diff::Removed(_, _, value) => { 326 Diff::Removed("ssh".to_string(), "authorized_keys".to_string(), value) 327 } 328 Diff::Changed(_, _, old_value, new_value) => Diff::Changed( 329 "ssh".to_string(), 330 "authorized_keys".to_string(), 331 old_value, 332 new_value, 333 ), 334 Diff::Nested(_, _) => diff, // Unreachable 335 })); 336 } 337 (None, None) => {} 338 } 339 if !diffs.is_empty() { 340 vec![Diff::Nested("ssh".to_string(), diffs)] 341 } else { 342 vec![] 343 } 344} 345 346pub fn compare_configurations(old: &Configuration, new: &Configuration) -> Vec<Diff> { 347 let mut diffs = Vec::new(); 348 349 diffs.extend(compare_hashmap("stow", &old.stow, &new.stow)); 350 diffs.extend(compare_hashmap("mise", &old.mise, &new.mise)); 351 diffs.extend(compare_hashmap("nix", &old.nix, &new.nix)); 352 diffs.extend(compare_hashmap("pkgx", &old.pkgx, &new.pkgx)); 353 diffs.extend(compare_hashmap("curl", &old.curl, &new.curl)); 354 diffs.extend(compare_hashmap("alias", &old.alias, &new.alias)); 355 diffs.extend(compare_hashmap("npm", &old.npm, &new.npm)); 356 357 diffs.extend(compare_vec("apt-get", &old.apt_get, &new.apt_get)); 358 359 diffs.extend(compare_bool("blesh", &old.blesh, &new.blesh)); 360 diffs.extend(compare_bool("zoxide", &old.zoxide, &new.zoxide)); 361 diffs.extend(compare_bool("tailscale", &old.tailscale, &new.tailscale)); 362 diffs.extend(compare_bool("neofetch", &old.neofetch, &new.neofetch)); 363 diffs.extend(compare_bool("doppler", &old.doppler, &new.doppler)); 364 365 diffs.extend(compare_oh_my_posh(&old.oh_my_posh, &new.oh_my_posh)); 366 diffs.extend(compare_ssh_config(&old.ssh, &new.ssh)); 367 368 diffs 369}