use std::collections::HashMap;
use crate::models::{InstallOpts, PackageSource};
pub fn parse_crates2(json: &str) -> HashMap<String, InstallOpts> {
let mut out = HashMap::new();
let value: serde_json::Value = match serde_json::from_str(json) {
Ok(v) => v,
Err(_) => return out,
};
let installs = match value.get("installs").and_then(|v| v.as_object()) {
Some(m) => m,
None => return out,
};
for (key, entry) in installs {
let no_default_features = entry
.get("no_default_features")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let all_features = entry
.get("all_features")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let features = entry
.get("features")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|f| f.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_default();
out.insert(
key.clone(),
InstallOpts {
no_default_features,
all_features,
features,
},
);
}
out
}
pub fn load_install_opts() -> HashMap<String, InstallOpts> {
let path = match crate::package::registry::cargo_home() {
Some(p) => p.join(".crates2.json"),
None => return HashMap::new(),
};
let body = match std::fs::read_to_string(&path) {
Ok(b) => b,
Err(_) => return HashMap::new(),
};
parse_crates2(&body)
}
pub fn match_install_opts(
map: &HashMap<String, InstallOpts>,
name: &str,
source: &PackageSource,
) -> Option<InstallOpts> {
let candidates: Vec<(&String, &InstallOpts)> = map
.iter()
.filter(|(k, _)| k.split(' ').next() == Some(name))
.collect();
if candidates.is_empty() {
return None;
}
let want_prefix = match source {
PackageSource::Crates => "registry+",
PackageSource::Git { .. } => "git+",
PackageSource::Path { .. } => "path+",
PackageSource::Unknown(_) => "",
};
if !want_prefix.is_empty() {
if let Some((_, opts)) = candidates.iter().find(|(k, _)| {
k.find('(')
.and_then(|i| k.get(i + 1..))
.map(|s| s.starts_with(want_prefix))
.unwrap_or(false)
}) {
return Some((*opts).clone());
}
}
Some(candidates[0].1.clone())
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = r#"{
"installs": {
"ripgrep 14.1.1 (registry+https://github.com/rust-lang/crates.io-index)": {
"version_req": null, "bins": ["rg"], "features": ["pcre2"],
"all_features": false, "no_default_features": false,
"profile": "release", "target": null, "rustc": null
},
"cargo-binstall 1.19.1 (registry+https://github.com/rust-lang/crates.io-index)": {
"version_req": null, "bins": ["cargo-binstall"], "features": [],
"all_features": false, "no_default_features": false,
"profile": "release", "target": null, "rustc": null
},
"fat 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)": {
"features": [], "all_features": true, "no_default_features": false
},
"slim 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)": {
"features": [], "all_features": false, "no_default_features": true
}
}
}"#;
#[test]
fn parses_features_list() {
let m = parse_crates2(SAMPLE);
let o = m
.get("ripgrep 14.1.1 (registry+https://github.com/rust-lang/crates.io-index)")
.unwrap();
assert_eq!(o.features, vec!["pcre2".to_string()]);
assert!(!o.all_features);
assert!(!o.no_default_features);
}
#[test]
fn default_package_is_default() {
let m = parse_crates2(SAMPLE);
let o = m
.get("cargo-binstall 1.19.1 (registry+https://github.com/rust-lang/crates.io-index)")
.unwrap();
assert!(o.is_default());
}
#[test]
fn parses_all_features_and_no_default() {
let m = parse_crates2(SAMPLE);
assert!(
m.get("fat 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)")
.unwrap()
.all_features
);
assert!(
m.get("slim 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)")
.unwrap()
.no_default_features
);
}
#[test]
fn malformed_json_yields_empty_map() {
assert!(parse_crates2("{not valid json").is_empty());
assert!(parse_crates2("").is_empty());
}
#[test]
fn missing_installs_key_yields_empty_map() {
assert!(parse_crates2(r#"{"v1":{}}"#).is_empty());
}
#[test]
fn missing_fields_default_to_false_empty() {
let m = parse_crates2(
r#"{"installs":{"x 1.0.0 (registry+https://example)":{"bins":["x"]}}}"#,
);
let o = m.get("x 1.0.0 (registry+https://example)").unwrap();
assert!(o.is_default());
}
#[test]
fn match_by_name_simple() {
let m = parse_crates2(SAMPLE);
let o = match_install_opts(&m, "ripgrep", &PackageSource::Crates).unwrap();
assert_eq!(o.features, vec!["pcre2".to_string()]);
}
#[test]
fn match_no_entry_returns_none() {
let m = parse_crates2(SAMPLE);
assert!(match_install_opts(&m, "does-not-exist", &PackageSource::Crates).is_none());
}
#[test]
fn match_prefers_source_consistent_entry() {
let json = r#"{"installs":{
"dup 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)": {"features":["reg"]},
"dup 0.9.0 (git+https://github.com/x/dup#abc)": {"features":["git"]}
}}"#;
let m = parse_crates2(json);
assert_eq!(
match_install_opts(&m, "dup", &PackageSource::Crates).unwrap().features,
vec!["reg".to_string()]
);
assert_eq!(
match_install_opts(
&m,
"dup",
&PackageSource::Git { url: "https://github.com/x/dup".into(), rev: None }
)
.unwrap()
.features,
vec!["git".to_string()]
);
}
}