Skip to main content

cargo_ferris_wheel/
cli.rs

1use std::path::PathBuf;
2
3use clap::{Parser, Subcommand};
4
5use crate::common::{CommonArgs, CycleDisplayArgs, FormatArgs};
6
7#[derive(Parser)]
8#[command(
9    bin_name = "cargo",
10    subcommand_required = true,
11    subcommand_precedence_over_arg = true,
12    version
13)]
14pub struct CargoArgs {
15    #[command(subcommand)]
16    pub command: CargoCommand,
17}
18
19#[derive(Subcommand)]
20pub enum CargoCommand {
21    #[command(name = "ferris-wheel")]
22    FerrisWheel(Cli),
23}
24
25#[derive(Parser)]
26#[command(
27    name = "ferris-wheel",
28    about = "🎡 Detect workspace dependency cycles in Rust monorepos",
29    long_about = "cargo-ferris-wheel analyzes your Rust workspace structure to find circular \
30                  dependencies between workspaces. It includes all dependency types by default \
31                  and provides multiple visualization options.",
32    version
33)]
34pub struct Cli {
35    #[command(subcommand)]
36    pub command: Commands,
37}
38
39#[derive(Subcommand)]
40pub enum Commands {
41    /// Inspect the carnival rides for dangerous cycles
42    ///
43    /// Analyzes your workspace dependency graph to find circular dependencies
44    /// between workspaces. Circular dependencies prevent proper build ordering
45    /// and can cause issues with tools like hakari. This command helps you
46    /// identify and fix these cycles before they cause problems.
47    #[command(
48        long_about = "Analyze workspace dependencies to detect circular dependency chains. This \
49                      command scans all Cargo.toml files in your workspace, builds a dependency \
50                      graph, and uses Tarjan's algorithm to find strongly connected components \
51                      (cycles). By default, it checks for cycles between workspaces, but can also \
52                      check for cycles within individual workspaces using --intra-workspace."
53    )]
54    Inspect {
55        #[command(flatten)]
56        common: CommonArgs,
57
58        #[command(flatten)]
59        format: FormatArgs,
60
61        #[command(flatten)]
62        cycle_display: CycleDisplayArgs,
63
64        /// Exit with error code if cycles found
65        #[arg(long, env = "CARGO_FERRIS_WHEEL_ERROR_ON_CYCLES")]
66        error_on_cycles: bool,
67
68        /// Check for cycles within workspaces (intra-workspace) instead of
69        /// between workspaces
70        #[arg(long, env = "CARGO_FERRIS_WHEEL_INTRA_WORKSPACE")]
71        intra_workspace: bool,
72    },
73
74    /// Create a spectacular visualization of your dependency carnival
75    ///
76    /// Generates visual representations of your workspace dependency graph
77    /// in multiple formats. Perfect for documentation, debugging complex
78    /// dependency relationships, or understanding your monorepo structure.
79    #[command(
80        long_about = "Generate visual dependency graphs in various formats including ASCII art, \
81                      Mermaid diagrams, Graphviz DOT files, and D2 diagrams. The generated graphs \
82                      show workspace relationships, highlight circular dependencies, and can \
83                      include crate-level details. Use this to visualize and understand complex \
84                      dependency structures in your monorepo."
85    )]
86    Spectacle {
87        #[command(flatten)]
88        common: CommonArgs,
89
90        /// Graph format
91        #[arg(
92            short,
93            long,
94            value_enum,
95            default_value = "ascii",
96            env = "CARGO_FERRIS_WHEEL_GRAPH_FORMAT"
97        )]
98        format: GraphFormat,
99
100        /// Output file (stdout if not specified)
101        #[arg(short, long, env = "CARGO_FERRIS_WHEEL_OUTPUT")]
102        output: Option<PathBuf>,
103
104        /// Highlight cycles in the graph
105        #[arg(
106            long,
107            default_value = "true",
108            env = "CARGO_FERRIS_WHEEL_HIGHLIGHT_CYCLES"
109        )]
110        highlight_cycles: bool,
111
112        /// Include crate-level details
113        #[arg(long, env = "CARGO_FERRIS_WHEEL_SHOW_CRATES")]
114        show_crates: bool,
115    },
116
117    /// Put a spotlight on cycles involving a specific crate
118    ///
119    /// Focuses the cycle detection on a specific crate, showing only the
120    /// circular dependencies that involve that crate. Useful for debugging
121    /// why a particular crate is part of a dependency cycle.
122    #[command(
123        long_about = "Analyze circular dependencies involving a specific crate. This command \
124                      filters the cycle detection results to show only cycles that include the \
125                      specified crate, making it easier to understand and fix issues with a \
126                      particular component. Works for both inter-workspace and intra-workspace \
127                      cycle detection."
128    )]
129    Spotlight {
130        /// Name of the crate to analyze
131        #[arg(value_name = "CRATE_NAME", env = "CARGO_FERRIS_WHEEL_CRATE_NAME")]
132        crate_name: String,
133
134        #[command(flatten)]
135        common: CommonArgs,
136
137        #[command(flatten)]
138        format: FormatArgs,
139
140        #[command(flatten)]
141        cycle_display: CycleDisplayArgs,
142
143        /// Check for cycles within workspaces (intra-workspace) instead of
144        /// between workspaces
145        #[arg(long, env = "CARGO_FERRIS_WHEEL_INTRA_WORKSPACE")]
146        intra_workspace: bool,
147    },
148
149    /// See the full lineup of workspace dependencies
150    ///
151    /// Shows the dependency relationships between workspaces in your monorepo.
152    /// Can display dependencies, reverse dependencies (dependents), and
153    /// transitive dependencies to help you understand your project structure.
154    #[command(
155        long_about = "Display workspace dependency relationships in your monorepo. Shows which \
156                      workspaces depend on others, and with --reverse, which workspaces are \
157                      depended upon. The --transitive flag includes indirect dependencies. This \
158                      is particularly useful for understanding the impact of changes and planning \
159                      refactoring efforts."
160    )]
161    Lineup {
162        /// Specific workspace to analyze (shows all workspaces if not
163        /// specified)
164        #[arg(
165            long,
166            value_name = "WORKSPACE_NAME",
167            env = "CARGO_FERRIS_WHEEL_WORKSPACE"
168        )]
169        workspace: Option<String>,
170
171        /// Show reverse dependencies (what depends on the specified workspace)
172        #[arg(long, env = "CARGO_FERRIS_WHEEL_REVERSE")]
173        reverse: bool,
174
175        /// Include transitive dependencies (dependencies of dependencies)
176        #[arg(long, env = "CARGO_FERRIS_WHEEL_TRANSITIVE")]
177        transitive: bool,
178
179        #[command(flatten)]
180        common: CommonArgs,
181
182        #[command(flatten)]
183        format: FormatArgs,
184    },
185
186    /// Discover the ripple effects from changed files
187    ///
188    /// Analyzes which workspaces and crates are affected by changes to specific
189    /// files. Essential for CI/CD pipelines to determine what needs to be
190    /// rebuilt or retested based on file changes.
191    #[command(
192        long_about = "Determine which workspaces and crates are affected by file changes. This \
193                      command maps changed files to their containing crates, then traces through \
194                      the dependency graph to find all affected components. Perfect for \
195                      optimizing CI pipelines by only building and testing what actually changed. \
196                      Supports JSON output for easy integration."
197    )]
198    Ripples {
199        /// List of changed files
200        #[arg(
201            required = true,
202            value_name = "FILES",
203            help = "Files that have changed",
204            env = "CARGO_FERRIS_WHEEL_FILES"
205        )]
206        files: Vec<String>,
207
208        /// Include crate-level information in output
209        #[arg(long)]
210        show_crates: bool,
211
212        /// Include only directly affected crates (no reverse dependencies)
213        #[arg(long, env = "CARGO_FERRIS_WHEEL_DIRECT_ONLY")]
214        direct_only: bool,
215
216        /// Exclude dev-dependencies from analysis
217        #[arg(long, env = "CARGO_FERRIS_WHEEL_EXCLUDE_DEV")]
218        exclude_dev: bool,
219
220        /// Exclude build-dependencies from analysis
221        #[arg(long, env = "CARGO_FERRIS_WHEEL_EXCLUDE_BUILD")]
222        exclude_build: bool,
223
224        /// Exclude target-specific dependencies
225        #[arg(long, env = "CARGO_FERRIS_WHEEL_EXCLUDE_TARGET")]
226        exclude_target: bool,
227
228        #[command(flatten)]
229        format: FormatArgs,
230    },
231}
232
233#[derive(Clone, Copy, Debug, PartialEq, clap::ValueEnum)]
234pub enum OutputFormat {
235    Human,
236    Json,
237    Junit,
238    #[value(name = "github")]
239    GitHub,
240}
241
242#[derive(Clone, Copy, Debug, clap::ValueEnum)]
243pub enum GraphFormat {
244    Ascii,
245    Mermaid,
246    Dot,
247    D2,
248}