rustversion_detect/version.rs
1//! Defines the rust version types.
2
3use core::fmt::{self, Display, Formatter};
4use core::num::ParseIntError;
5use core::str::FromStr;
6
7use crate::date::Date;
8
9/// Specifies a specific stable version, like `1.48`.
10#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11pub struct StableVersionSpec {
12 /// The major version
13 pub major: u32,
14 /// The minor version
15 pub minor: u32,
16 /// The patch version.
17 ///
18 /// If this is `None`, it will match any patch version.
19 pub patch: Option<u32>,
20}
21impl StableVersionSpec {
22 /// Specify a minor version like `1.32`.
23 ///
24 /// # Panics
25 /// Panics if the major version is not `1`.
26 #[inline]
27 pub fn minor(major: u32, minor: u32) -> Self {
28 check_major_version(major);
29 StableVersionSpec {
30 major,
31 minor,
32 patch: None,
33 }
34 }
35
36 /// Specify a patch version like `1.32.4`.
37 ///
38 /// # Panics
39 /// Panics if the major version is not `1`.
40 #[inline]
41 pub fn patch(major: u32, minor: u32, patch: u32) -> Self {
42 check_major_version(major);
43 StableVersionSpec {
44 major,
45 minor,
46 patch: Some(patch),
47 }
48 }
49
50 /// Convert this specification into a concrete [`RustVersion`].
51 ///
52 /// If the patch version is not specified,
53 /// it is assumed to be zero.
54 #[inline]
55 pub fn to_version(&self) -> RustVersion {
56 RustVersion::stable(self.major, self.minor, self.patch.unwrap_or(0))
57 }
58}
59impl FromStr for StableVersionSpec {
60 type Err = StableVersionParseError;
61
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 let mut iter = s.split('.');
64 let major = iter
65 .next()
66 .ok_or(StableVersionParseError::BadNumberParts)?
67 .parse::<u32>()?;
68 let minor = iter
69 .next()
70 .ok_or(StableVersionParseError::BadNumberParts)?
71 .parse::<u32>()?;
72 let patch = match iter.next() {
73 Some(patch_text) => Some(patch_text.parse::<u32>()?),
74 None => None,
75 };
76 if iter.next().is_some() {
77 return Err(StableVersionParseError::BadNumberParts);
78 }
79 if major != 1 {
80 return Err(StableVersionParseError::InvalidMajorVersion);
81 }
82 Ok(StableVersionSpec {
83 major,
84 minor,
85 patch,
86 })
87 }
88}
89
90/// An error while parsing a [`StableVersionSpec`].
91///
92/// The specifics of this error are implementation-dependent.
93#[derive(Clone, Debug)]
94pub enum StableVersionParseError {
95 #[doc(hidden)]
96 InvalidNumber(ParseIntError),
97 #[doc(hidden)]
98 BadNumberParts,
99 #[doc(hidden)]
100 InvalidMajorVersion,
101}
102impl From<ParseIntError> for StableVersionParseError {
103 #[inline]
104 fn from(cause: ParseIntError) -> Self {
105 StableVersionParseError::InvalidNumber(cause)
106 }
107}
108
109/// Show the specification in a manner consistent with the `spec!` macro.
110impl Display for StableVersionSpec {
111 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
112 write!(f, "{}.{}", self.major, self.minor)?;
113 if let Some(patch) = self.patch {
114 write!(f, ".{}", patch)?;
115 }
116 Ok(())
117 }
118}
119
120/// Indicates the rust version.
121#[derive(Copy, Clone, Debug, Eq, PartialEq)]
122pub struct RustVersion {
123 /// The major version.
124 ///
125 /// Should always be one.
126 pub major: u32,
127 /// The minor version of rust.
128 pub minor: u32,
129 /// The patch version of the rust compiler.
130 pub patch: u32,
131 /// The channel of the rust compiler.
132 pub channel: Channel,
133}
134impl RustVersion {
135 /// Create a stable version with the specified combination of major, minor, and patch.
136 ///
137 /// The major version must be 1.0.
138 #[inline]
139 pub fn stable(major: u32, minor: u32, patch: u32) -> RustVersion {
140 check_major_version(major);
141 RustVersion {
142 major,
143 minor,
144 patch,
145 channel: Channel::Stable,
146 }
147 }
148
149 /// Check if this version is after the specified stable minor version.
150 ///
151 /// The patch version is unspecified and will be ignored.
152 ///
153 /// This is a shorthand for calling [`Self::is_since_stable`] with a minor version
154 /// spec created with [`StableVersionSpec::minor`].
155 ///
156 /// The major version must always be one, or a panic could happen.
157 ///
158 /// ## Example
159 /// ```
160 /// # use rustversion_detect::RustVersion;
161 ///
162 /// assert!(RustVersion::stable(1, 32, 2).is_since_minor_version(1, 32));
163 /// assert!(RustVersion::stable(1, 48, 0).is_since_minor_version(1, 40));
164 /// ```
165 #[inline]
166 pub fn is_since_minor_version(&self, major: u32, minor: u32) -> bool {
167 self.is_since_stable(StableVersionSpec::minor(major, minor))
168 }
169
170 /// Check if this version is after the specified stable patch version.
171 ///
172 /// This is a shorthand for calling [`Self::is_since_stable`] with a patch version
173 /// spec created with [`StableVersionSpec::patch`].
174 ///
175 /// The major version must always be one, or a panic could happen.
176 ///
177 /// ## Example
178 /// ```
179 /// # use rustversion_detect::RustVersion;
180 ///
181 /// assert!(RustVersion::stable(1, 32, 2).is_since_patch_version(1, 32, 1));
182 /// assert!(RustVersion::stable(1, 48, 0).is_since_patch_version(1, 40, 5));
183 /// ```
184 #[inline]
185 pub fn is_since_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
186 self.is_since_stable(StableVersionSpec::patch(major, minor, patch))
187 }
188
189 /// Check if this version is after the given [stable version spec](StableVersionSpec).
190 ///
191 /// In general, the [`Self::is_since_minor_version`] and [`Self::is_since_patch_version`]
192 /// helper methods are preferable.
193 ///
194 /// This ignores the channel.
195 ///
196 /// The negation of [`Self::is_before_stable`].
197 ///
198 /// Behavior is (mostly) equivalent to `#[rustversion::since($spec)]`
199 ///
200 /// ## Example
201 /// ```
202 /// # use rustversion_detect::{RustVersion, StableVersionSpec};
203 ///
204 /// assert!(RustVersion::stable(1, 32, 2).is_since_stable(StableVersionSpec::minor(1, 32)));
205 /// assert!(RustVersion::stable(1, 48, 0).is_since_stable(StableVersionSpec::patch(1, 32, 7)))
206 /// ```
207 #[inline]
208 pub fn is_since_stable(&self, spec: StableVersionSpec) -> bool {
209 self.major > spec.major
210 || (self.major == spec.major
211 && (self.minor > spec.minor
212 || (self.minor == spec.minor
213 && match spec.patch {
214 None => true, // missing spec always matches
215 Some(patch_spec) => self.patch >= patch_spec,
216 })))
217 }
218
219 /// Check if the version is less than the given [stable version spec](StableVersionSpec).
220 ///
221 /// This ignores the channel.
222 ///
223 /// In general, the [`Self::is_before_minor_version`] and [`Self::is_before_patch_version`]
224 /// helper methods are preferable.
225 ///
226 /// The negation of [`Self::is_since_stable`].
227 ///
228 /// Behavior is (mostly) equivalent to `#[rustversion::before($spec)]`
229 #[inline]
230 pub fn is_before_stable(&self, spec: StableVersionSpec) -> bool {
231 !self.is_since_stable(spec)
232 }
233
234 /// Check if this version is before the specified stable minor version.
235 ///
236 /// The patch version is unspecified and will be ignored.
237 ///
238 /// This is a shorthand for calling [`Self::is_before_stable`] with a minor version
239 /// spec created with [`StableVersionSpec::minor`].
240 ///
241 /// The major version must always be one, or a panic could happen.
242 #[inline]
243 pub fn is_before_minor_version(&self, major: u32, minor: u32) -> bool {
244 self.is_before_stable(StableVersionSpec::minor(major, minor))
245 }
246
247 /// Check if this version is before the specified stable patch version.
248 ///
249 /// This is a shorthand for calling [`Self::is_before_stable`] with a patch version
250 /// spec created with [`StableVersionSpec::patch`].
251 ///
252 /// The major version must always be one, or a panic could happen.
253 #[inline]
254 pub fn is_before_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
255 self.is_before_stable(StableVersionSpec::patch(major, minor, patch))
256 }
257
258 /// If this version is a nightly version after the specified start date.
259 ///
260 /// Stable and beta versions are always considered before every nightly versions.
261 /// Development versions are considered after every nightly version.
262 ///
263 /// The negation of [`Self::is_before_nightly`].
264 ///
265 /// Behavior is (mostly) equivalent to `#[rustversion::since($date)]`
266 ///
267 /// See also [`Date::is_since`].
268 #[inline]
269 pub fn is_since_nightly(&self, start: Date) -> bool {
270 match self.channel {
271 Channel::Nightly { date } => date.is_since(start),
272 Channel::Stable | Channel::Beta => false, // before every nightly
273 Channel::Development => true, // after every nightly version
274 Channel::__NonExhaustive => unreachable!(),
275 }
276 }
277
278 /// If this version comes before the nightly version with the specified start date.
279 ///
280 /// Stable and beta versions are always considered before every nightly versions.
281 /// Development versions are considered after every nightly version.
282 ///
283 /// The negation of [`Self::is_since_nightly`].
284 ///
285 /// See also [`Date::is_before`].
286 #[inline]
287 pub fn is_before_nightly(&self, start: Date) -> bool {
288 match self.channel {
289 Channel::Nightly { date } => date <= start,
290 Channel::Stable | Channel::Beta => false, // before every nightly
291 Channel::Development => true, // after every nightly version
292 Channel::__NonExhaustive => unreachable!(),
293 }
294 }
295
296 /// Check if this is a nightly compiler version.
297 #[inline]
298 pub fn is_nightly(&self) -> bool {
299 self.channel.is_nightly()
300 }
301
302 /// Check if this is a stable compiler version.
303 #[inline]
304 pub fn is_stable(&self) -> bool {
305 self.channel.is_stable()
306 }
307
308 /// Check if this is a beta compiler version.
309 #[inline]
310 pub fn is_beta(&self) -> bool {
311 self.channel.is_beta()
312 }
313
314 /// Check if this is a development compiler version.
315 #[inline]
316 pub fn is_development(&self) -> bool {
317 self.channel.is_development()
318 }
319}
320
321impl From<StableVersionSpec> for RustVersion {
322 #[inline]
323 fn from(value: StableVersionSpec) -> Self {
324 value.to_version()
325 }
326}
327
328/// Displays the version in a manner similar to `rustc --version`.
329///
330/// The format here is not stable and may change in the future.
331impl Display for RustVersion {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
334 match self.channel {
335 Channel::Stable => Ok(()), // nothing
336 Channel::Beta => f.write_str("-beta"),
337 Channel::Nightly { ref date } => {
338 write!(f, "-nightly ({})", date)
339 }
340 Channel::Development => f.write_str("-dev"),
341 Channel::__NonExhaustive => unreachable!(),
342 }
343 }
344}
345
346/// The [channel] of the rust compiler release.
347///
348/// [channel]: https://rust-lang.github.io/rustup/concepts/channels.html
349#[derive(Copy, Clone, Debug, Eq, PartialEq)]
350pub enum Channel {
351 /// A stable compiler version.
352 Stable,
353 /// A beta compiler version.
354 Beta,
355 /// A nightly compiler version.
356 Nightly {
357 /// The date that the compiler was released.
358 date: Date,
359 },
360 /// A development compiler version.
361 ///
362 /// These are compiled directly instead of distributed through [rustup](https://rustup.rs).
363 Development,
364 #[doc(hidden)]
365 __NonExhaustive,
366}
367impl Channel {
368 /// Check if this is the nightly channel.
369 #[inline]
370 pub fn is_nightly(&self) -> bool {
371 // NOTE: Can't use matches! because of minimum rust version
372 match *self {
373 Channel::Nightly { .. } => true,
374 _ => false,
375 }
376 }
377
378 /// Check if this is the stable channel.
379 #[inline]
380 pub fn is_stable(&self) -> bool {
381 match *self {
382 Channel::Stable => true,
383 _ => false,
384 }
385 }
386
387 /// Check if this is the beta channel.
388 #[inline]
389 pub fn is_beta(&self) -> bool {
390 match *self {
391 Channel::Beta => true,
392 _ => false,
393 }
394 }
395
396 /// Check if this is the development channel.
397 #[inline]
398 pub fn is_development(&self) -> bool {
399 match *self {
400 Channel::Development => true,
401 _ => false,
402 }
403 }
404}
405
406#[inline]
407fn check_major_version(major: u32) {
408 assert_eq!(major, 1, "Major version must be 1.*");
409}
410
411#[cfg(test)]
412mod test {
413 use super::{RustVersion, StableVersionSpec};
414
415 // (before, after)
416 fn versions() -> Vec<(RustVersion, RustVersion)> {
417 vec![
418 (RustVersion::stable(1, 7, 8), RustVersion::stable(1, 89, 0)),
419 (RustVersion::stable(1, 18, 0), RustVersion::stable(1, 80, 3)),
420 ]
421 }
422
423 #[cfg(test)]
424 impl RustVersion {
425 #[inline]
426 pub(crate) fn to_spec(&self) -> StableVersionSpec {
427 StableVersionSpec::patch(self.major, self.minor, self.patch)
428 }
429 }
430
431 #[test]
432 fn test_before_after() {
433 for (before, after) in versions() {
434 assert!(
435 before.is_before_stable(after.to_spec()),
436 "{} & {}",
437 before,
438 after
439 );
440 assert!(
441 after.is_since_stable(before.to_spec()),
442 "{} & {}",
443 before,
444 after
445 );
446 }
447 }
448}