1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 extern crate env_logger;
15 extern crate rustfmt_nightly as rustfmt;
19 use std::path::{Path, PathBuf};
20 use std::process::Command;
21 use std::str::FromStr;
23 use getopts::{Matches, Options};
25 use rustfmt::{load_config, CliOptions, Input, Session};
27 fn prune_files(files: Vec<&str>) -> Vec<&str> {
28 let prefixes: Vec<_> = files
30 .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
31 .map(|f| &f[..f.len() - 6])
34 let mut pruned_prefixes = vec![];
36 if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) {
37 pruned_prefixes.push(p1);
40 debug!("prefixes: {:?}", pruned_prefixes);
45 if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
48 pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
53 fn git_diff(commits: &str) -> String {
54 let mut cmd = Command::new("git");
57 cmd.arg(format!("HEAD~{}", commits));
59 let output = cmd.output().expect("Couldn't execute `git diff`");
60 String::from_utf8_lossy(&output.stdout).into_owned()
63 fn get_files(input: &str) -> Vec<&str> {
66 .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
67 .map(|line| &line[6..])
71 fn fmt_files(files: &[&str]) -> i32 {
73 load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
75 let mut exit_code = 0;
76 let mut out = stdout();
77 let mut session = Session::new(config, Some(&mut out));
79 let report = session.format(Input::File(PathBuf::from(file))).unwrap();
80 if report.has_warnings() {
81 eprintln!("{}", report);
83 if !session.has_no_errors() {
92 impl CliOptions for NullOptions {
93 fn apply_to(self, _: &mut rustfmt::Config) {
96 fn config_path(&self) -> Option<&Path> {
101 fn uncommitted_files() -> Vec<String> {
102 let mut cmd = Command::new("git");
105 cmd.arg("--modified");
106 cmd.arg("--exclude-standard");
107 let output = cmd.output().expect("Couldn't execute Git");
108 let stdout = String::from_utf8_lossy(&output.stdout);
111 .filter(|s| s.ends_with(".rs"))
112 .map(|s| s.to_owned())
116 fn check_uncommitted() {
117 let uncommitted = uncommitted_files();
118 debug!("uncommitted files: {:?}", uncommitted);
119 if !uncommitted.is_empty() {
120 println!("Found untracked changes:");
121 for f in &uncommitted {
124 println!("Commit your work, or run with `-u`.");
125 println!("Exiting.");
126 std::process::exit(1);
130 fn make_opts() -> Options {
131 let mut opts = Options::new();
132 opts.optflag("h", "help", "show this message");
133 opts.optflag("c", "check", "check only, don't format (unimplemented)");
134 opts.optflag("u", "uncommitted", "format uncommitted files");
145 fn from_args(matches: &Matches, opts: &Options) -> Config {
146 // `--help` display help message and quit
147 if matches.opt_present("h") {
148 let message = format!(
149 "\nusage: {} <commits> [options]\n\n\
150 commits: number of commits to format, default: 1",
151 env::args_os().next().unwrap().to_string_lossy()
153 println!("{}", opts.usage(&message));
154 std::process::exit(0);
157 let mut config = Config {
158 commits: "1".to_owned(),
163 if matches.opt_present("c") {
168 if matches.opt_present("u") {
169 config.uncommitted = true;
172 if matches.free.len() > 1 {
173 panic!("unknown arguments, use `-h` for usage");
175 if matches.free.len() == 1 {
176 let commits = matches.free[0].trim();
177 if u32::from_str(commits).is_err() {
178 panic!("Couldn't parse number of commits");
180 config.commits = commits.to_owned();
190 let opts = make_opts();
192 .parse(env::args().skip(1))
193 .expect("Couldn't parse command line");
194 let config = Config::from_args(&matches, &opts);
196 if !config.uncommitted {
200 let stdout = git_diff(&config.commits);
201 let files = get_files(&stdout);
202 debug!("files: {:?}", files);
203 let files = prune_files(files);
204 debug!("pruned files: {:?}", files);
205 let exit_code = fmt_files(&files);
206 std::process::exit(exit_code);