use super::closure::Selectors;
use crate::core::types;
use std::collections::{HashMap, HashSet};
pub(super) type Dropped = Vec<(String, String)>;
fn push_dropped(dropped: &mut Dropped, id: String, cause: &str) {
if dropped.iter().any(|(d, _)| *d == id) {
return;
}
dropped.push((id, cause.to_string()));
}
fn collect_dropped(
config: &types::ForjarConfig,
closure: &HashSet<String>,
dropped: &mut Dropped,
cause: &str,
matches: impl Fn(&str) -> bool,
) {
let hits: Vec<String> = config
.resources
.keys()
.filter(|id| closure.contains(*id) && matches(id))
.cloned()
.collect();
for id in hits {
push_dropped(dropped, id, cause);
}
}
pub(super) fn drop_negatives(
config: &mut types::ForjarConfig,
sel: &Selectors<'_>,
closure: &HashSet<String>,
verbose: bool,
) -> Result<Dropped, String> {
let mut dropped: Dropped = Vec::new();
if let Some(p) = sel.exclude {
let cause = format!("--exclude '{p}'");
collect_dropped(config, closure, &mut dropped, &cause, |id| {
crate::cli::helpers_state::simple_glob_match(p, id)
});
}
if let Some(s) = sel.skip {
let cause = format!("--skip '{s}'");
collect_dropped(config, closure, &mut dropped, &cause, |id| id == s);
}
if let Some(m) = sel.only_machine {
narrow_only_machine(config, m, closure, &mut dropped)?;
}
if let Some(m) = sel.exclude_machine {
narrow_exclude_machine(config, m, closure, &mut dropped, verbose);
}
Ok(dropped)
}
fn narrow_only_machine(
config: &mut types::ForjarConfig,
machine: &str,
closure: &HashSet<String>,
dropped: &mut Dropped,
) -> Result<(), String> {
let cause = format!("--only-machine '{machine}'");
let mut kept_any = false;
for (id, r) in config.resources.iter_mut() {
if !closure.contains(id.as_str()) || dropped.iter().any(|(d, _)| d == id) {
continue;
}
if r.machine.iter().any(|m| m == machine) {
r.machine = types::MachineTarget::Single(machine.to_string());
kept_any = true;
} else {
dropped.push((id.clone(), cause.clone()));
}
}
if kept_any {
return Ok(());
}
Err(format!(
"--only-machine '{machine}' matches no resource in this config"
))
}
fn narrow_exclude_machine(
config: &mut types::ForjarConfig,
machine: &str,
closure: &HashSet<String>,
dropped: &mut Dropped,
verbose: bool,
) {
let cause = format!("--exclude-machine '{machine}'");
let mut kept_any = false;
for (id, r) in config.resources.iter_mut() {
if !closure.contains(id.as_str()) {
continue;
}
let rest: Vec<String> = r
.machine
.iter()
.filter(|m| *m != machine)
.map(str::to_string)
.collect();
r.machine = types::MachineTarget::Multiple(rest);
if r.machine.is_empty() {
push_dropped(dropped, id.clone(), &cause);
} else {
kept_any = true;
}
}
if !kept_any && verbose {
eprintln!("--exclude-machine '{machine}': no resources remain to apply");
}
}
pub(super) fn contract_edges(
config: &mut types::ForjarConfig,
keep: &[String],
removed: &HashSet<String>,
) -> Vec<(String, String)> {
if removed.is_empty() {
return Vec::new();
}
let dep_map: HashMap<String, Vec<String>> = config
.resources
.iter()
.map(|(id, r)| (id.clone(), r.depends_on.clone()))
.collect();
let mut cut: Vec<(String, String)> = Vec::new();
for id in keep {
let Some(old) = dep_map.get(id) else { continue };
if !old.iter().any(|d| removed.contains(d)) {
continue;
}
for d in old.iter().filter(|d| removed.contains(*d)) {
cut.push((id.clone(), d.clone()));
}
let contracted = contract_one(old, &dep_map, removed);
if let Some(r) = config.resources.get_mut(id) {
r.depends_on = contracted;
}
}
cut
}
fn contract_one(
old: &[String],
dep_map: &HashMap<String, Vec<String>>,
removed: &HashSet<String>,
) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for d in old {
expand_dep(d, dep_map, removed, &mut out, &mut seen);
}
out
}
fn expand_dep(
dep: &str,
dep_map: &HashMap<String, Vec<String>>,
removed: &HashSet<String>,
out: &mut Vec<String>,
seen: &mut HashSet<String>,
) {
if !removed.contains(dep) {
if !out.iter().any(|d| d == dep) {
out.push(dep.to_string());
}
return;
}
if !seen.insert(dep.to_string()) {
return;
}
let Some(deps) = dep_map.get(dep) else { return };
for d in deps {
expand_dep(d, dep_map, removed, out, seen);
}
}