Refactor to make a sensible public API

0.5 - lots of breaking changes

cc #2639
This commit is contained in:
Nick Cameron 2018-04-20 21:08:20 +12:00
parent 6a31741eaa
commit ca610d35b3
16 changed files with 315 additions and 296 deletions

2
Cargo.lock generated
View file

@ -451,7 +451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "rustfmt-nightly" name = "rustfmt-nightly"
version = "0.4.2" version = "0.5.0"
dependencies = [ dependencies = [
"assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "assert_cli 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cargo_metadata 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -1,7 +1,7 @@
[package] [package]
name = "rustfmt-nightly" name = "rustfmt-nightly"
version = "0.4.2" version = "0.5.0"
authors = ["Nicholas Cameron <ncameron@mozilla.com>", "The Rustfmt developers"] authors = ["Nicholas Cameron <ncameron@mozilla.com>", "The Rustfmt developers"]
description = "Tool to find and fix Rust formatting issues" description = "Tool to find and fix Rust formatting issues"
repository = "https://github.com/rust-lang-nursery/rustfmt" repository = "https://github.com/rust-lang-nursery/rustfmt"
@ -49,8 +49,8 @@ cargo_metadata = "0.5.1"
rustc-ap-syntax = "103.0.0" rustc-ap-syntax = "103.0.0"
[dev-dependencies] [dev-dependencies]
lazy_static = "1.0.0"
assert_cli = "0.5" assert_cli = "0.5"
lazy_static = "1.0.0"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.11" libc = "0.2.11"

View file

@ -14,23 +14,16 @@ extern crate env_logger;
extern crate getopts; extern crate getopts;
extern crate rustfmt_nightly as rustfmt; extern crate rustfmt_nightly as rustfmt;
use std::env;
use std::fs::File; use std::fs::File;
use std::io::{self, stdout, Read, Write}; use std::io::{self, stdout, Read, Write};
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::str::FromStr;
use std::{env, error};
use getopts::{Matches, Options}; use getopts::{Matches, Options};
use rustfmt::checkstyle; use rustfmt::{emit_post_matter, emit_pre_matter, load_config, CliOptions, Config, FmtResult,
use rustfmt::config::file_lines::FileLines; WriteMode, WRITE_MODE_LIST};
use rustfmt::config::{get_toml_path, Color, Config, WriteMode}; use rustfmt::{format_and_emit_report, FileName, Input, Summary};
use rustfmt::{run, FileName, Input, Summary};
type FmtError = Box<error::Error + Send + Sync>;
type FmtResult<T> = std::result::Result<T, FmtError>;
const WRITE_MODE_LIST: &str = "[replace|overwrite|display|plain|diff|coverage|checkstyle|check]";
fn main() { fn main() {
env_logger::init(); env_logger::init();
@ -66,7 +59,6 @@ enum Operation {
/// Format files and their child modules. /// Format files and their child modules.
Format { Format {
files: Vec<PathBuf>, files: Vec<PathBuf>,
config_path: Option<PathBuf>,
minimal_config_path: Option<String>, minimal_config_path: Option<String>,
}, },
/// Print the help message. /// Print the help message.
@ -82,105 +74,9 @@ enum Operation {
/// No file specified, read from stdin /// No file specified, read from stdin
Stdin { Stdin {
input: String, input: String,
config_path: Option<PathBuf>,
}, },
} }
/// Parsed command line options.
#[derive(Clone, Debug, Default)]
struct CliOptions {
skip_children: Option<bool>,
verbose: bool,
verbose_diff: bool,
write_mode: Option<WriteMode>,
color: Option<Color>,
file_lines: FileLines, // Default is all lines in all files.
unstable_features: bool,
error_on_unformatted: Option<bool>,
}
impl CliOptions {
fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
let mut options = CliOptions::default();
options.verbose = matches.opt_present("verbose");
options.verbose_diff = matches.opt_present("verbose-diff");
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: {}, expected one of {}",
write_mode, WRITE_MODE_LIST
)));
}
}
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))),
}
}
if let Some(ref file_lines) = matches.opt_str("file-lines") {
options.file_lines = file_lines.parse()?;
}
if matches.opt_present("skip-children") {
options.skip_children = Some(true);
}
if matches.opt_present("error-on-unformatted") {
options.error_on_unformatted = Some(true);
}
Ok(options)
}
fn apply_to(self, config: &mut Config) {
config.set().verbose(self.verbose);
config.set().verbose_diff(self.verbose_diff);
config.set().file_lines(self.file_lines);
config.set().unstable_features(self.unstable_features);
if let Some(skip_children) = self.skip_children {
config.set().skip_children(skip_children);
}
if let Some(error_on_unformatted) = self.error_on_unformatted {
config.set().error_on_unformatted(error_on_unformatted);
}
if let Some(write_mode) = self.write_mode {
config.set().write_mode(write_mode);
}
if let Some(color) = self.color {
config.set().color(color);
}
}
}
/// 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>)> {
if let Some(config_file) = config_path {
let toml = Config::from_toml_path(config_file.as_ref())?;
return Ok((toml, Some(config_file)));
}
Config::from_resolved_toml_path(input_file).map_err(FmtError::from)
}
fn make_opts() -> Options { fn make_opts() -> Options {
let mut opts = Options::new(); let mut opts = Options::new();
@ -280,10 +176,10 @@ fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
} }
Ok((WriteMode::None, Summary::default())) Ok((WriteMode::None, Summary::default()))
} }
Operation::Stdin { input, config_path } => { Operation::Stdin { input } => {
// try to read config from local directory // try to read config from local directory
let (mut config, _) = let options = CliOptions::from_matches(&matches)?;
match_cli_path_or_file(config_path, &env::current_dir().unwrap())?; let (mut config, _) = load_config(None, Some(&options))?;
// write_mode is always Plain for Stdin. // write_mode is always Plain for Stdin.
config.set().write_mode(WriteMode::Plain); config.set().write_mode(WriteMode::Plain);
@ -300,57 +196,40 @@ fn execute(opts: &Options) -> FmtResult<(WriteMode, Summary)> {
} }
let mut error_summary = Summary::default(); let mut error_summary = Summary::default();
if config.version_meets_requirement(&mut error_summary) { emit_pre_matter(&config)?;
let mut out = &mut stdout(); match format_and_emit_report(Input::Text(input), &config) {
checkstyle::output_header(&mut out, config.write_mode())?; Ok(summary) => error_summary.add(summary),
error_summary.add(run(Input::Text(input), &config)); Err(_) => error_summary.add_operational_error(),
checkstyle::output_footer(&mut out, config.write_mode())?;
} }
emit_post_matter(&config)?;
Ok((WriteMode::Plain, error_summary)) Ok((WriteMode::Plain, error_summary))
} }
Operation::Format { Operation::Format {
files, files,
config_path,
minimal_config_path, minimal_config_path,
} => { } => {
let options = CliOptions::from_matches(&matches)?; let options = CliOptions::from_matches(&matches)?;
format(files, config_path, minimal_config_path, options) format(files, minimal_config_path, options)
} }
} }
} }
fn format( fn format(
files: Vec<PathBuf>, files: Vec<PathBuf>,
config_path: Option<PathBuf>,
minimal_config_path: Option<String>, minimal_config_path: Option<String>,
options: CliOptions, options: CliOptions,
) -> FmtResult<(WriteMode, Summary)> { ) -> FmtResult<(WriteMode, Summary)> {
for f in options.file_lines.files() { options.verify_file_lines(&files);
match *f { let (config, config_path) = load_config(None, Some(&options))?;
FileName::Real(ref f) if files.contains(f) => {}
FileName::Real(_) => {
eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
}
_ => eprintln!("Warning: Not a file '{}'", f),
}
}
let mut config = Config::default(); if config.verbose() {
// Load the config path file if provided
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) = config_path.as_ref() { if let Some(path) = config_path.as_ref() {
println!("Using rustfmt config file {}", path.display()); println!("Using rustfmt config file {}", path.display());
} }
} }
let write_mode = config.write_mode(); emit_pre_matter(&config)?;
let mut out = &mut stdout();
checkstyle::output_header(&mut out, write_mode)?;
let mut error_summary = Summary::default(); let mut error_summary = Summary::default();
for file in files { for file in files {
@ -362,11 +241,11 @@ fn format(
error_summary.add_operational_error(); error_summary.add_operational_error();
} else { } else {
// Check the file directory if the config-path could not be read or not provided // Check the file directory if the config-path could not be read or not provided
if config_path.is_none() { let local_config = if config_path.is_none() {
let (config_tmp, path_tmp) = let (local_config, config_path) =
Config::from_resolved_toml_path(file.parent().unwrap())?; load_config(Some(file.parent().unwrap()), Some(&options))?;
if options.verbose { if local_config.verbose() {
if let Some(path) = path_tmp.as_ref() { if let Some(path) = config_path {
println!( println!(
"Using rustfmt config file {} for {}", "Using rustfmt config file {} for {}",
path.display(), path.display(),
@ -374,18 +253,21 @@ fn format(
); );
} }
} }
config = config_tmp; local_config
} } else {
config.clone()
};
if !config.version_meets_requirement(&mut error_summary) { match format_and_emit_report(Input::File(file), &local_config) {
break; Ok(summary) => error_summary.add(summary),
Err(_) => {
error_summary.add_operational_error();
break;
}
} }
options.clone().apply_to(&mut config);
error_summary.add(run(Input::File(file), &config));
} }
} }
checkstyle::output_footer(&mut out, write_mode)?; emit_post_matter(&config)?;
// If we were given a path via dump-minimal-config, output any options // If we were given a path via dump-minimal-config, output any options
// that were used during formatting as TOML. // that were used during formatting as TOML.
@ -395,7 +277,7 @@ fn format(
file.write_all(toml.as_bytes())?; file.write_all(toml.as_bytes())?;
} }
Ok((write_mode, error_summary)) Ok((config.write_mode(), error_summary))
} }
fn print_usage_to_stdout(opts: &Options, reason: &str) { fn print_usage_to_stdout(opts: &Options, reason: &str) {
@ -451,28 +333,6 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
return Ok(Operation::Version); return Ok(Operation::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
)))
};
// Read the config_path and convert to parent dir if a file is provided.
// If a config file cannot be found from the given path, return error.
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 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,
};
// If no path is given, we won't output a minimal config. // If no path is given, we won't output a minimal config.
let minimal_config_path = matches.opt_str("dump-minimal-config"); let minimal_config_path = matches.opt_str("dump-minimal-config");
@ -481,10 +341,7 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
let mut buffer = String::new(); let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?; io::stdin().read_to_string(&mut buffer)?;
return Ok(Operation::Stdin { return Ok(Operation::Stdin { input: buffer });
input: buffer,
config_path,
});
} }
let files: Vec<_> = matches let files: Vec<_> = matches
@ -500,7 +357,6 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
Ok(Operation::Format { Ok(Operation::Format {
files, files,
config_path,
minimal_config_path, minimal_config_path,
}) })
} }

View file

@ -11,33 +11,26 @@
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::Path; use std::path::Path;
use config::WriteMode;
use rustfmt_diff::{DiffLine, Mismatch}; use rustfmt_diff::{DiffLine, Mismatch};
pub fn output_header<T>(out: &mut T, mode: WriteMode) -> Result<(), io::Error> pub fn output_header<T>(out: &mut T) -> Result<(), io::Error>
where where
T: Write, T: Write,
{ {
if mode == WriteMode::Checkstyle { let mut xml_heading = String::new();
let mut xml_heading = String::new(); xml_heading.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
xml_heading.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); xml_heading.push_str("\n");
xml_heading.push_str("\n"); xml_heading.push_str("<checkstyle version=\"4.3\">");
xml_heading.push_str("<checkstyle version=\"4.3\">"); write!(out, "{}", xml_heading)
write!(out, "{}", xml_heading)?;
}
Ok(())
} }
pub fn output_footer<T>(out: &mut T, mode: WriteMode) -> Result<(), io::Error> pub fn output_footer<T>(out: &mut T) -> Result<(), io::Error>
where where
T: Write, T: Write,
{ {
if mode == WriteMode::Checkstyle { let mut xml_tail = String::new();
let mut xml_tail = String::new(); xml_tail.push_str("</checkstyle>\n");
xml_tail.push_str("</checkstyle>\n"); write!(out, "{}", xml_tail)
write!(out, "{}", xml_tail)?;
}
Ok(())
} }
pub fn output_checkstyle_file<T>( pub fn output_checkstyle_file<T>(

View file

@ -80,6 +80,7 @@ macro_rules! is_nightly_channel {
macro_rules! create_config { macro_rules! create_config {
($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
#[cfg(test)]
use std::collections::HashSet; use std::collections::HashSet;
use std::io::Write; use std::io::Write;
@ -150,7 +151,7 @@ macro_rules! create_config {
} }
impl Config { impl Config {
pub fn version_meets_requirement(&self, error_summary: &mut Summary) -> bool { pub(crate) fn version_meets_requirement(&self) -> bool {
if self.was_set().required_version() { if self.was_set().required_version() {
let version = env!("CARGO_PKG_VERSION"); let version = env!("CARGO_PKG_VERSION");
let required_version = self.required_version(); let required_version = self.required_version();
@ -160,7 +161,6 @@ macro_rules! create_config {
version, version,
required_version, required_version,
); );
error_summary.add_formatting_error();
return false; return false;
} }
} }
@ -207,7 +207,8 @@ macro_rules! create_config {
} }
/// Returns a hash set initialized with every user-facing config option name. /// Returns a hash set initialized with every user-facing config option name.
pub fn hash_set() -> HashSet<String> { #[cfg(test)]
pub(crate) fn hash_set() -> HashSet<String> {
let mut hash_set = HashSet::new(); let mut hash_set = HashSet::new();
$( $(
hash_set.insert(stringify!($i).to_owned()); hash_set.insert(stringify!($i).to_owned());
@ -215,7 +216,7 @@ macro_rules! create_config {
hash_set hash_set
} }
pub fn is_valid_name(name: &str) -> bool { pub(crate) fn is_valid_name(name: &str) -> bool {
match name { match name {
$( $(
stringify!($i) => true, stringify!($i) => true,
@ -224,7 +225,7 @@ macro_rules! create_config {
} }
} }
pub fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> { pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> {
let parsed: ::toml::Value = let parsed: ::toml::Value =
toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?; toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
let mut err: String = String::new(); let mut err: String = String::new();
@ -304,7 +305,7 @@ macro_rules! create_config {
/// ///
/// Return a `Config` if the config could be read and parsed from /// Return a `Config` if the config could be read and parsed from
/// the file, Error otherwise. /// the file, Error otherwise.
pub fn from_toml_path(file_path: &Path) -> Result<Config, Error> { pub(super) fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
let mut file = File::open(&file_path)?; let mut file = File::open(&file_path)?;
let mut toml = String::new(); let mut toml = String::new();
file.read_to_string(&mut toml)?; file.read_to_string(&mut toml)?;
@ -321,7 +322,7 @@ macro_rules! create_config {
/// ///
/// Returns the `Config` to use, and the path of the project file if there was /// Returns the `Config` to use, and the path of the project file if there was
/// one. /// one.
pub fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> { pub(super) fn from_resolved_toml_path(dir: &Path) -> Result<(Config, Option<PathBuf>), Error> {
/// Try to find a project file in the given directory and its parents. /// 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, /// Returns the path of a the nearest project file if one exists,

View file

@ -54,6 +54,7 @@ impl Range {
self.lo > self.hi self.lo > self.hi
} }
#[allow(dead_code)]
fn contains(self, other: Range) -> bool { fn contains(self, other: Range) -> bool {
if other.is_empty() { if other.is_empty() {
true true
@ -128,12 +129,12 @@ fn normalize_ranges(ranges: &mut HashMap<FileName, Vec<Range>>) {
impl FileLines { impl FileLines {
/// Creates a `FileLines` that contains all lines in all files. /// Creates a `FileLines` that contains all lines in all files.
pub fn all() -> FileLines { pub(crate) fn all() -> FileLines {
FileLines(None) FileLines(None)
} }
/// Returns true if this `FileLines` contains all lines in all files. /// Returns true if this `FileLines` contains all lines in all files.
pub fn is_all(&self) -> bool { pub(crate) fn is_all(&self) -> bool {
self.0.is_none() self.0.is_none()
} }
@ -166,22 +167,23 @@ impl FileLines {
} }
/// Returns true if `range` is fully contained in `self`. /// Returns true if `range` is fully contained in `self`.
pub fn contains(&self, range: &LineRange) -> bool { #[allow(dead_code)]
pub(crate) fn contains(&self, range: &LineRange) -> bool {
self.file_range_matches(range.file_name(), |r| r.contains(Range::from(range))) self.file_range_matches(range.file_name(), |r| r.contains(Range::from(range)))
} }
/// Returns true if any lines in `range` are in `self`. /// Returns true if any lines in `range` are in `self`.
pub fn intersects(&self, range: &LineRange) -> bool { pub(crate) fn intersects(&self, range: &LineRange) -> bool {
self.file_range_matches(range.file_name(), |r| r.intersects(Range::from(range))) self.file_range_matches(range.file_name(), |r| r.intersects(Range::from(range)))
} }
/// Returns true if `line` from `file_name` is in `self`. /// Returns true if `line` from `file_name` is in `self`.
pub fn contains_line(&self, file_name: &FileName, line: usize) -> bool { pub(crate) fn contains_line(&self, file_name: &FileName, line: usize) -> bool {
self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line) self.file_range_matches(file_name, |r| r.lo <= line && r.hi >= line)
} }
/// Returns true if any of the lines between `lo` and `hi` from `file_name` are in `self`. /// Returns true if any of the lines between `lo` and `hi` from `file_name` are in `self`.
pub fn intersects_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool { pub(crate) fn intersects_range(&self, file_name: &FileName, lo: usize, hi: usize) -> bool {
self.file_range_matches(file_name, |r| r.intersects(Range::new(lo, hi))) self.file_range_matches(file_name, |r| r.intersects(Range::new(lo, hi)))
} }
} }

View file

@ -82,8 +82,7 @@ impl TemplateParser {
/// ///
/// # Examples /// # Examples
/// ///
/// ``` /// ```ignore
/// # use rustfmt_nightly::config::license::TemplateParser;
/// assert_eq!( /// assert_eq!(
/// TemplateParser::parse( /// TemplateParser::parse(
/// r" /// r"

View file

@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
use regex::Regex;
use std::cell::Cell; use std::cell::Cell;
use std::default::Default; use std::default::Default;
use std::fs::File; use std::fs::File;
@ -15,23 +16,22 @@ use std::io::{Error, ErrorKind, Read};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{env, fs}; use std::{env, fs};
use regex::Regex; use {FmtError, FmtResult};
#[macro_use]
mod config_type;
#[macro_use]
mod options;
pub mod file_lines;
pub mod license;
pub mod lists;
pub mod summary;
use config::config_type::ConfigType; use config::config_type::ConfigType;
use config::file_lines::FileLines; use config::file_lines::FileLines;
pub use config::lists::*; pub use config::lists::*;
pub use config::options::*; pub use config::options::*;
use config::summary::Summary;
#[macro_use]
pub mod config_type;
#[macro_use]
pub mod options;
pub mod file_lines;
pub mod license;
pub mod lists;
pub mod summary;
/// This macro defines configuration options used in rustfmt. Each option /// This macro defines configuration options used in rustfmt. Each option
/// is defined as follows: /// is defined as follows:
@ -151,10 +151,37 @@ create_config! {
"'small' heuristic values"; "'small' heuristic values";
} }
/// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir` pub fn load_config(
/// file_path: Option<&Path>,
/// Return the path if a config file exists, empty if no file exists, and Error for IO errors options: Option<&CliOptions>,
pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> { ) -> FmtResult<(Config, Option<PathBuf>)> {
let over_ride = match options {
Some(opts) => config_path(opts)?,
None => None,
};
let result = if let Some(over_ride) = over_ride {
Config::from_toml_path(over_ride.as_ref())
.map(|p| (p, Some(over_ride.to_owned())))
.map_err(FmtError::from)
} else if let Some(file_path) = file_path {
Config::from_resolved_toml_path(file_path).map_err(FmtError::from)
} else {
Ok((Config::default(), None))
};
result.map(|(mut c, p)| {
if let Some(options) = options {
options.clone().apply_to(&mut c);
}
(c, p)
})
}
// Check for the presence of known config file names (`rustfmt.toml, `.rustfmt.toml`) in `dir`
//
// Return the path if a config file exists, empty if no file exists, and Error for IO errors
fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"]; const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"];
for config_file_name in &CONFIG_FILE_NAMES { for config_file_name in &CONFIG_FILE_NAMES {
let config_file = dir.join(config_file_name); let config_file = dir.join(config_file_name);
@ -175,6 +202,30 @@ pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
Ok(None) Ok(None)
} }
fn config_path(options: &CliOptions) -> FmtResult<Option<PathBuf>> {
let config_path_not_found = |path: &str| -> FmtResult<Option<PathBuf>> {
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.
// If a config file cannot be found from the given path, return error.
match options.config_path {
Some(ref path) if !path.exists() => config_path_not_found(path.to_str().unwrap()),
Some(ref path) if path.is_dir() => {
let config_file_path = get_toml_path(path)?;
if config_file_path.is_some() {
Ok(config_file_path)
} else {
config_path_not_found(path.to_str().unwrap())
}
}
ref path => Ok(path.to_owned()),
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::Config; use super::Config;

View file

@ -11,10 +11,15 @@
use syntax::codemap::FileName; use syntax::codemap::FileName;
use config::config_type::ConfigType; use config::config_type::ConfigType;
use config::file_lines::FileLines;
use config::lists::*; use config::lists::*;
use config::Config;
use {FmtError, FmtResult, WRITE_MODE_LIST};
use getopts::Matches;
use std::collections::HashSet; use std::collections::HashSet;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
/// Macro for deriving implementations of Serialize/Deserialize for enums /// Macro for deriving implementations of Serialize/Deserialize for enums
#[macro_export] #[macro_export]
@ -301,3 +306,101 @@ impl ::std::str::FromStr for IgnoreList {
Err("IgnoreList is not parsable") Err("IgnoreList is not parsable")
} }
} }
/// Parsed command line options.
#[derive(Clone, Debug, Default)]
pub struct CliOptions {
skip_children: Option<bool>,
verbose: bool,
verbose_diff: bool,
pub(super) config_path: Option<PathBuf>,
write_mode: Option<WriteMode>,
color: Option<Color>,
file_lines: FileLines, // Default is all lines in all files.
unstable_features: bool,
error_on_unformatted: Option<bool>,
}
impl CliOptions {
pub fn from_matches(matches: &Matches) -> FmtResult<CliOptions> {
let mut options = CliOptions::default();
options.verbose = matches.opt_present("verbose");
options.verbose_diff = matches.opt_present("verbose-diff");
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;
}
options.config_path = matches.opt_str("config-path").map(PathBuf::from);
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: {}, expected one of {}",
write_mode, WRITE_MODE_LIST
)));
}
}
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))),
}
}
if let Some(ref file_lines) = matches.opt_str("file-lines") {
options.file_lines = file_lines.parse()?;
}
if matches.opt_present("skip-children") {
options.skip_children = Some(true);
}
if matches.opt_present("error-on-unformatted") {
options.error_on_unformatted = Some(true);
}
Ok(options)
}
pub fn apply_to(self, config: &mut Config) {
config.set().verbose(self.verbose);
config.set().verbose_diff(self.verbose_diff);
config.set().file_lines(self.file_lines);
config.set().unstable_features(self.unstable_features);
if let Some(skip_children) = self.skip_children {
config.set().skip_children(skip_children);
}
if let Some(error_on_unformatted) = self.error_on_unformatted {
config.set().error_on_unformatted(error_on_unformatted);
}
if let Some(write_mode) = self.write_mode {
config.set().write_mode(write_mode);
}
if let Some(color) = self.color {
config.set().color(color);
}
}
pub fn verify_file_lines(&self, files: &[PathBuf]) {
for f in self.file_lines.files() {
match *f {
FileName::Real(ref f) if files.contains(f) => {}
FileName::Real(_) => {
eprintln!("Warning: Extra file listed in file_lines option '{}'", f)
}
_ => eprintln!("Warning: Not a file '{}'", f),
}
}
}
}

View file

@ -31,16 +31,16 @@ pub struct Summary {
} }
impl Summary { impl Summary {
pub fn mark_parse_time(&mut self) { pub(crate) fn mark_parse_time(&mut self) {
self.timer = self.timer.done_parsing(); self.timer = self.timer.done_parsing();
} }
pub fn mark_format_time(&mut self) { pub(crate) fn mark_format_time(&mut self) {
self.timer = self.timer.done_formatting(); self.timer = self.timer.done_formatting();
} }
/// Returns the time it took to parse the source files in nanoseconds. /// Returns the time it took to parse the source files in nanoseconds.
pub fn get_parse_time(&self) -> Option<Duration> { pub(crate) fn get_parse_time(&self) -> Option<Duration> {
match self.timer { match self.timer {
Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => { Timer::DoneParsing(init, parse_time) | Timer::DoneFormatting(init, parse_time, _) => {
// This should never underflow since `Instant::now()` guarantees monotonicity. // This should never underflow since `Instant::now()` guarantees monotonicity.
@ -52,7 +52,7 @@ impl Summary {
/// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is /// Returns the time it took to go from the parsed AST to the formatted output. Parsing time is
/// not included. /// not included.
pub fn get_format_time(&self) -> Option<Duration> { pub(crate) fn get_format_time(&self) -> Option<Duration> {
match self.timer { match self.timer {
Timer::DoneFormatting(_init, parse_time, format_time) => { Timer::DoneFormatting(_init, parse_time, format_time) => {
Some(format_time.duration_since(parse_time)) Some(format_time.duration_since(parse_time))
@ -77,15 +77,15 @@ impl Summary {
self.has_operational_errors = true; self.has_operational_errors = true;
} }
pub fn add_parsing_error(&mut self) { pub(crate) fn add_parsing_error(&mut self) {
self.has_parsing_errors = true; self.has_parsing_errors = true;
} }
pub fn add_formatting_error(&mut self) { pub(crate) fn add_formatting_error(&mut self) {
self.has_formatting_errors = true; self.has_formatting_errors = true;
} }
pub fn add_diff(&mut self) { pub(crate) fn add_diff(&mut self) {
self.has_diff = true; self.has_diff = true;
} }

View file

@ -14,11 +14,12 @@ use std::fs::{self, File};
use std::io::{self, BufWriter, Read, Write}; use std::io::{self, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use checkstyle::{output_checkstyle_file, output_footer, output_header}; use checkstyle::output_checkstyle_file;
use config::{Config, NewlineStyle, WriteMode}; use config::{Config, NewlineStyle, WriteMode};
use rustfmt_diff::{make_diff, output_modified, print_diff, Mismatch}; use rustfmt_diff::{make_diff, output_modified, print_diff, Mismatch};
use syntax::codemap::FileName; use syntax::codemap::FileName;
#[cfg(test)]
use FileRecord; use FileRecord;
// Append a newline to the end of each file. // Append a newline to the end of each file.
@ -26,7 +27,8 @@ pub fn append_newline(s: &mut String) {
s.push_str("\n"); s.push_str("\n");
} }
pub fn write_all_files<T>( #[cfg(test)]
pub(crate) fn write_all_files<T>(
file_map: &[FileRecord], file_map: &[FileRecord],
out: &mut T, out: &mut T,
config: &Config, config: &Config,
@ -34,11 +36,15 @@ pub fn write_all_files<T>(
where where
T: Write, T: Write,
{ {
output_header(out, config.write_mode()).ok(); if config.write_mode() == WriteMode::Checkstyle {
::checkstyle::output_header(out)?;
}
for &(ref filename, ref text) in file_map { for &(ref filename, ref text) in file_map {
write_file(text, filename, out, config)?; write_file(text, filename, out, config)?;
} }
output_footer(out, config.write_mode()).ok(); if config.write_mode() == WriteMode::Checkstyle {
::checkstyle::output_footer(out)?;
}
Ok(()) Ok(())
} }

View file

@ -21,7 +21,7 @@ use std::str::FromStr;
use getopts::{Matches, Options}; use getopts::{Matches, Options};
use rustfmt::{config, run, Input}; use rustfmt::{format_and_emit_report, load_config, Input};
fn prune_files(files: Vec<&str>) -> Vec<&str> { fn prune_files(files: Vec<&str>) -> Vec<&str> {
let prefixes: Vec<_> = files let prefixes: Vec<_> = files
@ -68,12 +68,11 @@ fn get_files(input: &str) -> Vec<&str> {
} }
fn fmt_files(files: &[&str]) -> i32 { fn fmt_files(files: &[&str]) -> i32 {
let (config, _) = config::Config::from_resolved_toml_path(Path::new(".")) let (config, _) = load_config(Some(Path::new(".")), None).expect("couldn't load config");
.unwrap_or_else(|_| (config::Config::default(), None));
let mut exit_code = 0; let mut exit_code = 0;
for file in files { for file in files {
let summary = run(Input::File(PathBuf::from(file)), &config); let summary = format_and_emit_report(Input::File(PathBuf::from(file)), &config).unwrap();
if !summary.has_no_errors() { if !summary.has_no_errors() {
exit_code = 1; exit_code = 1;
} }

View file

@ -19,7 +19,11 @@
#[macro_use] #[macro_use]
extern crate derive_new; extern crate derive_new;
extern crate diff; extern crate diff;
extern crate getopts;
extern crate itertools; extern crate itertools;
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate regex; extern crate regex;
@ -33,6 +37,7 @@ extern crate toml;
extern crate unicode_segmentation; extern crate unicode_segmentation;
use std::collections::HashMap; use std::collections::HashMap;
use std::error;
use std::fmt; use std::fmt;
use std::io::{self, stdout, BufRead, Write}; use std::io::{self, stdout, BufRead, Write};
use std::panic::{catch_unwind, AssertUnwindSafe}; use std::panic::{catch_unwind, AssertUnwindSafe};
@ -53,21 +58,28 @@ use shape::Indent;
use utils::use_colored_tty; use utils::use_colored_tty;
use visitor::{FmtVisitor, SnippetProvider}; use visitor::{FmtVisitor, SnippetProvider};
pub use config::options::CliOptions;
pub use config::summary::Summary; pub use config::summary::Summary;
pub use config::Config; pub use config::{file_lines, load_config, Config, WriteMode};
pub type FmtError = Box<error::Error + Send + Sync>;
pub type FmtResult<T> = std::result::Result<T, FmtError>;
pub const WRITE_MODE_LIST: &str =
"[replace|overwrite|display|plain|diff|coverage|checkstyle|check]";
#[macro_use] #[macro_use]
mod utils; mod utils;
mod attr; mod attr;
mod chains; mod chains;
pub mod checkstyle; pub(crate) mod checkstyle;
mod closures; mod closures;
pub mod codemap; pub(crate) mod codemap;
mod comment; mod comment;
pub mod config; pub(crate) mod config;
mod expr; mod expr;
pub mod filemap; pub(crate) mod filemap;
mod imports; mod imports;
mod issues; mod issues;
mod items; mod items;
@ -75,25 +87,27 @@ mod lists;
mod macros; mod macros;
mod matches; mod matches;
mod missed_spans; mod missed_spans;
pub mod modules; pub(crate) mod modules;
mod overflow; mod overflow;
mod patterns; mod patterns;
mod reorder; mod reorder;
mod rewrite; mod rewrite;
pub mod rustfmt_diff; pub(crate) mod rustfmt_diff;
mod shape; mod shape;
mod spanned; mod spanned;
mod string; mod string;
#[cfg(test)]
mod test;
mod types; mod types;
mod vertical; mod vertical;
pub mod visitor; pub(crate) mod visitor;
const STDIN: &str = "<stdin>"; const STDIN: &str = "<stdin>";
// A map of the files of a crate, with their new content // A map of the files of a crate, with their new content
pub type FileMap = Vec<FileRecord>; pub(crate) type FileMap = Vec<FileRecord>;
pub type FileRecord = (FileName, String); pub(crate) type FileRecord = (FileName, String);
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ErrorKind { pub enum ErrorKind {
@ -123,7 +137,7 @@ impl fmt::Display for ErrorKind {
} }
// Formatting errors that are identified *after* rustfmt has run. // Formatting errors that are identified *after* rustfmt has run.
pub struct FormattingError { struct FormattingError {
line: usize, line: usize,
kind: ErrorKind, kind: ErrorKind,
is_comment: bool, is_comment: bool,
@ -150,7 +164,7 @@ impl FormattingError {
} }
// (space, target) // (space, target)
pub fn format_len(&self) -> (usize, usize) { fn format_len(&self) -> (usize, usize) {
match self.kind { match self.kind {
ErrorKind::LineOverflow(found, max) => (max, found - max), ErrorKind::LineOverflow(found, max) => (max, found - max),
ErrorKind::TrailingWhitespace => { ErrorKind::TrailingWhitespace => {
@ -180,18 +194,18 @@ impl FormatReport {
} }
} }
pub fn warning_count(&self) -> usize { fn warning_count(&self) -> usize {
self.file_error_map self.file_error_map
.iter() .iter()
.map(|(_, errors)| errors.len()) .map(|(_, errors)| errors.len())
.sum() .sum()
} }
pub fn has_warnings(&self) -> bool { fn has_warnings(&self) -> bool {
self.warning_count() > 0 self.warning_count() > 0
} }
pub fn print_warnings_fancy( fn print_warnings_fancy(
&self, &self,
mut t: Box<term::Terminal<Output = io::Stderr>>, mut t: Box<term::Terminal<Output = io::Stderr>>,
) -> Result<(), term::Error> { ) -> Result<(), term::Error> {
@ -881,7 +895,10 @@ pub enum Input {
Text(String), Text(String),
} }
pub fn run(input: Input, config: &Config) -> Summary { pub fn format_and_emit_report(input: Input, config: &Config) -> FmtResult<Summary> {
if !config.version_meets_requirement() {
return Err(FmtError::from("Version mismatch"));
}
let out = &mut stdout(); let out = &mut stdout();
match format_input(input, config, Some(out)) { match format_input(input, config, Some(out)) {
Ok((summary, _, report)) => { Ok((summary, _, report)) => {
@ -896,22 +913,38 @@ pub fn run(input: Input, config: &Config) -> Summary {
Err(..) => panic!("Unable to write to stderr: {}", report), Err(..) => panic!("Unable to write to stderr: {}", report),
} }
} }
_ => msg!("{}", report), _ => eprintln!("{}", report),
} }
} }
summary Ok(summary)
} }
Err((msg, mut summary)) => { Err((msg, mut summary)) => {
msg!("Error writing files: {}", msg); eprintln!("Error writing files: {}", msg);
summary.add_operational_error(); summary.add_operational_error();
summary Ok(summary)
} }
} }
} }
pub fn emit_pre_matter(config: &Config) -> FmtResult<()> {
if config.write_mode() == WriteMode::Checkstyle {
let mut out = &mut stdout();
checkstyle::output_header(&mut out)?;
}
Ok(())
}
pub fn emit_post_matter(config: &Config) -> FmtResult<()> {
if config.write_mode() == WriteMode::Checkstyle {
let mut out = &mut stdout();
checkstyle::output_footer(&mut out)?;
}
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod test { mod unit_tests {
use super::{format_code_block, format_snippet, Config}; use super::{format_code_block, format_snippet, Config};
#[test] #[test]

View file

@ -197,14 +197,6 @@ impl Shape {
} }
} }
pub fn offset(width: usize, indent: Indent, offset: usize) -> Shape {
Shape {
width,
indent,
offset,
}
}
pub fn visual_indent(&self, extra_width: usize) -> Shape { pub fn visual_indent(&self, extra_width: usize) -> Shape {
let alignment = self.offset + extra_width; let alignment = self.offset + extra_width;
Shape { Shape {

View file

@ -9,13 +9,6 @@
// except according to those terms. // except according to those terms.
extern crate assert_cli; extern crate assert_cli;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate regex;
extern crate rustfmt_nightly as rustfmt;
extern crate term;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
@ -24,11 +17,11 @@ use std::iter::{Enumerate, Peekable};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::Chars; use std::str::Chars;
use rustfmt::config::summary::Summary; use config::summary::Summary;
use rustfmt::config::{Color, Config, ReportTactic}; use config::{Color, Config, ReportTactic};
use rustfmt::filemap::write_system_newlines; use filemap::write_system_newlines;
use rustfmt::rustfmt_diff::*; use rustfmt_diff::*;
use rustfmt::*; use *;
const DIFF_CONTEXT_SIZE: usize = 3; const DIFF_CONTEXT_SIZE: usize = 3;
const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md"; const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";

View file

@ -255,15 +255,6 @@ pub fn count_newlines(input: &str) -> usize {
input.as_bytes().iter().filter(|&&c| c == b'\n').count() input.as_bytes().iter().filter(|&&c| c == b'\n').count()
} }
macro_rules! msg {
($($arg:tt)*) => (
match writeln!(&mut ::std::io::stderr(), $($arg)* ) {
Ok(_) => {},
Err(x) => panic!("Unable to write to stderr: {}", x),
}
)
}
// For format_missing and last_pos, need to use the source callsite (if applicable). // For format_missing and last_pos, need to use the source callsite (if applicable).
// Required as generated code spans aren't guaranteed to follow on from the last span. // Required as generated code spans aren't guaranteed to follow on from the last span.
macro_rules! source { macro_rules! source {