-// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-extern crate env_logger;
-#[macro_use]
-extern crate failure;
-extern crate getopts;
-extern crate rustfmt_nightly as rustfmt;
+use env_logger;
+use failure::{err_msg, format_err, Error as FailureError, Fail};
+use io::Error as IoError;
+
+use rustfmt_nightly as rustfmt;
use std::env;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use failure::err_msg;
-
use getopts::{Matches, Options};
-use rustfmt::{
- load_config, CliOptions, Color, Config, EmitMode, ErrorKind, FileLines, FileName, Input,
- Session, Verbosity,
+use crate::rustfmt::{
+ load_config, CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
+ FormatReportFormatterBuilder, Input, Session, Verbosity,
};
fn main() {
// Exit with given exit code.
//
- // NOTE: This immediately terminates the process without doing any cleanup,
+ // NOTE: this immediately terminates the process without doing any cleanup,
// so make sure to finish all necessary cleanup before this is called.
std::process::exit(exit_code);
}
},
/// Print the help message.
Help(HelpOp),
- // Print version information
+ /// Print version information
Version,
/// Output default config to a file, or stdout if None
- ConfigOutputDefault {
- path: Option<String>,
- },
+ ConfigOutputDefault { path: Option<String> },
+ /// Output current config (as if formatting to a file) to stdout
+ ConfigOutputCurrent { path: Option<String> },
/// No file specified, read from stdin
- Stdin {
- input: String,
- },
+ Stdin { input: String },
+}
+
+/// Rustfmt operations errors.
+#[derive(Fail, Debug)]
+pub enum OperationError {
+ /// An unknown help topic was requested.
+ #[fail(display = "Unknown help topic: `{}`.", _0)]
+ UnknownHelpTopic(String),
+ /// An unknown print-config option was requested.
+ #[fail(display = "Unknown print-config option: `{}`.", _0)]
+ UnknownPrintConfigTopic(String),
+ /// Attempt to generate a minimal config from standard input.
+ #[fail(display = "The `--print-config=minimal` option doesn't work with standard input.")]
+ MinimalPathWithStdin,
+ /// An io error during reading or writing.
+ #[fail(display = "io error: {}", _0)]
+ IoError(IoError),
+}
+
+impl From<IoError> for OperationError {
+ fn from(e: IoError) -> OperationError {
+ OperationError::IoError(e)
+ }
}
/// Arguments to `--help`
);
let is_nightly = is_nightly();
let emit_opts = if is_nightly {
- "[files|stdout|coverage|checkstyle]"
+ "[files|stdout|coverage|checkstyle|json]"
} else {
"[files|stdout]"
};
found reverts to the input file path",
"[Path for the configuration file]",
);
+ opts.optopt("", "edition", "Rust edition to use", "[2015|2018]");
opts.optopt(
"",
"color",
"",
"print-config",
"Dumps a default or minimal config to PATH. A minimal config is the \
- subset of the current config file used for formatting the current program.",
- "[minimal|default] PATH",
+ subset of the current config file used for formatting the current program. \
+ `current` writes to stdout current config as if formatting the file at PATH.",
+ "[default|minimal|current] PATH",
+ );
+ opts.optflag(
+ "l",
+ "files-with-diff",
+ "Prints the names of mismatched files that were formatted. Prints the names of \
+ files that would be formated when used with `--check` mode. ",
);
if is_nightly {
}
fn is_nightly() -> bool {
- option_env!("CFG_RELEASE_CHANNEL")
- .map(|c| c == "nightly" || c == "dev")
- .unwrap_or(false)
+ option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
}
// Returned i32 is an exit code
-fn execute(opts: &Options) -> Result<i32, failure::Error> {
+fn execute(opts: &Options) -> Result<i32, FailureError> {
let matches = opts.parse(env::args().skip(1))?;
let options = GetOptsOptions::from_matches(&matches)?;
}
Ok(0)
}
+ Operation::ConfigOutputCurrent { path } => {
+ let path = match path {
+ Some(path) => path,
+ None => return Err(format_err!("PATH required for `--print-config current`")),
+ };
+
+ let file = PathBuf::from(path);
+ let file = file.canonicalize().unwrap_or(file);
+
+ let (config, _) = load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
+ let toml = config.all_options().to_toml().map_err(err_msg)?;
+ io::stdout().write_all(toml.as_bytes())?;
+
+ Ok(0)
+ }
Operation::Stdin { input } => format_string(input, options),
Operation::Format {
files,
}
}
-fn format_string(input: String, options: GetOptsOptions) -> Result<i32, failure::Error> {
+fn format_string(input: String, options: GetOptsOptions) -> Result<i32, FailureError> {
// try to read config from local directory
let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
files: Vec<PathBuf>,
minimal_config_path: Option<String>,
options: &GetOptsOptions,
-) -> Result<i32, failure::Error> {
+) -> Result<i32, FailureError> {
options.verify_file_lines(&files);
let (config, config_path) = load_config(None, Some(options.clone()))?;
Ok(exit_code)
}
-fn format_and_emit_report<T: Write>(session: &mut Session<T>, input: Input) {
+fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) {
match session.format(input) {
Ok(report) => {
if report.has_warnings() {
- match term::stderr() {
- Some(ref t)
- if session.config.color().use_colored_tty()
- && t.supports_color()
- && t.supports_attr(term::Attr::Bold) =>
- {
- match report.fancy_print(term::stderr().unwrap()) {
- Ok(..) => (),
- Err(..) => panic!("Unable to write to stderr: {}", report),
- }
- }
- _ => eprintln!("{}", report),
- }
+ eprintln!(
+ "{}",
+ FormatReportFormatterBuilder::new(&report)
+ .enable_colors(should_print_with_colors(session))
+ .build()
+ );
}
}
Err(msg) => {
}
}
+fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
+ match term::stderr() {
+ Some(ref t)
+ if session.config.color().use_colored_tty()
+ && t.supports_color()
+ && t.supports_attr(term::Attr::Bold) =>
+ {
+ true
+ }
+ _ => false,
+ }
+}
+
fn print_usage_to_stdout(opts: &Options, reason: &str) {
let sep = if reason.is_empty() {
String::new()
println!("rustfmt {}", version_info);
}
-fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
+fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
if matches.opt_present("h") {
let topic = matches.opt_str("h");
if topic == None {
} else if topic == Some("file-lines".to_owned()) {
return Ok(Operation::Help(HelpOp::FileLines));
} else {
- println!("Unknown help topic: `{}`\n", topic.unwrap());
- return Ok(Operation::Help(HelpOp::None));
+ return Err(OperationError::UnknownHelpTopic(topic.unwrap()));
}
}
+ let mut free_matches = matches.free.iter();
let mut minimal_config_path = None;
- if let Some(ref kind) = matches.opt_str("print-config") {
- let path = matches.free.get(0).cloned();
- if kind == "default" {
- return Ok(Operation::ConfigOutputDefault { path });
- } else if kind == "minimal" {
- minimal_config_path = path;
- if minimal_config_path.is_none() {
- println!("WARNING: PATH required for `--print-config minimal`");
+ if let Some(kind) = matches.opt_str("print-config") {
+ let path = free_matches.next().cloned();
+ match kind.as_str() {
+ "default" => return Ok(Operation::ConfigOutputDefault { path }),
+ "current" => return Ok(Operation::ConfigOutputCurrent { path }),
+ "minimal" => {
+ minimal_config_path = path;
+ if minimal_config_path.is_none() {
+ eprintln!("WARNING: PATH required for `--print-config minimal`.");
+ }
+ }
+ _ => {
+ return Err(OperationError::UnknownPrintConfigTopic(kind));
}
}
}
return Ok(Operation::Version);
}
- // if no file argument is supplied, read from stdin
- if matches.free.is_empty() {
- let mut buffer = String::new();
- io::stdin().read_to_string(&mut buffer)?;
-
- return Ok(Operation::Stdin { input: buffer });
- }
-
- let files: Vec<_> = matches
- .free
- .iter()
+ let files: Vec<_> = free_matches
.map(|s| {
let p = PathBuf::from(s);
// we will do comparison later, so here tries to canonicalize first
})
.collect();
+ // if no file argument is supplied, read from stdin
+ if files.is_empty() {
+ if minimal_config_path.is_some() {
+ return Err(OperationError::MinimalPathWithStdin);
+ }
+ let mut buffer = String::new();
+ io::stdin().read_to_string(&mut buffer)?;
+
+ return Ok(Operation::Stdin { input: buffer });
+ }
+
Ok(Operation::Format {
files,
minimal_config_path,
emit_mode: EmitMode,
backup: bool,
check: bool,
+ edition: Option<Edition>,
color: Option<Color>,
file_lines: FileLines, // Default is all lines in all files.
unstable_features: bool,
error_on_unformatted: Option<bool>,
+ print_misformatted_file_names: bool,
}
impl GetOptsOptions {
- pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, failure::Error> {
+ pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions, FailureError> {
let mut options = GetOptsOptions::default();
options.verbose = matches.opt_present("verbose");
options.quiet = matches.opt_present("quiet");
if options.check {
return Err(format_err!("Invalid to use `--emit` and `--check`"));
}
- if let Ok(emit_mode) = emit_mode_from_emit_str(emit_str) {
- options.emit_mode = emit_mode;
- } else {
- return Err(format_err!("Invalid value for `--emit`"));
- }
+
+ options.emit_mode = emit_mode_from_emit_str(emit_str)?;
+ }
+
+ if let Some(ref edition_str) = matches.opt_str("edition") {
+ options.edition = Some(edition_from_edition_str(edition_str)?);
}
if matches.opt_present("backup") {
options.backup = true;
}
+ if matches.opt_present("files-with-diff") {
+ options.print_misformatted_file_names = true;
+ }
+
if !rust_nightly {
if !STABLE_EMIT_MODES.contains(&options.emit_mode) {
return Err(format_err!(
if let Some(error_on_unformatted) = self.error_on_unformatted {
config.set().error_on_unformatted(error_on_unformatted);
}
+ if let Some(edition) = self.edition {
+ config.set().edition(edition);
+ }
if self.check {
config.set().emit_mode(EmitMode::Diff);
} else {
if let Some(color) = self.color {
config.set().color(color);
}
+ if self.print_misformatted_file_names {
+ config.set().print_misformatted_file_names(true);
+ }
}
fn config_path(&self) -> Option<&Path> {
}
}
-fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode, failure::Error> {
+fn edition_from_edition_str(edition_str: &str) -> Result<Edition, FailureError> {
+ match edition_str {
+ "2015" => Ok(Edition::Edition2015),
+ "2018" => Ok(Edition::Edition2018),
+ _ => Err(format_err!("Invalid value for `--edition`")),
+ }
+}
+
+fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode, FailureError> {
match emit_str {
"files" => Ok(EmitMode::Files),
"stdout" => Ok(EmitMode::Stdout),
"coverage" => Ok(EmitMode::Coverage),
"checkstyle" => Ok(EmitMode::Checkstyle),
+ "json" => Ok(EmitMode::Json),
_ => Err(format_err!("Invalid value for `--emit`")),
}
}