Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 128 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[package]
name = "codem8"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
rust-version = "1.85"
license = "MIT"
description = "A deterministic source code analysis CLI for duplicate code reports."
repository = "https://github.com/b4prog/CodeM8"
keywords = ["cli", "duplicate-detection", "source-code", "analysis"]
categories = ["command-line-utilities", "development-tools"]

[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ignore = "0.4"
rayon = "1"
regex = "1"
Expand Down
136 changes: 96 additions & 40 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::fmt::Write as _;
use std::path::PathBuf;

use clap::{ArgAction, Parser};

use crate::error::{CodeM8Error, Result};
use crate::language::supported_file_extensions;

Expand Down Expand Up @@ -71,6 +73,31 @@ pub struct CliConfig {
pub git_branch: bool,
}

#[derive(Debug, Parser)]
#[command(name = "codem8", disable_help_flag = true, disable_version_flag = true)]
struct ClapCli {
#[arg(long = "report-duplicate", action = ArgAction::Count)]
report_duplicate: u8,
#[arg(long = "codem8-verbose", action = ArgAction::Count)]
verbose: u8,
#[arg(long = "codem8-git-branch", action = ArgAction::Count)]
git_branch: u8,
#[arg(
long = "codem8-file-extension",
value_name = "extensions",
value_parser = parse_file_extensions,
action = ArgAction::Append
)]
file_extensions: Vec<Vec<String>>,
#[arg(
long = "codem8-files",
value_name = "paths",
value_parser = parse_file_list,
action = ArgAction::Append
)]
files: Vec<Vec<PathBuf>>,
}

#[must_use]
pub fn help_text() -> String {
let version = codem8_version_from_cargo_lock().unwrap_or("unknown");
Expand Down Expand Up @@ -113,56 +140,48 @@ where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut report_duplicate = false;
let mut verbose = false;
let mut file_extensions = None;
let mut files = None;
let mut git_branch = false;
for arg in args {
let arg = arg.into();
if arg == "--report-duplicate" {
report_duplicate = true;
} else if arg == "-verbose" {
verbose = true;
} else if arg == "-git-branch" {
if git_branch {
return Err(CodeM8Error::new(
"git branch mode was provided more than once",
));
}
git_branch = true;
} else if let Some(value) = arg.strip_prefix("-file-extension=") {
if file_extensions.is_some() {
return Err(CodeM8Error::new(
"file extensions were provided more than once",
));
}
file_extensions = Some(parse_file_extensions(value)?);
} else if let Some(value) = arg.strip_prefix("-files=") {
if files.is_some() {
return Err(CodeM8Error::new(
"explicit files were provided more than once",
));
}
files = Some(parse_file_list(value)?);
} else {
return Err(CodeM8Error::new(format!("unknown argument: {arg}")));
}
}
if !report_duplicate {
let parsed = ClapCli::try_parse_from(normalized_clap_args(args)?)
.map_err(|error| CodeM8Error::new(error.to_string().trim().to_owned()))?;
if parsed.report_duplicate == 0 {
return Err(CodeM8Error::with_help(
"no report switch provided; pass --report-duplicate",
));
}
if parsed.report_duplicate > 1 {
return Err(CodeM8Error::new(
"report switch was provided more than once",
));
}
if parsed.git_branch > 1 {
return Err(CodeM8Error::new(
"git branch mode was provided more than once",
));
}
if parsed.file_extensions.len() > 1 {
return Err(CodeM8Error::new(
"file extensions were provided more than once",
));
}
if parsed.files.len() > 1 {
return Err(CodeM8Error::new(
"explicit files were provided more than once",
));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let git_branch = parsed.git_branch != 0;
let files = parsed.files.into_iter().next();
if git_branch && files.is_some() {
return Err(CodeM8Error::new(
"git branch mode cannot be combined with explicit files",
));
}
Ok(CliConfig {
report_duplicate,
verbose,
file_extensions: file_extensions.unwrap_or_else(supported_file_extensions),
report_duplicate: parsed.report_duplicate != 0,
verbose: parsed.verbose != 0,
file_extensions: parsed
.file_extensions
.into_iter()
.next()
.unwrap_or_else(supported_file_extensions),
files,
git_branch,
})
Expand Down Expand Up @@ -226,6 +245,34 @@ fn is_help_argument(arg: &str) -> bool {
matches!(arg, "help" | "-h")
}

fn normalized_clap_args<I, S>(args: I) -> Result<Vec<String>>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut normalized = vec!["codem8".to_owned()];
for arg in args {
normalized.push(normalized_clap_arg(arg.into())?);
}
Ok(normalized)
}

fn normalized_clap_arg(arg: String) -> Result<String> {
if arg == "-verbose" {
Ok("--codem8-verbose".to_owned())
} else if arg == "-git-branch" {
Ok("--codem8-git-branch".to_owned())
} else if let Some(value) = arg.strip_prefix("-file-extension=") {
Ok(format!("--codem8-file-extension={value}"))
} else if let Some(value) = arg.strip_prefix("-files=") {
Ok(format!("--codem8-files={value}"))
} else if arg.starts_with("--") && arg != "--report-duplicate" {
Err(CodeM8Error::new(format!("unknown argument: {arg}")))
} else {
Ok(arg)
}
}

fn codem8_version_from_cargo_lock() -> Option<&'static str> {
cargo_lock_packages(CARGO_LOCK)
.find(|package| package.name == "codem8")
Expand Down Expand Up @@ -400,6 +447,15 @@ version = "0.4.2"
.contains("file extensions were provided more than once"));
}

#[test]
fn rejects_repeated_report_switches() {
let error = parse_args(["--report-duplicate", "--report-duplicate"])
.expect_err("repeated report switch fails");
assert!(error
.to_string()
.contains("report switch was provided more than once"));
}

#[test]
fn rejects_repeated_explicit_file_arguments() {
let error = parse_args(["--report-duplicate", "-files=a.ts", "-files=b.ts"])
Expand Down
2 changes: 1 addition & 1 deletion src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct DuplicateBlock {

impl DuplicateBlock {
#[must_use]
pub const fn line_count(&self) -> usize {
pub fn line_count(&self) -> usize {
self.normalized_lines.len()
}

Expand Down
Loading