use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::Component;
use std::path::Path;
use cargo_metadata::TargetKind;
use super::metadata_store::PackageRecord;
use super::metadata_store::PublishPolicy;
use super::parse::ExampleGroup;
use super::parse::ProjectType;
use crate::lint::LintRuns;
use crate::project::info::ProjectInfo;
use crate::project::vendored_package::VendoredPackage;
#[derive(Clone, Default)]
pub(crate) struct RustInfo {
pub info: ProjectInfo,
pub cargo: Cargo,
pub vendored: Vec<VendoredPackage>,
pub lint_runs: LintRuns,
pub crates_version: Option<String>,
pub crates_prerelease: Option<String>,
pub crates_downloads: Option<u64>,
}
impl RustInfo {
pub fn vendored(&self) -> &[VendoredPackage] { &self.vendored }
pub const fn vendored_mut(&mut self) -> &mut Vec<VendoredPackage> { &mut self.vendored }
pub fn crates_version(&self) -> Option<&str> { self.crates_version.as_deref() }
pub fn crates_prerelease(&self) -> Option<&str> { self.crates_prerelease.as_deref() }
pub const fn crates_downloads(&self) -> Option<u64> { self.crates_downloads }
pub fn set_crates_io(&mut self, version: String, prerelease: Option<String>, downloads: u64) {
self.crates_version = Some(version);
self.crates_prerelease = prerelease;
self.crates_downloads = Some(downloads);
}
}
impl Deref for RustInfo {
type Target = ProjectInfo;
fn deref(&self) -> &ProjectInfo { &self.info }
}
impl DerefMut for RustInfo {
fn deref_mut(&mut self) -> &mut ProjectInfo { &mut self.info }
}
#[derive(Clone, Debug)]
pub(crate) struct Cargo {
pub types: Vec<ProjectType>,
pub examples: Vec<ExampleGroup>,
pub benches: Vec<String>,
pub publishable: bool,
}
impl Default for Cargo {
fn default() -> Self {
Self {
types: Vec::new(),
examples: Vec::new(),
benches: Vec::new(),
publishable: true,
}
}
}
impl Cargo {
pub fn types(&self) -> &[ProjectType] { &self.types }
pub fn examples(&self) -> &[ExampleGroup] { &self.examples }
pub fn benches(&self) -> &[String] { &self.benches }
pub fn example_count(&self) -> usize { self.examples.iter().map(|g| g.names.len()).sum() }
#[allow(
dead_code,
reason = "no production callers; kept for future reuse against \
PackageRecord.targets."
)]
pub(super) fn is_binary(&self) -> bool {
self.types.iter().any(|t| matches!(t, ProjectType::Binary))
}
pub const fn publishable(&self) -> bool { self.publishable }
pub fn from_package_record(record: &PackageRecord) -> Self {
let manifest_dir = record.manifest_path.as_path().parent();
let mut has_lib = false;
let mut has_bin = false;
let mut has_proc_macro = false;
let mut example_groups: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut benches: Vec<String> = Vec::new();
for target in &record.targets {
for kind in &target.kinds {
match kind {
TargetKind::Bin => has_bin = true,
TargetKind::Lib
| TargetKind::RLib
| TargetKind::DyLib
| TargetKind::CDyLib
| TargetKind::StaticLib => has_lib = true,
TargetKind::ProcMacro => has_proc_macro = true,
TargetKind::Example => {
let category = example_category(manifest_dir, target.src_path.as_path());
example_groups
.entry(category)
.or_default()
.push(target.name.clone());
},
TargetKind::Bench => benches.push(target.name.clone()),
_ => {},
}
}
}
let mut types = Vec::new();
if has_proc_macro {
types.push(ProjectType::ProcMacro);
} else if has_lib {
types.push(ProjectType::Library);
}
if has_bin {
types.push(ProjectType::Binary);
}
let mut examples: Vec<ExampleGroup> = example_groups
.into_iter()
.map(|(category, mut names)| {
names.sort();
ExampleGroup { category, names }
})
.collect();
examples.sort_by(|a, b| {
let a_root = a.category.is_empty();
let b_root = b.category.is_empty();
match (a_root, b_root) {
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
_ => a.category.cmp(&b.category),
}
});
benches.sort();
let publishable = !matches!(record.publish, PublishPolicy::Never);
Self {
types,
examples,
benches,
publishable,
}
}
}
fn example_category(manifest_dir: Option<&Path>, src_path: &Path) -> String {
let Some(dir) = manifest_dir else {
return String::new();
};
let Ok(rel) = src_path.strip_prefix(dir) else {
return String::new();
};
let parts: Vec<String> = rel
.components()
.filter_map(|c| match c {
Component::Normal(seg) => Some(seg.to_string_lossy().into_owned()),
_ => None,
})
.collect();
if parts.len() >= 3 {
parts[1].clone()
} else {
String::new()
}
}