diff --git a/Cargo.toml b/Cargo.toml index a3a167920484..70f7f796baf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,10 @@ readme = "README.md" license = "Apache-2.0/MIT" include = ["src/*.rs", "Cargo.toml"] +[features] +default = ["cargo-fmt"] +cargo-fmt = [] + [dependencies] toml = "0.1.20" rustc-serialize = "0.3.14" diff --git a/README.md b/README.md index d79e53b7a328..f8dab7ec97ff 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,21 @@ or if you're using [`multirust`](https://github.com/brson/multirust) multirust run nightly cargo install --git https://github.com/rust-lang-nursery/rustfmt ``` +Usually cargo-fmt, which enables usage of Cargo subcommand `cargo fmt`, is +installed alongside rustfmt. To only install rustfmt run + +``` +cargo install --no-default-features --git https://github.com/rust-lang-nursery/rustfmt +``` ## Running -You can run Rustfmt by just typing `rustfmt filename` if you used `Cargo +You can run Rustfmt by just typing `rustfmt filename` if you used `cargo install`. This runs rustfmt on the given file, if the file includes out of line modules, then we reformat those too. So to run on a whole module or crate, you just need to run on the root file (usually mod.rs or lib.rs). Rustfmt can also -read data from stdin. +read data from stdin. Alternatively, you can use `cargo fmt` to format all +binary and library targets of your crate. You'll probably want to specify the write mode. Currently, there are modes for replace, overwrite, display, and coverage. The replace mode is the default @@ -42,6 +49,7 @@ screen, for example. You can run `rustfmt --help` for more information. +`cargo fmt` uses `--write-mode=overwrite` by default. ## Running Rustfmt from your editor @@ -59,8 +67,8 @@ First make sure you've got Rust **1.4.0** or greater available, then: `cargo test` to run all tests. -To run rustfmt after this, use `cargo run -- filename`. See the notes above on -running rustfmt. +To run rustfmt after this, use `cargo run --bin rustfmt -- filename`. See the +notes above on running rustfmt. ## What style does Rustfmt use? diff --git a/src/bin/cargo-fmt.rs b/src/bin/cargo-fmt.rs new file mode 100644 index 000000000000..f79899ff5b1b --- /dev/null +++ b/src/bin/cargo-fmt.rs @@ -0,0 +1,153 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/) + +#![cfg(not(test))] +#![cfg(feature="cargo-fmt")] + +extern crate getopts; +extern crate rustc_serialize; + +use std::path::PathBuf; +use std::process::Command; +use std::env; +use std::str; + +use getopts::Options; +use rustc_serialize::json::Json; + +fn main() { + let mut opts = getopts::Options::new(); + opts.optflag("h", "help", "show this message"); + + let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) { + Ok(m) => m, + Err(e) => { + print_usage(&opts, &e.to_string()); + return; + } + }; + + if matches.opt_present("h") { + print_usage(&opts, ""); + } else { + format_crate(&opts); + } +} + +fn print_usage(opts: &Options, reason: &str) { + let msg = format!("{}\nusage: cargo fmt [options]", reason); + println!("{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \ + Arguments after `--` are passes to rustfmt.", + opts.usage(&msg)); +} + +fn format_crate(opts: &Options) { + let targets = match get_targets() { + Ok(t) => t, + Err(e) => { + print_usage(opts, &e.to_string()); + return; + } + }; + + // Currently only bin and lib files get formatted + let files: Vec<_> = targets.into_iter() + .filter(|t| t.kind.is_lib() | t.kind.is_bin()) + .map(|t| t.path) + .collect(); + + format_files(&files, &get_fmt_args()).unwrap_or_else(|e| print_usage(opts, &e.to_string())); +} + +fn get_fmt_args() -> Vec { + let mut args = vec!["--write-mode=overwrite".to_string()]; + // All arguments after -- are passed to rustfmt + args.extend(env::args().skip_while(|a| a != "--").skip(1)); + + args +} + +#[derive(Debug)] +enum TargetKind { + Lib, // dylib, staticlib, lib + Bin, // bin + Other, // test, plugin,... +} + +impl TargetKind { + fn is_lib(&self) -> bool { + match self { + &TargetKind::Lib => true, + _ => false, + } + } + + fn is_bin(&self) -> bool { + match self { + &TargetKind::Bin => true, + _ => false, + } + } +} + +#[derive(Debug)] +struct Target { + path: PathBuf, + kind: TargetKind, +} + +// Returns a vector of all compile targets of a crate +fn get_targets() -> Result, std::io::Error> { + let mut targets: Vec = vec![]; + let output = try!(Command::new("cargo").arg("read-manifest").output()); + if output.status.success() { + // None of the unwraps should fail if output of `cargo read-manifest` is correct + let data = &String::from_utf8(output.stdout).unwrap(); + let json = Json::from_str(data).unwrap(); + let jtargets = json.find("targets").unwrap().as_array().unwrap(); + for jtarget in jtargets { + targets.push(target_from_json(jtarget)); + } + + Ok(targets) + } else { + // This happens when cargo-fmt is not used inside a crate + Err(std::io::Error::new(std::io::ErrorKind::NotFound, + str::from_utf8(&output.stderr).unwrap())) + } +} + +fn target_from_json(jtarget: &Json) -> Target { + let jtarget = jtarget.as_object().unwrap(); + let path = PathBuf::from(jtarget.get("src_path").unwrap().as_string().unwrap()); + let kinds = jtarget.get("kind").unwrap().as_array().unwrap(); + let kind = match kinds[0].as_string().unwrap() { + "bin" => TargetKind::Bin, + "lib" | "dylib" | "staticlib" => TargetKind::Lib, + _ => TargetKind::Other, + }; + + Target { + path: path, + kind: kind, + } +} + +fn format_files(files: &Vec, fmt_args: &Vec) -> Result<(), std::io::Error> { + let mut command = try!(Command::new("rustfmt") + .args(files) + .args(fmt_args) + .spawn()); + try!(command.wait()); + + Ok(()) +}