summaryrefslogtreecommitdiffstats
path: root/src/tools/rustfmt/src/git-rustfmt/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rustfmt/src/git-rustfmt/main.rs')
-rw-r--r--src/tools/rustfmt/src/git-rustfmt/main.rs192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/tools/rustfmt/src/git-rustfmt/main.rs b/src/tools/rustfmt/src/git-rustfmt/main.rs
new file mode 100644
index 000000000..579778edb
--- /dev/null
+++ b/src/tools/rustfmt/src/git-rustfmt/main.rs
@@ -0,0 +1,192 @@
+#[macro_use]
+extern crate log;
+
+use std::env;
+use std::io::stdout;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::str::FromStr;
+
+use getopts::{Matches, Options};
+use rustfmt_nightly as rustfmt;
+
+use crate::rustfmt::{load_config, CliOptions, FormatReportFormatterBuilder, Input, Session};
+
+fn prune_files(files: Vec<&str>) -> Vec<&str> {
+ let prefixes: Vec<_> = files
+ .iter()
+ .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
+ .map(|f| &f[..f.len() - 6])
+ .collect();
+
+ let mut pruned_prefixes = vec![];
+ for p1 in prefixes {
+ if p1.starts_with("src/bin/") || pruned_prefixes.iter().all(|p2| !p1.starts_with(p2)) {
+ pruned_prefixes.push(p1);
+ }
+ }
+ debug!("prefixes: {:?}", pruned_prefixes);
+
+ files
+ .into_iter()
+ .filter(|f| {
+ if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
+ return true;
+ }
+ pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
+ })
+ .collect()
+}
+
+fn git_diff(commits: &str) -> String {
+ let mut cmd = Command::new("git");
+ cmd.arg("diff");
+ if commits != "0" {
+ cmd.arg(format!("HEAD~{}", commits));
+ }
+ let output = cmd.output().expect("Couldn't execute `git diff`");
+ String::from_utf8_lossy(&output.stdout).into_owned()
+}
+
+fn get_files(input: &str) -> Vec<&str> {
+ input
+ .lines()
+ .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
+ .map(|line| &line[6..])
+ .collect()
+}
+
+fn fmt_files(files: &[&str]) -> i32 {
+ let (config, _) =
+ load_config::<NullOptions>(Some(Path::new(".")), None).expect("couldn't load config");
+
+ let mut exit_code = 0;
+ let mut out = stdout();
+ let mut session = Session::new(config, Some(&mut out));
+ for file in files {
+ let report = session.format(Input::File(PathBuf::from(file))).unwrap();
+ if report.has_warnings() {
+ eprintln!("{}", FormatReportFormatterBuilder::new(&report).build());
+ }
+ if !session.has_no_errors() {
+ exit_code = 1;
+ }
+ }
+ exit_code
+}
+
+struct NullOptions;
+
+impl CliOptions for NullOptions {
+ fn apply_to(self, _: &mut rustfmt::Config) {
+ unreachable!();
+ }
+ fn config_path(&self) -> Option<&Path> {
+ unreachable!();
+ }
+}
+
+fn uncommitted_files() -> Vec<String> {
+ let mut cmd = Command::new("git");
+ cmd.arg("ls-files");
+ cmd.arg("--others");
+ cmd.arg("--modified");
+ cmd.arg("--exclude-standard");
+ let output = cmd.output().expect("Couldn't execute Git");
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ stdout
+ .lines()
+ .filter(|s| s.ends_with(".rs"))
+ .map(std::borrow::ToOwned::to_owned)
+ .collect()
+}
+
+fn check_uncommitted() {
+ let uncommitted = uncommitted_files();
+ debug!("uncommitted files: {:?}", uncommitted);
+ if !uncommitted.is_empty() {
+ println!("Found untracked changes:");
+ for f in &uncommitted {
+ println!(" {}", f);
+ }
+ println!("Commit your work, or run with `-u`.");
+ println!("Exiting.");
+ std::process::exit(1);
+ }
+}
+
+fn make_opts() -> Options {
+ let mut opts = Options::new();
+ opts.optflag("h", "help", "show this message");
+ opts.optflag("c", "check", "check only, don't format (unimplemented)");
+ opts.optflag("u", "uncommitted", "format uncommitted files");
+ opts
+}
+
+struct Config {
+ commits: String,
+ uncommitted: bool,
+}
+
+impl Config {
+ fn from_args(matches: &Matches, opts: &Options) -> Config {
+ // `--help` display help message and quit
+ if matches.opt_present("h") {
+ let message = format!(
+ "\nusage: {} <commits> [options]\n\n\
+ commits: number of commits to format, default: 1",
+ env::args_os().next().unwrap().to_string_lossy()
+ );
+ println!("{}", opts.usage(&message));
+ std::process::exit(0);
+ }
+
+ let mut config = Config {
+ commits: "1".to_owned(),
+ uncommitted: false,
+ };
+
+ if matches.opt_present("c") {
+ unimplemented!();
+ }
+
+ if matches.opt_present("u") {
+ config.uncommitted = true;
+ }
+
+ if matches.free.len() > 1 {
+ panic!("unknown arguments, use `-h` for usage");
+ }
+ if matches.free.len() == 1 {
+ let commits = matches.free[0].trim();
+ if u32::from_str(commits).is_err() {
+ panic!("Couldn't parse number of commits");
+ }
+ config.commits = commits.to_owned();
+ }
+
+ config
+ }
+}
+
+fn main() {
+ env_logger::Builder::from_env("RUSTFMT_LOG").init();
+
+ let opts = make_opts();
+ let matches = opts
+ .parse(env::args().skip(1))
+ .expect("Couldn't parse command line");
+ let config = Config::from_args(&matches, &opts);
+
+ if !config.uncommitted {
+ check_uncommitted();
+ }
+
+ let stdout = git_diff(&config.commits);
+ let files = get_files(&stdout);
+ debug!("files: {:?}", files);
+ let files = prune_files(files);
+ debug!("pruned files: {:?}", files);
+ let exit_code = fmt_files(&files);
+ std::process::exit(exit_code);
+}