Skip to content

Commit

Permalink
Use subcommands for CLI instead of incompatible boolean flags
Browse files Browse the repository at this point in the history
  • Loading branch information
not-my-profile committed Jan 25, 2023
1 parent ce7f18c commit 9716ad2
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 250 deletions.
23 changes: 23 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Breaking Changes

## Unreleased

The subcommand handling of the CLI has been revised:

* For backwards-compatibility subcommands still start with a double-dash
`--`, they now however have to be passed as the first argument and
subcommands now fail when used together with unsupported argument
instead of silently ignoring them.

For example previously you could run:

ruff --respect-gitignore --format json --explain E402

While the `--explain` command doesn't at all support the
`--respect-gitignore` argument, ruff previously didn't complain, now
it does. With the new synopsis you also have to pass the command
first, so the previous command now becomes:

ruff --explain E402 --format json

* `--explain` previously treated `--format grouped` just like `--format text`
(this is no longer supported, use `--format text` instead)

## 0.0.226

### `misplaced-comparison-constant` (`PLC2201`) was deprecated in favor of `SIM300` ([#1980](https://github.com/charliermarsh/ruff/pull/1980))
Expand Down
92 changes: 15 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,86 +345,24 @@ See `ruff --help` for more:
```
Ruff: An extremely fast Python linter.
Usage: ruff [OPTIONS] [FILES]...
Usage: ruff [OPTIONS] <COMMAND>
Arguments:
[FILES]...
Commands:
--check Run ruff on the given files or directories (this command is used by default and may be omitted)
--add-noqa Automatically add `noqa` directives to failing lines
--explain Explain a rule
--clean Clear any caches in the current directory or any subdirectories
--show-files See the files Ruff will be run against with the current settings
--show-settings See the settings Ruff will use to lint a given Python file
Options:
--config <CONFIG>
Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
-v, --verbose
Enable verbose logging
-q, --quiet
Print lint violations, but nothing else
-s, --silent
Disable all logging (but still exit with status code "1" upon detecting lint violations)
-e, --exit-zero
Exit with status code "0", even upon detecting lint violations
-w, --watch
Run in watch mode by re-running whenever files change
--fix
Attempt to automatically fix lint violations
--fix-only
Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
--diff
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
-n, --no-cache
Disable cache reads
--isolated
Ignore all configuration files
--select <RULE_CODE>
Comma-separated list of rule codes to enable (or ALL, to enable all rules)
--extend-select <RULE_CODE>
Like --select, but adds additional rule codes on top of the selected ones
--ignore <RULE_CODE>
Comma-separated list of rule codes to disable
--extend-ignore <RULE_CODE>
Like --ignore, but adds additional rule codes on top of the ignored ones
--exclude <FILE_PATTERN>
List of paths, used to omit files and/or directories from analysis
--extend-exclude <FILE_PATTERN>
Like --exclude, but adds additional files and directories on top of those already excluded
--fixable <RULE_CODE>
List of rule codes to treat as eligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--unfixable <RULE_CODE>
List of rule codes to treat as ineligible for autofix. Only applicable when autofix itself is enabled (e.g., via `--fix`)
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
--cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=]
--show-source
Show violations with source code
--respect-gitignore
Respect file exclusions via `.gitignore` and other standard ignore files
--force-exclude
Enforce exclusions, even for paths passed to Ruff directly on the command-line
--update-check
Enable or disable automatic update checks
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
The minimum Python version that should be supported
--line-length <LINE_LENGTH>
Set the line-length for length-associated rules and automatic formatting
--add-noqa
Enable automatic additions of `noqa` directives to failing lines
--clean
Clear any caches in the current directory or any subdirectories
--explain <EXPLAIN>
Explain a rule
--show-files
See the files Ruff will be run against with the current settings
--show-settings
See the settings Ruff will use to lint a given Python file
-h, --help
Print help information
-V, --version
Print version information
-v, --verbose Enable verbose logging
-q, --quiet Print lint violations, but nothing else
-s, --silent Disable all logging (but still exit with status code "1" upon detecting lint violations)
-h, --help Print help information
-V, --version Print version information
To get help about a specific command run <COMMAND> --help.
```
<!-- End auto-generated cli help. -->

Expand Down
197 changes: 76 additions & 121 deletions ruff_cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(clippy::module_name_repetitions)]
use std::path::PathBuf;

use clap::{command, Parser};
Expand All @@ -15,25 +16,91 @@ use rustc_hash::FxHashMap;
#[command(
author,
name = "ruff",
about = "Ruff: An extremely fast Python linter."
about = "Ruff: An extremely fast Python linter.",
after_help = "To get help about a specific command run <COMMAND> --help."
)]
#[command(version)]
#[allow(clippy::struct_excessive_bools)]
pub struct Args {
#[arg(required_unless_present_any = ["clean", "explain", "generate_shell_completion"])]
#[command(subcommand)]
pub command: Command,
#[clap(flatten)]
pub log_level_args: LogLevelArgs,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, clap::Subcommand)]
#[clap(disable_help_subcommand = true)]
pub enum Command {
#[clap(flatten)]
Lint(LintCommand),
/// Explain a rule.
#[clap(name = "--explain")]
Explain {
#[arg(value_parser=Rule::from_code)]
rule: &'static Rule,

/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_FORMAT", default_value = "text")]
format: HelpFormat,
},
/// Clear any caches in the current directory or any subdirectories.
#[clap(name = "--clean")]
Clean,
/// Generate shell completion
#[clap(name = "--generate-shell-completion", hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
/// See the files Ruff will be run against with the current settings.
#[clap(name = "--show-files")]
ShowFiles(CommonOptions),
/// See the settings Ruff will use to lint a given Python file.
#[clap(name = "--show-settings")]
ShowSettings(CommonOptions),
}

