Skip to content
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
12 changes: 11 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ uv-cache-info = { path = "crates/uv-cache-info" }
uv-cli = { path = "crates/uv-cli" }
uv-client = { path = "crates/uv-client" }
uv-configuration = { path = "crates/uv-configuration" }
uv-console = { path = "crates/uv-console" }
uv-dispatch = { path = "crates/uv-dispatch" }
uv-distribution = { path = "crates/uv-distribution" }
uv-extract = { path = "crates/uv-extract" }
Expand Down
12 changes: 12 additions & 0 deletions crates/uv-console/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "uv-console"
version = "0.0.1"
edition = "2021"
description = "Utilities for interacting with the terminal"

[lints]
workspace = true

[dependencies]
ctrlc = { workspace = true }
console = { workspace = true }
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use anyhow::Result;
use console::{style, Key, Term};

/// Prompt the user for confirmation in the given [`Term`].
///
/// This is a slimmed-down version of `dialoguer::Confirm`, with the post-confirmation report
/// enabled.
pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool> {
pub fn confirm(message: &str, term: &Term, default: bool) -> std::io::Result<bool> {
// Set the Ctrl-C handler to exit the process.
let result = ctrlc::set_handler(move || {
let term = Term::stderr();
Expand All @@ -26,7 +25,7 @@ pub(crate) fn confirm(message: &str, term: &Term, default: bool) -> Result<bool>
// If multiple handlers were set, we assume that the existing handler is our
// confirmation handler, and continue.
}
Err(e) => return Err(e.into()),
Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::Other, err)),
}

let prompt = format!(
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-requirements/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pypi-types = { workspace = true }
requirements-txt = { workspace = true, features = ["http"] }
uv-client = { workspace = true }
uv-configuration = { workspace = true }
uv-console = { workspace = true }
uv-distribution = { workspace = true }
uv-fs = { workspace = true }
uv-git = { workspace = true }
Expand All @@ -33,7 +34,6 @@ uv-workspace = { workspace = true }
anyhow = { workspace = true }
configparser = { workspace = true }
console = { workspace = true }
ctrlc = { workspace = true }
fs-err = { workspace = true, features = ["tokio"] }
futures = { workspace = true }
rustc-hash = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion crates/uv-requirements/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pub use crate::sources::*;
pub use crate::specification::*;
pub use crate::unnamed::*;

mod confirm;
mod lookahead;
mod source_tree;
mod sources;
Expand Down
6 changes: 2 additions & 4 deletions crates/uv-requirements/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ use console::Term;
use uv_fs::Simplified;
use uv_warnings::warn_user;

use crate::confirm;

#[derive(Debug, Clone)]
pub enum RequirementsSource {
/// A package was provided on the command line (e.g., `pip install flask`).
Expand Down Expand Up @@ -96,7 +94,7 @@ impl RequirementsSource {
let prompt = format!(
"`{name}` looks like a local requirements file but was passed as a package name. Did you mean `-r {name}`?"
);
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
if confirmation {
return Self::from_requirements_file(name.into());
}
Expand All @@ -113,7 +111,7 @@ impl RequirementsSource {
let prompt = format!(
"`{name}` looks like a local metadata file but was passed as a package name. Did you mean `-r {name}`?"
);
let confirmation = confirm::confirm(&prompt, &term, true).unwrap();
let confirmation = uv_console::confirm(&prompt, &term, true).unwrap();
if confirmation {
return Self::from_requirements_file(name.into());
}
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ uv-cache-info = { workspace = true }
uv-cli = { workspace = true }
uv-client = { workspace = true }
uv-configuration = { workspace = true }
uv-console = { workspace = true }
uv-dispatch = { workspace = true }
uv-distribution = { workspace = true }
uv-extract = { workspace = true }
Expand Down Expand Up @@ -57,6 +58,7 @@ axoupdater = { workspace = true, features = [
"tokio",
], optional = true }
clap = { workspace = true, features = ["derive", "string", "wrap_help"] }
console = { workspace = true }
ctrlc = { workspace = true }
flate2 = { workspace = true, default-features = false }
fs-err = { workspace = true, features = ["tokio"] }
Expand Down
39 changes: 35 additions & 4 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::collections::hash_map::Entry;
use std::fmt::Write;
use std::path::{Path, PathBuf};

use anyhow::{bail, Context, Result};
use console::Term;
use itertools::Itertools;
use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap};
use std::collections::hash_map::Entry;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::LazyLock;
use tracing::debug;

use cache_key::RepositoryUrl;
Expand Down Expand Up @@ -48,6 +50,18 @@ use crate::commands::{pip, project, ExitStatus, SharedState};
use crate::printer::Printer;
use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef};

static CORRECTIONS: LazyLock<FxHashMap<PackageName, PackageName>> = LazyLock::new(|| {
[("dotenv", "python-dotenv"), ("sklearn", "scikit-learn")]
.iter()
.map(|(k, v)| {
(
PackageName::from_str(k).unwrap(),
PackageName::from_str(v).unwrap(),
)
})
.collect()
});

/// Add one or more packages to the project requirements.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn add(
Expand Down Expand Up @@ -371,6 +385,23 @@ pub(crate) async fn add(
}?;
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
for mut requirement in requirements {
// If the user requested a package that is often confused for another package, prompt them.
if let Some(correction) = CORRECTIONS.get(&requirement.name) {
let term = Term::stderr();
if term.is_term() {
let prompt = format!(
"`{}` is often confused for `{}`. Did you mean `{}`?",
requirement.name.cyan(),
correction.cyan(),
format!("uv add {correction}").green()
);
let confirmation = uv_console::confirm(&prompt, &term, true)?;
if confirmation {
requirement.name = correction.clone();
}
}
}

// Add the specified extras.
requirement.extras.extend(extras.iter().cloned());
requirement.extras.sort_unstable();
Expand Down