]> git.lizzy.rs Git - rust.git/blob - src/bin/cargo-fmt.rs
Merge pull request #704 from Sean1708/patch-1
[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
31     let matches = match opts.parse(env::args().skip(1).take_while(|a| a != "--")) {
32         Ok(m) => m,
33         Err(e) => {
34             print_usage(&opts, &e.to_string());
35             return;
36         }
37     };
38
39     if matches.opt_present("h") {
40         print_usage(&opts, "");
41     } else {
42         format_crate(&opts);
43     }
44 }
45
46 fn print_usage(opts: &Options, reason: &str) {
47     let msg = format!("{}\nusage: cargo fmt [options]", reason);
48     println!("{}\nThis utility formats all bin and lib files of the current crate using rustfmt. \
49               Arguments after `--` are passed to rustfmt.",
50              opts.usage(&msg));
51 }
52
53 fn format_crate(opts: &Options) {
54     let targets = match get_targets() {
55         Ok(t) => t,
56         Err(e) => {
57             print_usage(opts, &e.to_string());
58             return;
59         }
60     };
61
62     // Currently only bin and lib files get formatted
63     let files: Vec<_> = targets.into_iter()
64                                .filter(|t| t.kind.is_lib() | t.kind.is_bin())
65                                .map(|t| t.path)
66                                .collect();
67
68     format_files(&files, &get_fmt_args()).unwrap_or_else(|e| print_usage(opts, &e.to_string()));
69 }
70
71 fn get_fmt_args() -> Vec<String> {
72     let mut args = vec!["--write-mode=replace".to_string()];
73     // All arguments after -- are passed to rustfmt
74     args.extend(env::args().skip_while(|a| a != "--").skip(1));
75
76     args
77 }
78
79 #[derive(Debug)]
80 enum TargetKind {
81     Lib, // dylib, staticlib, lib
82     Bin, // bin
83     Other, // test, plugin,...
84 }
85
86 impl TargetKind {
87     fn is_lib(&self) -> bool {
88         match self {
89             &TargetKind::Lib => true,
90             _ => false,
91         }
92     }
93
94     fn is_bin(&self) -> bool {
95         match self {
96             &TargetKind::Bin => true,
97             _ => false,
98         }
99     }
100 }
101
102 #[derive(Debug)]
103 struct Target {
104     path: PathBuf,
105     kind: TargetKind,
106 }
107
108 // Returns a vector of all compile targets of a crate
109 fn get_targets() -> Result<Vec<Target>, std::io::Error> {
110     let mut targets: Vec<Target> = vec![];
111     let output = try!(Command::new("cargo").arg("read-manifest").output());
112     if output.status.success() {
113         // None of the unwraps should fail if output of `cargo read-manifest` is correct
114         let data = &String::from_utf8(output.stdout).unwrap();
115         let json = Json::from_str(data).unwrap();
116         let jtargets = json.find("targets").unwrap().as_array().unwrap();
117         for jtarget in jtargets {
118             targets.push(target_from_json(jtarget));
119         }
120
121         Ok(targets)
122     } else {
123         // This happens when cargo-fmt is not used inside a crate
124         Err(std::io::Error::new(std::io::ErrorKind::NotFound,
125                                 str::from_utf8(&output.stderr).unwrap()))
126     }
127 }
128
129 fn target_from_json(jtarget: &Json) -> Target {
130     let jtarget = jtarget.as_object().unwrap();
131     let path = PathBuf::from(jtarget.get("src_path").unwrap().as_string().unwrap());
132     let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
133     let kind = match kinds[0].as_string().unwrap() {
134         "bin" => TargetKind::Bin,
135         "lib" | "dylib" | "staticlib" => TargetKind::Lib,
136         _ => TargetKind::Other,
137     };
138
139     Target {
140         path: path,
141         kind: kind,
142     }
143 }
144
145 fn format_files(files: &Vec<PathBuf>, fmt_args: &Vec<String>) -> Result<(), std::io::Error> {
146     let mut command = try!(Command::new("rustfmt")
147                                .args(files)
148                                .args(fmt_args)
149                                .spawn());
150     try!(command.wait());
151
152     Ok(())
153 }