1 // Copyright 2015-2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 // Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)
17 extern crate serde_json as json;
21 use std::path::PathBuf;
22 use std::process::{Command, ExitStatus};
24 use std::collections::HashSet;
25 use std::iter::FromIterator;
29 use getopts::{Options, Matches};
32 let exit_status = execute();
33 std::io::stdout().flush().unwrap();
34 std::process::exit(exit_status);
41 let mut opts = getopts::Options::new();
42 opts.optflag("h", "help", "show this message");
43 opts.optflag("q", "quiet", "no output printed to stdout");
44 opts.optflag("v", "verbose", "use verbose output");
48 "specify package to format (only usable in workspaces)",
51 opts.optflag("", "all", "format all packages (only usable in workspaces)");
53 // If there is any invalid argument passed to `cargo fmt`, return without formatting.
54 if let Some(arg) = env::args()
56 .take_while(|a| a != "--")
57 .find(|a| !a.starts_with('-'))
59 print_usage(&opts, &format!("Invalid argument: `{}`.", arg));
63 let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
66 print_usage(&opts, &e.to_string());
71 let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
72 (false, false) => Verbosity::Normal,
73 (false, true) => Verbosity::Quiet,
74 (true, false) => Verbosity::Verbose,
76 print_usage(&opts, "quiet mode and verbose mode are not compatible");
81 if matches.opt_present("h") {
82 print_usage(&opts, "");
86 let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);
88 match format_crate(verbosity, workspace_hitlist) {
90 print_usage(&opts, &e.to_string());
93 Ok(status) => if status.success() {
96 status.code().unwrap_or(failure)
101 fn print_usage(opts: &Options, reason: &str) {
102 let msg = format!("{}\nusage: cargo fmt [options]", reason);
104 "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
105 Arguments after `--` are passed to rustfmt.",
110 #[derive(Debug, Clone, Copy, PartialEq)]
118 verbosity: Verbosity,
119 workspace_hitlist: WorkspaceHitlist,
120 ) -> Result<ExitStatus, std::io::Error> {
121 let targets = get_targets(workspace_hitlist)?;
123 // Currently only bin and lib files get formatted
124 let files: Vec<_> = targets
126 .filter(|t| t.kind.should_format())
127 .inspect(|t| if verbosity == Verbosity::Verbose {
128 println!("[{:?}] {:?}", t.kind, t.path)
133 format_files(&files, &get_fmt_args(), verbosity)
136 fn get_fmt_args() -> Vec<String> {
137 // All arguments after -- are passed to rustfmt
138 env::args().skip_while(|a| a != "--").skip(1).collect()
143 Lib, // dylib, staticlib, lib
145 Example, // example file
148 CustomBuild, // build script
149 ProcMacro, // a proc macro implementation
154 fn should_format(&self) -> bool {
158 TargetKind::Example |
161 TargetKind::CustomBuild |
162 TargetKind::ProcMacro => true,
174 #[derive(Debug, PartialEq, Eq)]
175 pub enum WorkspaceHitlist {
181 impl WorkspaceHitlist {
182 pub fn get_some<'a>(&'a self) -> Option<&'a [String]> {
183 if let &WorkspaceHitlist::Some(ref hitlist) = self {
190 pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
191 match (matches.opt_present("all"), matches.opt_present("p")) {
192 (false, false) => WorkspaceHitlist::None,
193 (true, _) => WorkspaceHitlist::All,
194 (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
199 // Returns a vector of all compile targets of a crate
200 fn get_targets(workspace_hitlist: WorkspaceHitlist) -> Result<Vec<Target>, std::io::Error> {
201 let mut targets: Vec<Target> = vec![];
202 if workspace_hitlist == WorkspaceHitlist::None {
203 let output = Command::new("cargo").arg("read-manifest").output()?;
204 if output.status.success() {
205 // None of the unwraps should fail if output of `cargo read-manifest` is correct
206 let data = &String::from_utf8(output.stdout).unwrap();
207 let json: Value = json::from_str(data).unwrap();
208 let json_obj = json.as_object().unwrap();
209 let jtargets = json_obj.get("targets").unwrap().as_array().unwrap();
210 for jtarget in jtargets {
211 targets.push(target_from_json(jtarget));
216 return Err(std::io::Error::new(
217 std::io::ErrorKind::NotFound,
218 str::from_utf8(&output.stderr).unwrap(),
221 // This happens when cargo-fmt is not used inside a crate or
222 // is used inside a workspace.
223 // To ensure backward compatability, we only use `cargo metadata` for workspaces.
224 // TODO: Is it possible only use metadata or read-manifest
225 let output = Command::new("cargo")
229 if output.status.success() {
230 let data = &String::from_utf8(output.stdout).unwrap();
231 let json: Value = json::from_str(data).unwrap();
232 let json_obj = json.as_object().unwrap();
233 let mut hitlist: HashSet<&String> = if workspace_hitlist != WorkspaceHitlist::All {
234 HashSet::from_iter(workspace_hitlist.get_some().unwrap())
236 HashSet::new() // Unused
238 let members: Vec<&Value> = json_obj
244 .filter(|member| if workspace_hitlist == WorkspaceHitlist::All {
247 let member_obj = member.as_object().unwrap();
248 let member_name = member_obj.get("name").unwrap().as_str().unwrap();
249 hitlist.take(&member_name.to_string()).is_some()
252 if hitlist.len() != 0 {
253 // Mimick cargo of only outputting one <package> spec.
254 return Err(std::io::Error::new(
255 std::io::ErrorKind::InvalidInput,
257 "package `{}` is not a member of the workspace",
258 hitlist.iter().next().unwrap()
262 for member in members {
263 let member_obj = member.as_object().unwrap();
264 let jtargets = member_obj.get("targets").unwrap().as_array().unwrap();
265 for jtarget in jtargets {
266 targets.push(target_from_json(jtarget));
271 Err(std::io::Error::new(
272 std::io::ErrorKind::NotFound,
273 str::from_utf8(&output.stderr).unwrap(),
278 fn target_from_json(jtarget: &Value) -> Target {
279 let jtarget = jtarget.as_object().unwrap();
280 let path = PathBuf::from(jtarget.get("src_path").unwrap().as_str().unwrap());
281 let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
282 let kind = match kinds[0].as_str().unwrap() {
283 "bin" => TargetKind::Bin,
284 "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
285 "test" => TargetKind::Test,
286 "example" => TargetKind::Example,
287 "bench" => TargetKind::Bench,
288 "custom-build" => TargetKind::CustomBuild,
289 "proc-macro" => TargetKind::ProcMacro,
290 _ => TargetKind::Other,
302 verbosity: Verbosity,
303 ) -> Result<ExitStatus, std::io::Error> {
304 let stdout = if verbosity == Verbosity::Quiet {
305 std::process::Stdio::null()
307 std::process::Stdio::inherit()
309 if verbosity == Verbosity::Verbose {
311 for a in fmt_args.iter() {
314 for f in files.iter() {
315 print!(" {}", f.display());
319 let mut command = Command::new("rustfmt")
324 .map_err(|e| match e.kind() {
325 std::io::ErrorKind::NotFound => std::io::Error::new(
326 std::io::ErrorKind::Other,
327 "Could not run rustfmt, please make sure it is in your PATH.",