1 extern crate env_logger;
5 extern crate rustfmt_nightly as rustfmt;
8 use std::path::{Path, PathBuf};
9 use std::process::Command;
10 use std::str::FromStr;
12 use getopts::{Matches, Options};
14 use rustfmt::{run, Input};
17 fn prune_files(files: Vec<&str>) -> Vec<&str> {
18 let prefixes: Vec<_> = files
20 .filter(|f| f.ends_with("mod.rs") || f.ends_with("lib.rs"))
21 .map(|f| &f[..f.len() - 6])
24 let mut pruned_prefixes = vec![];
26 let mut include = true;
27 if !p1.starts_with("src/bin/") {
28 for p2 in &pruned_prefixes {
29 if p1.starts_with(p2) {
36 pruned_prefixes.push(p1);
39 debug!("prefixes: {:?}", pruned_prefixes);
44 let mut include = true;
45 if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
48 for pp in &pruned_prefixes {
49 if f.starts_with(pp) {
59 fn git_diff(commits: &str) -> String {
60 let mut cmd = Command::new("git");
63 cmd.arg(format!("HEAD~{}", commits));
65 let output = cmd.output().expect("Couldn't execute `git diff`");
66 String::from_utf8_lossy(&output.stdout).into_owned()
69 fn get_files(input: &str) -> Vec<&str> {
72 .filter(|line| line.starts_with("+++ b/") && line.ends_with(".rs"))
73 .map(|line| &line[6..])
77 fn fmt_files(files: &[&str]) -> i32 {
78 let (config, _) = config::Config::from_resolved_toml_path(Path::new("."))
79 .unwrap_or_else(|_| (config::Config::default(), None));
81 let mut exit_code = 0;
83 let summary = run(Input::File(PathBuf::from(file)), &config);
84 if !summary.has_no_errors() {
91 fn uncommitted_files() -> Vec<String> {
92 let mut cmd = Command::new("git");
95 cmd.arg("--modified");
96 cmd.arg("--exclude-standard");
97 let output = cmd.output().expect("Couldn't execute Git");
98 let stdout = String::from_utf8_lossy(&output.stdout);
101 .filter(|s| s.ends_with(".rs"))
102 .map(|s| s.to_owned())
106 fn check_uncommitted() {
107 let uncommitted = uncommitted_files();
108 debug!("uncommitted files: {:?}", uncommitted);
109 if !uncommitted.is_empty() {
110 println!("Found untracked changes:");
111 for f in &uncommitted {
114 println!("Commit your work, or run with `-u`.");
115 println!("Exiting.");
116 std::process::exit(1);
120 fn make_opts() -> Options {
121 let mut opts = Options::new();
122 opts.optflag("h", "help", "show this message");
123 opts.optflag("c", "check", "check only, don't format (unimplemented)");
124 opts.optflag("u", "uncommitted", "format uncommitted files");
135 fn from_args(matches: &Matches, opts: &Options) -> Config {
136 // `--help` display help message and quit
137 if matches.opt_present("h") {
138 let message = format!(
139 "\nusage: {} <commits> [options]\n\n\
140 commits: number of commits to format, default: 1",
141 env::args_os().next().unwrap().to_string_lossy()
143 println!("{}", opts.usage(&message));
144 std::process::exit(0);
147 let mut config = Config {
148 commits: "1".to_owned(),
153 if matches.opt_present("c") {
158 if matches.opt_present("u") {
159 config.uncommitted = true;
162 if matches.free.len() > 1 {
163 panic!("unknown arguments, use `-h` for usage");
165 if matches.free.len() == 1 {
166 let commits = matches.free[0].trim();
167 if u32::from_str(commits).is_err() {
168 panic!("Couldn't parse number of commits");
170 config.commits = commits.to_owned();
178 let _ = env_logger::init();
180 let opts = make_opts();
181 let matches = opts.parse(env::args().skip(1))
182 .expect("Couldn't parse command line");
183 let config = Config::from_args(&matches, &opts);
185 if !config.uncommitted {
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);