sps_common/model/
version.rs

1// **File:** sps-core/src/model/version.rs (New file)
2use std::fmt;
3use std::str::FromStr;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::error::{Result, SpsError};
8
9/// Wrapper around semver::Version for formula versions.
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Version(semver::Version);
12
13impl Version {
14    pub fn parse(s: &str) -> Result<Self> {
15        // Attempt standard semver parse first
16        semver::Version::parse(s).map(Version).or_else(|_| {
17            // Homebrew often uses versions like "1.2.3_1" (revision) or just "123"
18            // Try to handle these by stripping suffixes or padding
19            // This is a simplified handling, Homebrew's PkgVersion is complex
20            let cleaned = s.split('_').next().unwrap_or(s); // Take part before _
21            let parts: Vec<&str> = cleaned.split('.').collect();
22            let padded = match parts.len() {
23                1 => format!("{}.0.0", parts[0]),
24                2 => format!("{}.{}.0", parts[0], parts[1]),
25                _ => cleaned.to_string(), // Use original if 3+ parts
26            };
27            semver::Version::parse(&padded).map(Version).map_err(|e| {
28                SpsError::VersionError(format!(
29                    "Failed to parse version '{s}' (tried '{padded}'): {e}"
30                ))
31            })
32        })
33    }
34}
35
36impl FromStr for Version {
37    type Err = SpsError;
38    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
39        Self::parse(s)
40    }
41}
42
43impl fmt::Display for Version {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        // TODO: Preserve original format if possible? PkgVersion complexity.
46        // For now, display the parsed semver representation.
47        write!(f, "{}", self.0)
48    }
49}
50
51// Manual Serialize/Deserialize to handle the Version<->String conversion
52impl Serialize for Version {
53    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
54    where
55        S: Serializer,
56    {
57        serializer.serialize_str(&self.to_string())
58    }
59}
60
61impl AsRef<Self> for Version {
62    fn as_ref(&self) -> &Self {
63        self
64    }
65}
66
67// Removed redundant ToString implementation as it conflicts with the blanket implementation in std.
68
69impl From<Version> for semver::Version {
70    fn from(version: Version) -> Self {
71        version.0
72    }
73}
74
75impl<'de> Deserialize<'de> for Version {
76    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        let s = String::deserialize(deserializer)?;
81        Self::from_str(&s).map_err(serde::de::Error::custom)
82    }
83}
84
85// Add to sps-core/src/utils/error.rs:
86// #[error("Version error: {0}")]
87// VersionError(String),
88
89// Add to sps-core/Cargo.toml:
90// [dependencies]
91// semver = "1.0"