]> git.lizzy.rs Git - rust.git/blob - src/bin/cargo-fmt.rs
Merge pull request #710 from JanLikar/master
[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     // All arguments after -- are passed to rustfmt
73     env::args().skip_while(|a| a != "--").skip(1).collect()
74 }
75
76 #[derive(Debug)]
77 enum TargetKind {
78     Lib, // dylib, staticlib, lib
79     Bin, // bin
80     Other, // test, plugin,...
81 }
82
83 impl TargetKind {
84     fn is_lib(&self) -> bool {
85         match self {
86             &TargetKind::Lib => true,
87             _ => false,
88         }
89     }
90
91     fn is_bin(&self) -> bool {
92         match self {
93             &TargetKind::Bin => true,
94             _ => false,
95         }
96     }
97 }
98
99 #[derive(Debug)]
100 struct Target {
101     path: PathBuf,
102     kind: TargetKind,
103 }
104
105 // Returns a vector of all compile targets of a crate
106 fn get_targets() -> Result<Vec<Target>, std::io::Error> {
107     let mut targets: Vec<Target> = vec![];
108     let output = try!(Command::new("cargo").arg("read-manifest").output());
109     if output.status.success() {
110         // None of the unwraps should fail if output of `cargo read-manifest` is correct
111         let data = &String::from_utf8(output.stdout).unwrap();
112         let json = Json::from_str(data).unwrap();
113         let jtargets = json.find("targets").unwrap().as_array().unwrap();
114         for jtarget in jtargets {
115             targets.push(target_from_json(jtarget));
116         }
117
118         Ok(targets)
119     } else {
120         // This happens when cargo-fmt is not used inside a crate
121         Err(std::io::Error::new(std::io::ErrorKind::NotFound,
122                                 str::from_utf8(&output.stderr).unwrap()))
123     }
124 }
125
126 fn target_from_json(jtarget: &Json) -> Target {
127     let jtarget = jtarget.as_object().unwrap();
128     let path = PathBuf::from(jtarget.get("src_path").unwrap().as_string().unwrap());
129     let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
130     let kind = match kinds[0].as_string().unwrap() {
131         "bin" => TargetKind::Bin,
132         "lib" | "dylib" | "staticlib" => TargetKind::Lib,
133         _ => TargetKind::Other,
134     };
135
136     Target {
137         path: path,
138         kind: kind,
139     }
140 }
141
142 fn format_files(files: &Vec<PathBuf>, fmt_args: &Vec<String>) -> Result<(), std::io::Error> {
143     let mut command = try!(Command::new("rustfmt")
144                                .args(files)
145                                .args(fmt_args)
146                                .spawn());
147     try!(command.wait());
148
149     Ok(())
150 }