#
cargo/ops/registry/
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 std::cmp;
    6
    7use anyhow::Context as _;
    8use url::Url;
    9
    10use crate::util::style;
    11use crate::util::style::LITERAL;
    12use crate::util::truncate_with_ellipsis;
    13use crate::CargoResult;
    14use crate::GlobalContext;
    15
    16use super::RegistryOrIndex;
    17
    18pub fn search(
    19    query: &str,
    20    gctx: &GlobalContext,
    21    reg_or_index: Option<RegistryOrIndex>,
    22    limit: u32,
    23) -> CargoResult<()> {
    24    let source_ids = super::get_source_id(gctx, reg_or_index.as_ref())?;
    25    let (mut registry, _) =
    26        super::registry(gctx, &source_ids, None, reg_or_index.as_ref(), false, None)?;
    27    let (crates, total_crates) = registry.search(query, limit).with_context(|| {
    28        format!(
    29            "failed to retrieve search results from the registry at {}",
    30            registry.host()
    31        )
    32    })?;
    33
    34    let names = crates
    35        .iter()
    36        .map(|krate| format!("{} = \"{}\"", krate.name, krate.max_version))
    37        .collect::<Vec<String>>();
    38
    39    let description_margin = names.iter().map(|s| s.len()).max().unwrap_or_default() + 4;
    40
    41    let description_length = cmp::max(80, 128 - description_margin);
    42
    43    let descriptions = crates.iter().map(|krate| {
    44        krate
    45            .description
    46            .as_ref()
    47            .map(|desc| truncate_with_ellipsis(&desc.replace("\n", " "), description_length))
    48    });
    49
    50    let mut shell = gctx.shell();
    51    let stdout = shell.out();
    52    let good = style::GOOD;
    53
    54    for (name, description) in names.into_iter().zip(descriptions) {
    55        let line = match description {
    56            Some(desc) => format!("{name: <description_margin$}# {desc}"),
    57            None => name,
    58        };
    59        let mut fragments = line.split(query).peekable();
    60        while let Some(fragment) = fragments.next() {
    61            let _ = write!(stdout, "{fragment}");
    62            if fragments.peek().is_some() {
    63                let _ = write!(stdout, "{good}{query}{good:#}");
    64            }
    65        }
    66        let _ = writeln!(stdout);
    67    }
    68
    69    let search_max_limit = 100;
    70    if total_crates > limit && limit < search_max_limit {
    71        let _ = writeln!(
    72            stdout,
    73            "... and {} crates more (use --limit N to see more)",
    74            total_crates - limit
    75        );
    76    } else if total_crates > limit && limit >= search_max_limit {
    77        let extra = if source_ids.original.is_crates_io() {
    78            let url = Url::parse_with_params("https://crates.io/search", &[("q", query)])?;
    79            format!(" (go to {url} to see more)")
    80        } else {
    81            String::new()
    82        };
    83        let _ = writeln!(
    84            stdout,
    85            "... and {} crates more{}",
    86            total_crates - limit,
    87            extra
    88        );
    89    }
    90
    91    if total_crates > 0 {
    92        let literal = LITERAL;
    93        shell.note(format_args!(
    94            "to learn more about a package, run `{literal}cargo info <name>{literal:#}`",
    95        ))?;
    96    }
    97
    98    Ok(())
    99}