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