use super::helpers::*;
use super::helpers_state::simple_glob_match;
use crate::core::types;
use std::collections::HashSet;
use std::path::Path;
pub(crate) fn cmd_extract(
file: &Path,
tag_filter: Option<&str>,
group_filter: Option<&str>,
glob_filter: Option<&str>,
output: Option<&Path>,
json: bool,
) -> Result<(), String> {
if tag_filter.is_none() && group_filter.is_none() && glob_filter.is_none() {
return Err("at least one of --tags, --group, or --glob is required".to_string());
}
let config = parse_and_validate(file)?;
let mut extracted = config.clone();
extracted.resources.retain(|id, resource| {
let tag_match = tag_filter
.map(|tag| resource.tags.iter().any(|t| t == tag))
.unwrap_or(true);
let group_match = group_filter
.map(|g| resource.resource_group.as_deref() == Some(g))
.unwrap_or(true);
let glob_match = glob_filter
.map(|g| simple_glob_match(g, id))
.unwrap_or(true);
tag_match && group_match && glob_match
});
if extracted.resources.is_empty() {
return Err("no resources match the given filters".to_string());
}
let referenced_machines = collect_referenced_machines(&extracted);
extracted
.machines
.retain(|k, _| referenced_machines.contains(k));
extracted.moved.retain(|m| {
extracted.resources.contains_key(&m.to) || extracted.resources.contains_key(&m.from)
});
let filter_desc = build_filter_desc(tag_filter, group_filter, glob_filter);
extracted.name = format!("{} (extract: {})", extracted.name, filter_desc);
let count = extracted.resources.len();
let machine_count = extracted.machines.len();
if json {
let out =
serde_json::to_string_pretty(&extracted).map_err(|e| format!("JSON error: {e}"))?;
write_or_print(output, &out)?;
} else {
let out = serde_yaml_ng::to_string(&extracted).map_err(|e| format!("YAML error: {e}"))?;
write_or_print(output, &out)?;
}
eprintln!("Extracted: {count} resources, {machine_count} machines (filter: {filter_desc})");
Ok(())
}
fn collect_referenced_machines(config: &types::ForjarConfig) -> HashSet<String> {
let mut machines = HashSet::new();
for resource in config.resources.values() {
for m in resource.machine.iter() {
machines.insert(m.to_owned());
}
}
machines
}
fn build_filter_desc(tag: Option<&str>, group: Option<&str>, glob: Option<&str>) -> String {
let mut parts = Vec::new();
if let Some(t) = tag {
parts.push(format!("tags={t}"));
}
if let Some(g) = group {
parts.push(format!("group={g}"));
}
if let Some(g) = glob {
parts.push(format!("glob={g}"));
}
parts.join(", ")
}
fn write_or_print(output: Option<&Path>, content: &str) -> Result<(), String> {
match output {
Some(path) => {
std::fs::write(path, content).map_err(|e| format!("write {}: {e}", path.display()))?;
eprintln!("Written to {}", path.display());
}
None => print!("{content}"),
}
Ok(())
}