Generate shell completion for uvx (#7388)

## Summary

Generate shell completion for uvx.

Create a `uvx` toplevel command just for completion by combining `uv
tool uvx` (hidden alias for `uv tool run`) with global arguments. This
explicit combination is needed otherwise global arguments are missing
(if they are missing, clap debug assertions fail when `uv tool run`
arguments refer to global arguments in directives like conflicts with).


Fixes #7258 

## Test Plan

- Tested using bash using `eval "$(cargo run --bin uv
generate-shell-completion bash)"`
This commit is contained in:
bluss 2024-09-17 05:27:19 +02:00 committed by GitHub
parent d1c7cb8bc2
commit e9378be919
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 13 deletions

View file

@ -76,6 +76,13 @@ pub struct Cli {
#[command(subcommand)]
pub command: Box<Commands>,
#[command(flatten)]
pub top_level: TopLevelArgs,
}
#[derive(Parser)]
#[command(disable_help_flag = true, disable_version_flag = true)]
pub struct TopLevelArgs {
#[command(flatten)]
pub cache_args: Box<CacheArgs>,

View file

@ -16,7 +16,7 @@ use uv_cli::{
compat::CompatArgs, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
ProjectCommand,
};
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace};
use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs};
#[cfg(feature = "self-update")]
use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs};
use uv_fs::CWD;
@ -58,17 +58,17 @@ pub(crate) mod version;
#[instrument(skip_all)]
async fn run(cli: Cli) -> Result<ExitStatus> {
// Enable flag to pick up warnings generated by workspace loading.
if !cli.global_args.quiet {
if !cli.top_level.global_args.quiet {
uv_warnings::enable();
}
// Switch directories as early as possible.
if let Some(directory) = cli.global_args.directory.as_ref() {
if let Some(directory) = cli.top_level.global_args.directory.as_ref() {
std::env::set_current_dir(directory)?;
}
// The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs.
let deprecated_isolated = if cli.global_args.isolated {
let deprecated_isolated = if cli.top_level.global_args.isolated {
match &*cli.command {
// Supports `--isolated` as its own argument, so we can't warn either way.
Commands::Tool(ToolNamespace {
@ -106,7 +106,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
// If found, this file is combined with the user configuration file.
// 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree,
// starting from the current directory.
let filesystem = if let Some(config_file) = cli.config_file.as_ref() {
let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() {
if config_file
.file_name()
.is_some_and(|file_name| file_name == "pyproject.toml")
@ -114,7 +114,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
warn_user!("The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--directory` argument instead.");
}
Some(FilesystemOptions::from_file(config_file)?)
} else if deprecated_isolated || cli.no_config {
} else if deprecated_isolated || cli.top_level.no_config {
None
} else if matches!(&*cli.command, Commands::Tool(_)) {
// For commands that operate at the user-level, ignore local configuration.
@ -175,10 +175,10 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
.combine(filesystem);
// Resolve the global settings.
let globals = GlobalSettings::resolve(&cli.global_args, filesystem.as_ref());
let globals = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref());
// Resolve the cache settings.
let cache_settings = CacheSettings::resolve(*cli.cache_args, filesystem.as_ref());
let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref());
// Configure the `tracing` crate, which controls internal logging.
#[cfg(feature = "tracing-durations-export")]
@ -687,7 +687,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.hash_checking,
args.python,
args.settings,
cli.no_config,
cli.top_level.no_config,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
@ -743,7 +743,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
args.settings.exclude_newer,
globals.concurrency,
globals.native_tls,
cli.no_config,
cli.top_level.no_config,
args.no_project,
&cache,
printer,
@ -757,7 +757,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
run_command,
script,
globals,
cli.no_config,
cli.top_level.no_config,
filesystem,
cache,
printer,
@ -777,7 +777,30 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
Ok(ExitStatus::Success)
}
Commands::GenerateShellCompletion(args) => {
// uv
args.shell.generate(&mut Cli::command(), &mut stdout());
// uvx: combine `uv tool uvx` with the top-level arguments
let mut uvx = Cli::command()
.find_subcommand("tool")
.unwrap()
.find_subcommand("uvx")
.unwrap()
.clone()
// Avoid duplicating the `--help` and `--version` flags from the top-level arguments.
.disable_help_flag(true)
.disable_version_flag(true)
.version(env!("CARGO_PKG_VERSION"));
// Copy the top-level arguments into the `uvx` command. (Like `Args::augment_args`, but
// expanded to skip collisions.)
for arg in TopLevelArgs::command().get_arguments() {
if arg.get_id() != "isolated" {
uvx = uvx.arg(arg);
}
}
args.shell.generate(&mut uvx, &mut stdout());
Ok(ExitStatus::Success)
}
Commands::Tool(ToolNamespace {
@ -974,7 +997,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
globals.python_downloads,
globals.native_tls,
globals.connectivity,
cli.no_config,
cli.top_level.no_config,
printer,
)
.await
@ -1000,7 +1023,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
commands::python_find(
args.request,
args.no_project,
cli.no_config,
cli.top_level.no_config,
args.system,
globals.python_preference,
&cache,