Skip to main content

cargo/ops/registry/
cargo_search.rs

1//! Interacts with the registry [search API][1].
2//!
3//! [1]: https://doc.rust-lang.org/nightly/cargo/reference/registry-web-api.html#search
4
5use crate::sources::CRATES_IO_DOMAIN;
6use std::cmp;
7
8use anyhow::Context as _;
9use url::Url;
10
11use crate::CargoResult;
12use crate::GlobalContext;
13use crate::util::style;
14use crate::util::style::LITERAL;
15use crate::util::truncate_with_ellipsis;
16
17use super::RegistryOrIndex;
18
19pub fn search(
20    query: &str,
21    gctx: &GlobalContext,
22    reg_or_index: Option<RegistryOrIndex>,
23    limit: u32,
24) -> CargoResult<()> {
25    let source_ids = super::get_source_id(gctx, reg_or_index.as_ref())?;
26    let (mut registry, _) =
27        super::registry(gctx, &source_ids, None, reg_or_index.as_ref(), false, None)?;
28    let (crates, total_crates) = registry.search(query, limit).with_context(|| {
29        format!(
30            "failed to retrieve search results from the registry at {}",
31            registry.host()
32        )
33    })?;
34
35    let names = crates
36        .iter()
37        .map(|krate| format!("{} = \"{}\"", krate.name, krate.max_version))
38        .collect::<Vec<String>>();
39
40    let description_margin = names.iter().map(|s| s.len()).max().unwrap_or_default() + 4;
41
42    let description_length = cmp::max(80, 128 - description_margin);
43
44    let descriptions = crates.iter().map(|krate| {
45        krate
46            .description
47            .as_ref()
48            .map(|desc| truncate_with_ellipsis(&desc.replace("\n", " "), description_length))
49    });
50
51    let mut shell = gctx.shell();
52
53    let links = crates
54        .iter()
55        .map(|krate| {
56            registry
57                .host_is_crates_io()
58                .then(|| {
59                    shell.out_hyperlink(format!(
60                        "https://{CRATES_IO_DOMAIN}/crates/{}/{}",
61                        krate.name, krate.max_version
62                    ))
63                })
64                .unwrap_or_default()
65        })
66        .collect::<Vec<_>>();
67
68    let stdout = shell.out();
69    let good = style::GOOD;
70
71    let write_with_highlighting = |stdout: &mut dyn std::io::Write, text: &str| {
72        let mut fragments = text.split(query).peekable();
73        while let Some(fragment) = fragments.next() {
74            let _ = write!(stdout, "{fragment}");
75            if fragments.peek().is_some() {
76                let _ = write!(stdout, "{good}{query}{good:#}");
77            }
78        }
79    };
80
81    for (name, (description, link)) in names.into_iter().zip(descriptions.zip(links)) {
82        let _ = write!(stdout, "{link}");
83        write_with_highlighting(stdout, &name);
84        let _ = write!(stdout, "{link:#}");
85
86        if let Some(description) = description {
87            let margin = " ".repeat(description_margin.saturating_sub(name.len()));
88            let _ = write!(stdout, "{margin}# ");
89            write_with_highlighting(stdout, &description);
90        }
91
92        let _ = writeln!(stdout);
93    }
94
95    let search_max_limit = 100;
96    if total_crates > limit && limit < search_max_limit {
97        let _ = writeln!(
98            stdout,
99            "... and {} crates more (use --limit N to see more)",
100            total_crates - limit
101        );
102    } else if total_crates > limit && limit >= search_max_limit {
103        let extra = if source_ids.original.is_crates_io() {
104            let url = Url::parse_with_params("https://crates.io/search", &[("q", query)])?;
105            format!(" (go to {url} to see more)")
106        } else {
107            String::new()
108        };
109        let _ = writeln!(
110            stdout,
111            "... and {} crates more{}",
112            total_crates - limit,
113            extra
114        );
115    }
116
117    if total_crates > 0 {
118        let literal = LITERAL;
119        shell.note(format_args!(
120            "to learn more about a package, run `{literal}cargo info <name>{literal:#}`",
121        ))?;
122    }
123
124    Ok(())
125}