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
This commit greatly simplifies the implementation of the CLI,
as well as the user expierence (since --help no longer lists all
options even though many of them are in fact incompatible).

To preserve backwards-compatability as much as possible aliases have
been added for the new subcommands, so for example the following two
commands are equivalent:

    ruff explain E402 --format json
    ruff --explain E402 --format json

However for this to work the legacy-format double-dash command has to
come first, i.e. the following no longer works:

    ruff --format json --explain E402

Since ruff previously had an implicitly default subcommand,
this is preserved for backwards compatibility, i.e. the following two
commands are equivalent:

    ruff .
    ruff check .

Previously ruff didn't complain about several argument combinations that
should have never been allowed, e.g:

    ruff --explain RUF001 --line-length 33

previously worked but now rightfully fails since the explain command
doesn't support a `--line-length` option.
  • Loading branch information
not-my-profile committed Jan 26, 2023
1 parent d238509 commit 3a6032a
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 189 deletions.
35 changes: 35 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Breaking Changes

## Unreleased

`--explain`, `--clean` and `--generate-shell-completion` are now
implemented as subcommands:

ruff . # still works and will always work
ruff check . # now also works

ruff --explain E402 # still works
ruff explain E402 # now also works

ruff --format json --explain E402 # no longer works
# the command has to come first:
ruff --explain E402 --format json # or using the new syntax:
ruff explain E402 --format json

Please also note that:

* the subcommands will now fail when invoked with unsupported arguments
instead of silently ignoring them, e.g. the following will now fail:

ruff --explain E402 --respect-gitignore

Since the `explain` command doesn't support `--respect-gitignore`.

* The semantics of `ruff <arg>` has changed when `<arg>` is a
subcommand, e.g. before `ruff explain` would look for a file or
directory `explain` in the current directory but now it just invokes
the explain command. Note that scripts invoking ruff should supply
`--` anyway before any positional arguments and the semantics of
`ruff -- <arg>` have not changed.

* `--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
73 changes: 10 additions & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,77 +346,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)
explain Explain a rule
clean Clear any caches in the current directory or any subdirectories
help Print this message or the help of the given subcommand(s)
Options:
--fix Attempt to automatically fix lint violations
--show-source Show violations with source code
--diff Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
-w, --watch Run in watch mode by re-running whenever files change
--fix-only Fix any fixable lint violations, but don't report on leftover violations. Implies `--fix`
--format <FORMAT> Output serialization format for violations [env: RUFF_FORMAT=] [possible values: text, json, junit, grouped, github, gitlab, pylint]
--config <CONFIG> Path to the `pyproject.toml` or `ruff.toml` file to use for configuration
--add-noqa Enable automatic additions of `noqa` directives to failing lines
--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
-V, --version Print version
Rule selection:
--select <RULE_CODE>
Comma-separated list of rule codes to enable (or ALL, to enable all rules)
--ignore <RULE_CODE>
Comma-separated list of rule codes to disable
--extend-select <RULE_CODE>
Like --select, but adds additional rule codes on top of the selected ones
--extend-ignore <RULE_CODE>
Like --ignore, but adds additional rule codes on top of the ignored ones
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--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`)
File selection:
--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
--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
Rule configuration:
--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
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
Miscellaneous:
-n, --no-cache
Disable cache reads
--isolated
Ignore all configuration files
--cache-dir <CACHE_DIR>
Path to the cache directory [env: RUFF_CACHE_DIR=]
--stdin-filename <STDIN_FILENAME>
The name of the file when passing it through stdin
-e, --exit-zero
Exit with status code "0", even upon detecting lint violations
--update-check
Enable or disable automatic update checks
Subcommands:
--explain <EXPLAIN> Explain a rule
--clean Clear any caches in the current directory or any subdirectories
-h, --help Print help
-V, --version Print version
Log levels:
-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)
To get help about a specific command, see 'ruff help <command>'.
```
<!-- End auto-generated cli help. -->

Expand Down
137 changes: 63 additions & 74 deletions ruff_cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,44 @@ 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, see 'ruff help <command>'."
)]
#[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)]
pub enum Command {
/// Run ruff on the given files or directories (this command is used by
/// default and may be omitted)
Check(CheckArgs),
/// Explain a rule.
#[clap(alias = "--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(alias = "--clean")]
Clean,
/// Generate shell completion
#[clap(alias = "--generate-shell-completion", hide = true)]
GenerateShellCompletion { shell: clap_complete_command::Shell },
}

#[derive(Debug, clap::Args)]
#[allow(clippy::struct_excessive_bools, clippy::module_name_repetitions)]
pub struct CheckArgs {
pub files: Vec<PathBuf>,
/// Attempt to automatically fix lint violations.
#[arg(long, overrides_with("no_fix"))]
Expand Down Expand Up @@ -183,74 +215,18 @@ pub struct Args {
#[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,
/// Explain a rule.
#[arg(
long,
value_parser=Rule::from_code,
help_heading="Subcommands",
// 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>,
/// Clear any caches in the current directory or any subdirectories.
#[arg(
long,
help_heading="Subcommands",
// 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,
/// 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.
Expand All @@ -263,32 +239,51 @@ pub struct Args {
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,
#[clap(flatten)]
pub log_level_args: LogLevelArgs,
}

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

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, clap::Args)]
pub struct LogLevelArgs {
/// Enable verbose logging.
#[arg(short, long, group = "verbosity", help_heading = "Log levels")]
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub verbose: bool,
/// Print lint violations, but nothing else.
#[arg(short, long, group = "verbosity", help_heading = "Log levels")]
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub quiet: bool,
/// Disable all logging (but still exit with status code "1" upon detecting
/// lint violations).
#[arg(short, long, group = "verbosity", help_heading = "Log levels")]
#[arg(
short,
long,
global = true,
group = "verbosity",
help_heading = "Log levels"
)]
pub silent: bool,
}

Expand All @@ -306,20 +301,17 @@ impl From<&LogLevelArgs> for LogLevel {
}
}

impl Args {
impl CheckArgs {
/// 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,
Expand Down Expand Up @@ -371,13 +363,10 @@ fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
#[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,
Expand Down
Loading

0 comments on commit 3a6032a

Please sign in to comment.