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, format_and_emit_report, load_config, CliOptions, Config,
29 FileName, FmtResult, Input, Summary, Verbosity, WriteMode,
34 let opts = make_opts();
36 let exit_code = match execute(&opts) {
37 Ok((write_mode, summary)) => {
38 if summary.has_operational_errors()
39 || summary.has_parsing_errors()
40 || (summary.has_diff && write_mode == WriteMode::Check)
48 eprintln!("{}", e.to_string());
52 // Make sure standard output is flushed before we exit.
53 std::io::stdout().flush().unwrap();
55 // Exit with given exit code.
57 // NOTE: This immediately terminates the process without doing any cleanup,
58 // so make sure to finish all necessary cleanup before this is called.
59 std::process::exit(exit_code);
62 /// Rustfmt operations.
64 /// Format files and their child modules.
67 minimal_config_path: Option<String>,
69 /// Print the help message.
71 // Print version information
73 /// Output default config to a file, or stdout if None
77 /// No file specified, read from stdin
83 /// Arguments to `--help`
90 fn make_opts() -> Options {
91 let mut opts = Options::new();
93 // Sorted in alphabetical order.
94 opts.optflag("", "backup", "Backup any modified files.");
98 "Run in 'check' mode. Exits with 0 if input if formatted correctly. Exits \
99 with 1 and prints a diff if formatting is required.",
104 "Use colored output (if supported)",
105 "[always|never|auto]",
110 "Recursively searches the given path for the rustfmt.toml config file. If not \
111 found reverts to the input file path",
112 "[Path for the configuration file]",
114 let is_nightly = is_nightly();
115 let emit_opts = if is_nightly {
116 "[files|stdout|coverage|checkstyle]"
120 opts.optopt("", "emit", "What data to emit and how", emit_opts);
124 "Show this message or help about a specific topic: `config` or `file-lines`",
130 "Dumps a default or minimal config to PATH. A minimal config is the \
131 subset of the current config file used for formatting the current program.",
132 "[minimal|default] PATH",
134 opts.optflag("v", "verbose", "Print verbose output");
135 opts.optflag("q", "quiet", "Print less output");
136 opts.optflag("V", "version", "Show version information");
142 "Enables unstable features. Only available on nightly channel.",
146 "error-on-unformatted",
147 "Error if unable to get comments or string literals within max_width, \
148 or they are left with trailing whitespaces (unstable).",
153 "Format specified line ranges. Run with `--help file-lines` for \
154 more detail (unstable).",
160 "Don't reformat child modules (unstable).",
167 fn is_nightly() -> bool {
168 option_env!("CFG_RELEASE_CHANNEL")
169 .map(|c| c == "nightly")
173 fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
174 let matches = opts.parse(env::args().skip(1))?;
175 let options = CliOptions::from_matches(&matches)?;
177 match determine_operation(&matches)? {
178 Operation::Help(HelpOp::None) => {
179 print_usage_to_stdout(opts, "");
180 Summary::print_exit_codes();
181 Ok((WriteMode::None, Summary::default()))
183 Operation::Help(HelpOp::Config) => {
184 Config::print_docs(&mut stdout(), options.unstable_features);
185 Ok((WriteMode::None, Summary::default()))
187 Operation::Help(HelpOp::FileLines) => {
188 print_help_file_lines();
189 Ok((WriteMode::None, Summary::default()))
191 Operation::Version => {
193 Ok((WriteMode::None, Summary::default()))
195 Operation::ConfigOutputDefault { path } => {
196 let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
197 if let Some(path) = path {
198 let mut file = File::create(path)?;
199 file.write_all(toml.as_bytes())?;
201 io::stdout().write_all(toml.as_bytes())?;
203 Ok((WriteMode::None, Summary::default()))
205 Operation::Stdin { input } => {
206 // try to read config from local directory
207 let (mut config, _) = load_config(Some(Path::new(".")), Some(&options))?;
209 // write_mode is always Display for Stdin.
210 config.set().write_mode(WriteMode::Display);
211 config.set().verbose(Verbosity::Quiet);
214 config.set().file_lines(options.file_lines);
215 for f in config.file_lines().files() {
217 FileName::Custom(ref f) if f == "stdin" => {}
218 _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
222 let mut error_summary = Summary::default();
223 emit_pre_matter(&config)?;
224 match format_and_emit_report(Input::Text(input), &config) {
225 Ok(summary) => error_summary.add(summary),
226 Err(_) => error_summary.add_operational_error(),
228 emit_post_matter(&config)?;
230 Ok((WriteMode::Display, error_summary))
235 } => format(files, minimal_config_path, options),
241 minimal_config_path: Option<String>,
243 ) -> FmtResult<(WriteMode, Summary)> {
244 options.verify_file_lines(&files);
245 let (config, config_path) = load_config(None, Some(&options))?;
247 if config.verbose() == Verbosity::Verbose {
248 if let Some(path) = config_path.as_ref() {
249 println!("Using rustfmt config file {}", path.display());
253 emit_pre_matter(&config)?;
254 let mut error_summary = Summary::default();
258 eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
259 error_summary.add_operational_error();
260 } else if file.is_dir() {
261 eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
262 error_summary.add_operational_error();
264 // Check the file directory if the config-path could not be read or not provided
265 let local_config = if config_path.is_none() {
266 let (local_config, config_path) =
267 load_config(Some(file.parent().unwrap()), Some(&options))?;
268 if local_config.verbose() == Verbosity::Verbose {
269 if let Some(path) = config_path {
271 "Using rustfmt config file {} for {}",
282 match format_and_emit_report(Input::File(file), &local_config) {
283 Ok(summary) => error_summary.add(summary),
285 error_summary.add_operational_error();
291 emit_post_matter(&config)?;
293 // If we were given a path via dump-minimal-config, output any options
294 // that were used during formatting as TOML.
295 if let Some(path) = minimal_config_path {
296 let mut file = File::create(path)?;
297 let toml = config.used_options().to_toml().map_err(err_msg)?;
298 file.write_all(toml.as_bytes())?;
301 Ok((config.write_mode(), error_summary))
304 fn print_usage_to_stdout(opts: &Options, reason: &str) {
305 let sep = if reason.is_empty() {
308 format!("{}\n\n", reason)
311 "{}Format Rust code\n\nusage: {} [options] <file>...",
313 env::args_os().next().unwrap().to_string_lossy()
315 println!("{}", opts.usage(&msg));
318 fn print_help_file_lines() {
320 "If you want to restrict reformatting to specific sets of lines, you can
321 use the `--file-lines` option. Its argument is a JSON array of objects
322 with `file` and `range` properties, where `file` is a file name, and
323 `range` is an array representing a range of lines like `[7,13]`. Ranges
324 are 1-based and inclusive of both end points. Specifying an empty array
325 will result in no files being formatted. For example,
328 rustfmt --file-lines '[
329 {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
330 {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
331 {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
332 {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]'
335 would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
336 and `15` of `src/foo.rs`. No other files would be formatted, even if they
337 are included as out of line modules from `src/lib.rs`."
342 let version_info = format!(
344 option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
345 include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
348 println!("rustfmt {}", version_info);
351 fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
352 if matches.opt_present("h") {
353 let topic = matches.opt_str("h");
355 return Ok(Operation::Help(HelpOp::None));
356 } else if topic == Some("config".to_owned()) {
357 return Ok(Operation::Help(HelpOp::Config));
358 } else if topic == Some("file-lines".to_owned()) {
359 return Ok(Operation::Help(HelpOp::FileLines));
361 println!("Unknown help topic: `{}`\n", topic.unwrap());
362 return Ok(Operation::Help(HelpOp::None));
366 let mut minimal_config_path = None;
367 if let Some(ref kind) = matches.opt_str("print-config") {
368 let path = matches.free.get(0).cloned();
369 if kind == "default" {
370 return Ok(Operation::ConfigOutputDefault { path });
371 } else if kind == "minimal" {
372 minimal_config_path = path;
373 if minimal_config_path.is_none() {
374 println!("WARNING: PATH required for `--print-config minimal`");
379 if matches.opt_present("version") {
380 return Ok(Operation::Version);
383 // if no file argument is supplied, read from stdin
384 if matches.free.is_empty() {
385 let mut buffer = String::new();
386 io::stdin().read_to_string(&mut buffer)?;
388 return Ok(Operation::Stdin { input: buffer });
391 let files: Vec<_> = matches
395 let p = PathBuf::from(s);
396 // we will do comparison later, so here tries to canonicalize first
397 // to get the expected behavior.
398 p.canonicalize().unwrap_or(p)
402 Ok(Operation::Format {