Skip to main content

cargo_ferris_wheel/executors/
graph.rs

1//! Graph command executor
2
3use 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        // Discover and analyze workspaces
29        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        // Build dependency graph
40        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        // Detect cycles if highlighting is requested
56        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        // Create renderer
67        let renderer =
68            crate::graph::GraphRenderer::new(config.highlight_cycles, config.show_crates);
69
70        // Determine output destination
71        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        // Render based on format
85        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}