Add git-fmt tool
This commit is contained in:
parent
036917b691
commit
aafaa2fc2e
2 changed files with 196 additions and 0 deletions
|
@ -22,6 +22,9 @@ name = "cargo-fmt"
|
|||
[[bin]]
|
||||
name = "rustfmt-format-diff"
|
||||
|
||||
[[bin]]
|
||||
name = "git-fmt"
|
||||
|
||||
[features]
|
||||
default = ["cargo-fmt", "rustfmt-format-diff"]
|
||||
cargo-fmt = []
|
||||
|
|
193
src/bin/git-fmt.rs
Normal file
193
src/bin/git-fmt.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
extern crate env_logger;
|
||||
extern crate getopts;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate rustfmt_nightly as rustfmt;
|
||||
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::str::FromStr;
|
||||
|
||||
use getopts::{Matches, Options};
|
||||
|
||||
use rustfmt::{run, Input};
|
||||
use rustfmt::config;
|
||||
|
||||
|
||||
fn prune_files<'a>(files: Vec<&'a str>) -> Vec<&'a 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.into_iter() {
|
||||
let mut include = true;
|
||||
if !p1.starts_with("src/bin/") {
|
||||
for p2 in pruned_prefixes.iter() {
|
||||
if p1.starts_with(p2) {
|
||||
include = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if include {
|
||||
pruned_prefixes.push(p1);
|
||||
}
|
||||
}
|
||||
debug!("prefixes: {:?}", pruned_prefixes);
|
||||
|
||||
files
|
||||
.into_iter()
|
||||
.filter(|f| {
|
||||
let mut include = true;
|
||||
if f.ends_with("mod.rs") || f.ends_with("lib.rs") || f.starts_with("src/bin/") {
|
||||
return true;
|
||||
}
|
||||
for pp in pruned_prefixes.iter() {
|
||||
if f.starts_with(pp) {
|
||||
include = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
include
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn git_diff(commits: String) -> 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, _) = config::Config::from_resolved_toml_path(Path::new("."))
|
||||
.unwrap_or_else(|_| (config::Config::default(), None));
|
||||
|
||||
let mut exit_code = 0;
|
||||
for file in files {
|
||||
let summary = run(Input::File(PathBuf::from(file)), &config);
|
||||
if !summary.has_no_errors() {
|
||||
exit_code = 1;
|
||||
}
|
||||
}
|
||||
exit_code
|
||||
}
|
||||
|
||||
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().map(|s| s.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.iter() {
|
||||
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,
|
||||
check: 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,
|
||||
check: false,
|
||||
};
|
||||
|
||||
if matches.opt_present("c") {
|
||||
config.check = true;
|
||||
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 let Err(_) = u32::from_str(&commits) {
|
||||
panic!("Couldn't parse number of commits");
|
||||
}
|
||||
config.commits = commits.to_owned();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _ = env_logger::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);
|
||||
}
|
Loading…
Reference in a new issue