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 #[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 #[arg(long, env = "CARGO_FERRIS_WHEEL_ERROR_ON_CYCLES")]
66 error_on_cycles: bool,
67
68 #[arg(long, env = "CARGO_FERRIS_WHEEL_INTRA_WORKSPACE")]
71 intra_workspace: bool,
72 },
73
74 #[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 #[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 #[arg(short, long, env = "CARGO_FERRIS_WHEEL_OUTPUT")]
102 output: Option<PathBuf>,
103
104 #[arg(
106 long,
107 default_value = "true",
108 env = "CARGO_FERRIS_WHEEL_HIGHLIGHT_CYCLES"
109 )]
110 highlight_cycles: bool,
111
112 #[arg(long, env = "CARGO_FERRIS_WHEEL_SHOW_CRATES")]
114 show_crates: bool,
115 },
116
117 #[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 #[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 #[arg(long, env = "CARGO_FERRIS_WHEEL_INTRA_WORKSPACE")]
146 intra_workspace: bool,
147 },
148
149 #[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 #[arg(
165 long,
166 value_name = "WORKSPACE_NAME",
167 env = "CARGO_FERRIS_WHEEL_WORKSPACE"
168 )]
169 workspace: Option<String>,
170
171 #[arg(long, env = "CARGO_FERRIS_WHEEL_REVERSE")]
173 reverse: bool,
174
175 #[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 #[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 #[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 #[arg(long)]
210 show_crates: bool,
211
212 #[arg(long, env = "CARGO_FERRIS_WHEEL_DIRECT_ONLY")]
214 direct_only: bool,
215
216 #[arg(long, env = "CARGO_FERRIS_WHEEL_EXCLUDE_DEV")]
218 exclude_dev: bool,
219
220 #[arg(long, env = "CARGO_FERRIS_WHEEL_EXCLUDE_BUILD")]
222 exclude_build: bool,
223
224 #[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}