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