#![cfg(not(test))]
extern crate env_logger;
+extern crate failure;
extern crate getopts;
extern crate rustfmt_nightly as rustfmt;
use std::env;
use std::fs::File;
use std::io::{self, stdout, Read, Write};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
+
+use failure::err_msg;
use getopts::{Matches, Options};
-use rustfmt::{emit_post_matter, emit_pre_matter, load_config, CliOptions, Config, FmtResult,
- WriteMode, WRITE_MODE_LIST};
-use rustfmt::{format_and_emit_report, FileName, Input, Summary};
+use rustfmt::{
+ emit_post_matter, emit_pre_matter, format_and_emit_report, load_config, CliOptions, Config,
+ FileName, FmtResult, Input, Summary, Verbosity, WriteMode,
+};
fn main() {
env_logger::init();
let exit_code = match execute(&opts) {
Ok((write_mode, summary)) => {
- if summary.has_operational_errors() || summary.has_parsing_errors()
+ if summary.has_operational_errors()
+ || summary.has_parsing_errors()
|| (summary.has_diff && write_mode == WriteMode::Check)
{
1
minimal_config_path: Option<String>,
},
/// Print the help message.
- Help,
+ Help(HelpOp),
// Print version information
Version,
- /// Print detailed configuration help.
- ConfigHelp,
/// Output default config to a file, or stdout if None
ConfigOutputDefault {
path: Option<String>,
},
}
+/// Arguments to `--help`
+enum HelpOp {
+ None,
+ Config,
+ FileLines,
+}
+
fn make_opts() -> Options {
let mut opts = Options::new();
// Sorted in alphabetical order.
+ opts.optflag("", "backup", "Backup any modified files.");
+ opts.optflag(
+ "",
+ "check",
+ "Run in 'check' mode. Exits with 0 if input if formatted correctly. Exits \
+ with 1 and prints a diff if formatting is required.",
+ );
opts.optopt(
"",
"color",
"Use colored output (if supported)",
"[always|never|auto]",
);
- opts.optflag(
- "",
- "config-help",
- "Show details of rustfmt configuration options",
- );
opts.optopt(
"",
"config-path",
found reverts to the input file path",
"[Path for the configuration file]",
);
- opts.opt(
- "",
- "dump-default-config",
- "Dumps default configuration to PATH. PATH defaults to stdout, if omitted.",
- "PATH",
- getopts::HasArg::Maybe,
- getopts::Occur::Optional,
- );
- opts.optopt(
- "",
- "dump-minimal-config",
- "Dumps configuration options that were checked during formatting to a file.",
- "PATH",
- );
- opts.optflag(
- "",
- "error-on-unformatted",
- "Error if unable to get comments or string literals within max_width, \
- or they are left with trailing whitespaces",
+ let is_nightly = is_nightly();
+ let emit_opts = if is_nightly {
+ "[files|stdout|coverage|checkstyle]"
+ } else {
+ "[files|stdout]"
+ };
+ opts.optopt("", "emit", "What data to emit and how", emit_opts);
+ opts.optflagopt(
+ "h",
+ "help",
+ "Show this message or help about a specific topic: `config` or `file-lines`",
+ "=TOPIC",
);
opts.optopt(
"",
- "file-lines",
- "Format specified line ranges. See README for more detail on the JSON format.",
- "JSON",
- );
- opts.optflag(
- "",
- "verbose-diff",
- "Emit a more verbose diff, indicating the end of lines.",
- );
- opts.optflag("h", "help", "Show this message");
- opts.optflag("", "skip-children", "Don't reformat child modules");
- opts.optflag(
- "",
- "unstable-features",
- "Enables unstable features. Only available on nightly channel",
+ "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",
);
opts.optflag("v", "verbose", "Print verbose output");
+ opts.optflag("q", "quiet", "Print less output");
opts.optflag("V", "version", "Show version information");
- opts.optopt(
- "",
- "write-mode",
- "How to write output (not usable when piping from stdin)",
- WRITE_MODE_LIST,
- );
+
+ if is_nightly {
+ opts.optflag(
+ "",
+ "unstable-features",
+ "Enables unstable features. Only available on nightly channel.",
+ );
+ opts.optflag(
+ "",
+ "error-on-unformatted",
+ "Error if unable to get comments or string literals within max_width, \
+ or they are left with trailing whitespaces (unstable).",
+ );
+ opts.optopt(
+ "",
+ "file-lines",
+ "Format specified line ranges. Run with `--help file-lines` for \
+ more detail (unstable).",
+ "JSON",
+ );
+ opts.optflag(
+ "",
+ "skip-children",
+ "Don't reformat child modules (unstable).",
+ );
+ }
opts
}
+fn is_nightly() -> bool {
+ option_env!("CFG_RELEASE_CHANNEL")
+ .map(|c| c == "nightly")
+ .unwrap_or(false)
+}
+
fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
let matches = opts.parse(env::args().skip(1))?;
+ let options = CliOptions::from_matches(&matches)?;
match determine_operation(&matches)? {
- Operation::Help => {
+ Operation::Help(HelpOp::None) => {
print_usage_to_stdout(opts, "");
Summary::print_exit_codes();
Ok((WriteMode::None, Summary::default()))
}
- Operation::Version => {
- print_version();
+ Operation::Help(HelpOp::Config) => {
+ Config::print_docs(&mut stdout(), options.unstable_features);
+ Ok((WriteMode::None, Summary::default()))
+ }
+ Operation::Help(HelpOp::FileLines) => {
+ print_help_file_lines();
Ok((WriteMode::None, Summary::default()))
}
- Operation::ConfigHelp => {
- Config::print_docs(&mut stdout(), matches.opt_present("unstable-features"));
+ Operation::Version => {
+ print_version();
Ok((WriteMode::None, Summary::default()))
}
Operation::ConfigOutputDefault { path } => {
- let toml = Config::default().all_options().to_toml()?;
+ let toml = Config::default().all_options().to_toml().map_err(err_msg)?;
if let Some(path) = path {
let mut file = File::create(path)?;
file.write_all(toml.as_bytes())?;
}
Operation::Stdin { input } => {
// try to read config from local directory
- let options = CliOptions::from_matches(&matches)?;
- let (mut config, _) = load_config(None, Some(&options))?;
+ let (mut config, _) = load_config(Some(Path::new(".")), Some(&options))?;
- // write_mode is always Plain for Stdin.
- config.set().write_mode(WriteMode::Plain);
+ // write_mode is always Display for Stdin.
+ config.set().write_mode(WriteMode::Display);
+ config.set().verbose(Verbosity::Quiet);
// parse file_lines
- if let Some(ref file_lines) = matches.opt_str("file-lines") {
- config.set().file_lines(file_lines.parse()?);
- for f in config.file_lines().files() {
- match *f {
- FileName::Custom(ref f) if f == "stdin" => {}
- _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
- }
+ config.set().file_lines(options.file_lines);
+ for f in config.file_lines().files() {
+ match *f {
+ FileName::Custom(ref f) if f == "stdin" => {}
+ _ => eprintln!("Warning: Extra file listed in file_lines option '{}'", f),
}
}
}
emit_post_matter(&config)?;
- Ok((WriteMode::Plain, error_summary))
+ Ok((WriteMode::Display, error_summary))
}
Operation::Format {
files,
minimal_config_path,
- } => {
- let options = CliOptions::from_matches(&matches)?;
- format(files, minimal_config_path, options)
- }
+ } => format(files, minimal_config_path, options),
}
}
options.verify_file_lines(&files);
let (config, config_path) = load_config(None, Some(&options))?;
- if config.verbose() {
+ if config.verbose() == Verbosity::Verbose {
if let Some(path) = config_path.as_ref() {
println!("Using rustfmt config file {}", path.display());
}
let local_config = if config_path.is_none() {
let (local_config, config_path) =
load_config(Some(file.parent().unwrap()), Some(&options))?;
- if local_config.verbose() {
+ if local_config.verbose() == Verbosity::Verbose {
if let Some(path) = config_path {
println!(
"Using rustfmt config file {} for {}",
// that were used during formatting as TOML.
if let Some(path) = minimal_config_path {
let mut file = File::create(path)?;
- let toml = config.used_options().to_toml()?;
+ let toml = config.used_options().to_toml().map_err(err_msg)?;
file.write_all(toml.as_bytes())?;
}
println!("{}", opts.usage(&msg));
}
+fn print_help_file_lines() {
+ println!(
+ "If you want to restrict reformatting to specific sets of lines, you can
+use the `--file-lines` option. Its argument is a JSON array of objects
+with `file` and `range` properties, where `file` is a file name, and
+`range` is an array representing a range of lines like `[7,13]`. Ranges
+are 1-based and inclusive of both end points. Specifying an empty array
+will result in no files being formatted. For example,
+
+```
+rustfmt --file-lines '[
+ {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
+ {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
+ {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
+ {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]'
+```
+
+would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
+and `15` of `src/foo.rs`. No other files would be formatted, even if they
+are included as out of line modules from `src/lib.rs`."
+ );
+}
+
fn print_version() {
let version_info = format!(
"{}-{}",
fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
if matches.opt_present("h") {
- return Ok(Operation::Help);
- }
-
- if matches.opt_present("config-help") {
- return Ok(Operation::ConfigHelp);
+ let topic = matches.opt_str("h");
+ if topic == None {
+ return Ok(Operation::Help(HelpOp::None));
+ } else if topic == Some("config".to_owned()) {
+ return Ok(Operation::Help(HelpOp::Config));
+ } 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));
+ }
}
- if matches.opt_present("dump-default-config") {
- // NOTE for some reason when configured with HasArg::Maybe + Occur::Optional opt_default
- // doesn't recognize `--foo bar` as a long flag with an argument but as a long flag with no
- // argument *plus* a free argument. Thus we check for that case in this branch -- this is
- // required for backward compatibility.
- if let Some(path) = matches.free.get(0) {
- return Ok(Operation::ConfigOutputDefault {
- path: Some(path.clone()),
- });
- } else {
- return Ok(Operation::ConfigOutputDefault {
- path: matches.opt_str("dump-default-config"),
- });
+ 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`");
+ }
}
}
return Ok(Operation::Version);
}
- // If no path is given, we won't output a minimal config.
- let minimal_config_path = matches.opt_str("dump-minimal-config");
-
// if no file argument is supplied, read from stdin
if matches.free.is_empty() {
let mut buffer = String::new();