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 25, 2023
1 parent ce7f18c commit 2953d29
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 250 deletions.
27 changes: 27 additions & 0 deletions BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Breaking Changes

## Unreleased

The subcommand handling of the CLI has been revised:

* For backwards-compatibility subcommands can 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

or, with the new syntax:

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
196 changes: 75 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,90 @@ 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(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 },
/// See the files Ruff will be run against with the current settings.
#[clap(alias = "--show-files")]
ShowFiles(CommonOptions),
/// See the settings Ruff will use to lint a given Python file.
#[clap(alias = "--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)
Check {
#[clap(flatten)]
options: CommonOptions,
#[clap(flatten)]
check_only_args: CheckOnlyArgs,
},
/// Automatically add `noqa` directives to failing lines.
#[clap(alias = "--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 +157,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 +192,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 +222,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 +278,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 2953d29

Please sign in to comment.