#[derive(Debug, clap::Subcommand)]
pub enum LintCommand {
/// Run ruff on the given files or directories (this command is used by
/// default and may be omitted)
#[clap(name = "--check")]
Check {
#[clap(flatten)]
options: CommonOptions,
#[clap(flatten)]
check_only_args: CheckOnlyArgs,
},
/// Automatically add `noqa` directives to failing lines.
#[clap(name = "--add-noqa")]
AddNoqa(CommonOptions),
}

#[derive(Debug, clap::Args)]
pub struct CheckOnlyArgs {
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// The name of the file when passing it through stdin.
#[arg(long)]
pub stdin_filename: Option<PathBuf>,
}

#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum HelpFormat {
Text,
Json,
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, clap::Args)]
pub struct CommonOptions {
#[arg(required = true)]
pub files: Vec<PathBuf>,
/// Path to the `pyproject.toml` or `ruff.toml` file to use for
/// configuration.
#[arg(long, conflicts_with = "isolated")]
pub config: Option<PathBuf>,
#[clap(flatten)]
pub log_level_args: LogLevelArgs,
/// Exit with status code "0", even upon detecting lint violations.
#[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 violations.
#[arg(long, overrides_with("no_fix"))]
fix: bool,
Expand Down Expand Up @@ -91,9 +158,6 @@ pub struct Args {
/// Output serialization format for violations.
#[arg(long, value_enum, env = "RUFF_FORMAT")]
pub format: Option<SerializationFormat>,
/// The name of the file when passing it through stdin.
#[arg(long)]
pub stdin_filename: Option<PathBuf>,
/// Path to the cache directory.
#[arg(long, env = "RUFF_CACHE_DIR")]
pub cache_dir: Option<PathBuf>,
Expand Down Expand Up @@ -129,101 +193,8 @@ pub struct Args {
/// formatting.
#[arg(long)]
pub line_length: Option<usize>,
/// Enable automatic additions of `noqa` directives to failing lines.
#[arg(
long,
// conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub add_noqa: bool,
/// Clear any caches in the current directory or any subdirectories.
#[arg(
long,
// Fake subcommands.
conflicts_with = "add_noqa",
// conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub clean: bool,
/// Explain a rule.
#[arg(
long,
value_parser=Rule::from_code,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
// conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub explain: Option<&'static Rule>,
/// Generate shell completion
#[arg(
long,
hide = true,
value_name = "SHELL",
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
// conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub generate_shell_completion: Option<clap_complete_command::Shell>,
/// See the files Ruff will be run against with the current settings.
#[arg(
long,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
// conflicts_with = "show_files",
conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub show_files: bool,
/// See the settings Ruff will use to lint a given Python file.
#[arg(
long,
// Fake subcommands.
conflicts_with = "add_noqa",
conflicts_with = "clean",
conflicts_with = "explain",
conflicts_with = "generate_shell_completion",
conflicts_with = "show_files",
// conflicts_with = "show_settings",
// Unsupported default-command arguments.
conflicts_with = "stdin_filename",
conflicts_with = "watch",
)]
pub show_settings: bool,
}

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, clap::Args)]
pub struct LogLevelArgs {
/// Enable verbose logging.
Expand Down Expand Up @@ -252,26 +223,18 @@ impl From<&LogLevelArgs> for LogLevel {
}
}

impl Args {
impl CommonOptions {
/// Partition the CLI into command-line arguments and configuration
/// overrides.
pub fn partition(self) -> (Arguments, Overrides) {
(
Arguments {
add_noqa: self.add_noqa,
clean: self.clean,
config: self.config,
diff: self.diff,
exit_zero: self.exit_zero,
explain: self.explain,
files: self.files,
generate_shell_completion: self.generate_shell_completion,
isolated: self.isolated,
no_cache: self.no_cache,
show_files: self.show_files,
show_settings: self.show_settings,
stdin_filename: self.stdin_filename,
watch: self.watch,
},
Overrides {
dummy_variable_rgx: self.dummy_variable_rgx,
Expand Down Expand Up @@ -316,20 +279,12 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
/// etc.).
#[allow(clippy::struct_excessive_bools)]
pub struct Arguments {
pub add_noqa: bool,
pub clean: bool,
pub config: Option<PathBuf>,
pub diff: bool,
pub exit_zero: bool,
pub explain: Option<&'static Rule>,
pub files: Vec<PathBuf>,
pub generate_shell_completion: Option<clap_complete_command::Shell>,
pub isolated: bool,
pub no_cache: bool,
pub show_files: bool,
pub show_settings: bool,
pub stdin_filename: Option<PathBuf>,
pub watch: bool,
}

/// CLI settings that function as configuration overrides.
Expand Down
Loading

0 comments on commit 9716ad2

Please sign in to comment.