Initial commit: git-issue-commit CLI tool
This commit is contained in:
132
src/parser/issue.rs
Normal file
132
src/parser/issue.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::parser::conventional::ConventionalMessage;
|
||||
|
||||
pub fn parse_issue_content(title: &str, body: &str) -> ConventionalMessage {
|
||||
let description = extract_description(title, body);
|
||||
let change_type = detect_change_type(&description, body);
|
||||
|
||||
ConventionalMessage {
|
||||
r#type: change_type,
|
||||
scope: None,
|
||||
description: clean_description(&description),
|
||||
body: None,
|
||||
breaking: is_breaking_change(body),
|
||||
breaking_description: extract_breaking_description(body),
|
||||
footer: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_description(title: &str, body: &str) -> String {
|
||||
if !title.is_empty() {
|
||||
title.to_string()
|
||||
} else if !body.is_empty() {
|
||||
let first_line = body.lines()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.find(|s| !s.starts_with('#'))
|
||||
.unwrap_or("");
|
||||
first_line.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_change_type(description: &str, body: &str) -> String {
|
||||
let text = format!("{} {}", description, body).to_lowercase();
|
||||
|
||||
if text.contains("fix") || text.contains("bug") || text.contains("error") || text.contains("crash") {
|
||||
return "fix".to_string();
|
||||
}
|
||||
if text.contains("feature") || text.contains("add") || text.contains("implement") {
|
||||
return "feat".to_string();
|
||||
}
|
||||
if text.contains("document") || text.contains("readme") || text.contains("comment") {
|
||||
return "docs".to_string();
|
||||
}
|
||||
if text.contains("test") || text.contains("coverage") {
|
||||
return "test".to_string();
|
||||
}
|
||||
if text.contains("refactor") || text.contains("restructur") {
|
||||
return "refactor".to_string();
|
||||
}
|
||||
if text.contains("performance") || text.contains("optimize") || text.contains("speed") {
|
||||
return "perf".to_string();
|
||||
}
|
||||
if text.contains("style") || text.contains("format") || text.contains("lint") {
|
||||
return "style".to_string();
|
||||
}
|
||||
if text.contains("ci") || text.contains("workflow") || text.contains("pipeline") {
|
||||
return "ci".to_string();
|
||||
}
|
||||
if text.contains("build") || text.contains("dependenc") || text.contains("package") {
|
||||
return "build".to_string();
|
||||
}
|
||||
if text.contains("revert") || text.contains("undo") {
|
||||
return "revert".to_string();
|
||||
}
|
||||
|
||||
"chore".to_string()
|
||||
}
|
||||
|
||||
fn clean_description(desc: &str) -> String {
|
||||
let cleaned = desc
|
||||
.trim()
|
||||
.trim_end_matches(|c: char| c == '.' || c == '!')
|
||||
.to_string();
|
||||
|
||||
cleaned
|
||||
.split_whitespace()
|
||||
.take(20)
|
||||
.collect::<Vec<&str>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn is_breaking_change(body: &str) -> bool {
|
||||
let body_lower = body.to_lowercase();
|
||||
body_lower.contains("breaking change") ||
|
||||
body_lower.contains("breaking changes") ||
|
||||
body_lower.contains("! ") ||
|
||||
body.contains("BREAKING CHANGE")
|
||||
}
|
||||
|
||||
fn extract_breaking_description(body: &str) -> Option<String> {
|
||||
let lines: Vec<&str> = body.lines().collect();
|
||||
let mut in_breaking_section = false;
|
||||
let mut breaking_lines = Vec::new();
|
||||
|
||||
for line in &lines {
|
||||
let line_lower = line.to_lowercase();
|
||||
if line_lower.contains("breaking change") && line_lower.contains("change") {
|
||||
in_breaking_section = true;
|
||||
let parts: Vec<&str> = line.splitn(2, "BREAKING CHANGE").collect();
|
||||
if parts.len() > 1 && !parts[1].trim().is_empty() && !parts[1].trim().starts_with(':') {
|
||||
breaking_lines.push(parts[1].trim());
|
||||
} else {
|
||||
let parts_lower: Vec<&str> = line.splitn(2, "BREAKING CHANGE").collect();
|
||||
if parts_lower.len() > 1 {
|
||||
let after_break = parts_lower[1].trim().trim_start_matches(':').trim();
|
||||
if !after_break.is_empty() {
|
||||
breaking_lines.push(after_break);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if in_breaking_section {
|
||||
if line.trim().is_empty() {
|
||||
if !breaking_lines.is_empty() {
|
||||
break;
|
||||
}
|
||||
} else if line.starts_with('#') && !line_lower.contains("breaking") {
|
||||
break;
|
||||
} else {
|
||||
breaking_lines.push(line.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if breaking_lines.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(breaking_lines.join(" ").trim().to_string())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user