]> git.lizzy.rs Git - rust.git/blob - src/bin/cargo-fmt.rs
9c5e4bf956751e6be93cbd14f8fbee5aead33eaf
[rust.git] / src / bin / cargo-fmt.rs
1 // Copyright 2015 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.
4 //
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.
10
11 // Inspired by Paul Woolcock's cargo-fmt (https://github.com/pwoolcoc/cargo-fmt/)
12
13 #![cfg(not(test))]
14 #![cfg(feature="cargo-fmt")]
15
16 extern crate getopts;
17 extern crate rustc_serialize;
18
19 use std::path::PathBuf;
20 use std::process::Command;
21 use std::env;
22 use std::str;
23
24 use getopts::Options;
25 use rustc_serialize::json::Json;
26
27 fn main() {
28     let mut opts = getopts::Options::new();
29     opts.optflag("h", "help", "show this message");
30     opts.optflag("q", "quiet", "no output printed to stdout");
31     opts.optflag("v", "verbose", "use verbose output");
32
33     let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
34         Ok(m) => m,
35         Err(e) => {
36             print_usage(&opts, &e.to_string());
37             return;
38         }
39     };
40
41     let (verbose, quiet) = (matches.opt_present("v"), matches.opt_present("q"));
42
43     if verbose && quiet {
44         print_usage(&opts, "quiet mode and verbose mode are not compatible");
45         return;
46     }
47
48     if matches.opt_present("h") {
49         print_usage(&opts, "");
50     } else {
51         format_crate(&opts, verbose, quiet);
52     }
53 }
54
55 fn print_usage(opts: &Options, reason: &str) {
56     let msg = format!("{}\nusage: cargo fmt [options]", reason);
57     println!("{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
58               Arguments after `--` are passed to rustfmt.",
59              opts.usage(&msg));
60 }
61
62 fn format_crate(opts: &Options, verbose: bool, quiet: bool) {
63     let targets = match get_targets() {
64         Ok(t) => t,
65         Err(e) => {
66             print_usage(opts, &e.to_string());
67             return;
68         }
69     };
70
71     // Currently only bin and lib files get formatted
72     let files: Vec<_> = targets.into_iter()
73                                .filter(|t| t.kind.is_lib() | t.kind.is_bin())
74                                .inspect(|t| {
75                                    if verbose {
76                                        println!("[{:?}] {:?}", t.kind, t.path)
77                                    }
78                                })
79                                .map(|t| t.path)
80                                .collect();
81
82     format_files(&files, &get_fmt_args(), verbose, quiet)
83         .unwrap_or_else(|e| print_usage(opts, &e.to_string()));
84 }
85
86 fn get_fmt_args() -> Vec<String> {
87     // All arguments after -- are passed to rustfmt
88     env::args().skip_while(|a| a != "--").skip(1).collect()
89 }
90
91 #[derive(Debug)]
92 enum TargetKind {
93     Lib, // dylib, staticlib, lib
94     Bin, // bin
95     Other, // test, plugin,...
96 }
97
98 impl TargetKind {
99     fn is_lib(&self) -> bool {
100         match self {
101             &TargetKind::Lib => true,
102             _ => false,
103         }
104     }
105
106     fn is_bin(&self) -> bool {
107         match self {
108             &TargetKind::Bin => true,
109             _ => false,
110         }
111     }
112 }
113
114 #[derive(Debug)]
115 pub struct Target {
116     path: PathBuf,
117     kind: TargetKind,
118 }
119
120 // Returns a vector of all compile targets of a crate
121 fn get_targets() -> Result<Vec<Target>, std::io::Error> {
122     let mut targets: Vec<Target> = vec![];
123     let output = try!(Command::new("cargo").arg("read-manifest").output());
124     if output.status.success() {
125         // None of the unwraps should fail if output of `cargo read-manifest` is correct
126         let data = &String::from_utf8(output.stdout).unwrap();
127         let json = Json::from_str(data).unwrap();
128         let jtargets = json.find("targets").unwrap().as_array().unwrap();
129         for jtarget in jtargets {
130             targets.push(target_from_json(jtarget));
131         }
132
133         Ok(targets)
134     } else {
135         // This happens when cargo-fmt is not used inside a crate
136         Err(std::io::Error::new(std::io::ErrorKind::NotFound,
137                                 str::from_utf8(&output.stderr).unwrap()))
138     }
139 }
140
141 fn target_from_json(jtarget: &Json) -> Target {
142     let jtarget = jtarget.as_object().unwrap();
143     let path = PathBuf::from(jtarget.get("src_path").unwrap().as_string().unwrap());
144     let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
145     let kind = match kinds[0].as_string().unwrap() {
146         "bin" => TargetKind::Bin,
147         "lib" | "dylib" | "staticlib" => TargetKind::Lib,
148         _ => TargetKind::Other,
149     };
150
151     Target {
152         path: path,
153         kind: kind,
154     }
155 }
156
157 fn format_files(files: &Vec<PathBuf>,
158                 fmt_args: &Vec<String>,
159                 verbose: bool,
160                 quiet: bool)
161                 -> Result<(), std::io::Error> {
162     let stdout = if quiet {
163         std::process::Stdio::null()
164     } else {
165         std::process::Stdio::inherit()
166     };
167     if verbose {
168         print!("rustfmt");
169         for a in fmt_args.iter() {
170             print!(" {}", a);
171         }
172         for f in files.iter() {
173             print!(" {}", f.display());
174         }
175         println!("");
176     }
177     let mut command = try!(Command::new("rustfmt")
178                                .stdout(stdout)
179                                .args(files)
180                                .args(fmt_args)
181                                .spawn());
182     try!(command.wait());
183
184     Ok(())
185 }