|
1 | 1 | use std::collections::HashMap;
|
2 | 2 | use std::fmt;
|
3 | 3 |
|
| 4 | +use anyhow::bail; |
4 | 5 | use semver::Version;
|
5 | 6 | use serde::{de, ser};
|
6 | 7 | use url::Url;
|
7 | 8 |
|
8 | 9 | use crate::core::PackageId;
|
9 | 10 | use crate::util::errors::{CargoResult, CargoResultExt};
|
10 | 11 | use crate::util::interning::InternedString;
|
| 12 | +use crate::util::lev_distance; |
11 | 13 | use crate::util::{validate_package_name, IntoUrl, ToSemver};
|
12 | 14 |
|
13 | 15 | /// Some or all of the data required to identify a package:
|
@@ -96,7 +98,7 @@ impl PackageIdSpec {
|
96 | 98 | /// Tries to convert a valid `Url` to a `PackageIdSpec`.
|
97 | 99 | fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
|
98 | 100 | if url.query().is_some() {
|
99 |
| - anyhow::bail!("cannot have a query string in a pkgid: {}", url) |
| 101 | + bail!("cannot have a query string in a pkgid: {}", url) |
100 | 102 | }
|
101 | 103 | let frag = url.fragment().map(|s| s.to_owned());
|
102 | 104 | url.set_fragment(None);
|
@@ -180,14 +182,57 @@ impl PackageIdSpec {
|
180 | 182 | where
|
181 | 183 | I: IntoIterator<Item = PackageId>,
|
182 | 184 | {
|
183 |
| - let mut ids = i.into_iter().filter(|p| self.matches(*p)); |
| 185 | + let all_ids: Vec<_> = i.into_iter().collect(); |
| 186 | + let mut ids = all_ids.iter().copied().filter(|&id| self.matches(id)); |
184 | 187 | let ret = match ids.next() {
|
185 | 188 | Some(id) => id,
|
186 |
| - None => anyhow::bail!( |
187 |
| - "package ID specification `{}` \ |
188 |
| - matched no packages", |
189 |
| - self |
190 |
| - ), |
| 189 | + None => { |
| 190 | + let mut suggestion = String::new(); |
| 191 | + let try_spec = |spec: PackageIdSpec, suggestion: &mut String| { |
| 192 | + let try_matches: Vec<_> = all_ids |
| 193 | + .iter() |
| 194 | + .copied() |
| 195 | + .filter(|&id| spec.matches(id)) |
| 196 | + .collect(); |
| 197 | + if !try_matches.is_empty() { |
| 198 | + suggestion.push_str("\nDid you mean one of these?\n"); |
| 199 | + minimize(suggestion, &try_matches, self); |
| 200 | + } |
| 201 | + }; |
| 202 | + if self.url.is_some() { |
| 203 | + try_spec( |
| 204 | + PackageIdSpec { |
| 205 | + name: self.name, |
| 206 | + version: self.version.clone(), |
| 207 | + url: None, |
| 208 | + }, |
| 209 | + &mut suggestion, |
| 210 | + ); |
| 211 | + } |
| 212 | + if suggestion.is_empty() && self.version.is_some() { |
| 213 | + try_spec( |
| 214 | + PackageIdSpec { |
| 215 | + name: self.name, |
| 216 | + version: None, |
| 217 | + url: None, |
| 218 | + }, |
| 219 | + &mut suggestion, |
| 220 | + ); |
| 221 | + } |
| 222 | + if suggestion.is_empty() { |
| 223 | + suggestion.push_str(&lev_distance::closest_msg( |
| 224 | + &self.name, |
| 225 | + all_ids.iter(), |
| 226 | + |id| id.name().as_str(), |
| 227 | + )); |
| 228 | + } |
| 229 | + |
| 230 | + bail!( |
| 231 | + "package ID specification `{}` did not match any packages{}", |
| 232 | + self, |
| 233 | + suggestion |
| 234 | + ); |
| 235 | + } |
191 | 236 | };
|
192 | 237 | return match ids.next() {
|
193 | 238 | Some(other) => {
|
|
0 commit comments