use std::path::PathBuf; use clap::{command, Parser}; use regex::Regex; use rustc_hash::FxHashMap; use crate::checks::CheckCode; use crate::checks_gen::CheckCodePrefix; use crate::logging::LogLevel; use crate::settings::types::{ FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat, }; #[derive(Debug, Parser)] #[command(author, about = "Ruff: An extremely fast Python linter.")] #[command(version)] #[allow(clippy::struct_excessive_bools)] pub struct Cli { #[arg(required_unless_present_any = ["explain", "generate_shell_completion"])] pub files: Vec, /// Path to the `pyproject.toml` file to use for configuration. #[arg(long)] pub config: Option, /// Enable verbose logging. #[arg(short, long, group = "verbosity")] pub verbose: bool, /// Only log errors. #[arg(short, long, group = "verbosity")] pub quiet: bool, /// Disable all logging (but still exit with status code "1" upon detecting /// errors). #[arg(short, long, group = "verbosity")] pub silent: bool, /// Exit with status code "0", even upon detecting errors. #[arg(short, long)] pub exit_zero: bool, /// Run in watch mode by re-running whenever files change. #[arg(short, long)] pub watch: bool, /// Attempt to automatically fix lint errors. #[arg(long, overrides_with("no_fix"))] fix: bool, #[clap(long, overrides_with("fix"), hide = true)] no_fix: bool, /// Disable cache reads. #[arg(short, long)] pub no_cache: bool, /// List of error codes to enable. #[arg(long, value_delimiter = ',')] pub select: Option>, /// Like --select, but adds additional error codes on top of the selected /// ones. #[arg(long, value_delimiter = ',')] pub extend_select: Option>, /// List of error codes to ignore. #[arg(long, value_delimiter = ',')] pub ignore: Option>, /// Like --ignore, but adds additional error codes on top of the ignored /// ones. #[arg(long, value_delimiter = ',')] pub extend_ignore: Option>, /// List of paths, used to exclude files and/or directories from checks. #[arg(long, value_delimiter = ',')] pub exclude: Option>, /// Like --exclude, but adds additional files and directories on top of the /// excluded ones. #[arg(long, value_delimiter = ',')] pub extend_exclude: Option>, /// List of error codes to treat as eligible for autofix. Only applicable /// when autofix itself is enabled (e.g., via `--fix`). #[arg(long, value_delimiter = ',')] pub fixable: Option>, /// List of error codes to treat as ineligible for autofix. Only applicable /// when autofix itself is enabled (e.g., via `--fix`). #[arg(long, value_delimiter = ',')] pub unfixable: Option>, /// List of mappings from file pattern to code to exclude #[arg(long, value_delimiter = ',')] pub per_file_ignores: Option>, /// Output serialization format for error messages. #[arg(long, value_enum)] pub format: Option, /// Show violations with source code. #[arg(long)] pub show_source: bool, /// See the files Ruff will be run against with the current settings. #[arg(long)] pub show_files: bool, /// See Ruff's settings. #[arg(long)] pub show_settings: bool, /// Enable automatic additions of noqa directives to failing lines. #[arg(long)] pub add_noqa: bool, /// Regular expression matching the name of dummy variables. #[arg(long)] pub dummy_variable_rgx: Option, /// The minimum Python version that should be supported. #[arg(long)] pub target_version: Option, /// Set the line-length for length-associated checks and automatic /// formatting. #[arg(long)] pub line_length: Option, /// Max McCabe complexity allowed for a function. #[arg(long)] pub max_complexity: Option, /// Round-trip auto-formatting. // TODO(charlie): This should be a sub-command. #[arg(long, hide = true)] pub autoformat: bool, /// The name of the file when passing it through stdin. #[arg(long)] pub stdin_filename: Option, /// Explain a rule. #[arg(long)] pub explain: Option, /// Generate shell completion #[arg(long, hide = true, value_name = "SHELL")] pub generate_shell_completion: Option, } impl Cli { /// Partition the CLI into command-line arguments and configuration /// overrides. pub fn partition(self) -> (Arguments, Overrides) { ( Arguments { add_noqa: self.add_noqa, autoformat: self.autoformat, config: self.config, exit_zero: self.exit_zero, explain: self.explain, files: self.files, generate_shell_completion: self.generate_shell_completion, no_cache: self.no_cache, quiet: self.quiet, show_files: self.show_files, show_settings: self.show_settings, silent: self.silent, stdin_filename: self.stdin_filename, verbose: self.verbose, watch: self.watch, }, Overrides { dummy_variable_rgx: self.dummy_variable_rgx, exclude: self.exclude, extend_exclude: self.extend_exclude, extend_ignore: self.extend_ignore, extend_select: self.extend_select, fixable: self.fixable, ignore: self.ignore, line_length: self.line_length, max_complexity: self.max_complexity, per_file_ignores: self.per_file_ignores, select: self.select, show_source: self.show_source, target_version: self.target_version, unfixable: self.unfixable, // TODO(charlie): Included in `pyproject.toml`, but not inherited. fix: resolve_bool_arg(self.fix, self.no_fix), format: self.format, }, ) } } fn resolve_bool_arg(yes: bool, no: bool) -> Option { match (yes, no) { (true, false) => Some(true), (false, true) => Some(false), (false, false) => None, (..) => unreachable!("Clap should make this impossible"), } } /// CLI settings that are distinct from configuration (commands, lists of files, /// etc.). #[allow(clippy::struct_excessive_bools)] pub struct Arguments { pub add_noqa: bool, pub autoformat: bool, pub config: Option, pub exit_zero: bool, pub explain: Option, pub files: Vec, pub generate_shell_completion: Option, pub no_cache: bool, pub quiet: bool, pub show_files: bool, pub show_settings: bool, pub silent: bool, pub stdin_filename: Option, pub verbose: bool, pub watch: bool, } /// CLI settings that function as configuration overrides. #[allow(clippy::struct_excessive_bools)] pub struct Overrides { pub dummy_variable_rgx: Option, pub exclude: Option>, pub extend_exclude: Option>, pub extend_ignore: Option>, pub extend_select: Option>, pub fixable: Option>, pub ignore: Option>, pub line_length: Option, pub max_complexity: Option, pub per_file_ignores: Option>, pub select: Option>, pub show_source: bool, pub target_version: Option, pub unfixable: Option>, // TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`. pub fix: Option, pub format: Option, } /// Map the CLI settings to a `LogLevel`. pub fn extract_log_level(cli: &Arguments) -> LogLevel { if cli.silent { LogLevel::Silent } else if cli.quiet { LogLevel::Quiet } else if cli.verbose { LogLevel::Verbose } else { LogLevel::Default } } /// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`. pub fn collect_per_file_ignores(pairs: Vec) -> Vec { let mut per_file_ignores: FxHashMap> = FxHashMap::default(); for pair in pairs { per_file_ignores .entry(pair.pattern) .or_insert_with(Vec::new) .push(pair.prefix); } per_file_ignores .into_iter() .map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes)) .collect() }