pub mod github;
pub mod signature;
pub mod uninstall;
pub mod update;
pub mod upgrade;
use std::cmp::Ordering;
pub fn target_triple() -> &'static str {
match std::env::consts::ARCH {
"aarch64" => "aarch64-unknown-linux-musl",
_ => "x86_64-unknown-linux-musl",
}
}
pub fn current_version() -> String {
format!("v{}", env!("CARGO_PKG_VERSION"))
}
pub fn normalize_tag(version: &str) -> String {
let v = version.trim();
if v.starts_with('v') {
v.to_string()
} else {
format!("v{v}")
}
}
pub fn parse_version(version: &str) -> Option<(u64, u64, u64)> {
let v = version.trim().trim_start_matches('v');
let core = v.split(['-', '+']).next().unwrap_or(v);
let mut parts = core.split('.');
let major = parts.next()?.parse().ok()?;
let minor = parts.next()?.parse().ok()?;
let patch = parts.next().unwrap_or("0").parse().ok()?;
if parts.next().is_some() {
return None;
}
Some((major, minor, patch))
}
pub fn compare_versions(a: &str, b: &str) -> Ordering {
match (parse_version(a), parse_version(b)) {
(Some(va), Some(vb)) => va.cmp(&vb),
_ => Ordering::Equal,
}
}
pub fn update_available(current: &str, latest: &str) -> bool {
compare_versions(current, latest) == Ordering::Less
}
pub fn asset_names_for_version(tag: &str, target: &str) -> (String, String) {
let tag = normalize_tag(tag);
let archive = format!("mnemo-{tag}-{target}.tar.gz");
let sha = format!("{archive}.sha256");
(archive, sha)
}
pub fn sha256_hex(data: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(data);
let digest = hasher.finalize();
digest.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn parse_sha256_file(content: &str) -> Option<String> {
let token = content.split_whitespace().next()?;
let lower = token.to_lowercase();
if lower.len() == 64 && lower.chars().all(|c| c.is_ascii_hexdigit()) {
Some(lower)
} else {
None
}
}
pub fn verify_sha256(data: &[u8], expected_hex: &str) -> bool {
sha256_hex(data) == expected_hex.trim().to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn comparaison_versions() {
assert_eq!(compare_versions("v0.4.0", "v0.5.0"), Ordering::Less);
assert_eq!(compare_versions("v0.5.0", "v0.4.0"), Ordering::Greater);
assert_eq!(compare_versions("v0.5.0", "v0.5.0"), Ordering::Equal);
assert_eq!(compare_versions("0.5", "v0.5.0"), Ordering::Equal);
assert_eq!(compare_versions("v0.5.1", "v0.5.0"), Ordering::Greater);
assert_eq!(compare_versions("v1.0.0", "v0.9.9"), Ordering::Greater);
}
#[test]
fn update_disponible() {
assert!(update_available("v0.4.0", "v0.5.0"));
assert!(!update_available("v0.5.0", "v0.5.0"));
assert!(!update_available("v0.5.0", "v0.4.0"));
}
#[test]
fn parse_version_robuste() {
assert_eq!(parse_version("v0.5.0"), Some((0, 5, 0)));
assert_eq!(parse_version("1.2.3"), Some((1, 2, 3)));
assert_eq!(parse_version("v0.5.0-rc1"), Some((0, 5, 0)));
assert_eq!(parse_version("0.5"), Some((0, 5, 0)));
assert_eq!(parse_version("abc"), None);
assert_eq!(parse_version("0.5.0.1"), None);
}
#[test]
fn cible_par_defaut() {
let t = target_triple();
assert!(t.contains("linux-musl"));
}
#[test]
fn noms_assets() {
let (archive, sha) = asset_names_for_version("v0.5.0", "x86_64-unknown-linux-musl");
assert_eq!(archive, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz");
assert_eq!(sha, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz.sha256");
let (archive2, _) = asset_names_for_version("0.5.0", "x86_64-unknown-linux-musl");
assert_eq!(archive2, "mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz");
}
#[test]
fn sha256_ok_et_ko() {
let data = b"hello mnemo";
let hex = sha256_hex(data);
assert_eq!(hex.len(), 64);
assert!(verify_sha256(data, &hex));
assert!(verify_sha256(data, &hex.to_uppercase()));
assert!(!verify_sha256(data, &"0".repeat(64)));
assert!(!verify_sha256(b"autre contenu", &hex));
}
#[test]
fn parse_fichier_sha256() {
let hex = "a".repeat(64);
let line = format!("{hex} mnemo-v0.5.0-x86_64-unknown-linux-musl.tar.gz\n");
assert_eq!(parse_sha256_file(&line), Some(hex.clone()));
assert_eq!(parse_sha256_file(&hex), Some(hex.clone()));
assert_eq!(parse_sha256_file("pas un hash"), None);
assert_eq!(parse_sha256_file(""), None);
}
}