1use crate::{Error, Result};
8#[cfg(feature = "rhai")] use crate::{RhaiRes, rhai_err};
9#[cfg(feature = "rhai")] use rhai::Engine;
10use semver::{Prerelease, Version};
11
12#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17pub struct Semver {
18 pub version: Version,
19 pub use_v: bool,
20}
21
22impl Semver {
23 pub fn parse(str: &str) -> Result<Self> {
24 let use_v = str.starts_with("v");
25 let version = if use_v {
26 let mut chars = str.chars();
27 chars.next();
28 Version::parse(chars.as_str()).map_err(Error::Semver)?
29 } else {
30 Version::parse(str).map_err(Error::Semver)?
31 };
32 Ok(Self { version, use_v })
33 }
34
35 pub fn opt_parse(str: &str) -> Option<Self> {
36 Self::parse(str).ok()
37 }
38
39 #[cfg(feature = "rhai")]
40 pub fn rhai_parse(str: &str) -> RhaiRes<Self> {
41 Self::parse(str).map_err(rhai_err)
42 }
43
44 pub fn inc_major(&mut self) {
45 self.version.major += 1;
46 self.version.minor = 0;
47 self.version.patch = 0;
48 self.version.pre = Prerelease::EMPTY;
49 }
50
51 pub fn inc_minor(&mut self) {
52 self.version.minor += 1;
53 self.version.patch = 0;
54 self.version.pre = Prerelease::EMPTY;
55 }
56
57 pub fn inc_patch(&mut self) {
58 if self.version.pre.is_empty() {
59 self.version.patch += 1;
60 } else {
61 self.version.pre = Prerelease::EMPTY;
62 }
63 }
64
65 pub fn inc_beta(&mut self) -> Result<()> {
66 if self.version.pre.is_empty() || !self.version.pre.starts_with("beta.") {
67 self.version.patch += 1;
68 self.version.pre = Prerelease::new("beta.1").map_err(Error::Semver)?;
69 } else {
70 let str = self.version.pre.strip_prefix("beta.").unwrap().to_string();
71 let beta = str.parse::<u32>().unwrap() + 1;
72 self.version.pre = Prerelease::new(&format!("beta.{beta}")).map_err(Error::Semver)?;
73 }
74 Ok(())
75 }
76
77 #[cfg(feature = "rhai")]
78 pub fn rhai_inc_beta(&mut self) -> RhaiRes<()> {
79 self.inc_beta().map_err(rhai_err)
80 }
81
82 pub fn inc_alpha(&mut self) -> Result<()> {
83 if self.version.pre.is_empty() || !self.version.pre.starts_with("alpha.") {
84 self.version.patch += 1;
85 self.version.pre = Prerelease::new("alpha.1").map_err(Error::Semver)?;
86 } else {
87 let str = self.version.pre.strip_prefix("alpha.").unwrap().to_string();
88 let alpha = str.parse::<u32>().unwrap() + 1;
89 self.version.pre = Prerelease::new(&format!("alpha.{alpha}")).map_err(Error::Semver)?;
90 }
91 Ok(())
92 }
93
94 #[cfg(feature = "rhai")]
95 pub fn rhai_inc_alpha(&mut self) -> RhaiRes<()> {
96 self.inc_alpha().map_err(rhai_err)
97 }
98}
99
100impl std::fmt::Display for Semver {
101 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
102 if self.use_v {
103 write!(formatter, "v{}", self.version)
104 } else {
105 self.version.fmt(formatter)
106 }
107 }
108}
109
110#[cfg(feature = "rhai")]
111pub fn semver_rhai_register(engine: &mut Engine) {
112 engine
113 .register_type_with_name::<Semver>("Semver")
114 .register_fn("semver_from", Semver::rhai_parse)
115 .register_fn("inc_major", Semver::inc_major)
116 .register_fn("inc_minor", Semver::inc_minor)
117 .register_fn("inc_patch", Semver::inc_patch)
118 .register_fn("inc_beta", Semver::rhai_inc_beta)
119 .register_fn("inc_alpha", Semver::rhai_inc_alpha)
120 .register_fn("==", |a: Semver, b: Semver| a == b)
121 .register_fn("!=", |a: Semver, b: Semver| a != b)
122 .register_fn("<", |a: Semver, b: Semver| a < b)
123 .register_fn(">", |a: Semver, b: Semver| a > b)
124 .register_fn("<=", |a: Semver, b: Semver| a <= b)
125 .register_fn(">=", |a: Semver, b: Semver| a >= b)
126 .register_fn("to_string", |s: &mut Semver| s.to_string());
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_parse_without_v_prefix() {
135 let sv = Semver::parse("1.2.3").unwrap();
136 assert_eq!(sv.version.major, 1);
137 assert_eq!(sv.version.minor, 2);
138 assert_eq!(sv.version.patch, 3);
139 assert!(!sv.use_v);
140 }
141
142 #[test]
143 fn test_parse_with_v_prefix() {
144 let sv = Semver::parse("v1.2.3").unwrap();
145 assert_eq!(sv.version.major, 1);
146 assert_eq!(sv.version.minor, 2);
147 assert_eq!(sv.version.patch, 3);
148 assert!(sv.use_v);
149 }
150
151 #[test]
152 fn test_to_string_preserves_v_prefix() {
153 let mut sv = Semver::parse("v1.2.3").unwrap();
154 assert_eq!(Semver::to_string(&mut sv), "v1.2.3");
155 }
156
157 #[test]
158 fn test_to_string_without_v_prefix() {
159 let mut sv = Semver::parse("1.2.3").unwrap();
160 assert_eq!(Semver::to_string(&mut sv), "1.2.3");
161 }
162
163 #[test]
164 fn test_comparison_lt() {
165 let v1 = Semver::parse("1.2.3").unwrap();
166 let v2 = Semver::parse("1.2.4").unwrap();
167 assert!(v1 < v2);
168 assert!(v2 > v1);
169 }
170
171 #[test]
172 fn test_comparison_eq() {
173 let v1 = Semver::parse("1.2.3").unwrap();
174 let v2 = Semver::parse("1.2.3").unwrap();
175 assert!(v1 == v2);
176 assert!(v1 <= v2);
177 assert!(v1 >= v2);
178 }
179
180 #[test]
181 fn test_comparison_major_beats_minor() {
182 let v1 = Semver::parse("2.0.0").unwrap();
183 let v2 = Semver::parse("1.99.99").unwrap();
184 assert!(v1 > v2);
185 }
186
187 #[test]
188 fn test_comparison_v_prefix_transparent() {
189 let v1 = Semver::parse("v1.2.3").unwrap();
190 let v2 = Semver::parse("2.0.0").unwrap();
191 assert!(v1 < v2);
192 }
193
194 #[test]
195 fn test_inc_major_resets_minor_and_patch() {
196 let mut sv = Semver::parse("1.2.3").unwrap();
197 sv.inc_major();
198 assert_eq!(sv.version.major, 2);
199 assert_eq!(sv.version.minor, 0);
200 assert_eq!(sv.version.patch, 0);
201 assert!(sv.version.pre.is_empty());
202 }
203
204 #[test]
205 fn test_inc_minor_resets_patch() {
206 let mut sv = Semver::parse("1.2.3").unwrap();
207 sv.inc_minor();
208 assert_eq!(sv.version.minor, 3);
209 assert_eq!(sv.version.patch, 0);
210 assert!(sv.version.pre.is_empty());
211 }
212
213 #[test]
214 fn test_inc_patch_stable() {
215 let mut sv = Semver::parse("1.2.3").unwrap();
216 sv.inc_patch();
217 assert_eq!(sv.version.patch, 4);
218 }
219
220 #[test]
221 fn test_inc_patch_clears_prerelease_without_bumping_patch() {
222 let mut sv = Semver::parse("1.2.3-beta.1").unwrap();
223 sv.inc_patch();
224 assert_eq!(sv.version.patch, 3);
225 assert!(sv.version.pre.is_empty());
226 }
227
228 #[test]
229 fn test_inc_beta_from_stable_bumps_patch() {
230 let mut sv = Semver::parse("1.2.3").unwrap();
231 sv.inc_beta().unwrap();
232 assert_eq!(sv.version.patch, 4);
233 assert_eq!(sv.version.pre.as_str(), "beta.1");
234 }
235
236 #[test]
237 fn test_inc_beta_from_existing_beta_increments_counter() {
238 let mut sv = Semver::parse("1.2.4-beta.1").unwrap();
239 sv.inc_beta().unwrap();
240 assert_eq!(sv.version.patch, 4);
241 assert_eq!(sv.version.pre.as_str(), "beta.2");
242 }
243
244 #[test]
245 fn test_inc_alpha_from_stable_bumps_patch() {
246 let mut sv = Semver::parse("1.2.3").unwrap();
247 sv.inc_alpha().unwrap();
248 assert_eq!(sv.version.patch, 4);
249 assert_eq!(sv.version.pre.as_str(), "alpha.1");
250 }
251
252 #[test]
253 fn test_inc_alpha_from_existing_alpha_increments_counter() {
254 let mut sv = Semver::parse("1.2.4-alpha.2").unwrap();
255 sv.inc_alpha().unwrap();
256 assert_eq!(sv.version.pre.as_str(), "alpha.3");
257 }
258
259 #[test]
260 fn test_parse_invalid_returns_error() {
261 assert!(Semver::parse("not-semver").is_err());
262 assert!(Semver::parse("1.2").is_err());
263 assert!(Semver::parse("").is_err());
264 }
265
266 #[test]
267 fn test_opt_parse_invalid_returns_none() {
268 assert!(Semver::opt_parse("not-semver").is_none());
269 }
270
271 #[test]
272 fn test_opt_parse_valid_returns_some() {
273 assert!(Semver::opt_parse("1.0.0").is_some());
274 }
275
276 #[test]
277 fn test_prerelease_is_less_than_stable() {
278 let pre = Semver::parse("1.2.3-alpha.1").unwrap();
279 let stable = Semver::parse("1.2.3").unwrap();
280 assert!(pre < stable);
281 }
282}