1
+ extern crate serde_json;
1
2
#[ macro_use]
2
3
extern crate quick_error;
3
- extern crate serde_json;
4
+ #[ macro_use]
5
+ extern crate clap;
4
6
extern crate colored;
5
7
6
8
extern crate rustfix;
7
9
8
10
use std:: fs:: File ;
9
11
use std:: io:: { Read , Write } ;
12
+ use std:: error:: Error ;
13
+ use std:: process:: Command ;
14
+
10
15
use colored:: Colorize ;
16
+ use clap:: { Arg , App } ;
17
+
18
+ use rustfix:: Suggestion ;
19
+ use rustfix:: diagnostics:: Diagnostic ;
11
20
12
21
const USER_OPTIONS : & ' static str = "What do you want to do? \
13
22
[r]eplace | [s]kip | save and [q]uit | [a]bort (without saving)";
@@ -21,7 +30,11 @@ fn main() {
21
30
std:: process:: exit ( 0 ) ;
22
31
}
23
32
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
+ }
25
38
std:: process:: exit ( 1 ) ;
26
39
}
27
40
}
@@ -32,64 +45,128 @@ macro_rules! flush {
32
45
}
33
46
34
47
fn 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;
89
167
}
90
168
}
91
169
}
92
-
93
170
}
94
171
95
172
if !accepted_suggestions. is_empty ( ) {
@@ -106,8 +183,6 @@ fn try_main() -> Result<(), ProgramError> {
106
183
println ! ( "\n Done." ) ;
107
184
}
108
185
109
- println ! ( "See you around!" ) ;
110
-
111
186
Ok ( ( ) )
112
187
}
113
188
@@ -116,22 +191,32 @@ quick_error! {
116
191
#[ derive( Debug ) ]
117
192
pub enum ProgramError {
118
193
UserAbort {
119
- description ( "Let's get outta here!" )
194
+ display ( "Let's get outta here!" )
120
195
}
121
196
/// Missing File
122
197
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)
124
203
}
125
204
/// Error while dealing with file or stdin/stdout
126
205
Io ( err: std:: io:: Error ) {
127
206
from( )
128
207
cause( err)
208
+ display( "I/O error" )
129
209
description( err. description( ) )
130
210
}
211
+ Utf8Error ( err: std:: string:: FromUtf8Error ) {
212
+ from( )
213
+ display( "Error reading input as UTF-8" )
214
+ }
131
215
/// Error with deserialization
132
216
Serde ( err: serde_json:: Error ) {
133
217
from( )
134
218
cause( err)
219
+ display( "Serde JSON error" )
135
220
description( err. description( ) )
136
221
}
137
222
}
@@ -188,7 +273,7 @@ fn indent(size: u32, s: &str) -> String {
188
273
///
189
274
/// This function is as stupid as possible. Make sure you call for the replacemnts in one file in
190
275
/// 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 > {
192
277
use std:: cmp:: max;
193
278
use std:: iter:: repeat;
194
279
@@ -202,7 +287,7 @@ fn apply_suggestion(suggestion: &rustfix::Suggestion) -> Result<(), ProgramError
202
287
. join ( "\n " ) ) ;
203
288
204
289
// 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 ( ';' ) ;
206
291
207
292
// Indentation
208
293
new_content. push_str ( "\n " ) ;
0 commit comments