1mod old_default_nes_palette; 2mod v0_10; 3mod v0_11; 4mod v0_12; 5mod v0_13; 6mod v0_14; 7mod v0_8; 8 9use crate::AppConfig; 10use std::cmp::Ordering; 11use std::fmt::{Display, Formatter}; 12use std::str::FromStr; 13use toml_edit::DocumentMut; 14 15#[derive(Debug, Clone, Copy, PartialEq, Eq)] 16struct SemVer { 17 major: u32, 18 minor: u32, 19 patch: u32, 20} 21 22impl SemVer { 23 const fn new(major: u32, minor: u32, patch: u32) -> Self { 24 Self { major, minor, patch } 25 } 26} 27 28impl PartialOrd for SemVer { 29 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { 30 Some(self.cmp(other)) 31 } 32} 33 34impl Ord for SemVer { 35 fn cmp(&self, other: &Self) -> Ordering { 36 self.major 37 .cmp(&other.major) 38 .then(self.minor.cmp(&other.minor)) 39 .then(self.patch.cmp(&other.patch)) 40 } 41} 42 43impl Display for SemVer { 44 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 45 write!(f, "{}.{}.{}", self.major, self.minor, self.patch) 46 } 47} 48 49impl FromStr for SemVer { 50 type Err = String; 51 52 fn from_str(s: &str) -> Result<Self, Self::Err> { 53 let err_fn = || format!("Invalid semver string: {s}"); 54 55 let Ok(split) = s.split('.').map(str::parse::<u32>).collect::<Result<Vec<_>, _>>() else { 56 return Err(err_fn()); 57 }; 58 59 match split.as_slice() { 60 &[major, minor, patch] => Ok(Self { major, minor, patch }), 61 _ => Err(err_fn()), 62 } 63 } 64} 65 66pub fn migrate_config_str(config_str: &mut String) { 67 let Ok(mut document) = config_str.parse::<DocumentMut>() else { return }; 68 69 let mut changed = false; 70 71 changed |= v0_12::migrate_document(&mut document); 72 changed |= v0_13::migrate_document(&mut document); 73 74 if changed { 75 *config_str = document.to_string(); 76 } 77} 78 79pub(crate) const fn current_config_version() -> &'static str { 80 env!("CARGO_PKG_VERSION") 81} 82 83#[must_use] 84pub fn migrate_config(config: &AppConfig, config_str: &str) -> Option<AppConfig> { 85 const MIN_VERSION_NO_MIGRATION: SemVer = SemVer::new(0, 14, 0); 86 87 if config 88 .config_version 89 .as_ref() 90 .and_then(|version| SemVer::from_str(version).ok()) 91 .is_some_and(|version| version >= MIN_VERSION_NO_MIGRATION) 92 { 93 return None; 94 } 95 96 let old_version = config 97 .config_version 98 .as_ref() 99 .and_then(|s| s.parse::<SemVer>().ok()) 100 .unwrap_or(SemVer::new(0, 0, 0)); 101 102 log::info!("Migrating config from version {old_version} to {}", current_config_version()); 103 104 let mut new_config = config.clone(); 105 if old_version < SemVer::new(0, 8, 3) { 106 v0_8::migrate_config_0_8_3(&mut new_config, config_str); 107 } 108 109 if old_version < SemVer::new(0, 8, 4) { 110 v0_8::migrate_config_0_8_4(&mut new_config); 111 } 112 113 if old_version < SemVer::new(0, 10, 2) { 114 v0_10::migrate_config_0_10_2(&mut new_config, config_str); 115 } 116 117 if old_version < SemVer::new(0, 11, 4) { 118 v0_11::migrate_config_0_11_4(&mut new_config, config_str); 119 } 120 121 if old_version < SemVer::new(0, 14, 0) { 122 v0_14::migrate_config_0_14_0(&mut new_config, config_str); 123 } 124 125 new_config.config_version = Some(current_config_version().into()); 126 127 Some(new_config) 128} 129 130#[cfg(test)] 131mod tests { 132 use super::*; 133 134 #[test] 135 fn migrate_empty_string_does_not_panic() { 136 migrate_config_str(&mut String::new()); 137 } 138}