rust_releases_rust_dist/
lib.rs1#![deny(missing_docs)]
2#![deny(clippy::all)]
3#![deny(unsafe_code)]
4#![allow(clippy::upper_case_acronyms)]
5#[cfg(test)]
10#[macro_use]
11extern crate rust_releases_io;
12
13use regex::{Captures, Regex};
14use rust_releases_core::{semver, Channel, FetchResources, Release, ReleaseIndex, Source};
15use rust_releases_io::Document;
16use std::collections::BTreeSet;
17use std::iter::FromIterator;
18
19pub(crate) mod errors;
20pub(crate) mod fetch;
21
22pub use crate::errors::{RustDistError, RustDistResult};
23
24pub struct RustDist {
28 source: Document,
29}
30
31impl RustDist {
32 #[cfg(test)]
33 pub(crate) fn from_document(source: Document) -> Self {
34 Self { source }
35 }
36}
37
38lazy_static::lazy_static! {
39 static ref MATCHER: Regex =
40 Regex::new(r"(?m)^dist/rustc-(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?:\-(alpha|beta|nightly)(\.\d+))?").unwrap();
41}
42
43impl Source for RustDist {
44 type Error = RustDistError;
45
46 fn build_index(&self) -> Result<ReleaseIndex, Self::Error> {
47 let buffer = self.source.buffer();
48 let content = std::str::from_utf8(buffer).map_err(RustDistError::UnrecognizedText)?;
49
50 let releases = MATCHER
51 .captures_iter(content)
52 .map(parse_release)
53 .collect::<RustDistResult<BTreeSet<Release>>>()?;
54
55 Ok(ReleaseIndex::from_iter(releases))
56 }
57}
58
59fn parse_release(capture: Captures) -> RustDistResult<Release> {
60 const MAJOR: &str = "major";
61 const MINOR: &str = "minor";
62 const PATCH: &str = "patch";
63
64 let major = capture[MAJOR].parse::<u64>().map_err(|_| {
65 RustDistError::UnableToParseVersionNumberComponent(&MAJOR, capture[MAJOR].to_string())
66 })?;
67 let minor = capture[MINOR].parse::<u64>().map_err(|_| {
68 RustDistError::UnableToParseVersionNumberComponent(&MINOR, capture[MINOR].to_string())
69 })?;
70 let patch = capture[PATCH].parse::<u64>().map_err(|_| {
71 RustDistError::UnableToParseVersionNumberComponent(&PATCH, capture[PATCH].to_string())
72 })?;
73
74 Ok(Release::new_stable(semver::Version::new(
75 major, minor, patch,
76 )))
77}
78
79impl FetchResources for RustDist {
80 type Error = RustDistError;
81
82 fn fetch_channel(channel: Channel) -> Result<Self, Self::Error> {
83 if let Channel::Stable = channel {
84 let source = fetch::fetch()?;
85 Ok(Self { source })
86 } else {
87 Err(RustDistError::ChannelNotAvailable(channel))
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::RustDist;
95 use rust_releases_core::{semver, Release, ReleaseIndex};
96 use rust_releases_io::Document;
97 use std::fs;
98
99 #[test]
100 fn source_rust_dist() {
101 let path = [
102 env!("CARGO_MANIFEST_DIR"),
103 "/../../resources/rust_dist/dist_static-rust-lang-org.txt",
104 ]
105 .join("");
106
107 let buffer = fs::read(path).unwrap();
108 let document = Document::new(buffer);
109
110 let strategy = RustDist::from_document(document);
111 let index = ReleaseIndex::from_source(strategy).unwrap();
112
113 let releases = index.releases();
115
116 assert_eq!(releases.len(), 74);
117 assert_eq!(
118 releases[0],
119 Release::new_stable(semver::Version::new(1, 53, 0))
120 );
121 assert_eq!(
122 releases[releases.len() - 1],
123 Release::new_stable(semver::Version::new(1, 0, 0))
124 );
125 }
126}