#![cfg(not(test))]
-
-extern crate log;
-extern crate rustfmt;
-extern crate toml;
extern crate env_logger;
extern crate getopts;
-
-use rustfmt::{run, Input, Summary};
-use rustfmt::file_lines::FileLines;
-use rustfmt::config::{Config, WriteMode};
+extern crate rustfmt_nightly as rustfmt;
use std::{env, error};
-use std::fs::{self, File};
-use std::io::{self, ErrorKind, Read, Write};
+use std::fs::File;
+use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use getopts::{Matches, Options};
+use rustfmt::{run, Input, Summary};
+use rustfmt::file_lines::FileLines;
+use rustfmt::config::{get_toml_path, Color, Config, WriteMode};
+
type FmtError = Box<error::Error + Send + Sync>;
type FmtResult<T> = std::result::Result<T, FmtError>;
Version,
/// Print detailed configuration help.
ConfigHelp,
- /// Output default config to a file
- ConfigOutputDefault { path: String },
+ /// Output default config to a file, or stdout if None
+ ConfigOutputDefault {
+ path: Option<String>,
+ },
/// No file specified, read from stdin
Stdin {
input: String,
skip_children: bool,
verbose: bool,
write_mode: Option<WriteMode>,
+ color: Option<Color>,
file_lines: FileLines, // Default is all lines in all files.
+ unstable_features: bool,
}
impl CliOptions {
let mut options = CliOptions::default();
options.skip_children = matches.opt_present("skip-children");
options.verbose = matches.opt_present("verbose");
+ let unstable_features = matches.opt_present("unstable-features");
+ let rust_nightly = option_env!("CFG_RELEASE_CHANNEL")
+ .map(|c| c == "nightly")
+ .unwrap_or(false);
+ if unstable_features && !rust_nightly {
+ return Err(FmtError::from(
+ "Unstable features are only available on Nightly channel",
+ ));
+ } else {
+ options.unstable_features = unstable_features;
+ }
if let Some(ref write_mode) = matches.opt_str("write-mode") {
if let Ok(write_mode) = WriteMode::from_str(write_mode) {
options.write_mode = Some(write_mode);
} else {
- return Err(FmtError::from(format!("Invalid write-mode: {}", write_mode)));
+ return Err(FmtError::from(format!(
+ "Invalid write-mode: {}",
+ write_mode
+ )));
+ }
+ }
+
+ if let Some(ref color) = matches.opt_str("color") {
+ match Color::from_str(color) {
+ Ok(color) => options.color = Some(color),
+ _ => return Err(FmtError::from(format!("Invalid color: {}", color))),
}
}
config.set().skip_children(self.skip_children);
config.set().verbose(self.verbose);
config.set().file_lines(self.file_lines);
+ config.set().unstable_features(self.unstable_features);
if let Some(write_mode) = self.write_mode {
config.set().write_mode(write_mode);
}
- }
-}
-
-const CONFIG_FILE_NAMES: [&'static str; 2] = [".rustfmt.toml", "rustfmt.toml"];
-
-/// Try to find a project file in the given directory and its parents. Returns the path of a the
-/// nearest project file if one exists, or `None` if no project file was found.
-fn lookup_project_file(dir: &Path) -> FmtResult<Option<PathBuf>> {
- let mut current = if dir.is_relative() {
- env::current_dir()?.join(dir)
- } else {
- dir.to_path_buf()
- };
-
- current = fs::canonicalize(current)?;
-
- loop {
- for config_file_name in &CONFIG_FILE_NAMES {
- let config_file = current.join(config_file_name);
- match fs::metadata(&config_file) {
- // Only return if it's a file to handle the unlikely situation of a directory named
- // `rustfmt.toml`.
- Ok(ref md) if md.is_file() => return Ok(Some(config_file)),
- // Return the error if it's something other than `NotFound`; otherwise we didn't
- // find the project file yet, and continue searching.
- Err(e) => {
- if e.kind() != ErrorKind::NotFound {
- return Err(FmtError::from(e));
- }
- }
- _ => {}
- }
+ if let Some(color) = self.color {
+ config.set().color(color);
}
-
- // If the current directory has no parent, we're done searching.
- if !current.pop() {
- return Ok(None);
- }
- }
-}
-
-fn open_config_file(file_path: &Path) -> FmtResult<(Config, Option<PathBuf>)> {
- let mut file = File::open(&file_path)?;
- let mut toml = String::new();
- file.read_to_string(&mut toml)?;
- match Config::from_toml(&toml) {
- Ok(cfg) => Ok((cfg, Some(file_path.to_path_buf()))),
- Err(err) => Err(FmtError::from(err)),
- }
-}
-
-/// Resolve the config for input in `dir`.
-///
-/// Returns the `Config` to use, and the path of the project file if there was
-/// one.
-fn resolve_config(dir: &Path) -> FmtResult<(Config, Option<PathBuf>)> {
- let path = lookup_project_file(dir)?;
- if path.is_none() {
- return Ok((Config::default(), None));
}
- open_config_file(&path.unwrap())
}
/// read the given config file path recursively if present else read the project file path
-fn match_cli_path_or_file(config_path: Option<PathBuf>,
- input_file: &Path)
- -> FmtResult<(Config, Option<PathBuf>)> {
-
+fn match_cli_path_or_file(
+ config_path: Option<PathBuf>,
+ input_file: &Path,
+) -> FmtResult<(Config, Option<PathBuf>)> {
if let Some(config_file) = config_path {
- let (toml, path) = open_config_file(config_file.as_ref())?;
- if path.is_some() {
- return Ok((toml, path));
- }
+ let toml = Config::from_toml_path(config_file.as_ref())?;
+ return Ok((toml, Some(config_file)));
}
- resolve_config(input_file)
+ Config::from_resolved_toml_path(input_file).map_err(FmtError::from)
}
fn make_opts() -> Options {
let mut opts = Options::new();
- opts.optflag("h", "help", "show this message");
- opts.optflag("V", "version", "show version information");
- opts.optflag("v", "verbose", "print verbose output");
- opts.optopt("",
- "write-mode",
- "mode to write in (not usable when piping from stdin)",
- "[replace|overwrite|display|diff|coverage|checkstyle]");
- opts.optflag("", "skip-children", "don't reformat child modules");
-
- opts.optflag("",
- "config-help",
- "show details of rustfmt configuration options");
- opts.optopt("",
- "dump-default-config",
- "Dumps the default configuration to a file and exits.",
- "PATH");
- opts.optopt("",
- "dump-minimal-config",
- "Dumps configuration options that were checked during formatting to a file.",
- "PATH");
- opts.optopt("",
- "config-path",
- "Recursively searches the given path for the rustfmt.toml config file. If not \
- found reverts to the input file path",
- "[Path for the configuration file]");
- opts.optopt("",
- "file-lines",
- "Format specified line ranges. See README for more detail on the JSON format.",
- "JSON");
+
+ opts.optflag("h", "help", "Show this message");
+ opts.optflag("V", "version", "Show version information");
+ opts.optflag("v", "verbose", "Print verbose output");
+ opts.optflag("", "skip-children", "Don't reformat child modules");
+ opts.optflag(
+ "",
+ "unstable-features",
+ "Enables unstable features. Only available on nightly channel",
+ );
+ opts.optflag(
+ "",
+ "config-help",
+ "Show details of rustfmt configuration options",
+ );
+ opts.optflag(
+ "",
+ "error-on-unformatted",
+ "Error if unable to get comments or string literals within max_width, \
+ or they are left with trailing whitespaces",
+ );
+
+ opts.optopt(
+ "",
+ "write-mode",
+ "How to write output (not usable when piping from stdin)",
+ "[replace|overwrite|display|plain|diff|coverage|checkstyle]",
+ );
+ opts.optopt(
+ "",
+ "color",
+ "Use colored output (if supported)",
+ "[always|never|auto]",
+ );
+ opts.optopt(
+ "",
+ "dump-default-config",
+ "Dumps default configuration to PATH. PATH defaults to stdout, if omitted.",
+ "PATH",
+ );
+ opts.optopt(
+ "",
+ "dump-minimal-config",
+ "Dumps configuration options that were checked during formatting to a file.",
+ "PATH",
+ );
+ opts.optopt(
+ "",
+ "config-path",
+ "Recursively searches the given path for the rustfmt.toml config file. If not \
+ found reverts to the input file path",
+ "[Path for the configuration file]",
+ );
+ opts.optopt(
+ "",
+ "file-lines",
+ "Format specified line ranges. See README for more detail on the JSON format.",
+ "JSON",
+ );
opts
}
match determine_operation(&matches)? {
Operation::Help => {
- print_usage(opts, "");
+ print_usage_to_stdout(opts, "");
Summary::print_exit_codes();
- Ok(Summary::new())
+ Ok(Summary::default())
}
Operation::Version => {
print_version();
- Ok(Summary::new())
+ Ok(Summary::default())
}
Operation::ConfigHelp => {
Config::print_docs();
- Ok(Summary::new())
+ Ok(Summary::default())
}
Operation::ConfigOutputDefault { path } => {
- let mut file = File::create(path)?;
let toml = Config::default().all_options().to_toml()?;
- file.write_all(toml.as_bytes())?;
- Ok(Summary::new())
+ if let Some(path) = path {
+ let mut file = File::create(path)?;
+ file.write_all(toml.as_bytes())?;
+ } else {
+ io::stdout().write_all(toml.as_bytes())?;
+ }
+ Ok(Summary::default())
}
Operation::Stdin { input, config_path } => {
// try to read config from local directory
- let (mut config, _) = match_cli_path_or_file(config_path,
- &env::current_dir().unwrap())?;
+ let (mut config, _) =
+ match_cli_path_or_file(config_path, &env::current_dir().unwrap())?;
// write_mode is always Plain for Stdin.
config.set().write_mode(WriteMode::Plain);
config.set().file_lines(file_lines.parse()?);
for f in config.file_lines().files() {
if f != "stdin" {
- println!("Warning: Extra file listed in file_lines option '{}'", f);
+ eprintln!("Warning: Extra file listed in file_lines option '{}'", f);
}
}
}
- Ok(run(Input::Text(input), &config))
+ let mut error_summary = Summary::default();
+ if config.version_meets_requirement(&mut error_summary) {
+ error_summary.add(run(Input::Text(input), &config));
+ }
+
+ Ok(error_summary)
}
Operation::Format {
files,
for f in options.file_lines.files() {
if !files.contains(&PathBuf::from(f)) {
- println!("Warning: Extra file listed in file_lines option '{}'", f);
+ eprintln!("Warning: Extra file listed in file_lines option '{}'", f);
}
}
let mut config = Config::default();
- let mut path = None;
// Load the config path file if provided
- if let Some(config_file) = config_path {
- let (cfg_tmp, path_tmp) = open_config_file(config_file.as_ref())?;
- config = cfg_tmp;
- path = path_tmp;
+ if let Some(config_file) = config_path.as_ref() {
+ config = Config::from_toml_path(config_file.as_ref())?;
};
if options.verbose {
- if let Some(path) = path.as_ref() {
+ if let Some(path) = config_path.as_ref() {
println!("Using rustfmt config file {}", path.display());
}
}
- let mut error_summary = Summary::new();
+ let mut error_summary = Summary::default();
for file in files {
if !file.exists() {
- println!("Error: file `{}` does not exist", file.to_str().unwrap());
+ eprintln!("Error: file `{}` does not exist", file.to_str().unwrap());
error_summary.add_operational_error();
} else if file.is_dir() {
- println!("Error: `{}` is a directory", file.to_str().unwrap());
+ eprintln!("Error: `{}` is a directory", file.to_str().unwrap());
error_summary.add_operational_error();
} else {
// Check the file directory if the config-path could not be read or not provided
- if path.is_none() {
- let (config_tmp, path_tmp) = resolve_config(file.parent().unwrap())?;
+ if config_path.is_none() {
+ let (config_tmp, path_tmp) =
+ Config::from_resolved_toml_path(file.parent().unwrap())?;
if options.verbose {
if let Some(path) = path_tmp.as_ref() {
- println!("Using rustfmt config file {} for {}",
- path.display(),
- file.display());
+ println!(
+ "Using rustfmt config file {} for {}",
+ path.display(),
+ file.display()
+ );
}
}
config = config_tmp;
}
+ if !config.version_meets_requirement(&mut error_summary) {
+ break;
+ }
+
options.clone().apply_to(&mut config);
error_summary.add(run(Input::File(file), &config));
}
}
}
Err(e) => {
- print_usage(&opts, &e.to_string());
+ print_usage_to_stderr(&opts, &e.to_string());
1
}
};
std::process::exit(exit_code);
}
-fn print_usage(opts: &Options, reason: &str) {
- let reason = format!("{}\n\nusage: {} [options] <file>...",
- reason,
- env::args_os().next().unwrap().to_string_lossy());
- println!("{}", opts.usage(&reason));
+macro_rules! print_usage {
+ ($print:ident, $opts:ident, $reason:expr) => ({
+ let msg = format!(
+ "{}\n\nusage: {} [options] <file>...",
+ $reason,
+ env::args_os().next().unwrap().to_string_lossy()
+ );
+ $print!("{}", $opts.usage(&msg));
+ })
+}
+
+fn print_usage_to_stdout(opts: &Options, reason: &str) {
+ print_usage!(println, opts, reason);
+}
+
+fn print_usage_to_stderr(opts: &Options, reason: &str) {
+ print_usage!(eprintln, opts, reason);
}
fn print_version() {
- println!("{}-nightly{}",
- env!("CARGO_PKG_VERSION"),
- include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")))
+ println!(
+ "{}-nightly{}",
+ env!("CARGO_PKG_VERSION"),
+ include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"))
+ )
}
fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
return Ok(Operation::ConfigHelp);
}
- if let Some(path) = matches.opt_str("dump-default-config") {
- return Ok(Operation::ConfigOutputDefault { path });
+ 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"),
+ });
+ }
}
if matches.opt_present("version") {
}
let config_path_not_found = |path: &str| -> FmtResult<Operation> {
- Err(FmtError::from(format!("Error: unable to find a config file for the given path: `{}`",
- path)))
+ Err(FmtError::from(format!(
+ "Error: unable to find a config file for the given path: `{}`",
+ path
+ )))
};
// Read the config_path and convert to parent dir if a file is provided.
let config_path: Option<PathBuf> = match matches.opt_str("config-path").map(PathBuf::from) {
Some(ref path) if !path.exists() => return config_path_not_found(path.to_str().unwrap()),
Some(ref path) if path.is_dir() => {
- let mut config_file_path = None;
- for config_file_name in &CONFIG_FILE_NAMES {
- let temp_path = path.join(config_file_name);
- if temp_path.is_file() {
- config_file_path = Some(temp_path);
- }
- }
+ let config_file_path = get_toml_path(path)?;
if config_file_path.is_some() {
config_file_path
} else {
return config_path_not_found(path.to_str().unwrap());
}
}
- path @ _ => path,
+ path => path,
};
// If no path is given, we won't output a minimal config.
io::stdin().read_to_string(&mut buffer)?;
return Ok(Operation::Stdin {
- input: buffer,
- config_path: config_path,
- });
+ input: buffer,
+ config_path: config_path,
+ });
}
let files: Vec<_> = matches
.free
.iter()
.map(|s| {
- let p = PathBuf::from(s);
- // we will do comparison later, so here tries to canonicalize first
- // to get the expected behavior.
- p.canonicalize().unwrap_or(p)
- })
+ let p = PathBuf::from(s);
+ // we will do comparison later, so here tries to canonicalize first
+ // to get the expected behavior.
+ p.canonicalize().unwrap_or(p)
+ })
.collect();
Ok(Operation::Format {
- files: files,
- config_path: config_path,
- minimal_config_path: minimal_config_path,
- })
+ files: files,
+ config_path: config_path,
+ minimal_config_path: minimal_config_path,
+ })
}