This commit is contained in:
209
src/convention.rs
Normal file
209
src/convention.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum CommitType {
|
||||
Feat,
|
||||
Fix,
|
||||
Docs,
|
||||
Style,
|
||||
Refactor,
|
||||
Test,
|
||||
Chore,
|
||||
Build,
|
||||
Ci,
|
||||
Perf,
|
||||
Revert,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for CommitType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CommitType::Feat => write!(f, "feat"),
|
||||
CommitType::Fix => write!(f, "fix"),
|
||||
CommitType::Docs => write!(f, "docs"),
|
||||
CommitType::Style => write!(f, "style"),
|
||||
CommitType::Refactor => write!(f, "refactor"),
|
||||
CommitType::Test => write!(f, "test"),
|
||||
CommitType::Chore => write!(f, "chore"),
|
||||
CommitType::Build => write!(f, "build"),
|
||||
CommitType::Ci => write!(f, "ci"),
|
||||
CommitType::Perf => write!(f, "perf"),
|
||||
CommitType::Revert => write!(f, "revert"),
|
||||
CommitType::Custom(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for CommitType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"feat" | "feature" => Ok(CommitType::Feat),
|
||||
"fix" | "bugfix" => Ok(CommitType::Fix),
|
||||
"docs" | "documentation" => Ok(CommitType::Docs),
|
||||
"style" | "formatting" => Ok(CommitType::Style),
|
||||
"refactor" | "restructure" => Ok(CommitType::Refactor),
|
||||
"test" | "testing" => Ok(CommitType::Test),
|
||||
"chore" | "maintenance" => Ok(CommitType::Chore),
|
||||
"build" | "builds" => Ok(CommitType::Build),
|
||||
"ci" | "continuous" => Ok(CommitType::Ci),
|
||||
"perf" | "performance" => Ok(CommitType::Perf),
|
||||
"revert" | "reverts" => Ok(CommitType::Revert),
|
||||
_ => Ok(CommitType::Custom(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scope {
|
||||
pub name: String,
|
||||
pub is_custom: bool,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
is_custom: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn custom(name: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
is_custom: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Scope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommitSuggestion {
|
||||
pub commit_type: CommitType,
|
||||
pub scope: Option<Scope>,
|
||||
pub description: String,
|
||||
pub confidence: f64,
|
||||
pub file_count: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for CommitSuggestion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.scope {
|
||||
Some(scope) => write!(f, "{}({}): {}", self.commit_type, scope, self.description),
|
||||
None => write!(f, "{}: {}", self.commit_type, self.description),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommitConvention {
|
||||
pub type_mapping: HashMap<String, CommitType>,
|
||||
pub scope_patterns: Vec<(Regex, String)>,
|
||||
pub ignored_patterns: Vec<Regex>,
|
||||
}
|
||||
|
||||
impl Default for CommitConvention {
|
||||
fn default() -> Self {
|
||||
let mut type_mapping = HashMap::new();
|
||||
type_mapping.insert("src/**/*.rs".to_string(), CommitType::Feat);
|
||||
type_mapping.insert("lib/**/*.rs".to_string(), CommitType::Feat);
|
||||
type_mapping.insert("src/**/*.ts".to_string(), CommitType::Feat);
|
||||
type_mapping.insert("src/**/*.js".to_string(), CommitType::Feat);
|
||||
type_mapping.insert("src/**/*.py".to_string(), CommitType::Feat);
|
||||
type_mapping.insert("**/*test*.rs".to_string(), CommitType::Test);
|
||||
type_mapping.insert("**/*spec*.rs".to_string(), CommitType::Test);
|
||||
type_mapping.insert("tests/**/*.rs".to_string(), CommitType::Test);
|
||||
type_mapping.insert("**/*_test.py".to_string(), CommitType::Test);
|
||||
type_mapping.insert("test_*.py".to_string(), CommitType::Test);
|
||||
type_mapping.insert("**/*.md".to_string(), CommitType::Docs);
|
||||
type_mapping.insert("docs/**/*.md".to_string(), CommitType::Docs);
|
||||
type_mapping.insert("README*".to_string(), CommitType::Docs);
|
||||
type_mapping.insert("**/*.yaml".to_string(), CommitType::Chore);
|
||||
type_mapping.insert("**/*.yml".to_string(), CommitType::Chore);
|
||||
type_mapping.insert("Dockerfile*".to_string(), CommitType::Build);
|
||||
type_mapping.insert("**/package.json".to_string(), CommitType::Chore);
|
||||
type_mapping.insert("**/Cargo.toml".to_string(), CommitType::Chore);
|
||||
type_mapping.insert("**/*.lock".to_string(), CommitType::Chore);
|
||||
|
||||
let mut scope_patterns = Vec::new();
|
||||
scope_patterns.push((Regex::new(r"^src/([^/]+)/").unwrap(), "src"));
|
||||
scope_patterns.push((Regex::new(r"^lib/([^/]+)/").unwrap(), "lib"));
|
||||
scope_patterns.push((Regex::new(r"^app/([^/]+)/").unwrap(), "app"));
|
||||
scope_patterns.push((Regex::new(r"^packages/([^/]+)/").unwrap(), "packages"));
|
||||
scope_patterns.push((Regex::new(r"^features/([^/]+)/").unwrap(), "features"));
|
||||
scope_patterns.push((Regex::new(r"^components/([^/]+)/").unwrap(), "components"));
|
||||
scope_patterns.push((Regex::new(r"^controllers/([^/]+)/").unwrap(), "controllers"));
|
||||
scope_patterns.push((Regex::new(r"^models/([^/]+)/").unwrap(), "models"));
|
||||
scope_patterns.push((Regex::new(r"^services/([^/]+)/").unwrap(), "services"));
|
||||
|
||||
let ignored_patterns = vec![
|
||||
Regex::new(r"^target/").unwrap(),
|
||||
Regex::new(r"^node_modules/").unwrap(),
|
||||
Regex::new(r"^\.git/").unwrap(),
|
||||
Regex::new(r"^dist/").unwrap(),
|
||||
Regex::new(r"^build/").unwrap(),
|
||||
Regex::new(r"^\.idea/").unwrap(),
|
||||
Regex::new(r"^\.vscode/").unwrap(),
|
||||
Regex::new(r"\.DS_Store").unwrap(),
|
||||
];
|
||||
|
||||
Self {
|
||||
type_mapping,
|
||||
scope_patterns,
|
||||
ignored_patterns,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommitConvention {
|
||||
pub fn detect_type(&self, file_path: &str) -> Option<(CommitType, f64)> {
|
||||
for (pattern, commit_type) in &self.type_mapping {
|
||||
if self.matches_pattern(pattern, file_path) {
|
||||
let confidence = if pattern.contains("**/*") { 0.7 } else { 0.9 };
|
||||
return Some((commit_type.clone(), confidence));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn detect_scope(&self, file_path: &str) -> Option<Scope> {
|
||||
for (pattern, _base) in &self.scope_patterns {
|
||||
if let Some(caps) = pattern.captures(file_path) {
|
||||
if let Some(scope) = caps.get(1) {
|
||||
return Some(Scope::new(scope.as_str().to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn matches_pattern(&self, pattern: &str, file_path: &str) -> bool {
|
||||
let regex_pattern = pattern
|
||||
.replace("**/*", ".*")
|
||||
.replace("**", ".*")
|
||||
.replace("*", "[^/]*")
|
||||
.replace(".", "\\.");
|
||||
|
||||
Regex::new(®ex_pattern)
|
||||
.map(|re| re.is_match(file_path))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn should_ignore(&self, file_path: &str) -> bool {
|
||||
for pattern in &self.ignored_patterns {
|
||||
if pattern.is_match(file_path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user