use crate::error::{PassengerError, Result};
use crate::model::ManifestInfo;
use klepto::{FeatureEnable, FeatureModel, Klepto, KleptoBuilder, PackageInfo};
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
pub fn enable_text(e: &FeatureEnable) -> String {
match e {
FeatureEnable::Feature(f) => f.clone(),
FeatureEnable::Dep(d) => format!("dep:{d}"),
FeatureEnable::DepFeature { dep, feature, weak } => format!("{dep}{}/{feature}", if *weak { "?" } else { "" }),
}
}
pub fn manifest_info(model: &FeatureModel) -> ManifestInfo {
let mut features_raw: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut feature_deps: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for (name, info) in model.features.iter().filter(|(_, i)| !i.implicit) {
features_raw.insert(name.clone(), info.enables.iter().map(enable_text).collect());
feature_deps.insert(name.clone(), model.deps_enabled_by(name));
}
let mut dep_features: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for (feat, deps) in &feature_deps {
for dep in deps {
dep_features.entry(dep.clone()).or_default().insert(feat.clone());
}
}
ManifestInfo {
crate_name: model.package.clone(),
features_raw,
all_deps: model.dependencies.iter().map(|d| d.name.clone()).collect(),
optional_deps: model.dependencies.iter().filter(|d| d.optional).map(|d| d.name.clone()).collect(),
feature_deps,
dep_features,
}
}
pub fn builder(root: &Path) -> Result<KleptoBuilder> {
Klepto::new("workspace")
.scan_workspace_root(root)
.map_err(|e| PassengerError::unsupported(e.to_string()))?
.exclude_generated()
.map_err(|e| PassengerError::unsupported(e.to_string()))
}
fn same_file(a: &Path, b: &Path) -> bool {
a == b || std::fs::canonicalize(a).ok().is_some_and(|ca| std::fs::canonicalize(b).ok().is_some_and(|cb| ca == cb))
}
pub fn package_with_manifest<'p>(packages: &'p BTreeMap<String, PackageInfo>, manifest: &Path) -> Option<&'p PackageInfo> {
packages.values().find(|p| same_file(&p.manifest_path, manifest))
}
pub fn manifest_of(packages: &BTreeMap<String, PackageInfo>, package: &str) -> Option<PathBuf> {
packages.get(&package.replace('-', "_")).map(|p| p.manifest_path.clone())
}
pub fn manifest_name(manifest: &Path) -> Option<String> {
let text = std::fs::read_to_string(manifest).ok()?;
let doc: toml::Table = text.parse().ok()?;
doc.get("package")?.get("name")?.as_str().map(str::to_string)
}
pub fn cargo_names(root: &Path) -> std::collections::BTreeMap<String, String> {
let Ok(b) = builder(root) else { return Default::default() };
b.packages().iter().filter_map(|(n, p)| manifest_name(&p.manifest_path).filter(|m| m != n).map(|m| (n.clone(), m))).collect()
}