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/)
16 extern crate cargo_metadata;
18 extern crate serde_json as json;
22 use std::hash::{Hash, Hasher};
23 use std::io::{self, Write};
24 use std::path::{Path, PathBuf};
25 use std::process::{Command, ExitStatus};
27 use std::collections::HashSet;
28 use std::iter::FromIterator;
30 use getopts::{Matches, Options};
33 let exit_status = execute();
34 std::io::stdout().flush().unwrap();
35 std::process::exit(exit_status);
42 let mut opts = getopts::Options::new();
43 opts.optflag("h", "help", "show this message");
44 opts.optflag("q", "quiet", "no output printed to stdout");
45 opts.optflag("v", "verbose", "use verbose output");
49 "specify package to format (only usable in workspaces)",
52 opts.optflag("", "all", "format all packages (only usable in workspaces)");
54 // If there is any invalid argument passed to `cargo fmt`, return without formatting.
55 if let Some(arg) = env::args()
57 .take_while(|a| a != "--")
58 .find(|a| !a.starts_with('-'))
60 print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
64 let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
67 print_usage_to_stderr(&opts, &e.to_string());
72 let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
73 (false, false) => Verbosity::Normal,
74 (false, true) => Verbosity::Quiet,
75 (true, false) => Verbosity::Verbose,
77 print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
82 if matches.opt_present("h") {
83 print_usage_to_stdout(&opts, "");
87 let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);
89 match format_crate(verbosity, &workspace_hitlist) {
91 print_usage_to_stderr(&opts, &e.to_string());
94 Ok(status) => if status.success() {
97 status.code().unwrap_or(failure)
102 macro_rules! print_usage {
103 ($print:ident, $opts:ident, $reason:expr) => ({
104 let msg = format!("{}\nusage: cargo fmt [options]", $reason);
106 "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
107 Arguments after `--` are passed to rustfmt.",
113 fn print_usage_to_stdout(opts: &Options, reason: &str) {
114 print_usage!(println, opts, reason);
117 fn print_usage_to_stderr(opts: &Options, reason: &str) {
118 print_usage!(eprintln, opts, reason);
121 #[derive(Debug, Clone, Copy, PartialEq)]
129 verbosity: Verbosity,
130 workspace_hitlist: &WorkspaceHitlist,
131 ) -> Result<ExitStatus, io::Error> {
132 let targets = get_targets(workspace_hitlist)?;
134 // Currently only bin and lib files get formatted
135 let files: Vec<_> = targets
137 .filter(|t| t.kind.should_format())
139 if verbosity == Verbosity::Verbose {
140 println!("[{:?}] {:?}", t.kind, t.path)
146 format_files(&files, &get_fmt_args(), verbosity)
149 fn get_fmt_args() -> Vec<String> {
150 // All arguments after -- are passed to rustfmt
151 env::args().skip_while(|a| a != "--").skip(1).collect()
156 Lib, // dylib, staticlib, lib
158 Example, // example file
161 CustomBuild, // build script
162 ProcMacro, // a proc macro implementation
167 fn should_format(&self) -> bool {
171 | TargetKind::Example
174 | TargetKind::CustomBuild
175 | TargetKind::ProcMacro => true,
180 fn from_str(s: &str) -> Self {
182 "bin" => TargetKind::Bin,
183 "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
184 "test" => TargetKind::Test,
185 "example" => TargetKind::Example,
186 "bench" => TargetKind::Bench,
187 "custom-build" => TargetKind::CustomBuild,
188 "proc-macro" => TargetKind::ProcMacro,
189 _ => TargetKind::Other,
194 /// Target uses a `path` field for equality and hashing.
197 /// A path to the main source file of the target.
199 /// A kind of target (e.g. lib, bin, example, ...).
204 pub fn from_target(target: &cargo_metadata::Target) -> Self {
205 let path = PathBuf::from(&target.src_path);
206 let canonicalized = fs::canonicalize(&path).unwrap_or(path);
210 kind: TargetKind::from_str(&target.kind[0]),
215 impl PartialEq for Target {
216 fn eq(&self, other: &Target) -> bool {
217 self.path == other.path
221 impl Eq for Target {}
223 impl Hash for Target {
224 fn hash<H: Hasher>(&self, state: &mut H) {
225 self.path.hash(state);
229 #[derive(Debug, PartialEq, Eq)]
230 pub enum WorkspaceHitlist {
236 impl WorkspaceHitlist {
237 pub fn get_some(&self) -> Option<&[String]> {
238 if let WorkspaceHitlist::Some(ref hitlist) = *self {
245 pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
246 match (matches.opt_present("all"), matches.opt_present("p")) {
247 (false, false) => WorkspaceHitlist::None,
248 (true, _) => WorkspaceHitlist::All,
249 (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
254 fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<HashSet<Target>, io::Error> {
255 let mut targets = HashSet::new();
257 match *workspace_hitlist {
258 WorkspaceHitlist::None => get_targets_root_only(&mut targets)?,
259 WorkspaceHitlist::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
260 WorkspaceHitlist::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
263 if targets.is_empty() {
265 io::ErrorKind::Other,
266 format!("Failed to find targets"),
273 fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
274 let metadata = get_cargo_metadata(None)?;
276 for package in metadata.packages {
277 for target in package.targets {
278 if target.name == package.name {
279 targets.insert(Target::from_target(&target));
287 fn get_targets_recursive(
288 manifest_path: Option<&Path>,
289 mut targets: &mut HashSet<Target>,
290 visited: &mut HashSet<String>,
291 ) -> Result<(), io::Error> {
292 let metadata = get_cargo_metadata(manifest_path)?;
294 for package in metadata.packages {
295 add_targets(&package.targets, &mut targets);
297 // Look for local dependencies.
298 for dependency in package.dependencies {
299 if dependency.source.is_some() || visited.contains(&dependency.name) {
303 let mut manifest_path = PathBuf::from(&package.manifest_path);
306 manifest_path.push(&dependency.name);
307 manifest_path.push("Cargo.toml");
309 if manifest_path.exists() {
310 visited.insert(dependency.name);
311 get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
319 fn get_targets_with_hitlist(
320 target_names: &[String],
321 targets: &mut HashSet<Target>,
322 ) -> Result<(), io::Error> {
323 let metadata = get_cargo_metadata(None)?;
325 let mut hitlist: HashSet<&String> = HashSet::from_iter(target_names);
327 for package in metadata.packages {
328 for target in package.targets {
329 if hitlist.remove(&target.name) {
330 targets.insert(Target::from_target(&target));
335 if hitlist.is_empty() {
338 let package = hitlist.iter().next().unwrap();
340 io::ErrorKind::InvalidInput,
341 format!("package `{}` is not a member of the workspace", package),
346 fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
347 for target in target_paths {
348 targets.insert(Target::from_target(&target));
355 verbosity: Verbosity,
356 ) -> Result<ExitStatus, io::Error> {
357 let stdout = if verbosity == Verbosity::Quiet {
358 std::process::Stdio::null()
360 std::process::Stdio::inherit()
363 if verbosity == Verbosity::Verbose {
369 print!(" {}", f.display());
374 let mut command = Command::new("rustfmt")
379 .map_err(|e| match e.kind() {
380 io::ErrorKind::NotFound => io::Error::new(
381 io::ErrorKind::Other,
382 "Could not run rustfmt, please make sure it is in your PATH.",
390 fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
391 match cargo_metadata::metadata(manifest_path) {
392 Ok(metadata) => Ok(metadata),
393 Err(..) => Err(io::Error::new(
394 io::ErrorKind::Other,
395 "`cargo manifest` failed.",