cargo_ferris_wheel/executors/
graph.rs1use std::fs::File;
4use std::io::{self, BufWriter};
5
6use console::style;
7use miette::{IntoDiagnostic, Result, WrapErr};
8
9use crate::analyzer::WorkspaceAnalyzer;
10use crate::cli::GraphFormat;
11use crate::config::GraphOptions;
12use crate::detector::CycleDetector;
13use crate::executors::CommandExecutor;
14use crate::graph::DependencyGraphBuilder;
15
16pub struct GraphExecutor;
17
18impl CommandExecutor for GraphExecutor {
19 type Config = GraphOptions;
20
21 fn execute(config: Self::Config) -> Result<()> {
22 eprintln!(
23 "{} Generating {} dependency graph...",
24 style("📊").cyan(),
25 format!("{:?}", config.format).to_lowercase()
26 );
27
28 let mut analyzer = WorkspaceAnalyzer::new();
30 analyzer
31 .discover_workspaces(&config.paths, None)
32 .wrap_err("Failed to discover workspaces")?;
33
34 if analyzer.workspaces().is_empty() {
35 eprintln!("{} No workspaces found to visualize", style("ℹ").blue());
36 return Ok(());
37 }
38
39 let mut graph_builder = DependencyGraphBuilder::new(
41 config.exclude_dev,
42 config.exclude_build,
43 config.exclude_target,
44 );
45 graph_builder
46 .build_cross_workspace_graph(
47 analyzer.workspaces(),
48 analyzer.crate_to_workspace(),
49 analyzer.crate_path_to_workspace(),
50 analyzer.crate_to_paths(),
51 None,
52 )
53 .wrap_err("Failed to build dependency graph")?;
54
55 let cycles = if config.highlight_cycles {
57 let mut detector = CycleDetector::new();
58 detector
59 .detect_cycles(graph_builder.graph())
60 .wrap_err("Failed to detect cycles")?;
61 detector.cycles().to_vec()
62 } else {
63 Vec::new()
64 };
65
66 let renderer =
68 crate::graph::GraphRenderer::new(config.highlight_cycles, config.show_crates);
69
70 let mut output_writer: Box<dyn io::Write> =
72 if let Some(output_path) = config.output.as_ref() {
73 Box::new(BufWriter::new(
74 File::create(output_path)
75 .into_diagnostic()
76 .wrap_err_with(|| {
77 format!("Failed to create output file '{}'", output_path.display())
78 })?,
79 ))
80 } else {
81 Box::new(io::stdout())
82 };
83
84 match config.format {
86 GraphFormat::Ascii => {
87 renderer
88 .render_ascii(graph_builder.graph(), &cycles, output_writer.as_mut())
89 .wrap_err("Failed to render ASCII graph")?;
90 }
91 GraphFormat::Mermaid => {
92 renderer
93 .render_mermaid(graph_builder.graph(), &cycles, output_writer.as_mut())
94 .wrap_err("Failed to render Mermaid graph")?;
95 }
96 GraphFormat::Dot => {
97 renderer
98 .render_dot(graph_builder.graph(), &cycles, output_writer.as_mut())
99 .wrap_err("Failed to render DOT graph")?;
100 }
101 GraphFormat::D2 => {
102 renderer
103 .render_d2(graph_builder.graph(), &cycles, output_writer.as_mut())
104 .wrap_err("Failed to render D2 graph")?;
105 }
106 }
107
108 if let Some(output_path) = config.output {
109 eprintln!(
110 "{} Graph written to {}",
111 style("✓").green(),
112 style(output_path.display()).bold()
113 );
114 }
115
116 Ok(())
117 }
118}