use crate::commands::range_source::open_local;
#[allow(clippy::too_many_arguments)]
pub(crate) fn reach(
file: &str,
predicate: &str,
mut seeds: Vec<String>,
seeds_file: Option<String>,
reverse: bool,
parallel: bool,
count: bool,
) -> anyhow::Result<()> {
use std::collections::HashMap;
let rete = open_local(file)?;
let dict = rete.dictionary();
if let Some(path) = seeds_file {
for line in std::fs::read_to_string(&path)?.lines() {
let t = line.trim();
if !t.is_empty() && !t.starts_with('#') {
seeds.push(t.to_string());
}
}
}
if seeds.is_empty() {
anyhow::bail!("no seeds given (use --seed <iri> and/or --seeds-file <path>)");
}
let adj: HashMap<u32, Vec<u32>> = if reverse {
let mut m: HashMap<u32, Vec<u32>> = HashMap::new();
for (s, o) in rete.predicate_pairs(predicate) {
m.entry(o).or_default().push(s); }
m
} else {
rete_core::build_adjacency(&rete, predicate)
};
let mut seed_nodes = Vec::with_capacity(seeds.len());
for s in &seeds {
match dict.node_of_term(s) {
Some(n) => seed_nodes.push(n),
None => eprintln!("warning: seed not in graph, skipped: {s}"),
}
}
let sets = if parallel {
rete_core::parallel::batch_reach_parallel(&adj, &seed_nodes)
} else {
rete_core::batch_reach_serial(&adj, &seed_nodes)
};
let dir = if reverse { "reached-by" } else { "reaches" };
for (node, set) in seed_nodes.iter().zip(sets.iter()) {
let seed_term = dict.node_term(*node).unwrap_or_else(|| format!("#{node}"));
if count {
println!("{seed_term} {dir} {} node(s)", set.len());
} else {
println!("{seed_term} {dir} {} node(s):", set.len());
for &n in set {
if let Some(t) = dict.node_term(n) {
println!(" {t}");
}
}
}
}
eprintln!(
"({} seed(s), predicate {predicate}, {})",
seed_nodes.len(),
if parallel { "parallel" } else { "serial" }
);
Ok(())
}