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