mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Implement uv help
manually instead of using Clap default (#4906)
Extends #4772 Implements `uv help` ourselves so we can do things like #4909 Adds hints to use `uv help` for more details during short help display.
This commit is contained in:
parent
2e307d9081
commit
5f20bdb2ee
5 changed files with 294 additions and 43 deletions
|
@ -52,7 +52,12 @@ fn extra_name_with_clap_error(arg: &str) -> Result<ExtraName> {
|
|||
#[command(name = "uv", author, version = uv_version::version(), long_version = crate::version::version())]
|
||||
#[command(about = "An extremely fast Python package manager.")]
|
||||
#[command(propagate_version = true)]
|
||||
#[command(disable_help_flag = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help` for more details.",
|
||||
after_long_help = "",
|
||||
disable_help_flag = true,
|
||||
disable_help_subcommand = true
|
||||
)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
|
@ -175,18 +180,39 @@ impl From<ColorChoice> for anstream::ColorChoice {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Commands {
|
||||
/// Resolve and install Python packages.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip`` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Pip(PipNamespace),
|
||||
/// Run and manage executable Python packages.
|
||||
#[command(
|
||||
after_help = "Use `uv help tool` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Tool(ToolNamespace),
|
||||
/// Manage Python installations.
|
||||
#[command(
|
||||
after_help = "Use `uv help python` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Python(PythonNamespace),
|
||||
/// Manage Python projects.
|
||||
#[command(flatten)]
|
||||
Project(Box<ProjectCommand>),
|
||||
/// Create a virtual environment.
|
||||
#[command(alias = "virtualenv", alias = "v")]
|
||||
#[command(
|
||||
alias = "virtualenv",
|
||||
alias = "v",
|
||||
after_help = "Use `uv help venv` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Venv(VenvArgs),
|
||||
/// Manage the cache.
|
||||
#[command(
|
||||
after_help = "Use `uv help cache` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Cache(CacheNamespace),
|
||||
/// Manage the `uv` executable.
|
||||
#[command(name = "self")]
|
||||
|
@ -203,6 +229,17 @@ pub enum Commands {
|
|||
/// Generate shell completion
|
||||
#[command(alias = "--generate-shell-completion", hide = true)]
|
||||
GenerateShellCompletion { shell: clap_complete_command::Shell },
|
||||
/// Display documentation for a command.
|
||||
#[command(help_template = "\
|
||||
{about-with-newline}
|
||||
{usage-heading} {usage}
|
||||
")]
|
||||
Help(HelpArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct HelpArgs {
|
||||
pub command: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
@ -253,22 +290,58 @@ pub struct PipNamespace {
|
|||
#[derive(Subcommand)]
|
||||
pub enum PipCommand {
|
||||
/// Compile a `requirements.in` file to a `requirements.txt` file.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip compile` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Compile(PipCompileArgs),
|
||||
/// Sync an environment with a `requirements.txt` file.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip sync` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Sync(PipSyncArgs),
|
||||
/// Install packages into an environment.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip install` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Install(PipInstallArgs),
|
||||
/// Uninstall packages from an environment.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip uninstall` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Uninstall(PipUninstallArgs),
|
||||
/// List, in requirements format, packages installed in an environment.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip freeze` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Freeze(PipFreezeArgs),
|
||||
/// List, in tabular format, packages installed in an environment.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip list` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
List(PipListArgs),
|
||||
/// Show information about one or more installed packages.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip show` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Show(PipShowArgs),
|
||||
/// Display the dependency tree for an environment.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip tree` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Tree(PipTreeArgs),
|
||||
/// Verify installed packages have compatible dependencies.
|
||||
#[command(
|
||||
after_help = "Use `uv help pip check` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Check(PipCheckArgs),
|
||||
}
|
||||
|
||||
|
@ -276,18 +349,38 @@ pub enum PipCommand {
|
|||
pub enum ProjectCommand {
|
||||
/// Run a command in the project environment.
|
||||
#[clap(hide = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help run` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Run(RunArgs),
|
||||
/// Sync the project's dependencies with the environment.
|
||||
#[clap(hide = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help sync` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Sync(SyncArgs),
|
||||
/// Resolve the project requirements into a lockfile.
|
||||
#[clap(hide = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help lock` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Lock(LockArgs),
|
||||
/// Add one or more packages to the project requirements.
|
||||
#[clap(hide = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help add` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Add(AddArgs),
|
||||
/// Remove one or more packages from the project requirements.
|
||||
#[clap(hide = true)]
|
||||
#[command(
|
||||
after_help = "Use `uv help remove` for more details.",
|
||||
after_long_help = ""
|
||||
)]
|
||||
Remove(RemoveArgs),
|
||||
/// Display the dependency tree for the project.
|
||||
#[clap(hide = true)]
|
||||
|
|
56
crates/uv/src/commands/help.rs
Normal file
56
crates/uv/src/commands/help.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::CommandFactory;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::ExitStatus;
|
||||
use crate::printer::Printer;
|
||||
use uv_cli::Cli;
|
||||
|
||||
pub(crate) fn help(query: &[String], printer: Printer) -> Result<ExitStatus> {
|
||||
let mut uv = Cli::command();
|
||||
|
||||
// It is very important to build the command before beginning inspection or subcommands
|
||||
// will be missing all of the propagated options.
|
||||
uv.build();
|
||||
|
||||
let command = find_command(query, &uv).map_err(|(unmatched, nearest)| {
|
||||
let missing = if unmatched.len() == query.len() {
|
||||
format!("`{}` for `uv`", query.join(" "))
|
||||
} else {
|
||||
format!("`{}` for `uv {}`", unmatched.join(" "), nearest.get_name())
|
||||
};
|
||||
anyhow!(
|
||||
"There is no command {}. Did you mean one of:\n {}",
|
||||
missing,
|
||||
nearest
|
||||
.get_subcommands()
|
||||
.filter(|cmd| !cmd.is_hide_set())
|
||||
.map(clap::Command::get_name)
|
||||
.filter(|name| *name != "help")
|
||||
.join("\n "),
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut command = command.clone();
|
||||
let help = command.render_long_help();
|
||||
writeln!(printer.stderr(), "{}", help.ansi())?;
|
||||
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Find the command corresponding to a set of arguments, e.g., `["uv", "pip", "install"]`.
|
||||
///
|
||||
/// If the command cannot be found, the nearest command is returned.
|
||||
fn find_command<'a>(
|
||||
query: &'a [String],
|
||||
cmd: &'a clap::Command,
|
||||
) -> Result<&'a clap::Command, (&'a [String], &'a clap::Command)> {
|
||||
let Some(next) = query.first() else {
|
||||
return Ok(cmd);
|
||||
};
|
||||
|
||||
let subcommand = cmd.find_subcommand(next).ok_or((query, cmd))?;
|
||||
find_command(&query[1..], subcommand)
|
||||
}
|
|
@ -8,6 +8,7 @@ pub(crate) use cache_clean::cache_clean;
|
|||
pub(crate) use cache_dir::cache_dir;
|
||||
pub(crate) use cache_prune::cache_prune;
|
||||
use distribution_types::InstalledMetadata;
|
||||
pub(crate) use help::help;
|
||||
pub(crate) use pip::check::pip_check;
|
||||
pub(crate) use pip::compile::pip_compile;
|
||||
pub(crate) use pip::freeze::pip_freeze;
|
||||
|
@ -51,6 +52,7 @@ use crate::printer::Printer;
|
|||
mod cache_clean;
|
||||
mod cache_dir;
|
||||
mod cache_prune;
|
||||
mod help;
|
||||
pub(crate) mod pip;
|
||||
mod project;
|
||||
mod python;
|
||||
|
|
|
@ -217,6 +217,9 @@ async fn run() -> Result<ExitStatus> {
|
|||
let cache = Cache::from_settings(cache_settings.no_cache, cache_settings.cache_dir)?;
|
||||
|
||||
match *cli.command {
|
||||
Commands::Help(args) => {
|
||||
commands::help(args.command.unwrap_or_default().as_slice(), printer)
|
||||
}
|
||||
Commands::Pip(PipNamespace {
|
||||
command: PipCommand::Compile(args),
|
||||
}) => {
|
||||
|
|
|
@ -11,6 +11,8 @@ fn help() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
An extremely fast Python package manager.
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
@ -22,7 +24,7 @@ fn help() {
|
|||
venv Create a virtual environment
|
||||
cache Manage the cache
|
||||
version Display uv's version
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
help Display documentation for a command
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
|
@ -107,7 +109,6 @@ fn help() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -129,7 +130,7 @@ fn help_flag() {
|
|||
venv Create a virtual environment
|
||||
cache Manage the cache
|
||||
version Display uv's version
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
help Display documentation for a command
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
|
@ -160,6 +161,8 @@ fn help_flag() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
Use `uv help` for more details.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
@ -182,7 +185,7 @@ fn help_short_flag() {
|
|||
venv Create a virtual environment
|
||||
cache Manage the cache
|
||||
version Display uv's version
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
help Display documentation for a command
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
|
@ -213,6 +216,8 @@ fn help_short_flag() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
Use `uv help` for more details.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
@ -225,6 +230,8 @@ fn help_subcommand() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Manage Python installations
|
||||
|
||||
Usage: uv python [OPTIONS] <COMMAND>
|
||||
|
@ -235,7 +242,6 @@ fn help_subcommand() {
|
|||
find Search for a Python installation
|
||||
dir Show the uv Python installation directory
|
||||
uninstall Uninstall Python versions
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
|
@ -320,7 +326,6 @@ fn help_subcommand() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -332,6 +337,8 @@ fn help_subsubcommand() {
|
|||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Download and install Python versions
|
||||
|
||||
Usage: uv python install [OPTIONS] [TARGETS]...
|
||||
|
@ -430,7 +437,6 @@ fn help_subsubcommand() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -452,7 +458,6 @@ fn help_flag_subcommand() {
|
|||
find Search for a Python installation
|
||||
dir Show the uv Python installation directory
|
||||
uninstall Uninstall Python versions
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
|
@ -483,6 +488,8 @@ fn help_flag_subcommand() {
|
|||
-V, --version
|
||||
Print version
|
||||
|
||||
Use `uv help python` for more details.
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
@ -547,11 +554,13 @@ fn help_unknown_subcommand() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand 'foobar'
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
error: There is no command `foobar` for `uv`. Did you mean one of:
|
||||
pip
|
||||
tool
|
||||
python
|
||||
venv
|
||||
cache
|
||||
version
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.help().arg("foo").arg("bar"), @r###"
|
||||
|
@ -560,11 +569,13 @@ fn help_unknown_subcommand() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand 'foo'
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
error: There is no command `foo bar` for `uv`. Did you mean one of:
|
||||
pip
|
||||
tool
|
||||
python
|
||||
venv
|
||||
cache
|
||||
version
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -578,11 +589,12 @@ fn help_unknown_subsubcommand() {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand 'foobar'
|
||||
|
||||
Usage: uv python [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
error: There is no command `foobar` for `uv python`. Did you mean one of:
|
||||
list
|
||||
install
|
||||
find
|
||||
dir
|
||||
uninstall
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -591,16 +603,107 @@ fn help_with_global_option() {
|
|||
let context = TestContext::new_with_versions(&[]);
|
||||
|
||||
uv_snapshot!(context.filters(), context.help().arg("--cache-dir").arg("/dev/null"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand '--cache-dir'
|
||||
An extremely fast Python package manager.
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
Commands:
|
||||
pip Resolve and install Python packages
|
||||
tool Run and manage executable Python packages
|
||||
python Manage Python installations
|
||||
venv Create a virtual environment
|
||||
cache Manage the cache
|
||||
version Display uv's version
|
||||
help Display documentation for a command
|
||||
|
||||
Options:
|
||||
-q, --quiet
|
||||
Do not print any output
|
||||
|
||||
-v, --verbose...
|
||||
Use verbose output.
|
||||
|
||||
You can configure fine-grained logging using the `RUST_LOG` environment variable.
|
||||
(<https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives>)
|
||||
|
||||
--color <COLOR_CHOICE>
|
||||
Control colors in output
|
||||
|
||||
[default: auto]
|
||||
|
||||
Possible values:
|
||||
- auto: Enables colored output only when the output is going to a terminal or TTY with
|
||||
support
|
||||
- always: Enables colored output regardless of the detected environment
|
||||
- never: Disables colored output
|
||||
|
||||
--native-tls
|
||||
Whether to load TLS certificates from the platform's native certificate store.
|
||||
|
||||
By default, `uv` loads certificates from the bundled `webpki-roots` crate. The
|
||||
`webpki-roots` are a reliable set of trust roots from Mozilla, and including them in `uv`
|
||||
improves portability and performance (especially on macOS).
|
||||
|
||||
However, in some cases, you may want to use the platform's native certificate store,
|
||||
especially if you're relying on a corporate trust root (e.g., for a mandatory proxy)
|
||||
that's included in your system's certificate store.
|
||||
|
||||
[env: UV_NATIVE_TLS=]
|
||||
|
||||
--offline
|
||||
Disable network access, relying only on locally cached data and locally available files
|
||||
|
||||
--python-preference <PYTHON_PREFERENCE>
|
||||
Whether to prefer using Python from uv or on the system
|
||||
|
||||
Possible values:
|
||||
- only-managed: Only use managed Python installations; never use system Python
|
||||
installations
|
||||
- installed: Prefer installed Python installations, only download managed Python
|
||||
installations if no system Python installation is found
|
||||
- managed: Prefer managed Python installations over system Python installations, even
|
||||
if fetching is required
|
||||
- system: Prefer system Python installations over managed Python installations
|
||||
- only-system: Only use system Python installations; never use managed Python
|
||||
installations
|
||||
|
||||
--python-fetch <PYTHON_FETCH>
|
||||
Whether to automatically download Python when required
|
||||
|
||||
Possible values:
|
||||
- automatic: Automatically fetch managed Python installations when needed
|
||||
- manual: Do not automatically fetch managed Python installations; require explicit
|
||||
installation
|
||||
|
||||
-n, --no-cache
|
||||
Avoid reading from or writing to the cache
|
||||
|
||||
[env: UV_NO_CACHE=]
|
||||
|
||||
--cache-dir [CACHE_DIR]
|
||||
Path to the cache directory.
|
||||
|
||||
Defaults to `$HOME/Library/Caches/uv` on macOS, `$XDG_CACHE_HOME/uv` or `$HOME/.cache/uv`
|
||||
on Linux, and `{FOLDERID_LocalAppData}/uv/cache` on Windows.
|
||||
|
||||
[env: UV_CACHE_DIR=]
|
||||
|
||||
--config-file <CONFIG_FILE>
|
||||
The path to a `uv.toml` file to use for configuration
|
||||
|
||||
[env: UV_CONFIG_FILE=]
|
||||
|
||||
-h, --help
|
||||
Print help
|
||||
|
||||
-V, --version
|
||||
Print version
|
||||
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -609,16 +712,14 @@ fn help_with_help() {
|
|||
let context = TestContext::new_with_versions(&[]);
|
||||
|
||||
uv_snapshot!(context.filters(), context.help().arg("--help"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Display documentation for a command
|
||||
|
||||
Usage: uv help [OPTIONS] [COMMAND]...
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand '--help'
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
|
@ -627,15 +728,11 @@ fn help_with_version() {
|
|||
let context = TestContext::new_with_versions(&[]);
|
||||
|
||||
uv_snapshot!(context.filters(), context.help().arg("--version"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
uv [VERSION] ([COMMIT] DATE)
|
||||
|
||||
----- stderr -----
|
||||
error: unrecognized subcommand '--version'
|
||||
|
||||
Usage: uv [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue