]> git.proxmox.com Git - rustc.git/blob - src/tools/rustfmt/src/git-rustfmt/main.rs
New upstream version 1.52.1+dfsg1
[rustc.git] / src / tools / rustfmt / src / git-rustfmt / main.rs
1 #[macro_use]
2 extern crate log;
3
4 use std::env;
5 use std::io::stdout;
6 use std::path::{Path, PathBuf};
7 use std::process::Command;
8 use std::str::FromStr;
9
10 use env_logger;
11 use getopts::{Matches, Options};
12 use rustfmt_nightly as rustfmt;
13
14 use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
15
16 fn prune_files(files: Vec<&str>) -> Vec<&str> {
17 let prefixes: Vec<_> = files
18 .iter()
19 .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
20 .map(|f| &f[..f.len() - 6])
21 .collect();
22
23 let mut pruned_prefixes = vec![];
24 for p1 in prefixes {
25 if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) {
26 pruned_prefixes.push(p1);
27 }
28 }
29 debug!("prefixes: {:?}", pruned_prefixes);
30
31 files
32 .into_iter()
33 .filter(|f| {
34 if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
35 return true;
36 }
37 pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
38 })
39 .collect()
40 }
41
42 fn git_diff(commits: &str) -> String {
43 let mut cmd = Command::new("git");
44 cmd.arg("diff");
45 if commits != "0" {
46 cmd.arg(format!("HEAD~{}", commits));
47 }
48 let output = cmd.output().expect("Couldn't execute `git diff`");
49 String::from_utf8_lossy(&output.stdout).into_owned()
50 }
51
52 fn get_files(input: &str) -> Vec<&str> {
53 input
54 .lines()
55 .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
56 .map(|line| &line[6..])
57 .collect()
58 }
59
60 fn fmt_files(files: &[&str]) -> i32 {
61 let (config, _) =
62 load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
63
64 let mut exit_code = 0;
65 let mut out = stdout();
66 let mut session = Session::new(config, Some(&mut out));
67 for file in files {
68 let report = session.format(Input::File(PathBuf::from(file))).unwrap();
69 if report.has_warnings() {
70 eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
71 }
72 if !session.has_no_errors() {
73 exit_code = 1;
74 }
75 }
76 exit_code
77 }
78
79 struct NullOptions;
80
81 impl CliOptions for NullOptions {
82 fn apply_to(self, _: &mut rustfmt::Config) {
83 unreachable!();
84 }
85 fn config_path(&self) -> Option<&Path> {
86 unreachable!();
87 }
88 }
89
90 fn uncommitted_files() -> Vec<String> {
91 let mut cmd = Command::new("git");
92 cmd.arg("ls-files");
93 cmd.arg("--others");
94 cmd.arg("--modified");
95 cmd.arg("--exclude-standard");
96 let output = cmd.output().expect("Couldn't execute Git");
97 let stdout = String::from_utf8_lossy(&output.stdout);
98 stdout
99 .lines()
100 .filter(|s| s.ends_with(".rs"))
101 .map(std::borrow::ToOwned::to_owned)
102 .collect()
103 }
104
105 fn check_uncommitted() {
106 let uncommitted = uncommitted_files();
107 debug!("uncommitted files: {:?}", uncommitted);
108 if !uncommitted.is_empty() {
109 println!("Found untracked changes:");
110 for f in &uncommitted {
111 println!(" {}", f);
112 }
113 println!("Commit your work, or run with `-u`.");
114 println!("Exiting.");
115 std::process::exit(1);
116 }
117 }
118
119 fn make_opts() -> Options {
120 let mut opts = Options::new();
121 opts.optflag("h", "help", "show this message");
122 opts.optflag("c", "check", "check only, don't format (unimplemented)");
123 opts.optflag("u", "uncommitted", "format uncommitted files");
124 opts
125 }
126
127 struct Config {
128 commits: String,
129 uncommitted: bool,
130 check: bool,
131 }
132
133 impl Config {
134 fn from_args(matches: &Matches, opts: &Options) -> Config {
135 // `--help` display help message and quit
136 if matches.opt_present("h") {
137 let message = format!(
138 "\nusage: {} <commits> [options]\n\n\
139 commits: number of commits to format, default: 1",
140 env::args_os().next().unwrap().to_string_lossy()
141 );
142 println!("{}", opts.usage(&message));
143 std::process::exit(0);
144 }
145
146 let mut config = Config {
147 commits: "1".to_owned(),
148 uncommitted: false,
149 check: false,
150 };
151
152 if matches.opt_present("c") {
153 config.check = true;
154 unimplemented!();
155 }
156
157 if matches.opt_present("u") {
158 config.uncommitted = true;
159 }
160
161 if matches.free.len() > 1 {
162 panic!("unknown arguments, use `-h` for usage");
163 }
164 if matches.free.len() == 1 {
165 let commits = matches.free[0].trim();
166 if u32::from_str(commits).is_err() {
167 panic!("Couldn't parse number of commits");
168 }
169 config.commits = commits.to_owned();
170 }
171
172 config
173 }
174 }
175
176 fn main() {
177 env_logger::init();
178
179 let opts = make_opts();
180 let matches = opts
181 .parse(env::args().skip(1))
182 .expect("Couldn't parse command line");
183 let config = Config::from_args(&matches, &opts);
184
185 if !config.uncommitted {
186 check_uncommitted();
187 }
188
189 let stdout = git_diff(&config.commits);
190 let files = get_files(&stdout);
191 debug!("files: {:?}", files);
192 let files = prune_files(files);
193 debug!("pruned files: {:?}", files);
194 let exit_code = fmt_files(&files);
195 std::process::exit(exit_code);
196 }