cargo_ferris_wheel/
lib.rs

1//! # Ferris Wheel - Detect Dependency Cycles in Rust Monorepos
2//!
3//! Ferris Wheel is a tool for detecting circular dependencies in Rust
4//! workspaces. It analyzes Cargo workspace structures and identifies dependency
5//! cycles that could lead to compilation issues or architectural problems.
6//!
7//! ## Main Components
8//!
9//! - **Analyzer**: Discovers and analyzes Rust workspaces and their
10//!   dependencies
11//! - **Detector**: Implements cycle detection algorithms (Tarjan's SCC)
12//! - **Graph**: Builds and manages the dependency graph representation
13//! - **Reports**: Generates human-readable and machine-readable reports
14//!
15//! ## Usage
16//!
17//! ### Real-World Example: Analyzing a Rust Monorepo
18//!
19//! ```no_run
20//! use std::path::PathBuf;
21//!
22//! use cargo_ferris_wheel::analyzer::WorkspaceAnalyzer;
23//! use cargo_ferris_wheel::detector::CycleDetector;
24//! use cargo_ferris_wheel::graph::DependencyGraphBuilder;
25//! use cargo_ferris_wheel::reports::{HumanReportGenerator, JsonReportGenerator, ReportGenerator};
26//! use miette::IntoDiagnostic;
27//!
28//! # fn main() -> miette::Result<()> {
29//! // Step 1: Discover all workspaces in your monorepo
30//! let mut analyzer = WorkspaceAnalyzer::new();
31//! let repo_root = PathBuf::from("/path/to/your/monorepo");
32//! analyzer.discover_workspaces(&[repo_root], None)?;
33//!
34//! println!("Found {} workspaces", analyzer.workspaces().len());
35//!
36//! // Step 2: Build the dependency graph
37//! let mut graph_builder = DependencyGraphBuilder::new(
38//!     false, // include dev dependencies
39//!     false, // include build dependencies
40//!     false, // include target-specific dependencies
41//! );
42//!
43//! graph_builder.build_cross_workspace_graph(
44//!     analyzer.workspaces(),
45//!     analyzer.crate_to_workspace(),
46//!     analyzer.crate_path_to_workspace(),
47//!     analyzer.crate_to_paths(),
48//!     None, // no progress reporter
49//! )?;
50//!
51//! // Step 3: Detect circular dependencies
52//! let mut detector = CycleDetector::new();
53//! detector.detect_cycles(graph_builder.graph())?;
54//!
55//! // Step 4: Generate reports
56//! if detector.has_cycles() {
57//!     println!(
58//!         "⚠️  Found {} circular dependencies!",
59//!         detector.cycle_count()
60//!     );
61//!
62//!     // Human-readable report for console output
63//!     let human_report = HumanReportGenerator::new(Some(5)); // show max 5 cycles
64//!     println!("{}", human_report.generate_report(&detector)?);
65//!
66//!     // JSON report for programmatic processing
67//!     let json_report = JsonReportGenerator::new();
68//!     let json_output = json_report.generate_report(&detector)?;
69//!     std::fs::write("cycles.json", json_output).into_diagnostic()?;
70//! } else {
71//!     println!("✅ No circular dependencies found!");
72//! }
73//! # Ok(())
74//! # }
75//! ```
76//!
77//! ### Example: Visualizing the Dependency Graph
78//!
79//! ```no_run
80//! use std::io::Write;
81//!
82//! use cargo_ferris_wheel::graph::GraphRenderer;
83//! use miette::IntoDiagnostic;
84//! # use std::path::PathBuf;
85//! # use cargo_ferris_wheel::{
86//! #     analyzer::WorkspaceAnalyzer,
87//! #     detector::CycleDetector,
88//! #     graph::DependencyGraphBuilder,
89//! # };
90//!
91//! # fn main() -> miette::Result<()> {
92//! # let mut analyzer = WorkspaceAnalyzer::new();
93//! # analyzer.discover_workspaces(&[PathBuf::from(".")], None)?;
94//! # let mut graph_builder = DependencyGraphBuilder::new(false, false, false);
95//! # graph_builder.build_cross_workspace_graph(
96//! #     analyzer.workspaces(),
97//! #     analyzer.crate_to_workspace(),
98//! #     analyzer.crate_path_to_workspace(),
99//! #     analyzer.crate_to_paths(),
100//! #     None,
101//! # )?;
102//! # let mut detector = CycleDetector::new();
103//! # detector.detect_cycles(graph_builder.graph())?;
104//! // Create a visual representation of your dependency graph
105//! let renderer = GraphRenderer::new(
106//!     true,  // highlight cycles
107//!     false, // don't show individual crate details
108//! );
109//!
110//! // Generate a Mermaid diagram (great for documentation)
111//! let mut mermaid_output = Vec::new();
112//! renderer.render_mermaid(
113//!     graph_builder.graph(),
114//!     detector.cycles(),
115//!     &mut mermaid_output,
116//! )?;
117//!
118//! std::fs::write("dependencies.mmd", mermaid_output).into_diagnostic()?;
119//!
120//! // Or generate a DOT file for Graphviz
121//! let mut dot_output = Vec::new();
122//! renderer.render_dot(graph_builder.graph(), detector.cycles(), &mut dot_output)?;
123//!
124//! std::fs::write("dependencies.dot", dot_output).into_diagnostic()?;
125//! # Ok(())
126//! # }
127//! ```
128//!
129//! ### Example: Filtering Dependencies
130//!
131//! ```no_run
132//! # use std::path::PathBuf;
133//! # use cargo_ferris_wheel::{
134//! #     analyzer::WorkspaceAnalyzer,
135//! #     detector::CycleDetector,
136//! #     graph::DependencyGraphBuilder,
137//! # };
138//! # fn main() -> miette::Result<()> {
139//! # let mut analyzer = WorkspaceAnalyzer::new();
140//! # analyzer.discover_workspaces(&[PathBuf::from(".")], None)?;
141//! // Check only production dependencies (exclude dev and build deps)
142//! let mut graph_builder = DependencyGraphBuilder::new(
143//!     true,  // exclude dev dependencies
144//!     true,  // exclude build dependencies
145//!     false, // include target-specific dependencies
146//! );
147//!
148//! graph_builder.build_cross_workspace_graph(
149//!     analyzer.workspaces(),
150//!     analyzer.crate_to_workspace(),
151//!     analyzer.crate_path_to_workspace(),
152//!     analyzer.crate_to_paths(),
153//!     None,
154//! )?;
155//!
156//! let mut detector = CycleDetector::new();
157//! detector.detect_cycles(graph_builder.graph())?;
158//!
159//! println!("Production dependency cycles: {}", detector.cycle_count());
160//! # Ok(())
161//! # }
162//! ```
163//!
164//! ### Example: Analyzing Specific Workspaces
165//!
166//! ```no_run
167//! # use std::path::PathBuf;
168//! # use cargo_ferris_wheel::{
169//! #     analyzer::WorkspaceAnalyzer,
170//! #     detector::{CycleDetector, WorkspaceCycle},
171//! #     graph::DependencyGraphBuilder,
172//! # };
173//! # fn main() -> miette::Result<()> {
174//! # let mut analyzer = WorkspaceAnalyzer::new();
175//! # analyzer.discover_workspaces(&[PathBuf::from(".")], None)?;
176//! # let mut graph_builder = DependencyGraphBuilder::new(false, false, false);
177//! # graph_builder.build_cross_workspace_graph(
178//! #     analyzer.workspaces(),
179//! #     analyzer.crate_to_workspace(),
180//! #     analyzer.crate_path_to_workspace(),
181//! #     analyzer.crate_to_paths(),
182//! #     None,
183//! # )?;
184//! # let mut detector = CycleDetector::new();
185//! # detector.detect_cycles(graph_builder.graph())?;
186//! // Find cycles involving a specific workspace
187//! let target_workspace = "backend-core";
188//!
189//! let cycles_with_target: Vec<&WorkspaceCycle> = detector
190//!     .cycles()
191//!     .iter()
192//!     .filter(|cycle| {
193//!         cycle
194//!             .workspace_names()
195//!             .contains(&target_workspace.to_string())
196//!     })
197//!     .collect();
198//!
199//! println!(
200//!     "Found {} cycles involving {}",
201//!     cycles_with_target.len(),
202//!     target_workspace
203//! );
204//!
205//! for (i, cycle) in cycles_with_target.iter().enumerate() {
206//!     println!("\nCycle #{}", i + 1);
207//!     println!("Workspaces: {}", cycle.workspace_names().join(" → "));
208//!
209//!     // Show specific crate-level dependencies
210//!     for edge in cycle.edges() {
211//!         println!(
212//!             "  {} → {} ({:?} dependency)",
213//!             edge.from_crate(),
214//!             edge.to_crate(),
215//!             edge.dependency_type()
216//!         );
217//!     }
218//! }
219//! # Ok(())
220//! # }
221//! ```
222
223// Private modules
224mod constants;
225mod dependency_filter;
226mod progress;
227mod toml_parser;
228mod utils;
229mod workspace_discovery;
230
231// Public modules
232pub mod analyzer;
233pub mod cli;
234pub mod commands;
235pub mod common;
236pub mod config;
237pub mod core;
238pub mod detector;
239pub mod error;
240pub mod executors;
241pub mod graph;
242pub mod reports;
243
244// Main entry point for the library
245pub fn run() -> miette::Result<()> {
246    use clap::Parser;
247
248    use crate::cli::{CargoArgs, CargoCommand};
249    use crate::commands::execute_command;
250
251    let cargo_args = CargoArgs::parse();
252    let CargoCommand::FerrisWheel(cli) = cargo_args.command;
253
254    execute_command(cli.command)
255}