mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:04:51 +00:00
Simplify some logic around configuration detection (#1197)
This commit is contained in:
parent
73794fc299
commit
ac6fa1dc88
7 changed files with 147 additions and 158 deletions
|
@ -198,6 +198,7 @@ pub struct Arguments {
|
|||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
#[derive(Clone)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Overrides {
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
|
|
|
@ -11,20 +11,15 @@ use crate::settings::types::SerializationFormat;
|
|||
use crate::{Configuration, Settings};
|
||||
|
||||
/// Print the user-facing configuration settings.
|
||||
pub fn show_settings(
|
||||
configuration: &Configuration,
|
||||
project_root: Option<&Path>,
|
||||
pyproject: Option<&Path>,
|
||||
) {
|
||||
pub fn show_settings(configuration: &Configuration, pyproject: Option<&Path>) {
|
||||
println!("Resolved configuration: {configuration:#?}");
|
||||
println!("Found project root at: {project_root:?}");
|
||||
println!("Found pyproject.toml at: {pyproject:?}");
|
||||
}
|
||||
|
||||
/// Show the list of files to be checked based on current settings.
|
||||
pub fn show_files(files: &[PathBuf], default: &Settings, overrides: &Overrides) {
|
||||
pub fn show_files(files: &[PathBuf], defaults: &Settings, overrides: &Overrides) {
|
||||
// Collect all files in the hierarchy.
|
||||
let (paths, _resolver) = collect_python_files(files, overrides, default);
|
||||
let (paths, _resolver) = collect_python_files(files, overrides, defaults);
|
||||
|
||||
// Print the list of files.
|
||||
for entry in paths
|
||||
|
|
30
src/lib.rs
30
src/lib.rs
|
@ -84,24 +84,22 @@ pub mod updates;
|
|||
mod vendored;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&[path.to_path_buf()]);
|
||||
match &project_root {
|
||||
Some(path) => debug!("Found project root at: {:?}", path),
|
||||
None => debug!("Unable to identify project root; assuming current directory..."),
|
||||
};
|
||||
let pyproject = pyproject::find_pyproject_toml(project_root.as_ref());
|
||||
match &pyproject {
|
||||
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
fn resolve(path: &Path) -> Result<Settings> {
|
||||
// Find the relevant `pyproject.toml`.
|
||||
let Some(pyproject) = pyproject::find_pyproject_toml(path) else {
|
||||
debug!("Unable to find pyproject.toml; using default settings...");
|
||||
return Settings::from_configuration(Configuration::default(), None);
|
||||
};
|
||||
|
||||
let settings = Settings::from_configuration(
|
||||
Configuration::from_pyproject(pyproject.as_ref())?,
|
||||
project_root.as_deref(),
|
||||
)?;
|
||||
// Load and parse the `pyproject.toml`.
|
||||
let options = pyproject::load_options(&pyproject)?;
|
||||
let configuration = Configuration::from_options(options)?;
|
||||
Settings::from_configuration(configuration, pyproject.parent())
|
||||
}
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str, autofix: bool) -> Result<Vec<Check>> {
|
||||
let settings = resolve(path)?;
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
|
94
src/main.rs
94
src/main.rs
|
@ -31,12 +31,13 @@ use ::ruff::settings::types::SerializationFormat;
|
|||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{cache, commands, fs};
|
||||
use ::ruff::{cache, commands};
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use log::{debug, error};
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
use path_absolutize::path_dedot;
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use rustpython_ast::Location;
|
||||
|
@ -60,14 +61,14 @@ fn run_once_stdin(
|
|||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
default: &Settings,
|
||||
defaults: &Settings,
|
||||
overrides: &Overrides,
|
||||
cache: bool,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Diagnostics {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = collect_python_files(files, overrides, default);
|
||||
let (paths, resolver) = collect_python_files(files, overrides, defaults);
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
|
@ -77,7 +78,7 @@ fn run_once(
|
|||
match entry {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path).unwrap_or(default);
|
||||
let settings = resolver.resolve(path).unwrap_or(defaults);
|
||||
lint_path(path, settings, &cache.into(), autofix)
|
||||
.map_err(|e| (Some(path.to_owned()), e.to_string()))
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ fn run_once(
|
|||
}
|
||||
.unwrap_or_else(|(path, message)| {
|
||||
if let Some(path) = &path {
|
||||
let settings = resolver.resolve(path).unwrap_or(default);
|
||||
let settings = resolver.resolve(path).unwrap_or(defaults);
|
||||
if settings.enabled.contains(&CheckCode::E902) {
|
||||
Diagnostics::new(vec![Message {
|
||||
kind: CheckKind::IOError(message),
|
||||
|
@ -121,10 +122,10 @@ fn run_once(
|
|||
diagnostics
|
||||
}
|
||||
|
||||
fn add_noqa(files: &[PathBuf], default: &Settings, overrides: &Overrides) -> usize {
|
||||
fn add_noqa(files: &[PathBuf], defaults: &Settings, overrides: &Overrides) -> usize {
|
||||
// Collect all the files to check.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = collect_python_files(files, overrides, default);
|
||||
let (paths, resolver) = collect_python_files(files, overrides, defaults);
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
|
@ -133,7 +134,7 @@ fn add_noqa(files: &[PathBuf], default: &Settings, overrides: &Overrides) -> usi
|
|||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path).unwrap_or(default);
|
||||
let settings = resolver.resolve(path).unwrap_or(defaults);
|
||||
match add_noqa_to_path(path, settings) {
|
||||
Ok(count) => Some(count),
|
||||
Err(e) => {
|
||||
|
@ -150,10 +151,10 @@ fn add_noqa(files: &[PathBuf], default: &Settings, overrides: &Overrides) -> usi
|
|||
modifications
|
||||
}
|
||||
|
||||
fn autoformat(files: &[PathBuf], default: &Settings, overrides: &Overrides) -> usize {
|
||||
fn autoformat(files: &[PathBuf], defaults: &Settings, overrides: &Overrides) -> usize {
|
||||
// Collect all the files to format.
|
||||
let start = Instant::now();
|
||||
let (paths, resolver) = collect_python_files(files, overrides, default);
|
||||
let (paths, resolver) = collect_python_files(files, overrides, defaults);
|
||||
let duration = start.elapsed();
|
||||
debug!("Identified files to lint in: {:?}", duration);
|
||||
|
||||
|
@ -162,7 +163,7 @@ fn autoformat(files: &[PathBuf], default: &Settings, overrides: &Overrides) -> u
|
|||
.flatten()
|
||||
.filter_map(|entry| {
|
||||
let path = entry.path();
|
||||
let settings = resolver.resolve(path).unwrap_or(default);
|
||||
let settings = resolver.resolve(path).unwrap_or(defaults);
|
||||
match autoformat_path(path, settings) {
|
||||
Ok(()) => Some(()),
|
||||
Err(e) => {
|
||||
|
@ -185,50 +186,36 @@ fn inner_main() -> Result<ExitCode> {
|
|||
let log_level = extract_log_level(&cli);
|
||||
set_up_logging(&log_level)?;
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
anyhow::bail!("specify --show-settings or show-files (not both)")
|
||||
}
|
||||
if let Some(shell) = cli.generate_shell_completion {
|
||||
shell.generate(&mut Cli::command(), &mut io::stdout());
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// Find the project root and pyproject.toml.
|
||||
// TODO(charlie): look in the current directory, but respect `--config`.
|
||||
let project_root = cli.config.as_ref().map_or_else(
|
||||
|| pyproject::find_project_root(&cli.files),
|
||||
|config| config.parent().map(fs::normalize_path),
|
||||
);
|
||||
// Find the `pyproject.toml`.
|
||||
let pyproject = cli
|
||||
.config
|
||||
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
|
||||
match &project_root {
|
||||
Some(path) => debug!("Found project root at: {:?}", path),
|
||||
None => debug!("Unable to identify project root; assuming current directory..."),
|
||||
};
|
||||
match &pyproject {
|
||||
Some(path) => debug!("Found pyproject.toml at: {:?}", path),
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
.or_else(|| pyproject::find_pyproject_toml(&path_dedot::CWD));
|
||||
|
||||
// Reconcile configuration from pyproject.toml and command-line arguments.
|
||||
let mut configuration = Configuration::from_pyproject(pyproject.as_ref())?;
|
||||
configuration.merge(&overrides);
|
||||
|
||||
if cli.show_settings && cli.show_files {
|
||||
eprintln!("Error: specify --show-settings or show-files (not both).");
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
// Reconcile configuration from `pyproject.toml` and command-line arguments.
|
||||
let mut configuration = pyproject
|
||||
.as_ref()
|
||||
.map(|path| Configuration::from_pyproject(path))
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
configuration.merge(overrides.clone());
|
||||
|
||||
if cli.show_settings {
|
||||
// TODO(charlie): This would be more useful if required a single file, and told
|
||||
// you the settings used to lint that file.
|
||||
commands::show_settings(
|
||||
&configuration,
|
||||
project_root.as_deref(),
|
||||
pyproject.as_deref(),
|
||||
);
|
||||
commands::show_settings(&configuration, pyproject.as_deref());
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
// TODO(charlie): Included in `pyproject.toml`, but not inherited.
|
||||
// Extract options that are included in the `pyproject.toml`, but aren't in
|
||||
// `Settings`.
|
||||
let fix = if configuration.fix {
|
||||
fixer::Mode::Apply
|
||||
} else if matches!(configuration.format, SerializationFormat::Json) {
|
||||
|
@ -238,7 +225,12 @@ fn inner_main() -> Result<ExitCode> {
|
|||
};
|
||||
let format = configuration.format;
|
||||
|
||||
let settings = Settings::from_configuration(configuration, project_root.as_deref())?;
|
||||
// Construct the "default" settings. These are used when no `pyproject.toml`
|
||||
// files are present, or files are injected from outside of the hierarchy.
|
||||
let defaults = Settings::from_configuration(
|
||||
configuration,
|
||||
pyproject.as_ref().and_then(|path| path.parent()),
|
||||
)?;
|
||||
|
||||
if let Some(code) = cli.explain {
|
||||
commands::explain(&code, format)?;
|
||||
|
@ -246,7 +238,7 @@ fn inner_main() -> Result<ExitCode> {
|
|||
}
|
||||
|
||||
if cli.show_files {
|
||||
commands::show_files(&cli.files, &settings, &overrides);
|
||||
commands::show_files(&cli.files, &defaults, &overrides);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
|
@ -278,7 +270,7 @@ fn inner_main() -> Result<ExitCode> {
|
|||
|
||||
let messages = run_once(
|
||||
&cli.files,
|
||||
&settings,
|
||||
&defaults,
|
||||
&overrides,
|
||||
cache_enabled,
|
||||
&fixer::Mode::None,
|
||||
|
@ -294,8 +286,8 @@ fn inner_main() -> Result<ExitCode> {
|
|||
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(e) => {
|
||||
let paths = e?.paths;
|
||||
Ok(event) => {
|
||||
let paths = event?.paths;
|
||||
let py_changed = paths.iter().any(|p| {
|
||||
p.extension()
|
||||
.map(|ext| ext == "py" || ext == "pyi")
|
||||
|
@ -307,7 +299,7 @@ fn inner_main() -> Result<ExitCode> {
|
|||
|
||||
let messages = run_once(
|
||||
&cli.files,
|
||||
&settings,
|
||||
&defaults,
|
||||
&overrides,
|
||||
cache_enabled,
|
||||
&fixer::Mode::None,
|
||||
|
@ -315,16 +307,16 @@ fn inner_main() -> Result<ExitCode> {
|
|||
printer.write_continuously(&messages)?;
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
} else if cli.add_noqa {
|
||||
let modifications = add_noqa(&cli.files, &settings, &overrides);
|
||||
let modifications = add_noqa(&cli.files, &defaults, &overrides);
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Added {modifications} noqa directives.");
|
||||
}
|
||||
} else if cli.autoformat {
|
||||
let modifications = autoformat(&cli.files, &settings, &overrides);
|
||||
let modifications = autoformat(&cli.files, &defaults, &overrides);
|
||||
if modifications > 0 && log_level >= LogLevel::Default {
|
||||
println!("Formatted {modifications} files.");
|
||||
}
|
||||
|
@ -335,9 +327,9 @@ fn inner_main() -> Result<ExitCode> {
|
|||
let diagnostics = if is_stdin {
|
||||
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
||||
let path = Path::new(&filename);
|
||||
run_once_stdin(&settings, path, &fix)?
|
||||
run_once_stdin(&defaults, path, &fix)?
|
||||
} else {
|
||||
run_once(&cli.files, &settings, &overrides, cache_enabled, &fix)
|
||||
run_once(&cli.files, &defaults, &overrides, cache_enabled, &fix)
|
||||
};
|
||||
|
||||
// Always try to print violations (the printer itself may suppress output),
|
||||
|
|
|
@ -42,7 +42,7 @@ pub fn settings_for_path(pyproject: &Path, overrides: &Overrides) -> Result<(Pat
|
|||
.to_path_buf();
|
||||
let options = pyproject::load_options(pyproject)?;
|
||||
let mut configuration = Configuration::from_options(options)?;
|
||||
configuration.merge(overrides);
|
||||
configuration.merge(overrides.clone());
|
||||
let settings = Settings::from_configuration(configuration, Some(&project_root))?;
|
||||
Ok((project_root, settings))
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use log::debug;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
@ -81,15 +80,8 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
|
|||
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
|
||||
|
||||
impl Configuration {
|
||||
pub fn from_pyproject(pyproject: Option<&PathBuf>) -> Result<Self> {
|
||||
Self::from_options(pyproject.map_or_else(
|
||||
|| {
|
||||
debug!("No pyproject.toml found.");
|
||||
debug!("Falling back to default configuration...");
|
||||
Ok(Options::default())
|
||||
},
|
||||
|path| load_options(path),
|
||||
)?)
|
||||
pub fn from_pyproject(pyproject: &Path) -> Result<Self> {
|
||||
Self::from_options(load_options(pyproject)?)
|
||||
}
|
||||
|
||||
pub fn from_options(options: Options) -> Result<Self> {
|
||||
|
@ -184,54 +176,90 @@ impl Configuration {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn merge(&mut self, overrides: &Overrides) {
|
||||
if let Some(dummy_variable_rgx) = &overrides.dummy_variable_rgx {
|
||||
self.dummy_variable_rgx = dummy_variable_rgx.clone();
|
||||
pub fn merge(&mut self, overrides: Overrides) {
|
||||
if let Some(dummy_variable_rgx) = overrides.dummy_variable_rgx {
|
||||
self.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
if let Some(exclude) = &overrides.exclude {
|
||||
self.exclude = exclude.clone();
|
||||
if let Some(exclude) = overrides.exclude {
|
||||
self.exclude = exclude;
|
||||
}
|
||||
if let Some(extend_exclude) = &overrides.extend_exclude {
|
||||
self.extend_exclude = extend_exclude.clone();
|
||||
if let Some(extend_exclude) = overrides.extend_exclude {
|
||||
self.extend_exclude = extend_exclude;
|
||||
}
|
||||
if let Some(extend_ignore) = &overrides.extend_ignore {
|
||||
self.extend_ignore = extend_ignore.clone();
|
||||
if let Some(extend_ignore) = overrides.extend_ignore {
|
||||
self.extend_ignore = extend_ignore;
|
||||
}
|
||||
if let Some(extend_select) = &overrides.extend_select {
|
||||
self.extend_select = extend_select.clone();
|
||||
if let Some(extend_select) = overrides.extend_select {
|
||||
self.extend_select = extend_select;
|
||||
}
|
||||
if let Some(fix) = &overrides.fix {
|
||||
self.fix = *fix;
|
||||
if let Some(fix) = overrides.fix {
|
||||
self.fix = fix;
|
||||
}
|
||||
if let Some(fixable) = &overrides.fixable {
|
||||
self.fixable = fixable.clone();
|
||||
if let Some(fixable) = overrides.fixable {
|
||||
self.fixable = fixable;
|
||||
}
|
||||
if let Some(format) = &overrides.format {
|
||||
self.format = *format;
|
||||
if let Some(format) = overrides.format {
|
||||
self.format = format;
|
||||
}
|
||||
if let Some(ignore) = &overrides.ignore {
|
||||
self.ignore = ignore.clone();
|
||||
if let Some(ignore) = overrides.ignore {
|
||||
self.ignore = ignore;
|
||||
}
|
||||
if let Some(line_length) = &overrides.line_length {
|
||||
self.line_length = *line_length;
|
||||
if let Some(line_length) = overrides.line_length {
|
||||
self.line_length = line_length;
|
||||
}
|
||||
if let Some(max_complexity) = &overrides.max_complexity {
|
||||
self.mccabe.max_complexity = *max_complexity;
|
||||
if let Some(max_complexity) = overrides.max_complexity {
|
||||
self.mccabe.max_complexity = max_complexity;
|
||||
}
|
||||
if let Some(per_file_ignores) = &overrides.per_file_ignores {
|
||||
self.per_file_ignores = collect_per_file_ignores(per_file_ignores.clone());
|
||||
if let Some(per_file_ignores) = overrides.per_file_ignores {
|
||||
self.per_file_ignores = collect_per_file_ignores(per_file_ignores);
|
||||
}
|
||||
if let Some(select) = &overrides.select {
|
||||
self.select = select.clone();
|
||||
if let Some(select) = overrides.select {
|
||||
self.select = select;
|
||||
}
|
||||
if let Some(show_source) = &overrides.show_source {
|
||||
self.show_source = *show_source;
|
||||
if let Some(show_source) = overrides.show_source {
|
||||
self.show_source = show_source;
|
||||
}
|
||||
if let Some(target_version) = &overrides.target_version {
|
||||
self.target_version = *target_version;
|
||||
if let Some(target_version) = overrides.target_version {
|
||||
self.target_version = target_version;
|
||||
}
|
||||
if let Some(unfixable) = &overrides.unfixable {
|
||||
self.unfixable = unfixable.clone();
|
||||
if let Some(unfixable) = overrides.unfixable {
|
||||
self.unfixable = unfixable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Configuration {
|
||||
fn default() -> Self {
|
||||
Configuration {
|
||||
allowed_confusables: FxHashSet::default(),
|
||||
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
src: vec![Path::new(".").to_path_buf()],
|
||||
target_version: PythonVersion::Py310,
|
||||
exclude: DEFAULT_EXCLUDE.clone(),
|
||||
extend_exclude: Vec::default(),
|
||||
extend_ignore: Vec::default(),
|
||||
select: vec![CheckCodePrefix::E, CheckCodePrefix::F],
|
||||
extend_select: Vec::default(),
|
||||
external: Vec::default(),
|
||||
fix: false,
|
||||
fixable: CATEGORIES.to_vec(),
|
||||
unfixable: Vec::default(),
|
||||
format: SerializationFormat::default(),
|
||||
ignore: Vec::default(),
|
||||
ignore_init_module_imports: false,
|
||||
line_length: 88,
|
||||
per_file_ignores: Vec::default(),
|
||||
show_source: false,
|
||||
// Plugins
|
||||
flake8_annotations: flake8_annotations::settings::Settings::default(),
|
||||
flake8_bugbear: flake8_bugbear::settings::Settings::default(),
|
||||
flake8_import_conventions: flake8_import_conventions::settings::Settings::default(),
|
||||
flake8_quotes: flake8_quotes::settings::Settings::default(),
|
||||
flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(),
|
||||
isort: isort::settings::Settings::default(),
|
||||
mccabe: mccabe::settings::Settings::default(),
|
||||
pep8_naming: pep8_naming::settings::Settings::default(),
|
||||
pyupgrade: pyupgrade::settings::Settings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use common_path::common_path_all;
|
||||
use path_absolutize::Absolutize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::fs;
|
||||
|
@ -35,14 +33,14 @@ fn parse_pyproject_toml(path: &Path) -> Result<Pyproject> {
|
|||
Ok(toml::from_str(&contents)?)
|
||||
}
|
||||
|
||||
pub fn find_pyproject_toml(path: Option<&PathBuf>) -> Option<PathBuf> {
|
||||
if let Some(path) = path {
|
||||
let path_pyproject_toml = path.join("pyproject.toml");
|
||||
if path_pyproject_toml.is_file() {
|
||||
return Some(path_pyproject_toml);
|
||||
/// Find the nearest `pyproject.toml` file.
|
||||
pub fn find_pyproject_toml(path: &Path) -> Option<PathBuf> {
|
||||
for directory in path.ancestors() {
|
||||
let pyproject = directory.join("pyproject.toml");
|
||||
if pyproject.is_file() {
|
||||
return Some(pyproject);
|
||||
}
|
||||
}
|
||||
|
||||
find_user_pyproject_toml()
|
||||
}
|
||||
|
||||
|
@ -57,28 +55,6 @@ fn find_user_pyproject_toml() -> Option<PathBuf> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
|
||||
let absolute_sources: Vec<PathBuf> = sources
|
||||
.iter()
|
||||
.flat_map(|source| source.absolutize().map(|path| path.to_path_buf()))
|
||||
.collect();
|
||||
if let Some(prefix) = common_path_all(absolute_sources.iter().map(PathBuf::as_path)) {
|
||||
for directory in prefix.ancestors() {
|
||||
if directory.join(".git").is_dir() {
|
||||
return Some(directory.to_path_buf());
|
||||
}
|
||||
if directory.join(".hg").is_dir() {
|
||||
return Some(directory.to_path_buf());
|
||||
}
|
||||
if directory.join("pyproject.toml").is_file() {
|
||||
return Some(directory.to_path_buf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn load_options(pyproject: &Path) -> Result<Options> {
|
||||
Ok(parse_pyproject_toml(pyproject)
|
||||
.map_err(|err| anyhow!("Failed to parse `{}`: {}", pyproject.to_string_lossy(), err))?
|
||||
|
@ -90,7 +66,6 @@ pub fn load_options(pyproject: &Path) -> Result<Options> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -100,7 +75,7 @@ mod tests {
|
|||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::flake8_tidy_imports::settings::Strictness;
|
||||
use crate::settings::pyproject::{
|
||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||
find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
|
||||
};
|
||||
use crate::settings::types::PatternPrefixPair;
|
||||
use crate::{
|
||||
|
@ -369,14 +344,14 @@ other-attribute = 1
|
|||
#[test]
|
||||
fn find_and_parse_pyproject_toml() -> Result<()> {
|
||||
let cwd = current_dir()?;
|
||||
let project_root =
|
||||
find_project_root(&[PathBuf::from("resources/test/fixtures/__init__.py")]).unwrap();
|
||||
assert_eq!(project_root, cwd.join("resources/test/fixtures"));
|
||||
let pyproject =
|
||||
find_pyproject_toml(&cwd.join("resources/test/fixtures/__init__.py")).unwrap();
|
||||
assert_eq!(
|
||||
pyproject,
|
||||
cwd.join("resources/test/fixtures/pyproject.toml")
|
||||
);
|
||||
|
||||
let path = find_pyproject_toml(Some(&project_root)).unwrap();
|
||||
assert_eq!(path, cwd.join("resources/test/fixtures/pyproject.toml"));
|
||||
|
||||
let pyproject = parse_pyproject_toml(&path)?;
|
||||
let pyproject = parse_pyproject_toml(&pyproject)?;
|
||||
let config = pyproject.tool.and_then(|tool| tool.ruff).unwrap();
|
||||
assert_eq!(
|
||||
config,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue