1use std::collections::HashSet;
2use std::fmt::{Display, Formatter};
3use std::io;
4use std::path::{Path, PathBuf};
5use std::sync::{Arc, Mutex};
6
7use termcolor::Color;
8
9#[derive(Clone, Default)]
10pub struct TidyFlags {
12 bless: bool,
14}
15
16impl TidyFlags {
17 pub fn new(cfg_args: &[String]) -> Self {
18 let mut flags = Self::default();
19
20 for arg in cfg_args {
21 match arg.as_str() {
22 "--bless" => flags.bless = true,
23 _ => continue,
24 }
25 }
26 flags
27 }
28}
29
30#[derive(Clone)]
36pub struct TidyCtx {
37 tidy_flags: TidyFlags,
38 diag_ctx: Arc<Mutex<DiagCtxInner>>,
39}
40
41impl TidyCtx {
42 pub fn new(root_path: &Path, verbose: bool, tidy_flags: TidyFlags) -> Self {
43 Self {
44 diag_ctx: Arc::new(Mutex::new(DiagCtxInner {
45 running_checks: Default::default(),
46 finished_checks: Default::default(),
47 root_path: root_path.to_path_buf(),
48 verbose,
49 })),
50 tidy_flags,
51 }
52 }
53
54 pub fn is_bless_enabled(&self) -> bool {
55 self.tidy_flags.bless
56 }
57
58 pub fn start_check<Id: Into<CheckId>>(&self, id: Id) -> RunningCheck {
59 let mut id = id.into();
60
61 let mut ctx = self.diag_ctx.lock().unwrap();
62
63 id.path = match id.path {
65 Some(path) => Some(path.strip_prefix(&ctx.root_path).unwrap_or(&path).to_path_buf()),
66 None => None,
67 };
68
69 ctx.start_check(id.clone());
70 RunningCheck {
71 id,
72 bad: false,
73 ctx: self.diag_ctx.clone(),
74 #[cfg(test)]
75 errors: vec![],
76 }
77 }
78
79 pub fn into_failed_checks(self) -> Vec<FinishedCheck> {
80 let ctx = Arc::into_inner(self.diag_ctx).unwrap().into_inner().unwrap();
81 assert!(ctx.running_checks.is_empty(), "Some checks are still running");
82 ctx.finished_checks.into_iter().filter(|c| c.bad).collect()
83 }
84}
85
86struct DiagCtxInner {
87 running_checks: HashSet<CheckId>,
88 finished_checks: HashSet<FinishedCheck>,
89 verbose: bool,
90 root_path: PathBuf,
91}
92
93impl DiagCtxInner {
94 fn start_check(&mut self, id: CheckId) {
95 if self.has_check_id(&id) {
96 panic!("Starting a check named `{id:?}` for the second time");
97 }
98
99 self.running_checks.insert(id);
100 }
101
102 fn finish_check(&mut self, check: FinishedCheck) {
103 assert!(
104 self.running_checks.remove(&check.id),
105 "Finishing check `{:?}` that was not started",
106 check.id
107 );
108
109 if check.bad {
110 output_message("FAIL", Some(&check.id), Some(COLOR_ERROR));
111 } else if self.verbose {
112 output_message("OK", Some(&check.id), Some(COLOR_SUCCESS));
113 }
114
115 self.finished_checks.insert(check);
116 }
117
118 fn has_check_id(&self, id: &CheckId) -> bool {
119 self.running_checks
120 .iter()
121 .chain(self.finished_checks.iter().map(|c| &c.id))
122 .any(|c| c == id)
123 }
124}
125
126#[derive(PartialEq, Eq, Hash, Clone, Debug)]
128pub struct CheckId {
129 pub name: String,
130 pub path: Option<PathBuf>,
131}
132
133impl CheckId {
134 pub fn new(name: &'static str) -> Self {
135 Self { name: name.to_string(), path: None }
136 }
137
138 pub fn path(self, path: &Path) -> Self {
139 Self { path: Some(path.to_path_buf()), ..self }
140 }
141}
142
143impl From<&'static str> for CheckId {
144 fn from(name: &'static str) -> Self {
145 Self::new(name)
146 }
147}
148
149impl Display for CheckId {
150 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151 write!(f, "{}", self.name)?;
152 if let Some(path) = &self.path {
153 write!(f, " ({})", path.display())?;
154 }
155 Ok(())
156 }
157}
158
159#[derive(PartialEq, Eq, Hash, Debug)]
160pub struct FinishedCheck {
161 id: CheckId,
162 bad: bool,
163}
164
165impl FinishedCheck {
166 pub fn id(&self) -> &CheckId {
167 &self.id
168 }
169}
170
171pub struct RunningCheck {
173 id: CheckId,
174 bad: bool,
175 ctx: Arc<Mutex<DiagCtxInner>>,
176 #[cfg(test)]
177 errors: Vec<String>,
178}
179
180impl RunningCheck {
181 pub fn new_noop() -> Self {
186 let ctx = TidyCtx::new(Path::new(""), false, TidyFlags::default());
187 ctx.start_check("noop")
188 }
189
190 pub fn error<T: Display>(&mut self, msg: T) {
192 self.mark_as_bad();
193 let msg = msg.to_string();
194 output_message(&msg, Some(&self.id), Some(COLOR_ERROR));
195 #[cfg(test)]
196 self.errors.push(msg);
197 }
198
199 pub fn warning<T: Display>(&mut self, msg: T) {
201 output_message(&msg.to_string(), Some(&self.id), Some(COLOR_WARNING));
202 }
203
204 pub fn message<T: Display>(&mut self, msg: T) {
206 output_message(&msg.to_string(), Some(&self.id), None);
207 }
208
209 pub fn verbose_msg<T: Display>(&mut self, msg: T) {
211 if self.is_verbose_enabled() {
212 self.message(msg);
213 }
214 }
215
216 pub fn is_bad(&self) -> bool {
218 self.bad
219 }
220
221 pub fn is_verbose_enabled(&self) -> bool {
223 self.ctx.lock().unwrap().verbose
224 }
225
226 #[cfg(test)]
227 pub fn get_errors(&self) -> Vec<String> {
228 self.errors.clone()
229 }
230
231 fn mark_as_bad(&mut self) {
232 self.bad = true;
233 }
234}
235
236impl Drop for RunningCheck {
237 fn drop(&mut self) {
238 self.ctx.lock().unwrap().finish_check(FinishedCheck { id: self.id.clone(), bad: self.bad })
239 }
240}
241
242pub const COLOR_SUCCESS: Color = Color::Green;
243pub const COLOR_ERROR: Color = Color::Red;
244pub const COLOR_WARNING: Color = Color::Yellow;
245
246pub fn output_message(msg: &str, id: Option<&CheckId>, color: Option<Color>) {
249 use termcolor::{ColorChoice, ColorSpec};
250
251 let stderr: &mut dyn termcolor::WriteColor = if cfg!(test) {
252 &mut StderrForUnitTests
253 } else {
254 &mut termcolor::StandardStream::stderr(ColorChoice::Auto)
255 };
256
257 if let Some(color) = &color {
258 stderr.set_color(ColorSpec::new().set_fg(Some(*color))).unwrap();
259 }
260
261 match id {
262 Some(id) => {
263 write!(stderr, "tidy [{}", id.name).unwrap();
264 if let Some(path) = &id.path {
265 write!(stderr, " ({})", path.display()).unwrap();
266 }
267 write!(stderr, "]").unwrap();
268 }
269 None => {
270 write!(stderr, "tidy").unwrap();
271 }
272 }
273 if color.is_some() {
274 stderr.set_color(&ColorSpec::new()).unwrap();
275 }
276
277 writeln!(stderr, ": {msg}").unwrap();
278}
279
280struct StderrForUnitTests;
284
285impl io::Write for StderrForUnitTests {
286 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
287 eprint!("{}", String::from_utf8_lossy(buf));
288 Ok(buf.len())
289 }
290
291 fn flush(&mut self) -> io::Result<()> {
292 Ok(())
293 }
294}
295
296impl termcolor::WriteColor for StderrForUnitTests {
297 fn supports_color(&self) -> bool {
298 false
299 }
300
301 fn set_color(&mut self, _spec: &termcolor::ColorSpec) -> io::Result<()> {
302 Ok(())
303 }
304
305 fn reset(&mut self) -> io::Result<()> {
306 Ok(())
307 }
308}