claude_wrapper/
version.rs1use std::fmt;
2use std::str::FromStr;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct CliVersion {
21 pub major: u32,
22 pub minor: u32,
23 pub patch: u32,
24}
25
26impl CliVersion {
27 #[must_use]
29 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
30 Self {
31 major,
32 minor,
33 patch,
34 }
35 }
36
37 pub fn parse_version_output(output: &str) -> Result<Self, VersionParseError> {
41 let version_str = output.split_whitespace().next().unwrap_or("");
42 version_str.parse()
43 }
44
45 #[must_use]
47 pub fn satisfies_minimum(&self, minimum: &CliVersion) -> bool {
48 self >= minimum
49 }
50}
51
52impl PartialOrd for CliVersion {
53 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
54 Some(self.cmp(other))
55 }
56}
57
58impl Ord for CliVersion {
59 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
60 self.major
61 .cmp(&other.major)
62 .then(self.minor.cmp(&other.minor))
63 .then(self.patch.cmp(&other.patch))
64 }
65}
66
67impl fmt::Display for CliVersion {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
70 }
71}
72
73impl FromStr for CliVersion {
74 type Err = VersionParseError;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 let parts: Vec<&str> = s.split('.').collect();
78 if parts.len() != 3 {
79 return Err(VersionParseError(s.to_string()));
80 }
81
82 let major = parts[0]
83 .parse()
84 .map_err(|_| VersionParseError(s.to_string()))?;
85 let minor = parts[1]
86 .parse()
87 .map_err(|_| VersionParseError(s.to_string()))?;
88 let patch = parts[2]
89 .parse()
90 .map_err(|_| VersionParseError(s.to_string()))?;
91
92 Ok(Self {
93 major,
94 minor,
95 patch,
96 })
97 }
98}
99
100#[derive(Debug, Clone, thiserror::Error)]
102#[error("invalid version string: {0:?}")]
103pub struct VersionParseError(pub String);
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_parse_simple() {
111 let v: CliVersion = "2.1.71".parse().unwrap();
112 assert_eq!(v.major, 2);
113 assert_eq!(v.minor, 1);
114 assert_eq!(v.patch, 71);
115 }
116
117 #[test]
118 fn test_parse_version_output() {
119 let v = CliVersion::parse_version_output("2.1.71 (Claude Code)").unwrap();
120 assert_eq!(v, CliVersion::new(2, 1, 71));
121 }
122
123 #[test]
124 fn test_parse_version_output_trimmed() {
125 let v = CliVersion::parse_version_output(" 2.1.71 (Claude Code)\n").unwrap();
126 assert_eq!(v, CliVersion::new(2, 1, 71));
127 }
128
129 #[test]
130 fn test_display() {
131 let v = CliVersion::new(2, 1, 71);
132 assert_eq!(v.to_string(), "2.1.71");
133 }
134
135 #[test]
136 fn test_ordering() {
137 let v1 = CliVersion::new(2, 0, 0);
138 let v2 = CliVersion::new(2, 1, 0);
139 let v3 = CliVersion::new(2, 1, 71);
140 let v4 = CliVersion::new(3, 0, 0);
141
142 assert!(v1 < v2);
143 assert!(v2 < v3);
144 assert!(v3 < v4);
145 assert!(v1 < v4);
146 }
147
148 #[test]
149 fn test_satisfies_minimum() {
150 let v = CliVersion::new(2, 1, 71);
151 assert!(v.satisfies_minimum(&CliVersion::new(2, 0, 0)));
152 assert!(v.satisfies_minimum(&CliVersion::new(2, 1, 71)));
153 assert!(!v.satisfies_minimum(&CliVersion::new(2, 2, 0)));
154 assert!(!v.satisfies_minimum(&CliVersion::new(3, 0, 0)));
155 }
156
157 #[test]
158 fn test_parse_invalid() {
159 assert!("not-a-version".parse::<CliVersion>().is_err());
160 assert!("2.1".parse::<CliVersion>().is_err());
161 assert!("2.1.x".parse::<CliVersion>().is_err());
162 }
163}