use std::fs;
const VERSION_FILE: &str = "version";
pub fn init_version_tracking() -> Result<(), Box<dyn std::error::Error>> {
let mathypad_dir = get_mathypad_dir()?;
if !mathypad_dir.exists() {
fs::create_dir_all(&mathypad_dir)?;
}
Ok(())
}
pub fn update_stored_version() -> Result<(), Box<dyn std::error::Error>> {
let mathypad_dir = get_mathypad_dir()?;
let version_file = mathypad_dir.join(VERSION_FILE);
let current_version = env!("CARGO_PKG_VERSION");
fs::write(&version_file, current_version)?;
Ok(())
}
pub fn get_stored_version() -> Option<String> {
let mathypad_dir = get_mathypad_dir().ok()?;
let version_file = mathypad_dir.join(VERSION_FILE);
if version_file.exists() {
fs::read_to_string(&version_file)
.ok()?
.trim()
.to_string()
.into()
} else {
None
}
}
pub fn get_current_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
pub fn is_first_run() -> bool {
get_stored_version().is_none()
}
pub fn is_newer_version() -> bool {
match get_stored_version() {
Some(stored) => {
let current = get_current_version();
version_compare(current, &stored) > 0
}
None => true, }
}
pub fn get_changelog_since_version() -> Option<String> {
let current_version = get_current_version();
match get_stored_version() {
Some(stored_version) => {
if version_compare(current_version, &stored_version) <= 0 {
return None;
}
extract_changelog_between_versions(&stored_version, current_version)
}
None => {
extract_latest_version_changelog()
}
}
}
fn extract_latest_version_changelog() -> Option<String> {
let changelog = include_str!("../CHANGELOG.md");
let lines: Vec<&str> = changelog.lines().collect();
let mut result = Vec::new();
let mut found_first_version = false;
for line in lines {
if line.starts_with("## [") {
if found_first_version {
break;
} else {
found_first_version = true;
result.push(line);
continue;
}
}
if found_first_version {
result.push(line);
}
}
if found_first_version && !result.is_empty() {
Some(result.join("\n"))
} else {
None
}
}
fn extract_changelog_between_versions(from_version: &str, to_version: &str) -> Option<String> {
let changelog = include_str!("../CHANGELOG.md");
let lines: Vec<&str> = changelog.lines().collect();
let mut result = Vec::new();
let mut in_range = false;
let mut found_to_version = false;
for line in lines {
if line.starts_with("## [") {
if let Some(version) = extract_version_from_header(line) {
if version == to_version {
in_range = true;
found_to_version = true;
result.push(line);
continue;
}
if in_range && version == from_version {
break;
}
if in_range {
result.push(line);
continue;
}
}
}
if in_range {
result.push(line);
}
}
if found_to_version && !result.is_empty() {
Some(result.join("\n"))
} else {
None
}
}
fn extract_version_from_header(line: &str) -> Option<String> {
if let Some(start) = line.find('[') {
if let Some(end) = line.find(']') {
if end > start {
return Some(line[start + 1..end].to_string());
}
}
}
None
}
fn get_mathypad_dir() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let home_dir = dirs::home_dir().ok_or("Could not determine home directory")?;
Ok(home_dir.join(".mathypad"))
}
fn version_compare(a: &str, b: &str) -> i32 {
let parse_version =
|v: &str| -> Vec<u32> { v.split('.').filter_map(|part| part.parse().ok()).collect() };
let version_a = parse_version(a);
let version_b = parse_version(b);
let max_len = version_a.len().max(version_b.len());
for i in 0..max_len {
let a_part = version_a.get(i).copied().unwrap_or(0);
let b_part = version_b.get(i).copied().unwrap_or(0);
match a_part.cmp(&b_part) {
std::cmp::Ordering::Less => return -1,
std::cmp::Ordering::Greater => return 1,
std::cmp::Ordering::Equal => continue,
}
}
0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_compare() {
assert_eq!(version_compare("1.0.0", "1.0.0"), 0);
assert_eq!(version_compare("1.0.1", "1.0.0"), 1);
assert_eq!(version_compare("1.0.0", "1.0.1"), -1);
assert_eq!(version_compare("1.1.0", "1.0.9"), 1);
assert_eq!(version_compare("2.0.0", "1.9.9"), 1);
assert_eq!(version_compare("1.0", "1.0.0"), 0);
}
#[test]
fn test_extract_version_from_header() {
assert_eq!(
extract_version_from_header("## [0.1.9] - 2025-06-19"),
Some("0.1.9".to_string())
);
assert_eq!(
extract_version_from_header("## [1.0.0] - 2025-01-01"),
Some("1.0.0".to_string())
);
assert_eq!(extract_version_from_header("### Not a version"), None);
assert_eq!(extract_version_from_header("## No brackets"), None);
}
#[test]
fn test_extract_latest_version_changelog() {
let result = extract_latest_version_changelog();
assert!(result.is_some(), "Should find the latest version changelog");
let changelog = result.unwrap();
assert!(
changelog.starts_with("## ["),
"Should start with version header"
);
assert!(changelog.len() > 20, "Should have substantial content");
}
}