use std::fs;
use std::path::Path;
pub(super) fn install_kind(exe: &Path) -> InstallKind {
let Some(dir) = exe.parent() else {
return InstallKind::Standalone;
};
for candidate in [Some(dir), dir.parent()].into_iter().flatten() {
match cargo_record(candidate) {
CargoRecord::ListsCrate | CargoRecord::Unreadable => return InstallKind::Cargo,
CargoRecord::Absent | CargoRecord::ListsSomethingElse => {}
}
}
InstallKind::Standalone
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum CargoRecord {
Absent,
ListsCrate,
ListsSomethingElse,
Unreadable,
}
pub(super) fn cargo_record(dir: &Path) -> CargoRecord {
let mut found = false;
if let Ok(text) = fs::read_to_string(dir.join(".crates2.json")) {
found = true;
match serde_json::from_str::<serde_json::Value>(&text) {
Ok(json) => {
let lists = json
.get("installs")
.and_then(|value| value.as_object())
.map(|installs| installs.keys().any(|key| names_this_crate(key)))
.unwrap_or(false);
if lists {
return CargoRecord::ListsCrate;
}
}
Err(_) => return CargoRecord::Unreadable,
}
}
if let Ok(text) = fs::read_to_string(dir.join(".crates.toml")) {
found = true;
match toml::from_str::<toml::Table>(&text) {
Ok(table) => {
let lists = table
.get("v1")
.and_then(|value| value.as_table())
.map(|v1| v1.keys().any(|key| names_this_crate(key)))
.unwrap_or(false);
if lists {
return CargoRecord::ListsCrate;
}
}
Err(_) => return CargoRecord::Unreadable,
}
}
if found {
CargoRecord::ListsSomethingElse
} else {
CargoRecord::Absent
}
}
pub(super) fn names_this_crate(key: &str) -> bool {
key.split_once(' ')
.map(|(name, _)| name == env!("CARGO_PKG_NAME"))
.unwrap_or(false)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum InstallKind {
Cargo,
Standalone,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_record_beside_the_bin_directory_is_what_makes_it_cargos() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(
tmp.path().join(".crates2.json"),
r#"{"installs":{"pmpx 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["pmpx"]}}}"#,
)
.unwrap();
assert_eq!(install_kind(&bin.join("pmpx")), InstallKind::Cargo);
}
#[test]
fn the_older_record_works_too() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(
tmp.path().join(".crates.toml"),
"[v1]\n\"pmpx 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"pmpx\"]\n",
)
.unwrap();
assert_eq!(install_kind(&bin.join("pmpx")), InstallKind::Cargo);
}
#[test]
fn a_record_beside_the_binary_itself_counts() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join(".crates.toml"),
"[v1]\n\"pmpx 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)\" = [\"pmpx\"]\n",
)
.unwrap();
assert_eq!(install_kind(&tmp.path().join("pmpx")), InstallKind::Cargo);
}
#[test]
fn another_crates_record_does_not_claim_this_binary() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(
tmp.path().join(".crates2.json"),
r#"{"installs":{"cargo-bumpp 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["bumpp"]}}}"#,
)
.unwrap();
assert_eq!(install_kind(&bin.join("pmpx")), InstallKind::Standalone);
}
#[test]
fn an_unreadable_record_is_treated_as_cargos() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::write(tmp.path().join(".crates2.json"), "not json at all").unwrap();
assert_eq!(install_kind(&bin.join("pmpx")), InstallKind::Cargo);
}
#[test]
fn no_record_at_all_is_standalone() {
let tmp = tempfile::tempdir().unwrap();
let bin = tmp.path().join("bin");
std::fs::create_dir_all(&bin).unwrap();
assert_eq!(install_kind(&bin.join("pmpx")), InstallKind::Standalone);
}
}