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