Make `--config` and `--isolated` global flags (#10150)

This commit is contained in:
Alex Waygood 2024-03-04 11:19:40 +00:00 committed by GitHub
parent 8dde81a905
commit 8b749e1d4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 251 additions and 147 deletions

View File

@ -28,6 +28,52 @@ use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::options::{Options, PycodestyleOptions}; use ruff_workspace::options::{Options, PycodestyleOptions};
use ruff_workspace::resolver::ConfigurationTransformer; use ruff_workspace::resolver::ConfigurationTransformer;
/// All configuration options that can be passed "globally",
/// i.e., can be passed to all subcommands
#[derive(Debug, Default, Clone, clap::Args)]
pub struct GlobalConfigArgs {
#[clap(flatten)]
log_level_args: LogLevelArgs,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
global = true,
help_heading = "Global options",
)]
pub config: Vec<SingleConfigArgument>,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Global options", global = true)]
pub isolated: bool,
}
impl GlobalConfigArgs {
pub fn log_level(&self) -> LogLevel {
LogLevel::from(&self.log_level_args)
}
#[must_use]
fn partition(self) -> (LogLevel, Vec<SingleConfigArgument>, bool) {
(self.log_level(), self.config, self.isolated)
}
}
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command( #[command(
author, author,
@ -38,9 +84,9 @@ use ruff_workspace::resolver::ConfigurationTransformer;
#[command(version)] #[command(version)]
pub struct Args { pub struct Args {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub(crate) command: Command,
#[clap(flatten)] #[clap(flatten)]
pub log_level_args: LogLevelArgs, pub(crate) global_options: GlobalConfigArgs,
} }
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -153,20 +199,6 @@ pub struct CheckCommand {
preview: bool, preview: bool,
#[clap(long, overrides_with("preview"), hide = true)] #[clap(long, overrides_with("preview"), hide = true)]
no_preview: bool, no_preview: bool,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
)]
pub config: Vec<SingleConfigArgument>,
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules). /// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
#[arg( #[arg(
long, long,
@ -298,17 +330,6 @@ pub struct CheckCommand {
/// Disable cache reads. /// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")] #[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
pub no_cache: bool, pub no_cache: bool,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Miscellaneous")]
pub isolated: bool,
/// Path to the cache directory. /// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")] #[arg(long, env = "RUFF_CACHE_DIR", help_heading = "Miscellaneous")]
pub cache_dir: Option<PathBuf>, pub cache_dir: Option<PathBuf>,
@ -400,20 +421,6 @@ pub struct FormatCommand {
/// difference between the current file and how the formatted file would look like. /// difference between the current file and how the formatted file would look like.
#[arg(long)] #[arg(long)]
pub diff: bool, pub diff: bool,
/// Either a path to a TOML configuration file (`pyproject.toml` or `ruff.toml`),
/// or a TOML `<KEY> = <VALUE>` pair
/// (such as you might find in a `ruff.toml` configuration file)
/// overriding a specific configuration option.
/// Overrides of individual settings using this option always take precedence
/// over all configuration files, including configuration files that were also
/// specified using `--config`.
#[arg(
long,
action = clap::ArgAction::Append,
value_name = "CONFIG_OPTION",
value_parser = ConfigArgumentParser,
)]
pub config: Vec<SingleConfigArgument>,
/// Disable cache reads. /// Disable cache reads.
#[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")] #[arg(short, long, env = "RUFF_NO_CACHE", help_heading = "Miscellaneous")]
@ -454,17 +461,6 @@ pub struct FormatCommand {
/// Set the line-length. /// Set the line-length.
#[arg(long, help_heading = "Format configuration")] #[arg(long, help_heading = "Format configuration")]
pub line_length: Option<LineLength>, pub line_length: Option<LineLength>,
/// Ignore all configuration files.
//
// Note: We can't mark this as conflicting with `--config` here
// as `--config` can be used for specifying configuration overrides
// as well as configuration files.
// Specifying a configuration file conflicts with `--isolated`;
// specifying a configuration override does not.
// If a user specifies `ruff check --isolated --config=ruff.toml`,
// we emit an error later on, after the initial parsing by clap.
#[arg(long, help_heading = "Miscellaneous")]
pub isolated: bool,
/// The name of the file when passing it through stdin. /// The name of the file when passing it through stdin.
#[arg(long, help_heading = "Miscellaneous")] #[arg(long, help_heading = "Miscellaneous")]
pub stdin_filename: Option<PathBuf>, pub stdin_filename: Option<PathBuf>,
@ -505,7 +501,7 @@ pub enum HelpFormat {
} }
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[derive(Debug, clap::Args)] #[derive(Debug, Default, Clone, clap::Args)]
pub struct LogLevelArgs { pub struct LogLevelArgs {
/// Enable verbose logging. /// Enable verbose logging.
#[arg( #[arg(
@ -553,6 +549,10 @@ impl From<&LogLevelArgs> for LogLevel {
/// Configuration-related arguments passed via the CLI. /// Configuration-related arguments passed via the CLI.
#[derive(Default)] #[derive(Default)]
pub struct ConfigArguments { pub struct ConfigArguments {
/// Whether the user specified --isolated on the command line
pub(crate) isolated: bool,
/// The logging level to be used, derived from command-line arguments passed
pub(crate) log_level: LogLevel,
/// Path to a pyproject.toml or ruff.toml configuration file (etc.). /// Path to a pyproject.toml or ruff.toml configuration file (etc.).
/// Either 0 or 1 configuration file paths may be provided on the command line. /// Either 0 or 1 configuration file paths may be provided on the command line.
config_file: Option<PathBuf>, config_file: Option<PathBuf>,
@ -573,21 +573,19 @@ impl ConfigArguments {
} }
fn from_cli_arguments( fn from_cli_arguments(
config_options: Vec<SingleConfigArgument>, global_options: GlobalConfigArgs,
per_flag_overrides: ExplicitConfigOverrides, per_flag_overrides: ExplicitConfigOverrides,
isolated: bool,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let mut new = Self { let (log_level, config_options, isolated) = global_options.partition();
per_flag_overrides, let mut config_file: Option<PathBuf> = None;
..Self::default() let mut overrides = Configuration::default();
};
for option in config_options { for option in config_options {
match option { match option {
SingleConfigArgument::SettingsOverride(overridden_option) => { SingleConfigArgument::SettingsOverride(overridden_option) => {
let overridden_option = Arc::try_unwrap(overridden_option) let overridden_option = Arc::try_unwrap(overridden_option)
.unwrap_or_else(|option| option.deref().clone()); .unwrap_or_else(|option| option.deref().clone());
new.overrides = new.overrides.combine(Configuration::from_options( overrides = overrides.combine(Configuration::from_options(
overridden_option, overridden_option,
None, None,
&path_dedot::CWD, &path_dedot::CWD,
@ -606,7 +604,7 @@ The argument `--config={}` cannot be used with `--isolated`
path.display() path.display()
); );
} }
if let Some(ref config_file) = new.config_file { if let Some(ref config_file) = config_file {
let (first, second) = (config_file.display(), path.display()); let (first, second) = (config_file.display(), path.display());
bail!( bail!(
"\ "\
@ -617,11 +615,17 @@ You cannot specify more than one configuration file on the command line.
" "
); );
} }
new.config_file = Some(path); config_file = Some(path);
} }
} }
} }
Ok(new) Ok(Self {
isolated,
log_level,
config_file,
overrides,
per_flag_overrides,
})
} }
} }
@ -635,7 +639,10 @@ impl ConfigurationTransformer for ConfigArguments {
impl CheckCommand { impl CheckCommand {
/// Partition the CLI into command-line arguments and configuration /// Partition the CLI into command-line arguments and configuration
/// overrides. /// overrides.
pub fn partition(self) -> anyhow::Result<(CheckArguments, ConfigArguments)> { pub fn partition(
self,
global_options: GlobalConfigArgs,
) -> anyhow::Result<(CheckArguments, ConfigArguments)> {
let check_arguments = CheckArguments { let check_arguments = CheckArguments {
add_noqa: self.add_noqa, add_noqa: self.add_noqa,
diff: self.diff, diff: self.diff,
@ -644,7 +651,6 @@ impl CheckCommand {
exit_zero: self.exit_zero, exit_zero: self.exit_zero,
files: self.files, files: self.files,
ignore_noqa: self.ignore_noqa, ignore_noqa: self.ignore_noqa,
isolated: self.isolated,
no_cache: self.no_cache, no_cache: self.no_cache,
output_file: self.output_file, output_file: self.output_file,
show_files: self.show_files, show_files: self.show_files,
@ -688,8 +694,7 @@ impl CheckCommand {
extension: self.extension, extension: self.extension,
}; };
let config_args = let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
Ok((check_arguments, config_args)) Ok((check_arguments, config_args))
} }
} }
@ -697,12 +702,14 @@ impl CheckCommand {
impl FormatCommand { impl FormatCommand {
/// Partition the CLI into command-line arguments and configuration /// Partition the CLI into command-line arguments and configuration
/// overrides. /// overrides.
pub fn partition(self) -> anyhow::Result<(FormatArguments, ConfigArguments)> { pub fn partition(
self,
global_options: GlobalConfigArgs,
) -> anyhow::Result<(FormatArguments, ConfigArguments)> {
let format_arguments = FormatArguments { let format_arguments = FormatArguments {
check: self.check, check: self.check,
diff: self.diff, diff: self.diff,
files: self.files, files: self.files,
isolated: self.isolated,
no_cache: self.no_cache, no_cache: self.no_cache,
stdin_filename: self.stdin_filename, stdin_filename: self.stdin_filename,
range: self.range, range: self.range,
@ -722,8 +729,7 @@ impl FormatCommand {
..ExplicitConfigOverrides::default() ..ExplicitConfigOverrides::default()
}; };
let config_args = let config_args = ConfigArguments::from_cli_arguments(global_options, cli_overrides)?;
ConfigArguments::from_cli_arguments(self.config, cli_overrides, self.isolated)?;
Ok((format_arguments, config_args)) Ok((format_arguments, config_args))
} }
} }
@ -949,7 +955,6 @@ pub struct CheckArguments {
pub exit_zero: bool, pub exit_zero: bool,
pub files: Vec<PathBuf>, pub files: Vec<PathBuf>,
pub ignore_noqa: bool, pub ignore_noqa: bool,
pub isolated: bool,
pub no_cache: bool, pub no_cache: bool,
pub output_file: Option<PathBuf>, pub output_file: Option<PathBuf>,
pub show_files: bool, pub show_files: bool,
@ -967,7 +972,6 @@ pub struct FormatArguments {
pub no_cache: bool, pub no_cache: bool,
pub diff: bool, pub diff: bool,
pub files: Vec<PathBuf>, pub files: Vec<PathBuf>,
pub isolated: bool,
pub stdin_filename: Option<PathBuf>, pub stdin_filename: Option<PathBuf>,
pub range: Option<FormatRange>, pub range: Option<FormatRange>,
} }

View File

@ -61,13 +61,8 @@ impl FormatMode {
pub(crate) fn format( pub(crate) fn format(
cli: FormatArguments, cli: FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
log_level: LogLevel,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let pyproject_config = resolve( let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mode = FormatMode::from_cli(&cli); let mode = FormatMode::from_cli(&cli);
let files = resolve_default_files(cli.files, false); let files = resolve_default_files(cli.files, false);
let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?; let (paths, resolver) = python_files_in_path(&files, &pyproject_config, config_arguments)?;
@ -202,7 +197,7 @@ pub(crate) fn format(
} }
// Report on the formatting changes. // Report on the formatting changes.
if log_level >= LogLevel::Default { if config_arguments.log_level >= LogLevel::Default {
if mode.is_diff() { if mode.is_diff() {
// Allow piping the diff to e.g. a file by writing the summary to stderr // Allow piping the diff to e.g. a file by writing the summary to stderr
results.write_summary(&mut stderr().lock())?; results.write_summary(&mut stderr().lock())?;

View File

@ -23,11 +23,7 @@ pub(crate) fn format_stdin(
cli: &FormatArguments, cli: &FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
let pyproject_config = resolve( let pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mut resolver = Resolver::new(&pyproject_config); let mut resolver = Resolver::new(&pyproject_config);
warn_incompatible_formatter_settings(&resolver); warn_incompatible_formatter_settings(&resolver);

View File

@ -7,6 +7,7 @@ use std::process::ExitCode;
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use anyhow::Result; use anyhow::Result;
use args::GlobalConfigArgs;
use clap::CommandFactory; use clap::CommandFactory;
use colored::Colorize; use colored::Colorize;
use log::warn; use log::warn;
@ -117,7 +118,7 @@ fn resolve_default_files(files: Vec<PathBuf>, is_stdin: bool) -> Vec<PathBuf> {
pub fn run( pub fn run(
Args { Args {
command, command,
log_level_args, global_options,
}: Args, }: Args,
deprecated_alias_warning: Option<&'static str>, deprecated_alias_warning: Option<&'static str>,
) -> Result<ExitStatus> { ) -> Result<ExitStatus> {
@ -147,8 +148,7 @@ pub fn run(
#[cfg(windows)] #[cfg(windows)]
assert!(colored::control::set_virtual_terminal(true).is_ok()); assert!(colored::control::set_virtual_terminal(true).is_ok());
let log_level = LogLevel::from(&log_level_args); set_up_logging(global_options.log_level())?;
set_up_logging(&log_level)?;
if let Some(deprecated_alias_warning) = deprecated_alias_warning { if let Some(deprecated_alias_warning) = deprecated_alias_warning {
warn_user!("{}", deprecated_alias_warning); warn_user!("{}", deprecated_alias_warning);
@ -181,38 +181,34 @@ pub fn run(
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::Clean => { Command::Clean => {
commands::clean::clean(log_level)?; commands::clean::clean(global_options.log_level())?;
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::GenerateShellCompletion { shell } => { Command::GenerateShellCompletion { shell } => {
shell.generate(&mut Args::command(), &mut stdout()); shell.generate(&mut Args::command(), &mut stdout());
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
} }
Command::Check(args) => check(args, log_level), Command::Check(args) => check(args, global_options),
Command::Format(args) => format(args, log_level), Command::Format(args) => format(args, global_options),
} }
} }
fn format(args: FormatCommand, log_level: LogLevel) -> Result<ExitStatus> { fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
let (cli, config_arguments) = args.partition()?; let (cli, config_arguments) = args.partition(global_options)?;
if is_stdin(&cli.files, cli.stdin_filename.as_deref()) { if is_stdin(&cli.files, cli.stdin_filename.as_deref()) {
commands::format_stdin::format_stdin(&cli, &config_arguments) commands::format_stdin::format_stdin(&cli, &config_arguments)
} else { } else {
commands::format::format(cli, &config_arguments, log_level) commands::format::format(cli, &config_arguments)
} }
} }
pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> { pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
let (cli, config_arguments) = args.partition()?; let (cli, config_arguments) = args.partition(global_options)?;
// Construct the "default" settings. These are used when no `pyproject.toml` // Construct the "default" settings. These are used when no `pyproject.toml`
// files are present, or files are injected from outside of the hierarchy. // files are present, or files are injected from outside of the hierarchy.
let pyproject_config = resolve::resolve( let pyproject_config = resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
cli.isolated,
&config_arguments,
cli.stdin_filename.as_deref(),
)?;
let mut writer: Box<dyn Write> = match cli.output_file { let mut writer: Box<dyn Write> = match cli.output_file {
Some(path) if !cli.watch => { Some(path) if !cli.watch => {
@ -303,7 +299,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
} }
let modifications = let modifications =
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?; commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
if modifications > 0 && log_level >= LogLevel::Default { if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
let s = if modifications == 1 { "" } else { "s" }; let s = if modifications == 1 { "" } else { "s" };
#[allow(clippy::print_stderr)] #[allow(clippy::print_stderr)]
{ {
@ -315,7 +311,7 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
let printer = Printer::new( let printer = Printer::new(
output_format, output_format,
log_level, config_arguments.log_level,
fix_mode, fix_mode,
unsafe_fixes, unsafe_fixes,
printer_flags, printer_flags,
@ -372,11 +368,8 @@ pub fn check(args: CheckCommand, log_level: LogLevel) -> Result<ExitStatus> {
}; };
if matches!(change_kind, ChangeKind::Configuration) { if matches!(change_kind, ChangeKind::Configuration) {
pyproject_config = resolve::resolve( pyproject_config =
cli.isolated, resolve::resolve(&config_arguments, cli.stdin_filename.as_deref())?;
&config_arguments,
cli.stdin_filename.as_deref(),
)?;
} }
Printer::clear_screen()?; Printer::clear_screen()?;
printer.write_to_user("File change detected...\n"); printer.write_to_user("File change detected...\n");

View File

@ -16,12 +16,11 @@ use crate::args::ConfigArguments;
/// Resolve the relevant settings strategy and defaults for the current /// Resolve the relevant settings strategy and defaults for the current
/// invocation. /// invocation.
pub fn resolve( pub fn resolve(
isolated: bool,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
stdin_filename: Option<&Path>, stdin_filename: Option<&Path>,
) -> Result<PyprojectConfig> { ) -> Result<PyprojectConfig> {
// First priority: if we're running in isolated mode, use the default settings. // First priority: if we're running in isolated mode, use the default settings.
if isolated { if config_arguments.isolated {
let config = config_arguments.transform(Configuration::default()); let config = config_arguments.transform(Configuration::default());
let settings = config.into_settings(&path_dedot::CWD)?; let settings = config.into_settings(&path_dedot::CWD)?;
debug!("Isolated mode, not reading any pyproject.toml"); debug!("Isolated mode, not reading any pyproject.toml");

View File

@ -0,0 +1,105 @@
//! Tests for the --version command
use std::fs;
use std::process::Command;
use anyhow::Result;
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
use tempfile::TempDir;
const BIN_NAME: &str = "ruff";
const VERSION_FILTER: [(&str, &str); 1] = [(
r"\d+\.\d+\.\d+(\+\d+)?( \(\w{9} \d\d\d\d-\d\d-\d\d\))?",
"[VERSION]",
)];
#[test]
fn version_basics() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version"), @r###"
success: true
exit_code: 0
----- stdout -----
ruff [VERSION]
----- stderr -----
"###
);
});
}
/// `--config` is a global option,
/// so it's allowed to pass --config to subcommands such as `version`
/// -- the flag is simply ignored
#[test]
fn config_option_allowed_but_ignored() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_dot_toml = tempdir.path().join("ruff.toml");
fs::File::create(&ruff_dot_toml)?;
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.arg("version")
.arg("--config")
.arg(&ruff_dot_toml)
.args(["--config", "lint.isort.extra-standard-library = ['foo', 'bar']"]), @r###"
success: true
exit_code: 0
----- stdout -----
ruff [VERSION]
----- stderr -----
"###
);
});
Ok(())
}
#[test]
fn config_option_ignored_but_validated() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME))
.arg("version")
.args(["--config", "foo = bar"]), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'foo = bar' for '--config <CONFIG_OPTION>'
tip: A `--config` flag must either be a path to a `.toml` configuration file
or a TOML `<KEY> = <VALUE>` pair overriding a specific configuration
option
The supplied argument is not valid TOML:
TOML parse error at line 1, column 7
|
1 | foo = bar
| ^
invalid string
expected `"`, `'`
For more information, try '--help'.
"###
);
});
}
/// `--isolated` is also a global option,
#[test]
fn isolated_option_allowed() {
insta::with_settings!({filters => VERSION_FILTER.to_vec()}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).arg("version").arg("--isolated"), @r###"
success: true
exit_code: 0
----- stdout -----
ruff [VERSION]
----- stderr -----
"###
);
});
}

View File

@ -27,7 +27,7 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, LogLevelArgs}; use ruff::args::{ConfigArguments, FormatArguments, FormatCommand, GlobalConfigArgs, LogLevelArgs};
use ruff::resolve::resolve; use ruff::resolve::resolve;
use ruff_formatter::{FormatError, LineWidth, PrintError}; use ruff_formatter::{FormatError, LineWidth, PrintError};
use ruff_linter::logging::LogLevel; use ruff_linter::logging::LogLevel;
@ -43,7 +43,7 @@ fn parse_cli(dirs: &[PathBuf]) -> anyhow::Result<(FormatArguments, ConfigArgumen
.no_binary_name(true) .no_binary_name(true)
.get_matches_from(dirs); .get_matches_from(dirs);
let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?; let arguments: FormatCommand = FormatCommand::from_arg_matches(&args_matches)?;
let (cli, config_arguments) = arguments.partition()?; let (cli, config_arguments) = arguments.partition(GlobalConfigArgs::default())?;
Ok((cli, config_arguments)) Ok((cli, config_arguments))
} }
@ -52,11 +52,7 @@ fn find_pyproject_config(
cli: &FormatArguments, cli: &FormatArguments,
config_arguments: &ConfigArguments, config_arguments: &ConfigArguments,
) -> anyhow::Result<PyprojectConfig> { ) -> anyhow::Result<PyprojectConfig> {
let mut pyproject_config = resolve( let mut pyproject_config = resolve(config_arguments, cli.stdin_filename.as_deref())?;
cli.isolated,
config_arguments,
cli.stdin_filename.as_deref(),
)?;
// We don't want to format pyproject.toml // We don't want to format pyproject.toml
pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([ pyproject_config.settings.file_resolver.include = FilePatternSet::try_from_iter([
FilePattern::Builtin("*.py"), FilePattern::Builtin("*.py"),

View File

@ -4,8 +4,8 @@
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use ruff::check; use ruff::{args::GlobalConfigArgs, check};
use ruff_linter::logging::{set_up_logging, LogLevel}; use ruff_linter::logging::set_up_logging;
use std::process::ExitCode; use std::process::ExitCode;
mod format_dev; mod format_dev;
@ -28,6 +28,8 @@ const ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../");
struct Args { struct Args {
#[command(subcommand)] #[command(subcommand)]
command: Command, command: Command,
#[clap(flatten)]
global_options: GlobalConfigArgs,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
@ -57,8 +59,6 @@ enum Command {
Repeat { Repeat {
#[clap(flatten)] #[clap(flatten)]
args: ruff::args::CheckCommand, args: ruff::args::CheckCommand,
#[clap(flatten)]
log_level_args: ruff::args::LogLevelArgs,
/// Run this many times /// Run this many times
#[clap(long)] #[clap(long)]
repeat: usize, repeat: usize,
@ -75,9 +75,12 @@ enum Command {
} }
fn main() -> Result<ExitCode> { fn main() -> Result<ExitCode> {
let args = Args::parse(); let Args {
command,
global_options,
} = Args::parse();
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
match args.command { match command {
Command::GenerateAll(args) => generate_all::main(&args)?, Command::GenerateAll(args) => generate_all::main(&args)?,
Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?, Command::GenerateJSONSchema(args) => generate_json_schema::main(&args)?,
Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()), Command::GenerateRulesTable => println!("{}", generate_rules_table::generate()),
@ -89,14 +92,12 @@ fn main() -> Result<ExitCode> {
Command::PrintTokens(args) => print_tokens::main(&args)?, Command::PrintTokens(args) => print_tokens::main(&args)?,
Command::RoundTrip(args) => round_trip::main(&args)?, Command::RoundTrip(args) => round_trip::main(&args)?,
Command::Repeat { Command::Repeat {
args, args: subcommand_args,
repeat, repeat,
log_level_args,
} => { } => {
let log_level = LogLevel::from(&log_level_args); set_up_logging(global_options.log_level())?;
set_up_logging(&log_level)?;
for _ in 0..repeat { for _ in 0..repeat {
check(args.clone(), log_level)?; check(subcommand_args.clone(), global_options.clone())?;
} }
} }
Command::FormatDev(args) => { Command::FormatDev(args) => {

View File

@ -121,7 +121,7 @@ impl LogLevel {
} }
} }
pub fn set_up_logging(level: &LogLevel) -> Result<()> { pub fn set_up_logging(level: LogLevel) -> Result<()> {
fern::Dispatch::new() fern::Dispatch::new()
.format(|out, message, record| match record.level() { .format(|out, message, record| match record.level() {
Level::Error => { Level::Error => {

View File

@ -540,6 +540,17 @@ Log levels:
-s, --silent Disable all logging (but still exit with status code "1" upon -s, --silent Disable all logging (but still exit with status code "1" upon
detecting diagnostics) detecting diagnostics)
Global options:
--config <CONFIG_OPTION>
Either a path to a TOML configuration file (`pyproject.toml` or
`ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
find in a `ruff.toml` configuration file) overriding a specific
configuration option. Overrides of individual settings using this
option always take precedence over all configuration files, including
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
For help with a specific command, see: `ruff help <command>`. For help with a specific command, see: `ruff help <command>`.
``` ```
@ -596,13 +607,6 @@ Options:
--preview --preview
Enable preview mode; checks will include unstable rules and fixes. Enable preview mode; checks will include unstable rules and fixes.
Use `--no-preview` to disable Use `--no-preview` to disable
--config <CONFIG_OPTION>
Either a path to a TOML configuration file (`pyproject.toml` or
`ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
find in a `ruff.toml` configuration file) overriding a specific
configuration option. Overrides of individual settings using this
option always take precedence over all configuration files, including
configuration files that were also specified using `--config`
--extension <EXTENSION> --extension <EXTENSION>
List of mappings from file extension to language (one of ["python", List of mappings from file extension to language (one of ["python",
"ipynb", "pyi"]). For example, to treat `.ipy` files as IPython "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython
@ -658,8 +662,6 @@ File selection:
Miscellaneous: Miscellaneous:
-n, --no-cache -n, --no-cache
Disable cache reads [env: RUFF_NO_CACHE=] Disable cache reads [env: RUFF_NO_CACHE=]
--isolated
Ignore all configuration files
--cache-dir <CACHE_DIR> --cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=] Path to the cache directory [env: RUFF_CACHE_DIR=]
--stdin-filename <STDIN_FILENAME> --stdin-filename <STDIN_FILENAME>
@ -675,6 +677,17 @@ Log levels:
-q, --quiet Print diagnostics, but nothing else -q, --quiet Print diagnostics, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon -s, --silent Disable all logging (but still exit with status code "1" upon
detecting diagnostics) detecting diagnostics)
Global options:
--config <CONFIG_OPTION>
Either a path to a TOML configuration file (`pyproject.toml` or
`ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
find in a `ruff.toml` configuration file) overriding a specific
configuration option. Overrides of individual settings using this
option always take precedence over all configuration files, including
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
``` ```
<!-- End auto-generated check help. --> <!-- End auto-generated check help. -->
@ -699,13 +712,6 @@ Options:
Avoid writing any formatted files back; instead, exit with a non-zero Avoid writing any formatted files back; instead, exit with a non-zero
status code and the difference between the current file and how the status code and the difference between the current file and how the
formatted file would look like formatted file would look like
--config <CONFIG_OPTION>
Either a path to a TOML configuration file (`pyproject.toml` or
`ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
find in a `ruff.toml` configuration file) overriding a specific
configuration option. Overrides of individual settings using this
option always take precedence over all configuration files, including
configuration files that were also specified using `--config`
--extension <EXTENSION> --extension <EXTENSION>
List of mappings from file extension to language (one of ["python", List of mappings from file extension to language (one of ["python",
"ipynb", "pyi"]). For example, to treat `.ipy` files as IPython "ipynb", "pyi"]). For example, to treat `.ipy` files as IPython
@ -724,8 +730,6 @@ Miscellaneous:
Disable cache reads [env: RUFF_NO_CACHE=] Disable cache reads [env: RUFF_NO_CACHE=]
--cache-dir <CACHE_DIR> --cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=] Path to the cache directory [env: RUFF_CACHE_DIR=]
--isolated
Ignore all configuration files
--stdin-filename <STDIN_FILENAME> --stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin The name of the file when passing it through stdin
@ -755,6 +759,17 @@ Log levels:
-q, --quiet Print diagnostics, but nothing else -q, --quiet Print diagnostics, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon -s, --silent Disable all logging (but still exit with status code "1" upon
detecting diagnostics) detecting diagnostics)
Global options:
--config <CONFIG_OPTION>
Either a path to a TOML configuration file (`pyproject.toml` or
`ruff.toml`), or a TOML `<KEY> = <VALUE>` pair (such as you might
find in a `ruff.toml` configuration file) overriding a specific
configuration option. Overrides of individual settings using this
option always take precedence over all configuration files, including
configuration files that were also specified using `--config`
--isolated
Ignore all configuration files
``` ```
<!-- End auto-generated format help. --> <!-- End auto-generated format help. -->