fn export_to_graphml(
graph: &SimpleGraph,
result: &GraphMetricsResult,
output: &Option<PathBuf>,
) -> Result<()> {
let mut graphml = String::new();
write_graphml_header(&mut graphml)?;
write_graphml_nodes(&mut graphml, &result.nodes)?;
write_graphml_edges(&mut graphml, graph)?;
write_graphml_footer(&mut graphml)?;
write_graphml_file(&graphml, output)?;
Ok(())
}
fn write_graphml_header(graphml: &mut String) -> Result<()> {
use std::fmt::Write;
writeln!(graphml, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(
graphml,
r#"<graphml xmlns="http://graphml.graphdrawing.org/xmlns">"#
)?;
writeln!(graphml, r#" <graph id="G" edgedefault="directed">"#)?;
Ok(())
}
fn write_graphml_nodes(graphml: &mut String, nodes: &[NodeMetrics]) -> Result<()> {
use std::fmt::Write;
for node in nodes {
writeln!(graphml, r#" <node id="{}" />"#, node.name)?;
}
Ok(())
}
fn write_graphml_edges(graphml: &mut String, graph: &SimpleGraph) -> Result<()> {
use std::fmt::Write;
for (source, target) in graph.edge_endpoints() {
let source_name = graph.get_node(source);
let target_name = graph.get_node(target);
writeln!(
graphml,
r#" <edge source="{source_name}" target="{target_name}" />"#
)?;
}
Ok(())
}
fn write_graphml_footer(graphml: &mut String) -> Result<()> {
use std::fmt::Write;
writeln!(graphml, " </graph>")?;
writeln!(graphml, "</graphml>")?;
Ok(())
}
fn write_graphml_file(graphml: &str, output: &Option<PathBuf>) -> Result<()> {
if let Some(path) = output {
let graphml_path = path.with_extension("graphml");
std::fs::write(&graphml_path, graphml)?;
eprintln!("✅ GraphML exported to: {}", graphml_path.display());
}
Ok(())
}
fn format_output(
result: GraphMetricsResult,
format: crate::cli::GraphMetricsOutputFormat,
) -> Result<String> {
match format {
crate::cli::GraphMetricsOutputFormat::Json => format_gm_as_json(result),
crate::cli::GraphMetricsOutputFormat::Human
| crate::cli::GraphMetricsOutputFormat::Summary
| crate::cli::GraphMetricsOutputFormat::Detailed => format_gm_as_human(result),
crate::cli::GraphMetricsOutputFormat::Csv => format_gm_as_csv(result),
crate::cli::GraphMetricsOutputFormat::GraphML => {
Ok("GraphML export handled separately.".to_string())
}
crate::cli::GraphMetricsOutputFormat::Markdown => format_gm_as_markdown(result),
}
}
fn format_gm_as_json(result: GraphMetricsResult) -> Result<String> {
Ok(serde_json::to_string_pretty(&result)?)
}
fn format_gm_as_human(result: GraphMetricsResult) -> Result<String> {
let mut output = String::new();
write_gm_human_header(&mut output)?;
write_gm_statistics(&mut output, &result)?;
write_gm_top_nodes(&mut output, &result)?;
Ok(output)
}
fn write_gm_human_header(output: &mut String) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(output, "{}{}Graph Metrics Analysis{}\n", c::BOLD, c::UNDERLINE, c::RESET)?;
writeln!(output, "{}Graph Statistics{}", c::BOLD, c::RESET)?;
Ok(())
}
fn write_gm_statistics(output: &mut String, result: &GraphMetricsResult) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(output, " {}Total nodes:{} {}{}{}", c::BOLD, c::RESET, c::BOLD_WHITE, result.total_nodes, c::RESET)?;
writeln!(output, " {}Total edges:{} {}{}{}", c::BOLD, c::RESET, c::BOLD_WHITE, result.total_edges, c::RESET)?;
writeln!(output, " {}Density:{} {}{:.3}{}", c::BOLD, c::RESET, c::BOLD_WHITE, result.density, c::RESET)?;
writeln!(output, " {}Average degree:{} {}{:.2}{}", c::BOLD, c::RESET, c::BOLD_WHITE, result.average_degree, c::RESET)?;
writeln!(output, " {}Max degree:{} {}{}{}", c::BOLD, c::RESET, c::BOLD_WHITE, result.max_degree, c::RESET)?;
writeln!(
output,
" {}Connected components:{} {}{}{}",
c::BOLD, c::RESET, c::BOLD_WHITE, result.connected_components, c::RESET
)?;
Ok(())
}
fn write_gm_top_nodes(output: &mut String, result: &GraphMetricsResult) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(output, "\n{}Top Nodes by Centrality{}\n", c::BOLD, c::RESET)?;
for (i, node) in result.nodes.iter().enumerate() {
write_gm_node_details(output, i + 1, node)?;
}
Ok(())
}
fn write_gm_node_details(output: &mut String, index: usize, node: &NodeMetrics) -> Result<()> {
use crate::cli::colors as c;
use std::fmt::Write;
writeln!(output, " {}. {}{}{}", index, c::CYAN, node.name, c::RESET)?;
writeln!(
output,
" {}Degree:{} {}{:.3}{} (in: {}, out: {})",
c::BOLD, c::RESET, c::BOLD_WHITE, node.degree_centrality, c::RESET, node.in_degree, node.out_degree
)?;
writeln!(
output,
" {}Betweenness:{} {}{:.3}{}",
c::BOLD, c::RESET, c::BOLD_WHITE, node.betweenness_centrality, c::RESET
)?;
writeln!(output, " {}Closeness:{} {}{:.3}{}", c::BOLD, c::RESET, c::BOLD_WHITE, node.closeness_centrality, c::RESET)?;
writeln!(output, " {}PageRank:{} {}{:.3}{}", c::BOLD, c::RESET, c::BOLD_WHITE, node.pagerank, c::RESET)?;
writeln!(output)?;
Ok(())
}
fn format_gm_as_csv(result: GraphMetricsResult) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(
output,
"name,degree_centrality,betweenness,closeness,pagerank,in_degree,out_degree"
)?;
for node in result.nodes {
writeln!(
output,
"{},{:.3},{:.3},{:.3},{:.3},{},{}",
node.name,
node.degree_centrality,
node.betweenness_centrality,
node.closeness_centrality,
node.pagerank,
node.in_degree,
node.out_degree
)?;
}
Ok(output)
}
fn format_gm_as_markdown(result: GraphMetricsResult) -> Result<String> {
let mut output = String::new();
write_gm_markdown_header(&mut output)?;
write_gm_markdown_summary(&mut output, &result)?;
write_gm_markdown_top_nodes(&mut output, &result)?;
Ok(output)
}
fn write_gm_markdown_header(output: &mut String) -> Result<()> {
use std::fmt::Write;
writeln!(output, "# Graph Metrics Report\n")?;
writeln!(output, "## Summary\n")?;
Ok(())
}
fn write_gm_markdown_summary(output: &mut String, result: &GraphMetricsResult) -> Result<()> {
use std::fmt::Write;
writeln!(output, "| Metric | Value |")?;
writeln!(output, "|--------|-------|")?;
writeln!(output, "| Total Nodes | {} |", result.total_nodes)?;
writeln!(output, "| Total Edges | {} |", result.total_edges)?;
writeln!(output, "| Density | {:.3} |", result.density)?;
writeln!(output, "| Average Degree | {:.2} |", result.average_degree)?;
writeln!(output, "| Max Degree | {} |", result.max_degree)?;
writeln!(
output,
"| Connected Components | {} |",
result.connected_components
)?;
Ok(())
}
fn write_gm_markdown_top_nodes(output: &mut String, result: &GraphMetricsResult) -> Result<()> {
use std::fmt::Write;
writeln!(output, "\n## Top Nodes\n")?;
writeln!(
output,
"| Node | Degree | Betweenness | Closeness | PageRank |"
)?;
writeln!(
output,
"|------|--------|-------------|-----------|----------|"
)?;
for node in result.nodes.iter().take(10) {
writeln!(
output,
"| {} | {:.3} | {:.3} | {:.3} | {:.3} |",
node.name,
node.degree_centrality,
node.betweenness_centrality,
node.closeness_centrality,
node.pagerank
)?;
}
Ok(())
}