use crate::manifest::Manifest;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
#[derive(Clone)]
pub struct AlsoIn {
pub prefix: PathBuf,
pub version: String,
}
pub fn known_others(current: &Path) -> Vec<PathBuf> {
let mut candidates = vec![PathBuf::from("/usr/local")];
#[allow(deprecated)] if let Some(home) = std::env::home_dir() {
candidates.push(home.join(".local"));
}
candidates.retain(|c| c != current);
candidates
}
pub fn also_installed(current: &Path) -> BTreeMap<String, Vec<AlsoIn>> {
also_installed_from(known_others(current))
}
fn also_installed_from(others: impl IntoIterator<Item = PathBuf>) -> BTreeMap<String, Vec<AlsoIn>> {
let mut map: BTreeMap<String, Vec<AlsoIn>> = BTreeMap::new();
for other in others {
let Ok(manifest) = Manifest::load(&other) else {
continue;
};
for (name, entry) in &manifest.crates {
map.entry(name.clone()).or_default().push(AlsoIn {
prefix: other.clone(),
version: entry.version.clone(),
});
}
}
map
}
pub fn describe(entries: &[AlsoIn]) -> String {
use std::fmt::Write as _;
let mut out = String::new();
for a in entries {
let _ = write!(out, " [also in {} @{}]", a.prefix.display(), a.version);
}
crate::text::sanitize(&out)
}
pub fn describe_for(map: &BTreeMap<String, Vec<AlsoIn>>, name: &str) -> String {
map.get(name).map(|v| describe(v)).unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
fn write_manifest(prefix: &Path, name: &str, version: &str) {
let dir = prefix.join("share/cargo-lbin");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(
dir.join("manifest.json"),
format!(
r#"{{"version":1,"crates":{{"{name}":{{"version":"{version}","bins":["{name}"],"locked":false,"pinned":false}}}}}}"#
),
)
.unwrap();
}
#[test]
fn sees_the_other_prefix_and_never_itself() {
let root = std::env::temp_dir().join("cargo-lbin-test-prefixes");
let _ = std::fs::remove_dir_all(&root);
let here = root.join("here");
let there = root.join("there");
write_manifest(&here, "local-only", "1.0.0");
write_manifest(&there, "elsewhere", "2.3.4");
let map = also_installed_from([there.clone()]);
assert!(map.contains_key("elsewhere"));
assert!(
!map.contains_key("local-only"),
"the current prefix is not 'also'"
);
let s = describe_for(&map, "elsewhere");
assert!(s.contains("[also in") && s.contains("@2.3.4"), "{s}");
assert_eq!(describe_for(&map, "local-only"), "");
let others = known_others(Path::new("/usr/local"));
assert!(others.iter().all(|p| p != Path::new("/usr/local")));
let _ = std::fs::remove_dir_all(&root);
}
}