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.
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.
13 extern crate env_logger;
16 extern crate rustfmt_nightly as rustfmt;
20 use std::io::{self, stdout, Read, Write};
21 use std::path::{Path, PathBuf};
25 use getopts::{Matches, Options};
28 emit_post_matter, emit_pre_matter, load_config, CliOptions, Config, FmtResult, WriteMode,
31 use rustfmt::{format_and_emit_report, FileName, Input, Summary};
35 let opts = make_opts();
37 let exit_code = match execute(&opts) {
38 Ok((write_mode, summary)) => {
39 if summary.has_operational_errors()
40 || summary.has_parsing_errors()
41 || (summary.has_diff && write_mode == WriteMode::Check)
49 eprintln!("{}", e.to_string());
53 // Make sure standard output is flushed before we exit.
54 std::io::stdout().flush().unwrap();
56 // Exit with given exit code.
58 // NOTE: This immediately terminates the process without doing any cleanup,
59 // so make sure to finish all necessary cleanup before this is called.
60 std::process::exit(exit_code);
63 /// Rustfmt operations.
65 /// Format files and their child modules.
68 minimal_config_path: Option<String>,
70 /// Print the help message.
72 // Print version information
74 /// Print detailed configuration help.
76 /// Output default config to a file, or stdout if None
80 /// No file specified, read from stdin
86 fn make_opts() -> Options {
87 let mut opts = Options::new();
89 // Sorted in alphabetical order.
93 "Use colored output (if supported)",
94 "[always|never|auto]",
99 "Show details of rustfmt configuration options",
104 "Recursively searches the given path for the rustfmt.toml config file. If not \
105 found reverts to the input file path",
106 "[Path for the configuration file]",
110 "dump-default-config",
111 "Dumps default configuration to PATH. PATH defaults to stdout, if omitted.",
113 getopts::HasArg::Maybe,
114 getopts::Occur::Optional,
118 "dump-minimal-config",
119 "Dumps configuration options that were checked during formatting to a file.",
124 "error-on-unformatted",
125 "Error if unable to get comments or string literals within max_width, \
126 or they are left with trailing whitespaces",
131 "Format specified line ranges. See README for more detail on the JSON format.",
137 "Emit a more verbose diff, indicating the end of lines.",
139 opts.optflag("h", "help", "Show this message");
140 opts.optflag("", "skip-children", "Don't reformat child modules");
144 "Enables unstable features. Only available on nightly channel",
146 opts.optflag("v", "verbose", "Print verbose output");
147 opts.optflag("V", "version", "Show version information");
151 "How to write output (not usable when piping from stdin)",
158 fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
159 let matches = opts.parse(env::args().skip(1))?;
161 match determine_operation(&matches)? {
163 print_usage_to_stdout(opts, "");
164 Summary::print_exit_codes();
165 Ok((WriteMode::None, Summary::default()))
167 Operation::Version => {
169 Ok((WriteMode::None, Summary::default()))
171 Operation::ConfigHelp => {
172 Config::print_docs(&mut stdout(), matches.opt_present("unstable-features"));
173 Ok((WriteMode::None, Summary::default()))
175 Operation::ConfigOutputDefault { path } => {
176 let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
177 if let Some(path) = path {
178 let mut file = File::create(path)?;
179 file.write_all(toml.as_bytes())?;
181 io::stdout().write_all(toml.as_bytes())?;
183 Ok((WriteMode::None, Summary::default()))
185 Operation::Stdin { input } => {
186 // try to read config from local directory
187 let options = CliOptions::from_matches(&matches)?;
188 let (mut config, _) = load_config(Some(Path::new(".")), Some(&options))?;
190 // write_mode is always Plain for Stdin.
191 config.set().write_mode(WriteMode::Plain);
194 if let Some(ref file_lines) = matches.opt_str("file-lines") {
197 .file_lines(file_lines.parse().map_err(err_msg)?);
198 for f in config.file_lines().files() {
200 FileName::Custom(ref f) if f == "stdin" => {}
201 _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
206 let mut error_summary = Summary::default();
207 emit_pre_matter(&config)?;
208 match format_and_emit_report(Input::Text(input), &config) {
209 Ok(summary) => error_summary.add(summary),
210 Err(_) => error_summary.add_operational_error(),
212 emit_post_matter(&config)?;
214 Ok((WriteMode::Plain, error_summary))
220 let options = CliOptions::from_matches(&matches)?;
221 format(files, minimal_config_path, options)
228 minimal_config_path: Option<String>,
230 ) -> FmtResult<(WriteMode, Summary)> {
231 options.verify_file_lines(&files);
232 let (config, config_path) = load_config(None, Some(&options))?;
234 if config.verbose() {
235 if let Some(path) = config_path.as_ref() {
236 println!("Using rustfmt config file {}", path.display());
240 emit_pre_matter(&config)?;
241 let mut error_summary = Summary::default();
245 eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
246 error_summary.add_operational_error();
247 } else if file.is_dir() {
248 eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
249 error_summary.add_operational_error();
251 // Check the file directory if the config-path could not be read or not provided
252 let local_config = if config_path.is_none() {
253 let (local_config, config_path) =
254 load_config(Some(file.parent().unwrap()), Some(&options))?;
255 if local_config.verbose() {
256 if let Some(path) = config_path {
258 "Using rustfmt config file {} for {}",
269 match format_and_emit_report(Input::File(file), &local_config) {
270 Ok(summary) => error_summary.add(summary),
272 error_summary.add_operational_error();
278 emit_post_matter(&config)?;
280 // If we were given a path via dump-minimal-config, output any options
281 // that were used during formatting as TOML.
282 if let Some(path) = minimal_config_path {
283 let mut file = File::create(path)?;
284 let toml = config.used_options().to_toml().map_err(err_msg)?;
285 file.write_all(toml.as_bytes())?;
288 Ok((config.write_mode(), error_summary))
291 fn print_usage_to_stdout(opts: &Options, reason: &str) {
292 let sep = if reason.is_empty() {
295 format!("{}\n\n", reason)
298 "{}Format Rust code\n\nusage: {} [options] <file>...",
300 env::args_os().next().unwrap().to_string_lossy()
302 println!("{}", opts.usage(&msg));
306 let version_info = format!(
308 option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
309 include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
312 println!("rustfmt {}", version_info);
315 fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
316 if matches.opt_present("h") {
317 return Ok(Operation::Help);
320 if matches.opt_present("config-help") {
321 return Ok(Operation::ConfigHelp);
324 if matches.opt_present("dump-default-config") {
325 // NOTE for some reason when configured with HasArg::Maybe + Occur::Optional opt_default
326 // doesn't recognize `--foo bar` as a long flag with an argument but as a long flag with no
327 // argument *plus* a free argument. Thus we check for that case in this branch -- this is
328 // required for backward compatibility.
329 if let Some(path) = matches.free.get(0) {
330 return Ok(Operation::ConfigOutputDefault {
331 path: Some(path.clone()),
334 return Ok(Operation::ConfigOutputDefault {
335 path: matches.opt_str("dump-default-config"),
340 if matches.opt_present("version") {
341 return Ok(Operation::Version);
344 // If no path is given, we won't output a minimal config.
345 let minimal_config_path = matches.opt_str("dump-minimal-config");
347 // if no file argument is supplied, read from stdin
348 if matches.free.is_empty() {
349 let mut buffer = String::new();
350 io::stdin().read_to_string(&mut buffer)?;
352 return Ok(Operation::Stdin { input: buffer });
355 let files: Vec<_> = matches
359 let p = PathBuf::from(s);
360 // we will do comparison later, so here tries to canonicalize first
361 // to get the expected behavior.
362 p.canonicalize().unwrap_or(p)
366 Ok(Operation::Format {