kernel-builder 0.7.6

Select, build and install kernel version from local sources.
Documentation
use crate::consts::KERNEL_PATTERN;
use semver::Version;
use std::cmp::Ordering;
use std::fmt;
use std::path::{Path, PathBuf};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VersionEntry {
    pub path: PathBuf,
    pub version_string: String,
    pub semver: Version,
}

impl VersionEntry {
    #[must_use]
    pub fn new(path: PathBuf, version_string: String, semver: Version) -> Self {
        Self {
            path,
            version_string,
            semver,
        }
    }

    #[must_use]
    pub fn from_path(path: &Path) -> Option<Self> {
        let filename = path.file_name()?.to_string_lossy();
        let version_string = filename.to_string();
        let version_only = version_string.strip_prefix(KERNEL_PATTERN)?;
        let semver = Version::parse(version_only).ok()?;
        Some(Self {
            path: path.to_path_buf(),
            version_string,
            semver,
        })
    }
}

impl PartialOrd for VersionEntry {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for VersionEntry {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.semver.cmp(&other.semver) {
            Ordering::Equal => self.version_string.cmp(&other.version_string),
            ord => ord,
        }
    }
}

impl fmt::Display for VersionEntry {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} ({})", self.version_string, self.semver)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_version_entry_from_path_valid() {
        let path = PathBuf::from("/usr/src/linux-6.5.2");
        let entry = VersionEntry::from_path(&path).unwrap();
        assert_eq!(entry.version_string, "linux-6.5.2");
        assert_eq!(entry.semver.major, 6);
        assert_eq!(entry.semver.minor, 5);
        assert_eq!(entry.semver.patch, 2);
    }

    #[test]
    fn test_version_entry_from_path_invalid() {
        let path = PathBuf::from("/usr/src/not-a-kernel");
        assert!(VersionEntry::from_path(&path).is_none());
    }

    #[test]
    fn test_version_entry_from_path_with_rc() {
        let path = PathBuf::from("/usr/src/linux-6.5.0-rc1");
        let entry = VersionEntry::from_path(&path).unwrap();
        assert_eq!(entry.version_string, "linux-6.5.0-rc1");
        assert!(!entry.semver.pre.is_empty());
    }

    #[test]
    fn test_version_sorting() {
        let mut versions = [
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-5.15.0")).unwrap(),
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-6.5.2")).unwrap(),
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-6.1.0")).unwrap(),
        ];

        versions.sort();
        let sorted: Vec<String> = versions.iter().map(|v| v.version_string.clone()).collect();
        assert_eq!(sorted, vec!["linux-5.15.0", "linux-6.1.0", "linux-6.5.2"]);
    }

    #[test]
    fn test_version_sorting_descending() {
        let mut versions = [
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-5.15.0")).unwrap(),
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-6.5.2")).unwrap(),
            VersionEntry::from_path(&PathBuf::from("/usr/src/linux-6.1.0")).unwrap(),
        ];

        versions.sort();
        versions.reverse();
        let sorted: Vec<String> = versions.iter().map(|v| v.version_string.clone()).collect();
        assert_eq!(sorted, vec!["linux-6.5.2", "linux-6.1.0", "linux-5.15.0"]);
    }

    #[test]
    fn test_version_entry_display() {
        let entry = VersionEntry::from_path(&PathBuf::from("/usr/src/linux-6.5.2")).unwrap();
        assert_eq!(entry.to_string(), "linux-6.5.2 (6.5.2)");
    }
}