1use clap::{Parser, Subcommand, ValueEnum};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy, ValueEnum)]
5pub enum ThresholdPreset {
6 Strict,
8 Balanced,
10 Lenient,
12}
13
14#[derive(Parser, Debug)]
15#[command(name = "debtmap")]
16#[command(about = "Code complexity and technical debt analyzer", long_about = None)]
17#[command(version)]
18pub struct Cli {
19 #[command(subcommand)]
20 pub command: Commands,
21}
22
23#[derive(Subcommand, Debug)]
24pub enum Commands {
25 Analyze {
27 path: PathBuf,
29
30 #[arg(short, long, value_enum, default_value = "terminal")]
32 format: OutputFormat,
33
34 #[arg(short, long)]
36 output: Option<PathBuf>,
37
38 #[arg(long, default_value = "10")]
40 threshold_complexity: u32,
41
42 #[arg(long, default_value = "50")]
44 threshold_duplication: usize,
45
46 #[arg(long, value_delimiter = ',')]
48 languages: Option<Vec<String>>,
49
50 #[arg(long = "coverage-file", visible_alias = "lcov")]
52 coverage_file: Option<PathBuf>,
53
54 #[arg(long = "context", visible_alias = "enable-context")]
56 enable_context: bool,
57
58 #[arg(long = "context-providers", value_delimiter = ',')]
60 context_providers: Option<Vec<String>>,
61
62 #[arg(long = "disable-context", value_delimiter = ',')]
64 disable_context: Option<Vec<String>>,
65
66 #[arg(long = "top", visible_alias = "head")]
68 top: Option<usize>,
69
70 #[arg(long = "tail")]
72 tail: Option<usize>,
73
74 #[arg(long = "semantic-off")]
76 semantic_off: bool,
77
78 #[arg(long = "explain-score", hide = true)]
80 explain_score: bool,
81
82 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
87 verbosity: u8,
88
89 #[arg(long = "verbose-macro-warnings")]
91 verbose_macro_warnings: bool,
92
93 #[arg(long = "show-macro-stats")]
95 show_macro_stats: bool,
96
97 #[arg(long = "group-by-category")]
99 group_by_category: bool,
100
101 #[arg(long = "min-priority")]
103 min_priority: Option<String>,
104
105 #[arg(long = "filter", value_delimiter = ',')]
107 filter_categories: Option<Vec<String>>,
108
109 #[arg(long = "no-context-aware")]
111 no_context_aware: bool,
112
113 #[arg(long = "threshold-preset", value_enum)]
115 threshold_preset: Option<ThresholdPreset>,
116 },
117
118 Init {
120 #[arg(short, long)]
122 force: bool,
123 },
124
125 Validate {
127 path: PathBuf,
129
130 #[arg(short, long)]
132 config: Option<PathBuf>,
133
134 #[arg(long = "coverage-file", visible_alias = "lcov")]
136 coverage_file: Option<PathBuf>,
137
138 #[arg(short, long, value_enum)]
140 format: Option<OutputFormat>,
141
142 #[arg(short, long)]
144 output: Option<PathBuf>,
145
146 #[arg(long = "context", visible_alias = "enable-context")]
148 enable_context: bool,
149
150 #[arg(long = "context-providers", value_delimiter = ',')]
152 context_providers: Option<Vec<String>>,
153
154 #[arg(long = "disable-context", value_delimiter = ',')]
156 disable_context: Option<Vec<String>>,
157
158 #[arg(long = "top", visible_alias = "head")]
160 top: Option<usize>,
161
162 #[arg(long = "tail")]
164 tail: Option<usize>,
165
166 #[arg(long = "semantic-off")]
168 semantic_off: bool,
169
170 #[arg(long = "explain-score", hide = true)]
172 explain_score: bool,
173
174 #[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count)]
179 verbosity: u8,
180 },
181}
182
183#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
184pub enum OutputFormat {
185 Json,
186 Markdown,
187 Terminal,
188}
189
190#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
191pub enum Priority {
192 Low,
193 Medium,
194 High,
195 Critical,
196}
197
198impl From<Priority> for crate::core::Priority {
199 fn from(p: Priority) -> Self {
200 match p {
201 Priority::Low => crate::core::Priority::Low,
202 Priority::Medium => crate::core::Priority::Medium,
203 Priority::High => crate::core::Priority::High,
204 Priority::Critical => crate::core::Priority::Critical,
205 }
206 }
207}
208
209impl From<OutputFormat> for crate::io::output::OutputFormat {
210 fn from(f: OutputFormat) -> Self {
211 match f {
212 OutputFormat::Json => crate::io::output::OutputFormat::Json,
213 OutputFormat::Markdown => crate::io::output::OutputFormat::Markdown,
214 OutputFormat::Terminal => crate::io::output::OutputFormat::Terminal,
215 }
216 }
217}
218
219pub fn parse_args() -> Cli {
220 Cli::parse()
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_priority_conversion() {
229 assert_eq!(
231 crate::core::Priority::from(Priority::Low),
232 crate::core::Priority::Low
233 );
234 assert_eq!(
235 crate::core::Priority::from(Priority::Medium),
236 crate::core::Priority::Medium
237 );
238 assert_eq!(
239 crate::core::Priority::from(Priority::High),
240 crate::core::Priority::High
241 );
242 assert_eq!(
243 crate::core::Priority::from(Priority::Critical),
244 crate::core::Priority::Critical
245 );
246 }
247
248 #[test]
249 fn test_output_format_conversion() {
250 assert_eq!(
252 crate::io::output::OutputFormat::from(OutputFormat::Json),
253 crate::io::output::OutputFormat::Json
254 );
255 assert_eq!(
256 crate::io::output::OutputFormat::from(OutputFormat::Markdown),
257 crate::io::output::OutputFormat::Markdown
258 );
259 assert_eq!(
260 crate::io::output::OutputFormat::from(OutputFormat::Terminal),
261 crate::io::output::OutputFormat::Terminal
262 );
263 }
264
265 #[test]
266 fn test_cli_parsing_analyze_command() {
267 use clap::Parser;
268
269 let args = vec![
270 "debtmap",
271 "analyze",
272 "/test/path",
273 "--format",
274 "json",
275 "--threshold-complexity",
276 "15",
277 "--threshold-duplication",
278 "100",
279 ];
280
281 let cli = Cli::parse_from(args);
282
283 match cli.command {
284 Commands::Analyze {
285 path,
286 format,
287 threshold_complexity,
288 threshold_duplication,
289 ..
290 } => {
291 assert_eq!(path, PathBuf::from("/test/path"));
292 assert_eq!(format, OutputFormat::Json);
293 assert_eq!(threshold_complexity, 15);
294 assert_eq!(threshold_duplication, 100);
295 }
296 _ => panic!("Expected Analyze command"),
297 }
298 }
299
300 #[test]
301 fn test_cli_parsing_init_command() {
302 use clap::Parser;
303
304 let args = vec!["debtmap", "init", "--force"];
305
306 let cli = Cli::parse_from(args);
307
308 match cli.command {
309 Commands::Init { force } => {
310 assert!(force);
311 }
312 _ => panic!("Expected Init command"),
313 }
314 }
315
316 #[test]
317 fn test_cli_parsing_validate_command() {
318 use clap::Parser;
319
320 let args = vec![
321 "debtmap",
322 "validate",
323 "/test/path",
324 "--config",
325 "/config/path",
326 ];
327
328 let cli = Cli::parse_from(args);
329
330 match cli.command {
331 Commands::Validate { path, config, .. } => {
332 assert_eq!(path, PathBuf::from("/test/path"));
333 assert_eq!(config, Some(PathBuf::from("/config/path")));
334 }
335 _ => panic!("Expected Validate command"),
336 }
337 }
338
339 #[test]
340 fn test_priority_ordering() {
341 assert!(Priority::Low < Priority::Medium);
343 assert!(Priority::Medium < Priority::High);
344 assert!(Priority::High < Priority::Critical);
345 }
346
347 #[test]
348 fn test_output_format_equality() {
349 assert_eq!(OutputFormat::Json, OutputFormat::Json);
351 assert_ne!(OutputFormat::Json, OutputFormat::Markdown);
352 assert_ne!(OutputFormat::Terminal, OutputFormat::Json);
353 }
354
355 #[test]
356 fn test_parse_args_wrapper() {
357 use clap::Parser;
360
361 let test_args = vec!["debtmap", "analyze", "."];
363 let cli = Cli::parse_from(test_args);
364
365 match cli.command {
367 Commands::Analyze { .. } => {
368 }
370 _ => panic!("Expected Analyze command from test args"),
371 }
372 }
373}