11use  std:: collections:: HashSet ; 
2- use  std:: fmt:: Display ; 
2+ use  std:: fmt:: { Display ,   Formatter } ; 
33use  std:: path:: { Path ,  PathBuf } ; 
44use  std:: sync:: { Arc ,  Mutex } ; 
55
6- use  termcolor:: WriteColor ; 
6+ use  termcolor:: { Color ,   WriteColor } ; 
77
88/// Collects diagnostics from all tidy steps, and contains shared information 
99/// that determines how should message and logs be presented. 
@@ -14,40 +14,56 @@ use termcolor::WriteColor;
1414pub  struct  DiagCtx ( Arc < Mutex < DiagCtxInner > > ) ; 
1515
1616impl  DiagCtx  { 
17-     pub  fn  new ( verbose :  bool )  -> Self  { 
17+     pub  fn  new ( root_path :   & Path ,   verbose :  bool )  -> Self  { 
1818        Self ( Arc :: new ( Mutex :: new ( DiagCtxInner  { 
1919            running_checks :  Default :: default ( ) , 
2020            finished_checks :  Default :: default ( ) , 
21+             root_path :  root_path. to_path_buf ( ) , 
2122            verbose, 
2223        } ) ) ) 
2324    } 
2425
2526    pub  fn  start_check < Id :  Into < CheckId > > ( & self ,  id :  Id )  -> RunningCheck  { 
26-         let  id = id. into ( ) ; 
27+         let  mut   id = id. into ( ) ; 
2728
2829        let  mut  ctx = self . 0 . lock ( ) . unwrap ( ) ; 
30+ 
31+         // Shorten path for shorter diagnostics 
32+         id. path  = match  id. path  { 
33+             Some ( path)  => Some ( path. strip_prefix ( & ctx. root_path ) . unwrap_or ( & path) . to_path_buf ( ) ) , 
34+             None  => None , 
35+         } ; 
36+ 
2937        ctx. start_check ( id. clone ( ) ) ; 
30-         RunningCheck  {  id,  bad :  false ,  ctx :  self . 0 . clone ( )  } 
38+         RunningCheck  { 
39+             id, 
40+             bad :  false , 
41+             ctx :  self . 0 . clone ( ) , 
42+             #[ cfg( test) ]  
43+             errors :  vec ! [ ] , 
44+         } 
3145    } 
3246
33-     pub  fn  into_conclusion ( self )  -> bool  { 
34-         let  ctx = self . 0 . lock ( ) . unwrap ( ) ; 
47+     pub  fn  into_failed_checks ( self )  -> Vec < FinishedCheck >  { 
48+         let  ctx = Arc :: into_inner ( self . 0 ) . unwrap ( ) . into_inner ( ) . unwrap ( ) ; 
3549        assert ! ( ctx. running_checks. is_empty( ) ,  "Some checks are still running" ) ; 
36-         ctx. finished_checks . iter ( ) . any ( |c| c. bad ) 
50+         ctx. finished_checks . into_iter ( ) . filter ( |c| c. bad ) . collect ( ) 
3751    } 
3852} 
3953
4054struct  DiagCtxInner  { 
4155    running_checks :  HashSet < CheckId > , 
4256    finished_checks :  HashSet < FinishedCheck > , 
4357    verbose :  bool , 
58+     root_path :  PathBuf , 
4459} 
4560
4661impl  DiagCtxInner  { 
4762    fn  start_check ( & mut  self ,  id :  CheckId )  { 
4863        if  self . has_check_id ( & id)  { 
4964            panic ! ( "Starting a check named `{id:?}` for the second time" ) ; 
5065        } 
66+ 
5167        self . running_checks . insert ( id) ; 
5268    } 
5369
@@ -57,6 +73,13 @@ impl DiagCtxInner {
5773            "Finishing check `{:?}` that was not started" , 
5874            check. id
5975        ) ; 
76+ 
77+         if  check. bad  { 
78+             output_message ( "FAIL" ,  Some ( & check. id ) ,  Some ( COLOR_ERROR ) ) ; 
79+         }  else  if  self . verbose  { 
80+             output_message ( "OK" ,  Some ( & check. id ) ,  Some ( COLOR_SUCCESS ) ) ; 
81+         } 
82+ 
6083        self . finished_checks . insert ( check) ; 
6184    } 
6285
@@ -71,8 +94,8 @@ impl DiagCtxInner {
7194/// Identifies a single step 
7295#[ derive( PartialEq ,  Eq ,  Hash ,  Clone ,  Debug ) ]  
7396pub  struct  CheckId  { 
74-     name :  String , 
75-     path :  Option < PathBuf > , 
97+     pub   name :  String , 
98+     pub   path :  Option < PathBuf > , 
7699} 
77100
78101impl  CheckId  { 
@@ -91,40 +114,70 @@ impl From<&'static str> for CheckId {
91114    } 
92115} 
93116
117+ impl  Display  for  CheckId  { 
118+     fn  fmt ( & self ,  f :  & mut  Formatter < ' _ > )  -> std:: fmt:: Result  { 
119+         write ! ( f,  "{}" ,  self . name) ?; 
120+         if  let  Some ( path)  = & self . path  { 
121+             write ! ( f,  " ({})" ,  path. display( ) ) ?; 
122+         } 
123+         Ok ( ( ) ) 
124+     } 
125+ } 
126+ 
94127#[ derive( PartialEq ,  Eq ,  Hash ,  Debug ) ]  
95- struct  FinishedCheck  { 
128+ pub   struct  FinishedCheck  { 
96129    id :  CheckId , 
97130    bad :  bool , 
98131} 
99132
133+ impl  FinishedCheck  { 
134+     pub  fn  id ( & self )  -> & CheckId  { 
135+         & self . id 
136+     } 
137+ } 
138+ 
100139/// Represents a single tidy check, identified by its `name`, running. 
101140pub  struct  RunningCheck  { 
102141    id :  CheckId , 
103142    bad :  bool , 
104143    ctx :  Arc < Mutex < DiagCtxInner > > , 
144+     #[ cfg( test) ]  
145+     errors :  Vec < String > , 
105146} 
106147
107148impl  RunningCheck  { 
149+     /// Creates a new instance of a running check without going through the diag 
150+ /// context. 
151+ /// Useful if you want to run some functions from tidy without configuring 
152+ /// diagnostics. 
153+ pub  fn  new_noop ( )  -> Self  { 
154+         let  ctx = DiagCtx :: new ( Path :: new ( "" ) ,  false ) ; 
155+         ctx. start_check ( "noop" ) 
156+     } 
157+ 
108158    /// Immediately output an error and mark the check as failed. 
109- pub  fn  error < T :  Display > ( & mut  self ,  t :  T )  { 
159+ pub  fn  error < T :  Display > ( & mut  self ,  msg :  T )  { 
110160        self . mark_as_bad ( ) ; 
111-         tidy_error ( & t. to_string ( ) ) . expect ( "failed to output error" ) ; 
161+         let  msg = msg. to_string ( ) ; 
162+         output_message ( & msg,  Some ( & self . id ) ,  Some ( COLOR_ERROR ) ) ; 
163+         #[ cfg( test) ]  
164+         self . errors . push ( msg) ; 
112165    } 
113166
114167    /// Immediately output a warning. 
115- pub  fn  warning < T :  Display > ( & mut  self ,  t :  T )  { 
116-         eprintln ! ( "WARNING: {t}" ) ; 
168+ pub  fn  warning < T :  Display > ( & mut  self ,  msg :  T )  { 
169+         output_message ( & msg . to_string ( ) ,   Some ( & self . id ) ,   Some ( COLOR_WARNING ) ) ; 
117170    } 
118171
119172    /// Output an informational message 
120- pub  fn  message < T :  Display > ( & mut  self ,  t :  T )  { 
121-         eprintln ! ( "{t}" ) ; 
173+ pub  fn  message < T :  Display > ( & mut  self ,  msg :  T )  { 
174+         output_message ( & msg . to_string ( ) ,   Some ( & self . id ) ,   None ) ; 
122175    } 
123176
124177    /// Output a message only if verbose output is enabled. 
125- pub  fn  verbose_msg < T :  Display > ( & mut  self ,  t :  T )  { 
178+ pub  fn  verbose_msg < T :  Display > ( & mut  self ,  msg :  T )  { 
126179        if  self . is_verbose_enabled ( )  { 
127-             self . message ( t ) ; 
180+             self . message ( msg ) ; 
128181        } 
129182    } 
130183
@@ -138,6 +191,11 @@ impl RunningCheck {
138191        self . ctx . lock ( ) . unwrap ( ) . verbose 
139192    } 
140193
194+     #[ cfg( test) ]  
195+     pub  fn  get_errors ( & self )  -> Vec < String >  { 
196+         self . errors . clone ( ) 
197+     } 
198+ 
141199    fn  mark_as_bad ( & mut  self )  { 
142200        self . bad  = true ; 
143201    } 
@@ -149,17 +207,37 @@ impl Drop for RunningCheck {
149207    } 
150208} 
151209
152- fn  tidy_error ( args :  & str )  -> std:: io:: Result < ( ) >  { 
210+ pub  const  COLOR_SUCCESS :  Color  = Color :: Green ; 
211+ pub  const  COLOR_ERROR :  Color  = Color :: Red ; 
212+ pub  const  COLOR_WARNING :  Color  = Color :: Yellow ; 
213+ 
214+ /// Output a message to stderr. 
215+ /// The message can be optionally scoped to a certain check, and it can also have a certain color. 
216+ pub  fn  output_message ( msg :  & str ,  id :  Option < & CheckId > ,  color :  Option < Color > )  { 
153217    use  std:: io:: Write ; 
154218
155-     use  termcolor:: { Color ,   ColorChoice ,  ColorSpec ,  StandardStream } ; 
219+     use  termcolor:: { ColorChoice ,  ColorSpec ,  StandardStream } ; 
156220
157-     let  mut  stderr = StandardStream :: stdout ( ColorChoice :: Auto ) ; 
158-     stderr. set_color ( ColorSpec :: new ( ) . set_fg ( Some ( Color :: Red ) ) ) ?; 
221+     let  mut  stderr = StandardStream :: stderr ( ColorChoice :: Auto ) ; 
222+     if  let  Some ( color)  = & color { 
223+         stderr. set_color ( ColorSpec :: new ( ) . set_fg ( Some ( * color) ) ) . unwrap ( ) ; 
224+     } 
159225
160-     write ! ( & mut  stderr,  "tidy error" ) ?; 
161-     stderr. set_color ( & ColorSpec :: new ( ) ) ?; 
226+     match  id { 
227+         Some ( id)  => { 
228+             write ! ( & mut  stderr,  "tidy [{}" ,  id. name) . unwrap ( ) ; 
229+             if  let  Some ( path)  = & id. path  { 
230+                 write ! ( & mut  stderr,  " ({})" ,  path. display( ) ) . unwrap ( ) ; 
231+             } 
232+             write ! ( & mut  stderr,  "]" ) . unwrap ( ) ; 
233+         } 
234+         None  => { 
235+             write ! ( & mut  stderr,  "tidy" ) . unwrap ( ) ; 
236+         } 
237+     } 
238+     if  color. is_some ( )  { 
239+         stderr. set_color ( & ColorSpec :: new ( ) ) . unwrap ( ) ; 
240+     } 
162241
163-     writeln ! ( & mut  stderr,  ": {args}" ) ?; 
164-     Ok ( ( ) ) 
242+     writeln ! ( & mut  stderr,  ": {msg}" ) . unwrap ( ) ; 
165243} 
0 commit comments