]> git.proxmox.com Git - rustc.git/blame - src/doc/rustc-dev-guide/ci/date-check/src/main.rs
New upstream version 1.56.0~beta.4+dfsg1
[rustc.git] / src / doc / rustc-dev-guide / ci / date-check / src / main.rs
CommitLineData
6a06907d
XL
1use std::{
2 collections::BTreeMap,
3 convert::TryInto as _,
4 env, fmt, fs,
5 path::{Path, PathBuf},
6};
7
8use chrono::{Datelike as _, TimeZone as _, Utc};
9use glob::glob;
10use regex::Regex;
11
12#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13struct Date {
14 year: u32,
15 month: u32,
16}
17
18impl Date {
19 fn months_since(self, other: Date) -> Option<u32> {
20 let self_chrono = Utc.ymd(self.year.try_into().unwrap(), self.month, 1);
21 let other_chrono = Utc.ymd(other.year.try_into().unwrap(), other.month, 1);
22 let duration_since = self_chrono.signed_duration_since(other_chrono);
23 let months_since = duration_since.num_days() / 30;
24 if months_since < 0 {
25 None
26 } else {
27 Some(months_since.try_into().unwrap())
28 }
29 }
30}
31
32impl fmt::Display for Date {
33 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 write!(f, "{:04}-{:02}", self.year, self.month)
35 }
36}
37
38fn make_date_regex() -> Regex {
39 Regex::new(
40 r"(?x) # insignificant whitespace mode
41 <!--\s*
94222f64 42 [dD]ate:\s*
6a06907d
XL
43 (?P<y>\d{4}) # year
44 -
45 (?P<m>\d{2}) # month
46 \s*-->",
47 )
48 .unwrap()
49}
50
51fn collect_dates_from_file(date_regex: &Regex, text: &str) -> Vec<(usize, Date)> {
52 let mut line = 1;
53 let mut end_of_last_cap = 0;
54 date_regex
55 .captures_iter(&text)
56 .map(|cap| {
57 (
58 cap.get(0).unwrap().range(),
59 Date {
60 year: cap["y"].parse().unwrap(),
61 month: cap["m"].parse().unwrap(),
62 },
63 )
64 })
65 .map(|(byte_range, date)| {
66 line += text[end_of_last_cap..byte_range.end]
67 .chars()
68 .filter(|c| *c == '\n')
69 .count();
70 end_of_last_cap = byte_range.end;
71 (line, date)
72 })
73 .collect()
74}
75
76fn collect_dates(paths: impl Iterator<Item = PathBuf>) -> BTreeMap<PathBuf, Vec<(usize, Date)>> {
77 let date_regex = make_date_regex();
78 let mut data = BTreeMap::new();
79 for path in paths {
80 let text = fs::read_to_string(&path).unwrap();
81 let dates = collect_dates_from_file(&date_regex, &text);
82 if !dates.is_empty() {
83 data.insert(path, dates);
84 }
85 }
86 data
87}
88
89fn filter_dates(
90 current_month: Date,
91 min_months_since: u32,
92 dates_by_file: impl Iterator<Item = (PathBuf, Vec<(usize, Date)>)>,
93) -> impl Iterator<Item = (PathBuf, Vec<(usize, Date)>)> {
94 dates_by_file
95 .map(move |(path, dates)| {
96 (
97 path,
98 dates
99 .into_iter()
100 .filter(|(_, date)| {
101 current_month
102 .months_since(*date)
103 .expect("found date that is after current month")
104 >= min_months_since
105 })
106 .collect::<Vec<_>>(),
107 )
108 })
109 .filter(|(_, dates)| !dates.is_empty())
110}
111
112fn main() {
113 let root_dir = env::args()
114 .nth(1)
115 .expect("expect root Markdown directory as CLI argument");
116 let root_dir_path = Path::new(&root_dir);
117 let glob_pat = format!("{}/**/*.md", root_dir);
118 let today_chrono = Utc::today();
119 let current_month = Date {
120 year: today_chrono.year_ce().1,
121 month: today_chrono.month(),
122 };
123
124 let dates_by_file = collect_dates(glob(&glob_pat).unwrap().map(Result::unwrap));
125 let dates_by_file: BTreeMap<_, _> =
126 filter_dates(current_month, 6, dates_by_file.into_iter()).collect();
127
128 if dates_by_file.is_empty() {
129 println!("empty");
130 } else {
131 println!("Date Reference Triage for {}", current_month);
132 println!("## Procedure");
133 println!();
134 println!(
135 "Each of these dates should be checked to see if the docs they annotate are \
136 up-to-date. Each date should be updated (in the Markdown file where it appears) to \
137 use the current month ({current_month}), or removed if the docs it annotates are not \
138 expected to fall out of date quickly.",
139 current_month = current_month
140 );
141 println!();
142 println!(
143 "Please check off each date once a PR to update it (and, if applicable, its \
144 surrounding docs) has been merged. Please also mention that you are working on a \
145 particular set of dates so duplicate work is avoided."
146 );
147 println!();
148 println!("Finally, once all the dates have been updated, please close this issue.");
149 println!();
150 println!("## Dates");
151 println!();
152
153 for (path, dates) in dates_by_file {
154 println!(
155 "- [ ] {}",
156 path.strip_prefix(&root_dir_path).unwrap().display()
157 );
158 for (line, date) in dates {
159 println!(" - [ ] line {}: {}", line, date);
160 }
161 }
162 println!();
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_months_since() {
172 let date1 = Date {
173 year: 2020,
174 month: 3,
175 };
176 let date2 = Date {
177 year: 2021,
178 month: 1,
179 };
180 assert_eq!(date2.months_since(date1), Some(10));
181 }
182
183 #[test]
184 fn test_date_regex() {
185 let regex = make_date_regex();
186 assert!(regex.is_match("foo <!-- date: 2021-01 --> bar"));
187 }
188
94222f64
XL
189 #[test]
190 fn test_date_regex_capitalized() {
191 let regex = make_date_regex();
192 assert!(regex.is_match("foo <!-- Date: 2021-08 --> bar"));
193 }
194
6a06907d
XL
195 #[test]
196 fn test_collect_dates_from_file() {
197 let text = "Test1\n<!-- date: 2021-01 -->\nTest2\nFoo<!-- date: 2021-02 \
198 -->\nTest3\nTest4\nFoo<!-- date: 2021-03 -->Bar\n<!-- date: 2021-04 \
199 -->\nTest5\nTest6\nTest7\n<!-- date: \n\n2021-05 -->\nTest8
200 ";
201 assert_eq!(
202 collect_dates_from_file(&make_date_regex(), text),
203 vec![
204 (
205 2,
206 Date {
207 year: 2021,
208 month: 1,
209 }
210 ),
211 (
212 4,
213 Date {
214 year: 2021,
215 month: 2,
216 }
217 ),
218 (
219 7,
220 Date {
221 year: 2021,
222 month: 3,
223 }
224 ),
225 (
226 8,
227 Date {
228 year: 2021,
229 month: 4,
230 }
231 ),
232 (
233 14,
234 Date {
235 year: 2021,
236 month: 5,
237 }
238 ),
239 ]
240 );
241 }
242}