Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement uv help manually instead of using Clap default #4906

Merged
merged 1 commit into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 95 additions & 2 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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")]
Expand All @@ -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)]
Expand Down Expand Up @@ -253,41 +290,97 @@ 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),
}

#[derive(Subcommand)]
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)]
Expand Down
56 changes: 56 additions & 0 deletions crates/uv/src/commands/help.rs
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 2 additions & 0 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}) => {
Expand Down
Loading
Loading