extern crate toml;
+use std::{env, fs};
+use std::cell::Cell;
+use std::fs::File;
+use std::io::{Error, ErrorKind, Read};
+use std::path::{Path, PathBuf};
+
use file_lines::FileLines;
-use lists::{SeparatorTactic, ListTactic};
+use lists::{ListTactic, SeparatorPlace, SeparatorTactic};
+use Summary;
macro_rules! configuration_option_enum{
($e:ident: $( $x:ident ),+ $(,)*) => {
$( $x ),+
}
- impl_enum_decodable!($e, $( $x ),+);
+ impl_enum_serialize_and_deserialize!($e, $( $x ),+);
}
}
configuration_option_enum! { Style:
Rfc, // Follow the style RFCs style.
- Default, // Follow the traditional Rustfmt style.
+ Legacy, // Follow the traditional Rustfmt style.
}
configuration_option_enum! { NewlineStyle:
pub fn to_list_tactic(self) -> ListTactic {
match self {
Density::Compressed => ListTactic::Mixed,
- Density::Tall |
- Density::CompressedIfEmpty => ListTactic::HorizontalVertical,
+ Density::Tall | Density::CompressedIfEmpty => ListTactic::HorizontalVertical,
Density::Vertical => ListTactic::Vertical,
}
}
macro_rules! create_config {
($($i:ident: $ty:ty, $def:expr, $( $dstring:expr ),+ );+ $(;)*) => (
- #[derive(Deserialize, Clone)]
+ #[derive(Clone)]
pub struct Config {
- $(pub $i: $ty),+
+ // For each config item, we store a bool indicating whether it has
+ // been accessed and the value, and a bool whether the option was
+ // manually initialised, or taken from the default,
+ $($i: (Cell<bool>, bool, $ty)),+
}
// Just like the Config struct but with each property wrapped
// as Option<T>. This is used to parse a rustfmt.toml that doesn't
// specity all properties of `Config`.
- // We first parse into `ParsedConfig`, then create a default `Config`
- // and overwrite the properties with corresponding values from `ParsedConfig`
- #[derive(Deserialize, Clone)]
- pub struct ParsedConfig {
+ // We first parse into `PartialConfig`, then create a default `Config`
+ // and overwrite the properties with corresponding values from `PartialConfig`.
+ #[derive(Deserialize, Serialize, Clone)]
+ pub struct PartialConfig {
$(pub $i: Option<$ty>),+
}
+ impl PartialConfig {
+ pub fn to_toml(&self) -> Result<String, String> {
+ // file_lines can't be specified in TOML
+ let mut cloned = self.clone();
+ cloned.file_lines = None;
+
+ toml::to_string(&cloned)
+ .map_err(|e| format!("Could not output config: {}", e.to_string()))
+ }
+ }
+
+ // Macro hygiene won't allow us to make `set_$i()` methods on Config
+ // for each item, so this struct is used to give the API to set values:
+ // `config.get().option(false)`. It's pretty ugly. Consider replacing
+ // with `config.set_option(false)` if we ever get a stable/usable
+ // `concat_idents!()`.
+ pub struct ConfigSetter<'a>(&'a mut Config);
+
+ impl<'a> ConfigSetter<'a> {
+ $(
+ pub fn $i(&mut self, value: $ty) {
+ (self.0).$i.2 = value;
+ }
+ )+
+ }
+
+ // Query each option, returns true if the user set the option, false if
+ // a default was used.
+ pub struct ConfigWasSet<'a>(&'a Config);
+
+ impl<'a> ConfigWasSet<'a> {
+ $(
+ pub fn $i(&self) -> bool {
+ (self.0).$i.1
+ }
+ )+
+ }
+
impl Config {
+ pub fn version_meets_requirement(&self, error_summary: &mut Summary) -> bool {
+ if self.was_set().required_version() {
+ let version = env!("CARGO_PKG_VERSION");
+ let required_version = self.required_version();
+ if version != required_version {
+ println!(
+ "Error: rustfmt version ({}) doesn't match the required version ({})",
+ version,
+ required_version,
+ );
+ error_summary.add_formatting_error();
+ return false;
+ }
+ }
+
+ true
+ }
- fn fill_from_parsed_config(mut self, parsed: ParsedConfig) -> Config {
+ $(
+ pub fn $i(&self) -> $ty {
+ self.$i.0.set(true);
+ self.$i.2.clone()
+ }
+ )+
+
+ pub fn set<'a>(&'a mut self) -> ConfigSetter<'a> {
+ ConfigSetter(self)
+ }
+
+ pub fn was_set<'a>(&'a self) -> ConfigWasSet<'a> {
+ ConfigWasSet(self)
+ }
+
+ fn fill_from_parsed_config(mut self, parsed: PartialConfig) -> Config {
$(
if let Some(val) = parsed.$i {
- self.$i = val;
+ self.$i.1 = true;
+ self.$i.2 = val;
}
)+
self
let table = parsed
.as_table()
.ok_or(String::from("Parsed config was not table"))?;
- for (key, _) in table {
+ for key in table.keys() {
match &**key {
$(
stringify!($i) => (),
}
}
- pub fn override_value(&mut self, key: &str, val: &str) {
+ pub fn used_options(&self) -> PartialConfig {
+ PartialConfig {
+ $(
+ $i: if self.$i.0.get() {
+ Some(self.$i.2.clone())
+ } else {
+ None
+ },
+ )+
+ }
+ }
+
+ pub fn all_options(&self) -> PartialConfig {
+ PartialConfig {
+ $(
+ $i: Some(self.$i.2.clone()),
+ )+
+ }
+ }
+
+ pub fn override_value(&mut self, key: &str, val: &str)
+ {
match key {
$(
stringify!($i) => {
- self.$i = val.parse::<$ty>()
+ self.$i.2 = val.parse::<$ty>()
.expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
stringify!($i),
val,
}
}
+ /// Construct a `Config` from the toml file specified at `file_path`.
+ ///
+ /// This method only looks at the provided path, for a method that
+ /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
+ ///
+ /// Return a `Config` if the config could be read and parsed from
+ /// the file, Error otherwise.
+ pub fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
+ let mut file = File::open(&file_path)?;
+ let mut toml = String::new();
+ file.read_to_string(&mut toml)?;
+ Config::from_toml(&toml).map_err(|err| Error::new(ErrorKind::InvalidData, err))
+ }
+
+ /// Resolve the config for input in `dir`.
+ ///
+ /// Searches for `rustfmt.toml` beginning with `dir`, and
+ /// recursively checking parents of `dir` if no config file is found.
+ /// If no config file exists in `dir` or in any parent, a
+ /// default `Config` will be returned (and the returned path will be empty).
+ ///
+ /// Returns the `Config` to use, and the path of the project file if there was
+ /// one.
+ pub 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.
+ /// Returns the path of a the nearest project file if one exists,
+ /// or `None` if no project file was found.
+ fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
+ let mut current = if dir.is_relative() {
+ env::current_dir()?.join(dir)
+ } else {
+ dir.to_path_buf()
+ };
+
+ current = fs::canonicalize(current)?;
+
+ loop {
+ match get_toml_path(¤t) {
+ Ok(Some(path)) => return Ok(Some(path)),
+ Err(e) => return Err(e),
+ _ => ()
+ }
+
+ // If the current directory has no parent, we're done searching.
+ if !current.pop() {
+ return Ok(None);
+ }
+ }
+ }
+
+ match resolve_project_file(dir)? {
+ None => Ok((Config::default(), None)),
+ Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
+ }
+ }
+
+
pub fn print_docs() {
use std::cmp;
let max = 0;
fn default() -> Config {
Config {
$(
- $i: $def,
+ $i: (Cell::new(false), false, $def),
)+
}
}
)
}
+/// 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
+pub fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
+ const CONFIG_FILE_NAMES: [&'static str; 2] = [".rustfmt.toml", "rustfmt.toml"];
+ for config_file_name in &CONFIG_FILE_NAMES {
+ let config_file = dir.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(e);
+ },
+ _ => {}
+ }
+ }
+ Ok(None)
+}
+
+
+
create_config! {
verbose: bool, false, "Use verbose output";
disable_all_formatting: bool, false, "Don't reformat anything";
via the --file-lines option";
max_width: usize, 100, "Maximum width of each line";
error_on_line_overflow: bool, true, "Error if unable to get all lines within max_width";
+ error_on_line_overflow_comments: bool, true, "Error if unable to get comments within max_width";
tab_spaces: usize, 4, "Number of spaces per tab";
fn_call_width: usize, 60,
"Maximum width of the args of a function call before falling back to vertical formatting";
newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings";
fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions";
item_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for structs and enums";
- control_style: Style, Style::Default, "Indent style for control flow statements";
+ control_style: Style, Style::Rfc, "Indent style for control flow statements";
control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine,
"Brace style for control flow constructs";
impl_empty_single_line: bool, true, "Put empty-body implementations on a single line";
trailing_comma: SeparatorTactic, SeparatorTactic::Vertical,
"How to handle trailing commas for lists";
+ trailing_semicolon: bool, true, "Add trailing semicolon after break, continue and return";
fn_empty_single_line: bool, true, "Put empty-body functions on a single line";
fn_single_line: bool, false, "Put single-expression functions on a single line";
fn_return_indent: ReturnIndent, ReturnIndent::WithArgs,
"Location of return type in function declaration";
- fn_args_paren_newline: bool, true, "If function argument parenthesis goes on a newline";
+ fn_args_paren_newline: bool, false, "If function argument parenthesis goes on a newline";
fn_args_density: Density, Density::Tall, "Argument density in functions";
- fn_args_layout: IndentStyle, IndentStyle::Visual,
+ fn_args_layout: IndentStyle, IndentStyle::Block,
"Layout of function arguments and tuple structs";
- array_layout: IndentStyle, IndentStyle::Visual, "Indent on arrays";
+ array_layout: IndentStyle, IndentStyle::Block, "Indent on arrays";
array_width: usize, 60,
"Maximum width of an array literal before falling back to vertical formatting";
+ array_horizontal_layout_threshold: usize, 0,
+ "How many elements array must have before rustfmt uses horizontal layout.";
type_punctuation_density: TypeDensity, TypeDensity::Wide,
"Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
- where_style: Style, Style::Default, "Overall strategy for where clauses";
+ where_style: Style, Style::Rfc, "Overall strategy for where clauses";
// TODO:
// 1. Should we at least try to put the where clause on the same line as the rest of the
// function decl?
// 2. Currently options `Tall` and `Vertical` produce the same output.
- where_density: Density, Density::CompressedIfEmpty, "Density of a where clause";
+ where_density: Density, Density::Vertical, "Density of a where clause";
where_layout: ListTactic, ListTactic::Vertical, "Element layout inside a where clause";
where_pred_indent: IndentStyle, IndentStyle::Visual,
"Indentation style of a where predicate";
- generics_indent: IndentStyle, IndentStyle::Visual, "Indentation of generics";
+ generics_indent: IndentStyle, IndentStyle::Block, "Indentation of generics";
struct_lit_style: IndentStyle, IndentStyle::Block, "Style of struct definition";
struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle,
"Multiline style on literal structs";
- fn_call_style: IndentStyle, IndentStyle::Visual, "Indentation for function calls, etc.";
+ fn_call_style: IndentStyle, IndentStyle::Block, "Indentation for function calls, etc.";
report_todo: ReportTactic, ReportTactic::Never,
"Report all, none or unnumbered occurrences of TODO in source file comments";
report_fixme: ReportTactic, ReportTactic::Never,
"Report all, none or unnumbered occurrences of FIXME in source file comments";
chain_indent: IndentStyle, IndentStyle::Block, "Indentation of chain";
chain_one_line_max: usize, 60, "Maximum length of a chain to fit on a single line";
+ chain_split_single_child: bool, false, "Split a chain with a single child if its length \
+ exceeds `chain_one_line_max`";
+ imports_indent: IndentStyle, IndentStyle::Visual, "Indent of imports";
+ imports_layout: ListTactic, ListTactic::Mixed, "Item layout inside a import block";
+ reorder_extern_crates: bool, true, "Reorder extern crate statements alphabetically";
+ reorder_extern_crates_in_group: bool, true, "Reorder extern crate statements in group";
reorder_imports: bool, false, "Reorder import statements alphabetically";
- reorder_imported_names: bool, false,
+ reorder_imports_in_group: bool, false, "Reorder import statements in group";
+ reorder_imported_names: bool, true,
"Reorder lists of names in import statements alphabetically";
single_line_if_else_max_width: usize, 50, "Maximum line length for single line if-else \
expressions. A value of zero means always break \
wrap_comments: bool, false, "Break comments to fit on the line";
comment_width: usize, 80, "Maximum length of comments. No effect unless wrap_comments = true";
normalize_comments: bool, false, "Convert /* */ comments to // comments where possible";
- wrap_match_arms: bool, true, "Wrap multiline match arms in blocks";
+ wrap_match_arms: bool, true, "Wrap the body of arms in blocks when it does not fit on \
+ the same line with the pattern of arms";
match_block_trailing_comma: bool, false,
"Put a trailing comma after a block based match arm (non-block arms are not affected)";
+ match_arm_forces_newline: bool, false,
+ "Force match arm bodies to be in a new lines";
indent_match_arms: bool, true, "Indent match arms instead of keeping them at the same \
indentation level as the match keyword";
+ match_pattern_separator_break_point: SeparatorPlace, SeparatorPlace::Back,
+ "Put a match sub-patterns' separator in front or back.";
closure_block_indent_threshold: isize, 7, "How many lines a closure must have before it is \
block indented. -1 means never use block indent.";
space_before_type_annotation: bool, false,
"Leave a space before the colon in a type annotation";
space_after_type_annotation_colon: bool, true,
"Leave a space after the colon in a type annotation";
+ space_before_struct_lit_field_colon: bool, false,
+ "Leave a space before the colon in a struct literal field";
+ space_after_struct_lit_field_colon: bool, true,
+ "Leave a space after the colon in a struct literal field";
space_before_bound: bool, false, "Leave a space before the colon in a trait or lifetime bound";
space_after_bound_colon: bool, true,
"Leave a space after the colon in a trait or lifetime bound";
spaces_within_square_brackets: bool, false, "Put spaces within non-empty square brackets";
spaces_within_parens: bool, false, "Put spaces within non-empty parentheses";
use_try_shorthand: bool, false, "Replace uses of the try! macro by the ? shorthand";
- write_mode: WriteMode, WriteMode::Replace,
- "What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage";
- condense_wildcard_suffices: bool, false, "Replace strings of _ wildcards by a single .. in \
- tuple patterns"
+ write_mode: WriteMode, WriteMode::Overwrite,
+ "What Write Mode to use when none is supplied: \
+ Replace, Overwrite, Display, Plain, Diff, Coverage";
+ condense_wildcard_suffixes: bool, false, "Replace strings of _ wildcards by a single .. in \
+ tuple patterns";
+ combine_control_expr: bool, true, "Combine control expressions with function calls.";
+ struct_field_align_threshold: usize, 0, "Align struct fields if their diffs fits within \
+ threshold.";
+ remove_blank_lines_at_start_or_end_of_block: bool, true,
+ "Remove blank lines at start or end of a block";
+ attributes_on_same_line_as_field: bool, true,
+ "Try to put attributes on the same line as fields.";
+ attributes_on_same_line_as_variant: bool, true,
+ "Try to put attributes on the same line as variants in enum declarations.";
+ multiline_closure_forces_block: bool, false,
+ "Force multiline closure bodies to be wrapped in a block";
+ multiline_match_arm_forces_block: bool, false,
+ "Force multiline match arm bodies to be wrapped in a block";
+ merge_derives: bool, true, "Merge multiple `#[derive(...)]` into a single one";
+ binop_separator: SeparatorPlace, SeparatorPlace::Front,
+ "Where to put a binary operator when a binary expression goes multiline.";
+ required_version: String, env!("CARGO_PKG_VERSION").to_owned(),
+ "Require a specific version of rustfmt."
+}
+
+#[cfg(test)]
+mod test {
+ use super::Config;
+
+ #[test]
+ fn test_config_set() {
+ let mut config = Config::default();
+ config.set().verbose(false);
+ assert_eq!(config.verbose(), false);
+ config.set().verbose(true);
+ assert_eq!(config.verbose(), true);
+ }
+
+ #[test]
+ fn test_config_used_to_toml() {
+ let config = Config::default();
+
+ let verbose = config.verbose();
+ let skip_children = config.skip_children();
+
+ let used_options = config.used_options();
+ let toml = used_options.to_toml().unwrap();
+ assert_eq!(
+ toml,
+ format!("verbose = {}\nskip_children = {}\n", verbose, skip_children)
+ );
+ }
+
+ #[test]
+ fn test_was_set() {
+ let config = Config::from_toml("hard_tabs = true").unwrap();
+
+ assert_eq!(config.was_set().hard_tabs(), true);
+ assert_eq!(config.was_set().verbose(), false);
+ }
}