1+ extern crate serde_json;
12#[ macro_use]
23extern crate quick_error;
3- extern crate serde_json;
4+ #[ macro_use]
5+ extern crate clap;
46extern crate colored;
57
68extern crate rustfix;
79
810use std:: fs:: File ;
911use std:: io:: { Read , Write } ;
12+ use std:: error:: Error ;
13+ use std:: process:: Command ;
14+
1015use colored:: Colorize ;
16+ use clap:: { Arg , App } ;
17+
18+ use rustfix:: Suggestion ;
19+ use rustfix:: diagnostics:: Diagnostic ;
1120
1221const USER_OPTIONS : & ' static str = "What do you want to do? \
1322 [r]eplace | [s]kip | save and [q]uit | [a]bort (without saving)";
@@ -21,7 +30,11 @@ fn main() {
2130 std:: process:: exit ( 0 ) ;
2231 }
2332 Err ( error) => {
24- writeln ! ( std:: io:: stderr( ) , "An error occured: {:#?}" , error) . unwrap ( ) ;
33+ writeln ! ( std:: io:: stderr( ) , "An error occured: {}" , error) . unwrap ( ) ;
34+ writeln ! ( std:: io:: stderr( ) , "{:?}" , error) . unwrap ( ) ;
35+ if let Some ( cause) = error. cause ( ) {
36+ writeln ! ( std:: io:: stderr( ) , "Cause: {:?}" , cause) . unwrap ( ) ;
37+ }
2538 std:: process:: exit ( 1 ) ;
2639 }
2740 }
@@ -32,64 +45,128 @@ macro_rules! flush {
3245}
3346
3447fn try_main ( ) -> Result < ( ) , ProgramError > {
35- let file_name = try!( std:: env:: args ( ) . skip ( 1 ) . next ( ) . ok_or ( ProgramError :: NoFile ) ) ;
36- let file = try!( read_file_to_string ( & file_name) ) ;
37-
38- let mut accepted_suggestions: Vec < rustfix:: Suggestion > = vec ! [ ] ;
39-
40- ' diagnostics: for line in file. lines ( ) . filter ( not_empty) {
41- let deserialized: rustfix:: diagnostics:: Diagnostic = try!( serde_json:: from_str ( & line) ) ;
42- ' suggestions: for suggestion in & rustfix:: collect_suggestions ( & deserialized, None ) {
43- println ! ( "\n \n {info}: {message}\n {arrow} \
44- {file}:{range}\n {suggestion}\n \n {text}\n \n {with}\n \n {replacement}\n ",
45- info = "Info" . green( ) . bold( ) ,
46- message = split_at_lint_name( & suggestion. message) ,
47- arrow = " -->" . blue( ) . bold( ) ,
48- suggestion = "Suggestion - Replace:" . yellow( ) . bold( ) ,
49- file = suggestion. file_name,
50- range = suggestion. line_range,
51- text = indent( 4 , & reset_indent( & suggestion. text) ) ,
52- with = "with:" . yellow( ) . bold( ) ,
53- replacement = indent( 4 , & suggestion. replacement) ) ;
54-
55- ' userinput: loop {
56- print ! ( "{arrow} {user_options}\n \
57- {prompt} ",
58- arrow = "==>" . green( ) . bold( ) ,
59- prompt = " >" . green( ) . bold( ) ,
60- user_options = USER_OPTIONS . green( ) ) ;
61-
62- flush ! ( ) ;
63- let mut input = String :: new ( ) ;
64- try!( std:: io:: stdin ( ) . read_line ( & mut input) ) ;
65-
66- match input. trim ( ) {
67- "s" => {
68- println ! ( "Skipped." ) ;
69- continue ' suggestions;
70- }
71- "r" => {
72- accepted_suggestions. push ( ( * suggestion) . clone ( ) ) ;
73- println ! ( "Suggestion accepted. I'll remember that and apply it later." ) ;
74- continue ' suggestions;
75- }
76- "q" => {
77- println ! ( "Thanks for playing!" ) ;
78- break ' diagnostics;
79- }
80- "a" => {
81- return Err ( ProgramError :: UserAbort ) ;
82- }
83- _ => {
84- println ! ( "{error}: I didn't quite get that. {user_options}" ,
85- error = "Error" . red( ) . bold( ) ,
86- user_options = USER_OPTIONS ) ;
87- continue ' userinput;
88- }
48+ let matches = App :: new ( "rustfix" )
49+ . about ( "Automatically apply suggestions made by rustc" )
50+ . version ( crate_version ! ( ) )
51+ . arg ( Arg :: with_name ( "clippy" )
52+ . long ( "clippy" )
53+ . help ( "Use `cargo clippy` for suggestions" ) )
54+ . arg ( Arg :: with_name ( "from_file" )
55+ . long ( "from-file" )
56+ . value_name ( "FILE" )
57+ . takes_value ( true )
58+ . help ( "Read suggestions from file (each line is a JSON object)" ) )
59+ . get_matches ( ) ;
60+
61+ // Get JSON output from rustc...
62+ let json = if let Some ( file_name) = matches. value_of ( "from_file" ) {
63+ // either by reading a file the user saved (probably only for debugging rustfix itself)...
64+ try!( json_from_file ( & file_name) )
65+ } else {
66+ // or by spawning a subcommand that runs rustc.
67+ let subcommand = if matches. is_present ( "clippy" ) {
68+ "clippy"
69+ } else {
70+ "rustc"
71+ } ;
72+ try!( json_from_subcommand ( subcommand) )
73+ } ;
74+
75+ let suggestions: Vec < Suggestion > = json. lines ( )
76+ . filter ( not_empty)
77+ . flat_map ( |line| serde_json:: from_str :: < Diagnostic > ( & line) )
78+ . flat_map ( |diagnostic| rustfix:: collect_suggestions ( & diagnostic, None ) )
79+ . collect ( ) ;
80+
81+ try!( handle_suggestions ( & suggestions) ) ;
82+
83+ Ok ( ( ) )
84+ }
85+
86+ fn json_from_file ( file_name : & str ) -> Result < String , ProgramError > {
87+ read_file_to_string ( & file_name) . map_err ( From :: from)
88+ }
89+
90+ fn json_from_subcommand ( subcommand : & str ) -> Result < String , ProgramError > {
91+ let output = try!( Command :: new ( "cargo" )
92+ . args ( & [ subcommand,
93+ "--quiet" ,
94+ "--color" , "never" ,
95+ "--" ,
96+ "-Z" , "unstable-options" ,
97+ "--error-format" , "json" ] )
98+ . output ( ) ) ;
99+
100+ let content = try!( String :: from_utf8 ( output. stderr ) ) ;
101+
102+ if !output. status . success ( ) {
103+ return Err ( ProgramError :: SubcommandError ( format ! ( "cargo {}" , subcommand) , content) ) ;
104+ }
105+
106+ Ok ( content)
107+ }
108+
109+ fn handle_suggestions ( suggestions : & [ Suggestion ] ) -> Result < ( ) , ProgramError > {
110+ let mut accepted_suggestions: Vec < & Suggestion > = vec ! [ ] ;
111+
112+ if suggestions. is_empty ( ) {
113+ println ! ( "I don't have any suggestions for you right now. Check back later!" ) ;
114+ return Ok ( ( ) ) ;
115+ }
116+
117+ ' suggestions: for suggestion in suggestions {
118+ println ! ( "\n \n {info}: {message}\n \
119+ {arrow} {file}:{range}\n \
120+ {suggestion}\n \n \
121+ {text}\n \n \
122+ {with}\n \n \
123+ {replacement}\n ",
124+ info = "Info" . green( ) . bold( ) ,
125+ message = split_at_lint_name( & suggestion. message) ,
126+ arrow = " -->" . blue( ) . bold( ) ,
127+ suggestion = "Suggestion - Replace:" . yellow( ) . bold( ) ,
128+ file = suggestion. file_name,
129+ range = suggestion. line_range,
130+ text = indent( 4 , & reset_indent( & suggestion. text) ) ,
131+ with = "with:" . yellow( ) . bold( ) ,
132+ replacement = indent( 4 , & suggestion. replacement) ) ;
133+
134+ ' userinput: loop {
135+ print ! ( "{arrow} {user_options}\n \
136+ {prompt} ",
137+ arrow = "==>" . green( ) . bold( ) ,
138+ prompt = " >" . green( ) . bold( ) ,
139+ user_options = USER_OPTIONS . green( ) ) ;
140+
141+ flush ! ( ) ;
142+ let mut input = String :: new ( ) ;
143+ try!( std:: io:: stdin ( ) . read_line ( & mut input) ) ;
144+
145+ match input. trim ( ) {
146+ "s" => {
147+ println ! ( "Skipped." ) ;
148+ continue ' suggestions;
149+ }
150+ "r" => {
151+ accepted_suggestions. push ( suggestion) ;
152+ println ! ( "Suggestion accepted. I'll remember that and apply it later." ) ;
153+ continue ' suggestions;
154+ }
155+ "q" => {
156+ println ! ( "Thanks for playing!" ) ;
157+ break ' suggestions;
158+ }
159+ "a" => {
160+ return Err ( ProgramError :: UserAbort ) ;
161+ }
162+ _ => {
163+ println ! ( "{error}: I didn't quite get that. {user_options}" ,
164+ error = "Error" . red( ) . bold( ) ,
165+ user_options = USER_OPTIONS ) ;
166+ continue ' userinput;
89167 }
90168 }
91169 }
92-
93170 }
94171
95172 if !accepted_suggestions. is_empty ( ) {
@@ -106,8 +183,6 @@ fn try_main() -> Result<(), ProgramError> {
106183 println ! ( "\n Done." ) ;
107184 }
108185
109- println ! ( "See you around!" ) ;
110-
111186 Ok ( ( ) )
112187}
113188
@@ -116,22 +191,32 @@ quick_error! {
116191 #[ derive( Debug ) ]
117192 pub enum ProgramError {
118193 UserAbort {
119- description ( "Let's get outta here!" )
194+ display ( "Let's get outta here!" )
120195 }
121196 /// Missing File
122197 NoFile {
123- description( "No input file given" )
198+ display( "No input file given" )
199+ }
200+ SubcommandError ( subcommand: String , output: String ) {
201+ display( "Error executing subcommand `{}`" , subcommand)
202+ description( output)
124203 }
125204 /// Error while dealing with file or stdin/stdout
126205 Io ( err: std:: io:: Error ) {
127206 from( )
128207 cause( err)
208+ display( "I/O error" )
129209 description( err. description( ) )
130210 }
211+ Utf8Error ( err: std:: string:: FromUtf8Error ) {
212+ from( )
213+ display( "Error reading input as UTF-8" )
214+ }
131215 /// Error with deserialization
132216 Serde ( err: serde_json:: Error ) {
133217 from( )
134218 cause( err)
219+ display( "Serde JSON error" )
135220 description( err. description( ) )
136221 }
137222 }
@@ -188,7 +273,7 @@ fn indent(size: u32, s: &str) -> String {
188273///
189274/// This function is as stupid as possible. Make sure you call for the replacemnts in one file in
190275/// reverse order to not mess up the lines for replacements further down the road.
191- fn apply_suggestion ( suggestion : & rustfix :: Suggestion ) -> Result < ( ) , ProgramError > {
276+ fn apply_suggestion ( suggestion : & Suggestion ) -> Result < ( ) , ProgramError > {
192277 use std:: cmp:: max;
193278 use std:: iter:: repeat;
194279
@@ -202,7 +287,7 @@ fn apply_suggestion(suggestion: &rustfix::Suggestion) -> Result<(), ProgramError
202287 . join ( "\n " ) ) ;
203288
204289 // Some suggestions seem to currently omit the trailing semicolon
205- let remember_a_semicolon = new_content . ends_with ( ';' ) ;
290+ let remember_a_semicolon = suggestion . text . trim ( ) . ends_with ( ';' ) ;
206291
207292 // Indentation
208293 new_content. push_str ( "\n " ) ;
0 commit comments