dry4rust/cli/
entry_point.rs1use std::env::current_dir;
7use std::io;
8use std::path::PathBuf;
9use std::process::ExitCode;
10
11use clap::Error as ClapError;
12use clap::Parser;
13
14use crate::cli::cli_error::CliError;
15use crate::cli::cli_overrides::CliOverrides;
16use crate::cli::command::Command;
17use crate::cli::output_format::OutputFormat;
18use crate::command_dispatcher::CommandDispatcher;
19use crate::rust::rust_analyzer::RustAnalyzer;
20
21#[derive(Parser)]
27#[command(
31 name = "cargo-dry4rust",
32 version,
33 about = "Detect duplicate code in Rust codebases",
34 long_about = None
35)]
36pub struct EntryPoint {
37 #[arg(hide = true, default_value = "")]
39 _cargo_subcommand: String,
40
41 #[command(subcommand)]
42 command: Option<Command>,
43
44 #[arg(short, long, global = true)]
46 path: Option<PathBuf>,
47
48 #[arg(long, global = true)]
50 min_nodes: Option<usize>,
51
52 #[arg(long, global = true)]
54 min_lines: Option<usize>,
55
56 #[arg(long, global = true)]
58 threshold: Option<f64>,
59
60 #[arg(long, global = true, default_value = "text")]
62 format: OutputFormat,
63
64 #[arg(long, global = true)]
66 exclude: Vec<String>,
67
68 #[arg(long, global = true)]
70 exclude_tests: bool,
71
72 #[arg(long, short = 's', global = true)]
74 sub_function: bool,
75
76 #[arg(long, global = true)]
78 min_sub_nodes: Option<usize>,
79
80 #[arg(long, global = true)]
83 baseline: Option<PathBuf>,
84}
85
86impl EntryPoint {
87 #[must_use]
92 pub fn run(args: Vec<String>) -> ExitCode {
93 match Self::try_parse_from(args) {
94 Ok(entry) => entry.execute(),
95 Err(error) => Self::parsing_failed(&error),
96 }
97 }
98
99 fn execute(self) -> ExitCode {
100 let analyzer = RustAnalyzer::new();
101 let root = self.root();
102 let dispatcher = CommandDispatcher::new(&analyzer, &root, self.format, self.overrides());
103 let stdout = io::stdout();
104 let mut writer = stdout.lock();
105 match dispatcher.dispatch(&self.command(), &mut writer) {
106 Ok(()) => ExitCode::SUCCESS,
107 Err(e) => Self::reported(&e),
108 }
109 }
110
111 fn parsing_failed(error: &ClapError) -> ExitCode {
114 let _ = error.print();
115 if error.use_stderr() {
116 ExitCode::from(2)
117 } else {
118 ExitCode::SUCCESS
119 }
120 }
121
122 #[must_use]
124 pub fn root(&self) -> PathBuf {
125 self.path
126 .clone()
127 .unwrap_or_else(|| current_dir().unwrap_or_else(|_| PathBuf::from(".")))
128 }
129
130 #[must_use]
132 pub fn command(&self) -> Command {
133 self.command.clone().unwrap_or(Command::Report)
134 }
135
136 #[must_use]
143 pub fn overrides(&self) -> CliOverrides {
144 CliOverrides {
145 min_nodes: self.min_nodes,
146 min_lines: self.min_lines,
147 threshold: self.threshold,
148 exclude: self.exclude.clone(),
149 exclude_tests: self.exclude_tests.then_some(true),
150 sub_function: self.sub_function.then_some(true),
151 min_sub_nodes: self.min_sub_nodes,
152 baseline: self.baseline.clone(),
153 }
154 }
155
156 #[must_use]
158 pub const fn format(&self) -> OutputFormat {
159 self.format
160 }
161
162 fn reported(error: &CliError) -> ExitCode {
163 if !matches!(error, CliError::CheckFailed) {
164 eprintln!("Error: {error}");
165 }
166 ExitCode::from(error.exit_code() as u8)
167 }
168}