use std::collections::HashSet;
use std::task::Poll;
use anyhow::{bail, Context as _};
use cargo::core::registry::PackageRegistry;
use cargo::core::{Dependency, PackageId, PackageIdSpec, Registry, SourceId, Workspace};
use cargo::ops::RegistryOrIndex;
use cargo::sources::source::{QueryKind, Source};
use cargo::sources::{RegistrySource, SourceConfigMap};
use cargo::util::auth::{auth_token, AuthorizationErrorReason};
use cargo::util::command_prelude::root_manifest;
use cargo::util::network::http::http_handle;
use cargo::{ops, CargoResult, Config};
use cargo_credential::Operation;
use crates_io::Registry as CratesIoRegistry;
use crates_io::User;
use super::view::{pretty_view, suggest_cargo_tree};
pub fn info(
spec: &PackageIdSpec,
config: &Config,
reg_or_index: Option<RegistryOrIndex>,
) -> CargoResult<()> {
let mut registry = PackageRegistry::new(config)?;
let _lock = config.acquire_package_cache_lock()?;
registry.lock_patches();
let mut package_id = root_manifest(None, config)
.ok()
.and_then(|root| Workspace::new(&root, config).ok())
.and_then(|ws| ops::load_pkg_lockfile(&ws).ok())
.and_then(|resolve| {
resolve
.as_ref()
.map(|r| r.iter())
.and_then(|it| it.filter(|&p| spec.matches(p)).max_by_key(|&p| p.version()))
});
let (use_package_source_id, source_ids) = get_source_id(config, reg_or_index, package_id)?;
if !use_package_source_id {
package_id = None;
}
query_and_pretty_view(spec, package_id, config, registry, source_ids)
}
fn query_and_pretty_view(
spec: &PackageIdSpec,
package_id: Option<PackageId>,
config: &Config,
mut registry: PackageRegistry,
source_ids: RegistrySourceIds,
) -> CargoResult<()> {
let from_workspace = package_id.is_some();
if !from_workspace {
if config.locked() {
anyhow::bail!("the option `--locked` can only be used within a workspace");
}
if config.frozen() {
anyhow::bail!("the option `--frozen` can only be used within a workspace");
}
}
let dep = Dependency::parse(spec.name(), None, source_ids.original)?;
let summaries = loop {
match registry.query_vec(&dep, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
};
let package_id = match package_id {
Some(id) => id,
None => {
let summary = summaries
.iter()
.filter(|s| spec.matches(s.package_id()))
.max_by_key(|s| s.package_id().version());
match summary {
Some(summary) => summary.package_id(),
None => {
anyhow::bail!(
"could not find `{}` in registry `{}`",
spec,
source_ids.original.url()
)
}
}
}
};
let package = registry.get(&[package_id])?;
let package = package.get_one(package_id)?;
let owners = try_list_owners(config, source_ids, package_id.name().as_str())?;
let mut shell = config.shell();
let stdout = shell.out();
pretty_view(package, &summaries, &owners, stdout)?;
if from_workspace {
suggest_cargo_tree(package_id, stdout)?;
}
Ok(())
}
fn try_list_owners(
config: &Config,
source_ids: RegistrySourceIds,
package_name: &str,
) -> CargoResult<Option<Vec<String>>> {
let registry = api_registry(config, source_ids)?;
match registry {
Some(mut registry) => {
let owners = registry.list_owners(package_name)?;
let names = owners.iter().map(get_username).collect();
Ok(Some(names))
}
None => Ok(None),
}
}
fn get_username(u: &User) -> String {
format!(
"{}{}",
u.login,
u.name
.as_ref()
.map(|name| format!(" ({})", name))
.unwrap_or_default(),
)
}
struct RegistrySourceIds {
original: SourceId,
replacement: SourceId,
}
fn get_source_id(
config: &Config,
reg_or_index: Option<RegistryOrIndex>,
package_id: Option<PackageId>,
) -> CargoResult<(bool, RegistrySourceIds)> {
let (use_package_source_id, sid) = match (®_or_index, package_id) {
(None, Some(package_id)) => (true, package_id.source_id()),
(None, None) => (false, SourceId::crates_io(config)?),
(Some(RegistryOrIndex::Index(url)), None) => (false, SourceId::for_registry(url)?),
(Some(RegistryOrIndex::Registry(r)), None) => (false, SourceId::alt_registry(config, r)?),
(Some(reg_or_index), Some(package_id)) => {
let sid = match reg_or_index {
RegistryOrIndex::Index(url) => SourceId::for_registry(url)?,
RegistryOrIndex::Registry(r) => SourceId::alt_registry(config, r)?,
};
let package_source_id = package_id.source_id();
if sid == package_source_id {
(true, sid)
} else {
let pkg_source_replacement_sid = SourceConfigMap::new(config)?
.load(package_source_id, &HashSet::new())?
.replaced_source_id();
if pkg_source_replacement_sid == sid {
(true, package_source_id)
} else {
(false, sid)
}
}
}
};
let builtin_replacement_sid = SourceConfigMap::empty(config)?
.load(sid, &HashSet::new())?
.replaced_source_id();
let replacement_sid = SourceConfigMap::new(config)?
.load(sid, &HashSet::new())?
.replaced_source_id();
if reg_or_index.is_none() && replacement_sid != builtin_replacement_sid {
if let Some(replacement_name) = replacement_sid.alt_registry_key() {
bail!("crates-io is replaced with remote registry {replacement_name};\ninclude `--registry {replacement_name}` or `--registry crates-io`");
} else {
bail!("crates-io is replaced with non-remote-registry source {replacement_sid};\ninclude `--registry crates-io` to use crates.io");
}
} else {
Ok((
use_package_source_id,
RegistrySourceIds {
original: sid,
replacement: builtin_replacement_sid,
},
))
}
}
fn api_registry(
config: &Config,
source_ids: RegistrySourceIds,
) -> CargoResult<Option<CratesIoRegistry>> {
let cfg = {
let mut src = RegistrySource::remote(source_ids.replacement, &HashSet::new(), config)?;
let cfg = loop {
match src.config()? {
Poll::Pending => src
.block_until_ready()
.with_context(|| format!("failed to update {}", source_ids.replacement))?,
Poll::Ready(cfg) => break cfg,
}
};
cfg.expect("remote registries must have config")
};
let api_host = match cfg.api {
Some(api_host) => api_host,
None => return Ok(None),
};
let token = match auth_token(
config,
&source_ids.original,
None,
Operation::Read,
vec![],
false,
) {
Ok(token) => Some(token),
Err(err) => {
if err.to_string().contains(
(AuthorizationErrorReason::TokenMissing)
.to_string()
.as_str(),
) {
return Ok(None);
}
return Err(err);
}
};
let handle = http_handle(config)?;
Ok(Some(CratesIoRegistry::new_handle(
api_host,
token,
handle,
cfg.auth_required,
)))
}