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;
20 use std::io::{self, Write};
21 use std::path::PathBuf;
22 use std::process::{Command, ExitStatus};
24 use std::collections::HashSet;
25 use std::iter::FromIterator;
27 use getopts::{Matches, Options};
31 let exit_status = execute();
32 std::io::stdout().flush().unwrap();
33 std::process::exit(exit_status);
40 let mut opts = getopts::Options::new();
41 opts.optflag("h", "help", "show this message");
42 opts.optflag("q", "quiet", "no output printed to stdout");
43 opts.optflag("v", "verbose", "use verbose output");
47 "specify package to format (only usable in workspaces)",
50 opts.optflag("", "all", "format all packages (only usable in workspaces)");
52 // If there is any invalid argument passed to `cargo fmt`, return without formatting.
53 if let Some(arg) = env::args()
55 .take_while(|a| a != "--")
56 .find(|a| !a.starts_with('-'))
58 print_usage_to_stderr(&opts, &format!("Invalid argument: `{}`.", arg));
62 let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
65 print_usage_to_stderr(&opts, &e.to_string());
70 let verbosity = match (matches.opt_present("v"), matches.opt_present("q")) {
71 (false, false) => Verbosity::Normal,
72 (false, true) => Verbosity::Quiet,
73 (true, false) => Verbosity::Verbose,
75 print_usage_to_stderr(&opts, "quiet mode and verbose mode are not compatible");
80 if matches.opt_present("h") {
81 print_usage_to_stdout(&opts, "");
85 let workspace_hitlist = WorkspaceHitlist::from_matches(&matches);
87 match format_crate(verbosity, &workspace_hitlist) {
89 print_usage_to_stderr(&opts, &e.to_string());
92 Ok(status) => if status.success() {
95 status.code().unwrap_or(failure)
100 macro_rules! print_usage {
101 ($print:ident, $opts:ident, $reason:expr) => ({
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.",
111 fn print_usage_to_stdout(opts: &Options, reason: &str) {
112 print_usage!(println, opts, reason);
115 fn print_usage_to_stderr(opts: &Options, reason: &str) {
116 print_usage!(eprintln, opts, reason);
119 #[derive(Debug, Clone, Copy, PartialEq)]
127 verbosity: Verbosity,
128 workspace_hitlist: &WorkspaceHitlist,
129 ) -> Result<ExitStatus, io::Error> {
130 let targets = get_targets(workspace_hitlist)?;
132 // Currently only bin and lib files get formatted
133 let files: Vec<_> = targets
135 .filter(|t| t.kind.should_format())
136 .inspect(|t| if verbosity == Verbosity::Verbose {
137 println!("[{:?}] {:?}", t.kind, t.path)
142 format_files(&files, &get_fmt_args(), verbosity)
145 fn get_fmt_args() -> Vec<String> {
146 // All arguments after -- are passed to rustfmt
147 env::args().skip_while(|a| a != "--").skip(1).collect()
152 Lib, // dylib, staticlib, lib
154 Example, // example file
157 CustomBuild, // build script
158 ProcMacro, // a proc macro implementation
163 fn should_format(&self) -> bool {
167 TargetKind::Example |
170 TargetKind::CustomBuild |
171 TargetKind::ProcMacro => true,
184 pub fn from_json(json_val: &Value) -> Option<Self> {
185 let jtarget = json_val.as_object()?;
186 let path = PathBuf::from(jtarget.get("src_path")?.as_str()?);
187 let kinds = jtarget.get("kind")?.as_array()?;
188 let kind = match kinds[0].as_str()? {
189 "bin" => TargetKind::Bin,
190 "lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
191 "test" => TargetKind::Test,
192 "example" => TargetKind::Example,
193 "bench" => TargetKind::Bench,
194 "custom-build" => TargetKind::CustomBuild,
195 "proc-macro" => TargetKind::ProcMacro,
196 _ => TargetKind::Other,
206 #[derive(Debug, PartialEq, Eq)]
207 pub enum WorkspaceHitlist {
213 impl WorkspaceHitlist {
214 pub fn get_some(&self) -> Option<&[String]> {
215 if let WorkspaceHitlist::Some(ref hitlist) = *self {
222 pub fn from_matches(matches: &Matches) -> WorkspaceHitlist {
223 match (matches.opt_present("all"), matches.opt_present("p")) {
224 (false, false) => WorkspaceHitlist::None,
225 (true, _) => WorkspaceHitlist::All,
226 (false, true) => WorkspaceHitlist::Some(matches.opt_strs("p")),
231 fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option<Value> {
232 json::from_str(str::from_utf8(v).ok()?).ok()
235 fn get_json_array_with<'a>(v: &'a Value, key: &str) -> Option<&'a Vec<Value>> {
236 v.as_object()?.get(key)?.as_array()
239 // `cargo metadata --no-deps | jq '.["packages"]'`
240 fn get_packages(v: &[u8]) -> Result<Vec<Value>, io::Error> {
241 let e = io::Error::new(
242 io::ErrorKind::NotFound,
243 String::from("`cargo metadata` returned json without a 'packages' key"),
245 match get_cargo_metadata_from_utf8(v) {
246 Some(ref json_obj) => get_json_array_with(json_obj, "packages").cloned().ok_or(e),
251 fn extract_target_from_package(package: &Value) -> Option<Vec<Target>> {
252 let jtargets = get_json_array_with(package, "targets")?;
253 let mut targets: Vec<Target> = vec![];
254 for jtarget in jtargets {
255 targets.push(Target::from_json(&jtarget)?);
260 fn filter_packages_with_hitlist<'a>(
261 packages: Vec<Value>,
262 workspace_hitlist: &'a WorkspaceHitlist,
263 ) -> Result<Vec<Value>, &'a String> {
264 if *workspace_hitlist == WorkspaceHitlist::All {
267 let mut hitlist: HashSet<&String> = workspace_hitlist
269 .map_or(HashSet::new(), HashSet::from_iter);
270 let members: Vec<Value> = packages
275 .and_then(|member_obj| {
278 .and_then(Value::as_str)
280 hitlist.take(&member_name.to_string()).is_some()
286 if hitlist.is_empty() {
289 Err(hitlist.into_iter().next().unwrap())
293 fn get_dependencies_from_package(package: &Value) -> Option<Vec<PathBuf>> {
294 let jdependencies = get_json_array_with(package, "dependencies")?;
295 let root_path = env::current_dir().ok()?;
296 let mut dependencies: Vec<PathBuf> = vec![];
297 for jdep in jdependencies {
298 let jdependency = jdep.as_object()?;
299 if !jdependency.get("source")?.is_null() {
302 let name = jdependency.get("name")?.as_str()?;
303 let mut path = root_path.clone();
305 dependencies.push(path);
310 // Returns a vector of local dependencies under this crate
311 fn get_path_to_local_dependencies(packages: &[Value]) -> Vec<PathBuf> {
312 let mut local_dependencies: Vec<PathBuf> = vec![];
313 for package in packages {
314 if let Some(mut d) = get_dependencies_from_package(package) {
315 local_dependencies.append(&mut d);
321 // Returns a vector of all compile targets of a crate
322 fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<Vec<Target>, io::Error> {
323 let output = Command::new("cargo")
324 .args(&["metadata", "--no-deps", "--format-version=1"])
326 if output.status.success() {
327 let cur_dir = env::current_dir()?;
328 let mut targets: Vec<Target> = vec![];
329 let packages = get_packages(&output.stdout)?;
331 // If we can find any local dependencies, we will try to get targets from those as well.
332 if *workspace_hitlist == WorkspaceHitlist::All {
333 for path in get_path_to_local_dependencies(&packages) {
334 match env::set_current_dir(path) {
335 Ok(..) => match get_targets(workspace_hitlist) {
336 Ok(ref mut t) => targets.append(t),
344 env::set_current_dir(cur_dir)?;
345 match filter_packages_with_hitlist(packages, workspace_hitlist) {
347 for package in packages {
348 if let Some(mut target) = extract_target_from_package(&package) {
349 targets.append(&mut target);
355 // Mimick cargo of only outputting one <package> spec.
357 io::ErrorKind::InvalidInput,
358 format!("package `{}` is not a member of the workspace", package),
364 io::ErrorKind::NotFound,
365 str::from_utf8(&output.stderr).unwrap(),
373 verbosity: Verbosity,
374 ) -> Result<ExitStatus, io::Error> {
375 let stdout = if verbosity == Verbosity::Quiet {
376 std::process::Stdio::null()
378 std::process::Stdio::inherit()
380 if verbosity == Verbosity::Verbose {
386 print!(" {}", f.display());
390 let mut command = Command::new("rustfmt")
395 .map_err(|e| match e.kind() {
396 io::ErrorKind::NotFound => io::Error::new(
397 io::ErrorKind::Other,
398 "Could not run rustfmt, please make sure it is in your PATH.",