Skip to content
This repository was archived by the owner on Nov 24, 2023. It is now read-only.

Commit e74e9b0

Browse files
committed
Use Cargo to Get Suggestions for Great Justice
1 parent 78a0edc commit e74e9b0

File tree

2 files changed

+151
-65
lines changed

2 files changed

+151
-65
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[package]
22
authors = ["Pascal Hertleif <[email protected]>"]
3+
license = "Apache-2.0/MIT"
34
name = "rustfix"
4-
version = "0.1.0"
55
readme = "README.md"
6-
license = "Apache-2.0/MIT"
6+
version = "0.1.0"
77

88
[dependencies]
9+
clap = "2.9.2"
910
colored = "1.2.0"
1011
quick-error = "1.1.0"
1112
serde = "0.7.13"

src/main.rs

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1+
extern crate serde_json;
12
#[macro_use]
23
extern crate quick_error;
3-
extern crate serde_json;
4+
#[macro_use]
5+
extern crate clap;
46
extern crate colored;
57

68
extern crate rustfix;
79

810
use std::fs::File;
911
use std::io::{Read, Write};
12+
use std::error::Error;
13+
use std::process::Command;
14+
1015
use colored::Colorize;
16+
use clap::{Arg, App};
17+
18+
use rustfix::Suggestion;
19+
use rustfix::diagnostics::Diagnostic;
1120

1221
const 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

3447
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;
89167
}
90168
}
91169
}
92-
93170
}
94171

95172
if !accepted_suggestions.is_empty() {
@@ -106,8 +183,6 @@ fn try_main() -> Result<(), ProgramError> {
106183
println!("\nDone.");
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

Comments
 (0)