Skip to content

Commit 8ec5627

Browse files
AlexWaygoodMichaReiserzanieb
authored
Allow arbitrary configuration options to be overridden via the CLI (#9599)
Fixes #8368 Fixes #9186 ## Summary Arbitrary TOML strings can be provided via the command-line to override configuration options in `pyproject.toml` or `ruff.toml`. As an example: to run over typeshed and respect typeshed's `pyproject.toml`, but override a specific isort setting and enable an additional pep8-naming setting: ``` cargo run -- check ../typeshed --no-cache --config ../typeshed/pyproject.toml --config "lint.isort.combine-as-imports=false" --config "lint.extend-select=['N801']" ``` --------- Co-authored-by: Micha Reiser <[email protected]> Co-authored-by: Zanie Blue <[email protected]>
1 parent b21ba71 commit 8ec5627

File tree

21 files changed

+1100
-236
lines changed

21 files changed

+1100
-236
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ serde_json = { workspace = true }
4949
shellexpand = { workspace = true }
5050
strum = { workspace = true, features = [] }
5151
thiserror = { workspace = true }
52+
toml = { workspace = true }
5253
tracing = { workspace = true, features = ["log"] }
5354
walkdir = { workspace = true }
5455
wild = { workspace = true }

crates/ruff/src/args.rs

Lines changed: 397 additions & 127 deletions
Large diffs are not rendered by default.

crates/ruff/src/commands/add_noqa.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ use ruff_linter::warn_user_once;
1212
use ruff_python_ast::{PySourceType, SourceType};
1313
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
1414

15-
use crate::args::CliOverrides;
15+
use crate::args::ConfigArguments;
1616

1717
/// Add `noqa` directives to a collection of files.
1818
pub(crate) fn add_noqa(
1919
files: &[PathBuf],
2020
pyproject_config: &PyprojectConfig,
21-
overrides: &CliOverrides,
21+
config_arguments: &ConfigArguments,
2222
) -> Result<usize> {
2323
// Collect all the files to check.
2424
let start = Instant::now();
25-
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
25+
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
2626
let duration = start.elapsed();
2727
debug!("Identified files to lint in: {:?}", duration);
2828

crates/ruff/src/commands/check.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use ruff_workspace::resolver::{
2424
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile,
2525
};
2626

27-
use crate::args::CliOverrides;
27+
use crate::args::ConfigArguments;
2828
use crate::cache::{Cache, PackageCacheMap, PackageCaches};
2929
use crate::diagnostics::Diagnostics;
3030
use crate::panic::catch_unwind;
@@ -34,15 +34,15 @@ use crate::panic::catch_unwind;
3434
pub(crate) fn check(
3535
files: &[PathBuf],
3636
pyproject_config: &PyprojectConfig,
37-
overrides: &CliOverrides,
37+
config_arguments: &ConfigArguments,
3838
cache: flags::Cache,
3939
noqa: flags::Noqa,
4040
fix_mode: flags::FixMode,
4141
unsafe_fixes: UnsafeFixes,
4242
) -> Result<Diagnostics> {
4343
// Collect all the Python files to check.
4444
let start = Instant::now();
45-
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
45+
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
4646
debug!("Identified files to lint in: {:?}", start.elapsed());
4747

4848
if paths.is_empty() {
@@ -233,7 +233,7 @@ mod test {
233233
use ruff_workspace::resolver::{PyprojectConfig, PyprojectDiscoveryStrategy};
234234
use ruff_workspace::Settings;
235235

236-
use crate::args::CliOverrides;
236+
use crate::args::ConfigArguments;
237237

238238
use super::check;
239239

@@ -272,7 +272,7 @@ mod test {
272272
// Notebooks are not included by default
273273
&[tempdir.path().to_path_buf(), notebook],
274274
&pyproject_config,
275-
&CliOverrides::default(),
275+
&ConfigArguments::default(),
276276
flags::Cache::Disabled,
277277
flags::Noqa::Disabled,
278278
flags::FixMode::Generate,

crates/ruff/src/commands/check_stdin.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ use ruff_linter::packaging;
66
use ruff_linter::settings::flags;
77
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, PyprojectConfig, Resolver};
88

9-
use crate::args::CliOverrides;
9+
use crate::args::ConfigArguments;
1010
use crate::diagnostics::{lint_stdin, Diagnostics};
1111
use crate::stdin::{parrot_stdin, read_from_stdin};
1212

1313
/// Run the linter over a single file, read from `stdin`.
1414
pub(crate) fn check_stdin(
1515
filename: Option<&Path>,
1616
pyproject_config: &PyprojectConfig,
17-
overrides: &CliOverrides,
17+
overrides: &ConfigArguments,
1818
noqa: flags::Noqa,
1919
fix_mode: flags::FixMode,
2020
) -> Result<Diagnostics> {

crates/ruff/src/commands/format.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
2929
use ruff_workspace::resolver::{match_exclusion, python_files_in_path, ResolvedFile, Resolver};
3030
use ruff_workspace::FormatterSettings;
3131

32-
use crate::args::{CliOverrides, FormatArguments, FormatRange};
32+
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
3333
use crate::cache::{Cache, FileCacheKey, PackageCacheMap, PackageCaches};
3434
use crate::panic::{catch_unwind, PanicError};
3535
use crate::resolve::resolve;
@@ -60,18 +60,17 @@ impl FormatMode {
6060
/// Format a set of files, and return the exit status.
6161
pub(crate) fn format(
6262
cli: FormatArguments,
63-
overrides: &CliOverrides,
63+
config_arguments: &ConfigArguments,
6464
log_level: LogLevel,
6565
) -> Result<ExitStatus> {
6666
let pyproject_config = resolve(
6767
cli.isolated,
68-
cli.config.as_deref(),
69-
overrides,
68+
config_arguments,
7069
cli.stdin_filename.as_deref(),
7170
)?;
7271
let mode = FormatMode::from_cli(&cli);
7372
let files = resolve_default_files(cli.files, false);
74-
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, overrides)?;
73+
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
7574

7675
if paths.is_empty() {
7776
warn_user_once!("No Python files found under the given path(s)");

crates/ruff/src/commands/format_stdin.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ruff_python_ast::{PySourceType, SourceType};
99
use ruff_workspace::resolver::{match_exclusion, python_file_at_path, Resolver};
1010
use ruff_workspace::FormatterSettings;
1111

12-
use crate::args::{CliOverrides, FormatArguments, FormatRange};
12+
use crate::args::{ConfigArguments, FormatArguments, FormatRange};
1313
use crate::commands::format::{
1414
format_source, warn_incompatible_formatter_settings, FormatCommandError, FormatMode,
1515
FormatResult, FormattedSource,
@@ -19,11 +19,13 @@ use crate::stdin::{parrot_stdin, read_from_stdin};
1919
use crate::ExitStatus;
2020

2121
/// Run the formatter over a single file, read from `stdin`.
22-
pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> Result<ExitStatus> {
22+
pub(crate) fn format_stdin(
23+
cli: &FormatArguments,
24+
config_arguments: &ConfigArguments,
25+
) -> Result<ExitStatus> {
2326
let pyproject_config = resolve(
2427
cli.isolated,
25-
cli.config.as_deref(),
26-
overrides,
28+
config_arguments,
2729
cli.stdin_filename.as_deref(),
2830
)?;
2931

@@ -34,7 +36,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
3436

3537
if resolver.force_exclude() {
3638
if let Some(filename) = cli.stdin_filename.as_deref() {
37-
if !python_file_at_path(filename, &mut resolver, overrides)? {
39+
if !python_file_at_path(filename, &mut resolver, config_arguments)? {
3840
if mode.is_write() {
3941
parrot_stdin()?;
4042
}

crates/ruff/src/commands/show_files.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ use itertools::Itertools;
77
use ruff_linter::warn_user_once;
88
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
99

10-
use crate::args::CliOverrides;
10+
use crate::args::ConfigArguments;
1111

1212
/// Show the list of files to be checked based on current settings.
1313
pub(crate) fn show_files(
1414
files: &[PathBuf],
1515
pyproject_config: &PyprojectConfig,
16-
overrides: &CliOverrides,
16+
config_arguments: &ConfigArguments,
1717
writer: &mut impl Write,
1818
) -> Result<()> {
1919
// Collect all files in the hierarchy.
20-
let (paths, _resolver) = python_files_in_path(files, pyproject_config, overrides)?;
20+
let (paths, _resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
2121

2222
if paths.is_empty() {
2323
warn_user_once!("No Python files found under the given path(s)");

crates/ruff/src/commands/show_settings.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ use itertools::Itertools;
66

77
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile};
88

9-
use crate::args::CliOverrides;
9+
use crate::args::ConfigArguments;
1010

1111
/// Print the user-facing configuration settings.
1212
pub(crate) fn show_settings(
1313
files: &[PathBuf],
1414
pyproject_config: &PyprojectConfig,
15-
overrides: &CliOverrides,
15+
config_arguments: &ConfigArguments,
1616
writer: &mut impl Write,
1717
) -> Result<()> {
1818
// Collect all files in the hierarchy.
19-
let (paths, resolver) = python_files_in_path(files, pyproject_config, overrides)?;
19+
let (paths, resolver) = python_files_in_path(files, pyproject_config, config_arguments)?;
2020

2121
// Print the list of files.
2222
let Some(path) = paths

crates/ruff/src/lib.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -204,24 +204,23 @@ pub fn run(
204204
}
205205

206206
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> {
207-
let (cli, overrides) = args.partition();
207+
let (cli, config_arguments) = args.partition()?;
208208

209209
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
210-
commands::format_stdin::format_stdin(&cli, &overrides)
210+
commands::format_stdin::format_stdin(&cli, &config_arguments)
211211
} else {
212-
commands::format::format(cli, &overrides, log_level)
212+
commands::format::format(cli, &config_arguments, log_level)
213213
}
214214
}
215215

216216
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
217-
let (cli, overrides) = args.partition();
217+
let (cli, config_arguments) = args.partition()?;
218218

219219
// Construct the "default" settings. These are used when no `pyproject.toml`
220220
// files are present, or files are injected from outside of the hierarchy.
221221
let pyproject_config = resolve::resolve(
222222
cli.isolated,
223-
cli.config.as_deref(),
224-
&overrides,
223+
&config_arguments,
225224
cli.stdin_filename.as_deref(),
226225
)?;
227226

@@ -239,11 +238,21 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
239238
let files = resolve_default_files(cli.files, is_stdin);
240239

241240
if cli.show_settings {
242-
commands::show_settings::show_settings(&files, &pyproject_config, &overrides, &mut writer)?;
241+
commands::show_settings::show_settings(
242+
&files,
243+
&pyproject_config,
244+
&config_arguments,
245+
&mut writer,
246+
)?;
243247
return Ok(ExitStatus::Success);
244248
}
245249
if cli.show_files {
246-
commands::show_files::show_files(&files, &pyproject_config, &overrides, &mut writer)?;
250+
commands::show_files::show_files(
251+
&files,
252+
&pyproject_config,
253+
&config_arguments,
254+
&mut writer,
255+
)?;
247256
return Ok(ExitStatus::Success);
248257
}
249258

@@ -302,7 +311,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
302311
if !fix_mode.is_generate() {
303312
warn_user!("--fix is incompatible with --add-noqa.");
304313
}
305-
let modifications = commands::add_noqa::add_noqa(&files, &pyproject_config, &overrides)?;
314+
let modifications =
315+
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
306316
if modifications > 0 && log_level >= LogLevel::Default {
307317
let s = if modifications == 1 { "" } else { "s" };
308318
#[allow(clippy::print_stderr)]
@@ -352,7 +362,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
352362
let messages = commands::check::check(
353363
&files,
354364
&pyproject_config,
355-
&overrides,
365+
&config_arguments,
356366
cache.into(),
357367
noqa.into(),
358368
fix_mode,
@@ -374,8 +384,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
374384
if matches!(change_kind, ChangeKind::Configuration) {
375385
pyproject_config = resolve::resolve(
376386
cli.isolated,
377-
cli.config.as_deref(),
378-
&overrides,
387+
&config_arguments,
379388
cli.stdin_filename.as_deref(),
380389
)?;
381390
}
@@ -385,7 +394,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
385394
let messages = commands::check::check(
386395
&files,
387396
&pyproject_config,
388-
&overrides,
397+
&config_arguments,
389398
cache.into(),
390399
noqa.into(),
391400
fix_mode,
@@ -402,15 +411,15 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
402411
commands::check_stdin::check_stdin(
403412
cli.stdin_filename.map(fs::normalize_path).as_deref(),
404413
&pyproject_config,
405-
&overrides,
414+
&config_arguments,
406415
noqa.into(),
407416
fix_mode,
408417
)?
409418
} else {
410419
commands::check::check(
411420
&files,
412421
&pyproject_config,
413-
&overrides,
422+
&config_arguments,
414423
cache.into(),
415424
noqa.into(),
416425
fix_mode,

crates/ruff/src/resolve.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ use ruff_workspace::resolver::{
1111
Relativity,
1212
};
1313

14-
use crate::args::CliOverrides;
14+
use crate::args::ConfigArguments;
1515

1616
/// Resolve the relevant settings strategy and defaults for the current
1717
/// invocation.
1818
pub fn resolve(
1919
isolated: bool,
20-
config: Option<&Path>,
21-
overrides: &CliOverrides,
20+
config_arguments: &ConfigArguments,
2221
stdin_filename: Option<&Path>,
2322
) -> Result<PyprojectConfig> {
2423
// First priority: if we're running in isolated mode, use the default settings.
2524
if isolated {
26-
let config = overrides.transform(Configuration::default());
25+
let config = config_arguments.transform(Configuration::default());
2726
let settings = config.into_settings(&path_dedot::CWD)?;
2827
debug!("Isolated mode, not reading any pyproject.toml");
2928
return Ok(PyprojectConfig::new(
@@ -36,12 +35,13 @@ pub fn resolve(
3635
// Second priority: the user specified a `pyproject.toml` file. Use that
3736
// `pyproject.toml` for _all_ configuration, and resolve paths relative to the
3837
// current working directory. (This matches ESLint's behavior.)
39-
if let Some(pyproject) = config
38+
if let Some(pyproject) = config_arguments
39+
.config_file()
4040
.map(|config| config.display().to_string())
4141
.map(|config| shellexpand::full(&config).map(|config| PathBuf::from(config.as_ref())))
4242
.transpose()?
4343
{
44-
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
44+
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
4545
debug!(
4646
"Using user-specified configuration file at: {}",
4747
pyproject.display()
@@ -67,7 +67,7 @@ pub fn resolve(
6767
"Using configuration file (via parent) at: {}",
6868
pyproject.display()
6969
);
70-
let settings = resolve_root_settings(&pyproject, Relativity::Parent, overrides)?;
70+
let settings = resolve_root_settings(&pyproject, Relativity::Parent, config_arguments)?;
7171
return Ok(PyprojectConfig::new(
7272
PyprojectDiscoveryStrategy::Hierarchical,
7373
settings,
@@ -84,7 +84,7 @@ pub fn resolve(
8484
"Using configuration file (via cwd) at: {}",
8585
pyproject.display()
8686
);
87-
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, overrides)?;
87+
let settings = resolve_root_settings(&pyproject, Relativity::Cwd, config_arguments)?;
8888
return Ok(PyprojectConfig::new(
8989
PyprojectDiscoveryStrategy::Hierarchical,
9090
settings,
@@ -97,7 +97,7 @@ pub fn resolve(
9797
// "closest" `pyproject.toml` file for every Python file later on, so these act
9898
// as the "default" settings.)
9999
debug!("Using Ruff default settings");
100-
let config = overrides.transform(Configuration::default());
100+
let config = config_arguments.transform(Configuration::default());
101101
let settings = config.into_settings(&path_dedot::CWD)?;
102102
Ok(PyprojectConfig::new(
103103
PyprojectDiscoveryStrategy::Hierarchical,

0 commit comments

Comments
 (0)