]> git.lizzy.rs Git - rust.git/blob - src/bin/rustfmt.rs
use std::error instead std::out
[rust.git] / src / bin / rustfmt.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 #![cfg(not(test))]
12
13
14 extern crate log;
15 extern crate rustfmt;
16 extern crate toml;
17 extern crate env_logger;
18 extern crate getopts;
19
20 use rustfmt::{run, Input};
21 use rustfmt::config::{Config, WriteMode};
22
23 use std::env;
24 use std::fs::{self, File};
25 use std::io::{self, ErrorKind, Read, Write};
26 use std::path::{Path, PathBuf};
27 use std::str::FromStr;
28
29 use getopts::{Matches, Options};
30
31
32 /// Rustfmt operations.
33 enum Operation {
34     /// Format files and their child modules.
35     Format {
36         files: Vec<PathBuf>,
37         config_path: Option<PathBuf>,
38     },
39     /// Print the help message.
40     Help,
41     // Print version information
42     Version,
43     /// Print detailed configuration help.
44     ConfigHelp,
45     /// Invalid program input.
46     InvalidInput {
47         reason: String,
48     },
49     /// No file specified, read from stdin
50     Stdin {
51         input: String,
52         config_path: Option<PathBuf>,
53     },
54 }
55
56 /// Try to find a project file in the given directory and its parents. Returns the path of a the
57 /// nearest project file if one exists, or `None` if no project file was found.
58 fn lookup_project_file(dir: &Path) -> io::Result<Option<PathBuf>> {
59     let mut current = if dir.is_relative() {
60         try!(env::current_dir()).join(dir)
61     } else {
62         dir.to_path_buf()
63     };
64
65     current = try!(fs::canonicalize(current));
66
67     loop {
68         let config_file = current.join("rustfmt.toml");
69         match fs::metadata(&config_file) {
70             Ok(md) => {
71                 // Properly handle unlikely situation of a directory named `rustfmt.toml`.
72                 if md.is_file() {
73                     return Ok(Some(config_file));
74                 }
75             }
76             // If it's not found, we continue searching; otherwise something went wrong and we
77             // return the error.
78             Err(e) => {
79                 if e.kind() != ErrorKind::NotFound {
80                     return Err(e);
81                 }
82             }
83         }
84
85         // If the current directory has no parent, we're done searching.
86         if !current.pop() {
87             return Ok(None);
88         }
89     }
90 }
91
92 /// Resolve the config for input in `dir`.
93 ///
94 /// Returns the `Config` to use, and the path of the project file if there was
95 /// one.
96 fn resolve_config(dir: &Path) -> io::Result<(Config, Option<PathBuf>)> {
97     let path = try!(lookup_project_file(dir));
98     if path.is_none() {
99         return Ok((Config::default(), None));
100     }
101     let path = path.unwrap();
102     let mut file = try!(File::open(&path));
103     let mut toml = String::new();
104     try!(file.read_to_string(&mut toml));
105     Ok((Config::from_toml(&toml), Some(path)))
106 }
107
108 /// read the given config file path recursively if present else read the project file path
109 fn match_cli_path_or_file(config_path: Option<PathBuf>,
110                           input_file: &Path)
111                           -> io::Result<(Config, Option<PathBuf>)> {
112
113     if let Some(config_file) = config_path {
114         let (toml, path) = try!(resolve_config(config_file.as_ref()));
115         if path.is_some() {
116             return Ok((toml, path));
117         }
118     }
119     resolve_config(input_file)
120 }
121
122 fn update_config(config: &mut Config, matches: &Matches) -> Result<(), String> {
123     config.verbose = matches.opt_present("verbose");
124     config.skip_children = matches.opt_present("skip-children");
125
126     let write_mode = matches.opt_str("write-mode");
127     match matches.opt_str("write-mode").map(|wm| WriteMode::from_str(&wm)) {
128         None => Ok(()),
129         Some(Ok(write_mode)) => {
130             config.write_mode = write_mode;
131             Ok(())
132         }
133         Some(Err(_)) => Err(format!("Invalid write-mode: {}", write_mode.expect("cannot happen"))),
134     }
135 }
136
137 fn execute() -> i32 {
138     let mut opts = Options::new();
139     opts.optflag("h", "help", "show this message");
140     opts.optflag("V", "version", "show version information");
141     opts.optflag("v", "verbose", "show progress");
142     opts.optopt("",
143                 "write-mode",
144                 "mode to write in (not usable when piping from stdin)",
145                 "[replace|overwrite|display|diff|coverage|checkstyle]");
146     opts.optflag("", "skip-children", "don't reformat child modules");
147
148     opts.optflag("",
149                  "config-help",
150                  "show details of rustfmt configuration options");
151     opts.optopt("",
152                 "config-path",
153                 "Recursively searches the given path for the rustfmt.toml config file. If not \
154                  found reverts to the input file path",
155                 "[Path for the configuration file]");
156
157     let matches = match opts.parse(env::args().skip(1)) {
158         Ok(m) => m,
159         Err(e) => {
160             print_usage(&opts, &e.to_string());
161             return 1;
162         }
163     };
164
165     let operation = determine_operation(&matches);
166
167     match operation {
168         Operation::InvalidInput { reason } => {
169             print_usage(&opts, &reason);
170             1
171         }
172         Operation::Help => {
173             print_usage(&opts, "");
174             0
175         }
176         Operation::Version => {
177             print_version();
178             0
179         }
180         Operation::ConfigHelp => {
181             Config::print_docs();
182             0
183         }
184         Operation::Stdin { input, config_path } => {
185             // try to read config from local directory
186             let (mut config, _) = match_cli_path_or_file(config_path, &env::current_dir().unwrap())
187                                       .expect("Error resolving config");
188
189             // write_mode is always Plain for Stdin.
190             config.write_mode = WriteMode::Plain;
191
192             run(Input::Text(input), &config);
193             0
194         }
195         Operation::Format { files, config_path } => {
196             let mut config = Config::default();
197             let mut path = None;
198             // Load the config path file if provided
199             if let Some(config_file) = config_path {
200                 let (cfg_tmp, path_tmp) = resolve_config(config_file.as_ref())
201                                               .expect(&format!("Error resolving config for {:?}",
202                                                                config_file));
203                 config = cfg_tmp;
204                 path = path_tmp;
205             };
206             if let Some(path) = path.as_ref() {
207                 println!("Using rustfmt config file {}", path.display());
208             }
209             for file in files {
210                 // Check the file directory if the config-path could not be read or not provided
211                 if path.is_none() {
212                     let (config_tmp, path_tmp) = resolve_config(file.parent().unwrap())
213                                                      .expect(&format!("Error resolving config \
214                                                                        for {}",
215                                                                       file.display()));
216                     if let Some(path) = path_tmp.as_ref() {
217                         println!("Using rustfmt config file {} for {}",
218                                  path.display(),
219                                  file.display());
220                     }
221                     config = config_tmp;
222                 }
223
224                 if let Err(e) = update_config(&mut config, &matches) {
225                     print_usage(&opts, &e);
226                     return 1;
227                 }
228                 run(Input::File(file), &config);
229             }
230             0
231         }
232     }
233 }
234
235 fn main() {
236     let _ = env_logger::init();
237     let exit_code = execute();
238
239     // Make sure standard output is flushed before we exit.
240     std::io::stdout().flush().unwrap();
241
242     // Exit with given exit code.
243     //
244     // NOTE: This immediately terminates the process without doing any cleanup,
245     // so make sure to finish all necessary cleanup before this is called.
246     std::process::exit(exit_code);
247 }
248
249 fn print_usage(opts: &Options, reason: &str) {
250     let reason = format!("{}\nusage: {} [options] <file>...",
251                          reason,
252                          env::args_os().next().unwrap().to_string_lossy());
253     println!("{}", opts.usage(&reason));
254 }
255
256 fn print_version() {
257     println!("{}.{}.{}{}",
258              option_env!("CARGO_PKG_VERSION_MAJOR").unwrap_or("X"),
259              option_env!("CARGO_PKG_VERSION_MINOR").unwrap_or("X"),
260              option_env!("CARGO_PKG_VERSION_PATCH").unwrap_or("X"),
261              option_env!("CARGO_PKG_VERSION_PRE").unwrap_or(""));
262 }
263
264 fn determine_operation(matches: &Matches) -> Operation {
265     if matches.opt_present("h") {
266         return Operation::Help;
267     }
268
269     if matches.opt_present("config-help") {
270         return Operation::ConfigHelp;
271     }
272
273     if matches.opt_present("version") {
274         return Operation::Version;
275     }
276
277     // Read the config_path and convert to parent dir if a file is provided.
278     let config_path: Option<PathBuf> = matches.opt_str("config-path")
279                                               .map(PathBuf::from)
280                                               .and_then(|dir| {
281                                                   if dir.is_file() {
282                                                       return dir.parent().map(|v| v.into());
283                                                   }
284                                                   Some(dir)
285                                               });
286
287     // if no file argument is supplied, read from stdin
288     if matches.free.is_empty() {
289
290         let mut buffer = String::new();
291         match io::stdin().read_to_string(&mut buffer) {
292             Ok(..) => (),
293             Err(e) => return Operation::InvalidInput { reason: e.to_string() },
294         }
295
296         return Operation::Stdin {
297             input: buffer,
298             config_path: config_path,
299         };
300     }
301
302     let files: Vec<_> = matches.free.iter().map(PathBuf::from).collect();
303
304     Operation::Format {
305         files: files,
306         config_path: config_path,
307     }
308 }