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;
20 use std::collections::HashSet;
23 use std::hash::{Hash, Hasher};
24 use std::io::{self, Write};
25 use std::iter::FromIterator;
26 use std::path::{Path, PathBuf};
27 use std::process::{Command, ExitStatus};
30 use getopts::{Matches, Options};
33 let exit_status = execute();
34 std::io::stdout().flush().unwrap();
35 std::process::exit(exit_status);
38 const SUCCESS: i32 = 0;
39 const FAILURE: i32 = 1;
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("", "version", "print rustfmt version and exit");
53 opts.optflag("", "all", "format all packages (only usable in workspaces)");
55 // If there is any invalid argument passed to `cargo fmt`, return without formatting.
56 let mut is_package_arg = false;
57 for arg in env::args().skip(2).take_while(|a| a != "--") {
58 if arg.starts_with('-') {
59 is_package_arg = arg.starts_with("--package");
60 } else if !is_package_arg {
61 print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
64 is_package_arg = false;
68 let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
71 print_usage_to_stderr(&opts, &e.to_string());
76 let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
77 (false, false) => Verbosity::Normal,
78 (false, true) => Verbosity::Quiet,
79 (true, false) => Verbosity::Verbose,
81 print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
86 if matches.opt_present("h") {
87 print_usage_to_stdout(&opts, "");
91 if matches.opt_present("version") {
92 return handle_command_status(get_version(verbosity), &opts);
95 let strategy = CargoFmtStrategy::from_matches(&matches);
96 handle_command_status(format_crate(verbosity, &strategy), &opts)
99 macro_rules! print_usage {
100 ($print:ident, $opts:ident, $reason:expr) => {{
101 let msg = format!("{}\nusage: cargo fmt [options]", $reason);
103 "{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
104 Arguments after `--` are passed to rustfmt.",
110 fn print_usage_to_stdout(opts: &Options, reason: &str) {
111 print_usage!(println, opts, reason);
114 fn print_usage_to_stderr(opts: &Options, reason: &str) {
115 print_usage!(eprintln, opts, reason);
118 #[derive(Debug, Clone, Copy, PartialEq)]
125 fn handle_command_status(status: Result<ExitStatus, io::Error>, opts: &getopts::Options) -> i32 {
128 print_usage_to_stderr(opts, &e.to_string());
132 if status.success() {
135 status.code().unwrap_or(FAILURE)
141 fn get_version(verbosity: Verbosity) -> Result<ExitStatus, io::Error> {
142 run_rustfmt(&[], &[String::from("--version")], verbosity)
146 verbosity: Verbosity,
147 strategy: &CargoFmtStrategy,
148 ) -> Result<ExitStatus, io::Error> {
149 let rustfmt_args = get_fmt_args();
150 let targets = if rustfmt_args
152 .any(|s| ["--print-config", "-h", "--help", "-V", "--verison"].contains(&s.as_str()))
156 get_targets(strategy)?
159 // Currently only bin and lib files get formatted
160 let files: Vec<_> = targets
163 if verbosity == Verbosity::Verbose {
164 println!("[{}] {:?}", t.kind, t.path)
169 run_rustfmt(&files, &rustfmt_args, verbosity)
172 fn get_fmt_args() -> Vec<String> {
173 // All arguments after -- are passed to rustfmt
174 env::args().skip_while(|a| a != "--").skip(1).collect()
177 /// Target uses a `path` field for equality and hashing.
180 /// A path to the main source file of the target.
182 /// A kind of target (e.g. lib, bin, example, ...).
187 pub fn from_target(target: &cargo_metadata::Target) -> Self {
188 let path = PathBuf::from(&target.src_path);
189 let canonicalized = fs::canonicalize(&path).unwrap_or(path);
193 kind: target.kind[0].clone(),
198 impl PartialEq for Target {
199 fn eq(&self, other: &Target) -> bool {
200 self.path == other.path
204 impl Eq for Target {}
206 impl Hash for Target {
207 fn hash<H: Hasher>(&self, state: &mut H) {
208 self.path.hash(state);
212 #[derive(Debug, PartialEq, Eq)]
213 pub enum CargoFmtStrategy {
214 /// Format every packages and dependencies.
216 /// Format packages that are specified by the command line argument.
218 /// Format the root packages only.
222 impl CargoFmtStrategy {
223 pub fn from_matches(matches: &Matches) -> CargoFmtStrategy {
224 match (matches.opt_present("all"), matches.opt_present("p")) {
225 (false, false) => CargoFmtStrategy::Root,
226 (true, _) => CargoFmtStrategy::All,
227 (false, true) => CargoFmtStrategy::Some(matches.opt_strs("p")),
232 /// Based on the specified `CargoFmtStrategy`, returns a set of main source files.
233 fn get_targets(strategy: &CargoFmtStrategy) -> Result<HashSet<Target>, io::Error> {
234 let mut targets = HashSet::new();
237 CargoFmtStrategy::Root => get_targets_root_only(&mut targets)?,
238 CargoFmtStrategy::All => get_targets_recursive(None, &mut targets, &mut HashSet::new())?,
239 CargoFmtStrategy::Some(ref hitlist) => get_targets_with_hitlist(hitlist, &mut targets)?,
242 if targets.is_empty() {
244 io::ErrorKind::Other,
245 "Failed to find targets".to_owned(),
252 fn get_targets_root_only(targets: &mut HashSet<Target>) -> Result<(), io::Error> {
253 let metadata = get_cargo_metadata(None)?;
254 let current_dir = env::current_dir()?.canonicalize()?;
255 let current_dir_manifest = current_dir.join("Cargo.toml");
256 let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?;
257 let in_workspace_root = workspace_root_path == current_dir;
259 for package in metadata.packages {
260 if in_workspace_root || PathBuf::from(&package.manifest_path) == current_dir_manifest {
261 for target in package.targets {
262 targets.insert(Target::from_target(&target));
270 fn get_targets_recursive(
271 manifest_path: Option<&Path>,
272 mut targets: &mut HashSet<Target>,
273 visited: &mut HashSet<String>,
274 ) -> Result<(), io::Error> {
275 let metadata = get_cargo_metadata(manifest_path)?;
277 for package in metadata.packages {
278 add_targets(&package.targets, &mut targets);
280 // Look for local dependencies.
281 for dependency in package.dependencies {
282 if dependency.source.is_some() || visited.contains(&dependency.name) {
286 let mut manifest_path = PathBuf::from(&package.manifest_path);
289 manifest_path.push(&dependency.name);
290 manifest_path.push("Cargo.toml");
292 if manifest_path.exists() {
293 visited.insert(dependency.name);
294 get_targets_recursive(Some(&manifest_path), &mut targets, visited)?;
302 fn get_targets_with_hitlist(
304 targets: &mut HashSet<Target>,
305 ) -> Result<(), io::Error> {
306 let metadata = get_cargo_metadata(None)?;
308 let mut workspace_hitlist: HashSet<&String> = HashSet::from_iter(hitlist);
310 for package in metadata.packages {
311 if workspace_hitlist.remove(&package.name) {
312 for target in package.targets {
313 targets.insert(Target::from_target(&target));
318 if workspace_hitlist.is_empty() {
321 let package = workspace_hitlist.iter().next().unwrap();
323 io::ErrorKind::InvalidInput,
324 format!("package `{}` is not a member of the workspace", package),
329 fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut HashSet<Target>) {
330 for target in target_paths {
331 targets.insert(Target::from_target(target));
338 verbosity: Verbosity,
339 ) -> Result<ExitStatus, io::Error> {
340 let stdout = if verbosity == Verbosity::Quiet {
341 std::process::Stdio::null()
343 std::process::Stdio::inherit()
346 if verbosity == Verbosity::Verbose {
352 print!(" {}", f.display());
357 let mut command = Command::new("rustfmt")
362 .map_err(|e| match e.kind() {
363 io::ErrorKind::NotFound => io::Error::new(
364 io::ErrorKind::Other,
365 "Could not run rustfmt, please make sure it is in your PATH.",
373 fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
374 match cargo_metadata::metadata(manifest_path) {
375 Ok(metadata) => Ok(metadata),
376 Err(..) => Err(io::Error::new(
377 io::ErrorKind::Other,
378 "`cargo manifest` failed.",