6 use std::path::{Path, PathBuf};
7 use std::process::Command;
10 use getopts::{Matches, Options};
11 use rustfmt_nightly as rustfmt;
13 use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
15 fn prune_files(files: Vec<&str>) -> Vec<&str> {
16 let prefixes: Vec<_> = files
18 .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
19 .map(|f| &f[..f.len() - 6])
22 let mut pruned_prefixes = vec![];
24 if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) {
25 pruned_prefixes.push(p1);
28 debug!("prefixes: {:?}", pruned_prefixes);
33 if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
36 pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
41 fn git_diff(commits: &str) -> String {
42 let mut cmd = Command::new("git");
45 cmd.arg(format!("HEAD~{}", commits));
47 let output = cmd.output().expect("Couldn't execute `git diff`");
48 String::from_utf8_lossy(&output.stdout).into_owned()
51 fn get_files(input: &str) -> Vec<&str> {
54 .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
55 .map(|line| &line[6..])
59 fn fmt_files(files: &[&str]) -> i32 {
61 load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
63 let mut exit_code = 0;
64 let mut out = stdout();
65 let mut session = Session::new(config, Some(&mut out));
67 let report = session.format(Input::File(PathBuf::from(file))).unwrap();
68 if report.has_warnings() {
69 eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
71 if !session.has_no_errors() {
80 impl CliOptions for NullOptions {
81 fn apply_to(self, _: &mut rustfmt::Config) {
84 fn config_path(&self) -> Option<&Path> {
89 fn uncommitted_files() -> Vec<String> {
90 let mut cmd = Command::new("git");
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);
99 .filter(|s| s.ends_with(".rs"))
100 .map(std::borrow::ToOwned::to_owned)
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 {
112 println!("Commit your work, or run with `-u`.");
113 println!("Exiting.");
114 std::process::exit(1);
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");
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()
140 println!("{}", opts.usage(&message));
141 std::process::exit(0);
144 let mut config = Config {
145 commits: "1".to_owned(),
149 if matches.opt_present("c") {
153 if matches.opt_present("u") {
154 config.uncommitted = true;
157 if matches.free.len() > 1 {
158 panic!("unknown arguments, use `-h` for usage");
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");
165 config.commits = commits.to_owned();
173 env_logger::Builder::from_env("RUSTFMT_LOG").init();
175 let opts = make_opts();
177 .parse(env::args().skip(1))
178 .expect("Couldn't parse command line");
179 let config = Config::from_args(&matches, &opts);
181 if !config.uncommitted {
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);