use std::env::current_dir;
use std::io;
use std::io::Write as _;
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Error as ClapError;
use clap::Parser;
use crate::cli::cli_error::CliError;
use crate::cli::cli_overrides::CliOverrides;
use crate::cli::command::Command;
use crate::cli::output_format::OutputFormat;
use crate::command_dispatcher::CommandDispatcher;
use crate::rust::rust_analyzer::RustAnalyzer;
#[derive(Parser)]
#[command(
name = "cargo-dry4rust",
version,
about = "Detect duplicate code in Rust codebases",
long_about = None
)]
pub struct EntryPoint {
#[arg(hide = true, default_value = "")]
_cargo_subcommand: String,
#[command(subcommand)]
command: Option<Command>,
#[arg(short, long, global = true)]
path: Option<PathBuf>,
#[arg(long, global = true)]
min_nodes: Option<usize>,
#[arg(long, global = true)]
min_lines: Option<usize>,
#[arg(long, global = true)]
threshold: Option<f64>,
#[arg(long, global = true, default_value = "text")]
format: OutputFormat,
#[arg(long, global = true)]
exclude: Vec<String>,
#[arg(long, global = true)]
exclude_tests: bool,
#[arg(long, short = 's', global = true)]
sub_function: bool,
#[arg(long, global = true)]
min_sub_nodes: Option<usize>,
#[arg(long, global = true)]
baseline: Option<PathBuf>,
}
impl EntryPoint {
#[must_use]
pub fn run(args: Vec<String>) -> ExitCode {
match Self::try_parse_from(args) {
Ok(entry) => entry.execute(),
Err(error) => Self::parsing_failed(&error),
}
}
fn execute(self) -> ExitCode {
let analyzer = RustAnalyzer::new();
let root = self.root();
let dispatcher = CommandDispatcher::new(&analyzer, &root, self.format, self.overrides());
match dispatcher.dispatch(&self.command()) {
Ok(outcome) => {
let stdout = io::stdout();
let mut writer = stdout.lock();
match writer
.write_all(outcome.text.as_bytes())
.and_then(|()| writer.flush())
{
Ok(()) => match outcome.result {
Ok(()) => ExitCode::SUCCESS,
Err(e) => Self::reported(&e),
},
Err(e) => Self::reported(&CliError::Io(e)),
}
}
Err(e) => Self::reported(&e),
}
}
fn parsing_failed(error: &ClapError) -> ExitCode {
let _ = error.print();
if error.use_stderr() {
ExitCode::from(2)
} else {
ExitCode::SUCCESS
}
}
#[must_use]
pub fn root(&self) -> PathBuf {
self.path
.clone()
.unwrap_or_else(|| current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
#[must_use]
pub fn command(&self) -> Command {
self.command.clone().unwrap_or(Command::Report)
}
#[must_use]
pub fn overrides(&self) -> CliOverrides {
CliOverrides {
min_nodes: self.min_nodes,
min_lines: self.min_lines,
threshold: self.threshold,
exclude: self.exclude.clone(),
exclude_tests: self.exclude_tests.then_some(true),
sub_function: self.sub_function.then_some(true),
min_sub_nodes: self.min_sub_nodes,
baseline: self.baseline.clone(),
}
}
#[must_use]
pub const fn format(&self) -> OutputFormat {
self.format
}
fn reported(error: &CliError) -> ExitCode {
if !matches!(error, CliError::CheckFailed) {
eprintln!("Error: {error}");
}
ExitCode::from(error.exit_code() as u8)
}
}