Skip to content

Commit

Permalink
Implement uv help manually instead of using Clap default
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Jul 9, 2024
1 parent 0bf562f commit ca1caee
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 204 deletions.
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

0 comments on commit ca1caee

Please sign in to comment.