Skip to content

Expose methods to locate and load config #1613

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 13 additions & 83 deletions src/bin/rustfmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ extern crate getopts;

use rustfmt::{run, Input, Summary};
use rustfmt::file_lines::FileLines;
use rustfmt::config::{Config, WriteMode};
use rustfmt::config::{Config, WriteMode, get_toml_path};

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;

Expand Down Expand Up @@ -95,78 +95,16 @@ impl CliOptions {
}
}

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 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>)> {

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(|e| FmtError::from(e))
}

fn make_opts() -> Options {
Expand Down Expand Up @@ -261,16 +199,13 @@ fn execute(opts: &Options) -> FmtResult<Summary> {
}

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());
}
}
Expand All @@ -285,8 +220,9 @@ fn execute(opts: &Options) -> FmtResult<Summary> {
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 {}",
Expand Down Expand Up @@ -391,13 +327,7 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
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 {
Expand Down
89 changes: 89 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
extern crate toml;

use std::cell::Cell;
use std::fs;
use std::fs::File;
use std::env;
use std::io::{Error, ErrorKind, Read};
use std::path::{Path, PathBuf};

use file_lines::FileLines;
use lists::{SeparatorTactic, ListTactic};
Expand Down Expand Up @@ -347,6 +352,64 @@ macro_rules! create_config {
}
}

/// 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(&current) {
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;
Expand Down Expand Up @@ -389,6 +452,32 @@ macro_rules! create_config {
)
}

/// 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";
Expand Down