use crate::check::Verdict;
use crate::git;
use std::fs::OpenOptions;
use std::io::Write;
pub fn find_jira(s: &str) -> Option<String> {
let b = s.as_bytes();
for start in 0..b.len() {
let mut e = start;
while e < b.len() && b[e].is_ascii_uppercase() {
e += 1;
}
let letters = e - start;
if !(3..=32).contains(&letters) || e >= b.len() || b[e] != b'-' {
continue;
}
let d0 = e + 1;
if d0 >= b.len() || !(b'1'..=b'9').contains(&b[d0]) {
continue;
}
let mut d = d0 + 1;
while d < b.len() && b[d].is_ascii_digit() && d - d0 < 32 {
d += 1;
}
if d - d0 < 2 {
continue;
}
return Some(s[start..d].to_string());
}
None
}
pub fn find_digits(s: &str, min: usize) -> Option<String> {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() {
if !b[i].is_ascii_digit() {
i += 1;
continue;
}
let start = i;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
if i - start >= min {
return Some(s[start..i].to_string());
}
}
None
}
fn append(path: &str, line: &str) {
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(file, "\n{line}");
}
}
pub fn run(args: &[std::ffi::OsString]) -> Verdict {
let Some(msg_file) = args.first().and_then(|a| a.to_str()) else {
return Verdict::Proceed;
};
let source = args.get(1).and_then(|a| a.to_str()).unwrap_or("");
if matches!(
source,
"message" | "template" | "merge" | "squash" | "commit"
) {
return Verdict::Proceed;
}
let branch = git::stdout(&["branch", "--show-current"]).unwrap_or_default();
if let Some(id) = find_jira(&branch) {
append(msg_file, &format!("Issue: {id}"));
return Verdict::Proceed;
}
if let Some(id) = find_digits(&branch, 3) {
append(msg_file, &format!("Issue: #id {id}"));
return Verdict::Proceed;
}
Verdict::Proceed
}
#[cfg(test)]
mod tests {
use super::{find_digits, find_jira};
#[test]
fn finds_a_jira_id() {
assert_eq!(
find_jira("feat/JIRA-1234-description").as_deref(),
Some("JIRA-1234")
);
assert_eq!(find_jira("ABC-12").as_deref(), Some("ABC-12"));
}
#[test]
fn rejects_shapes_the_regex_rejects() {
assert_eq!(find_jira("AB-1234"), None); assert_eq!(find_jira("ABC-1"), None); assert_eq!(find_jira("ABC-0123"), None); assert_eq!(find_jira("abc-1234"), None); assert_eq!(find_jira("feat/nothing-here"), None);
}
#[test]
fn long_letter_runs_match_at_a_later_offset() {
let s = format!("{}-12", "A".repeat(40));
assert_eq!(
find_jira(&s).as_deref(),
Some(&*format!("{}-12", "A".repeat(32)))
);
}
#[test]
fn finds_bare_ids_of_at_least_three_digits() {
assert_eq!(
find_digits("fix/1234-something", 3).as_deref(),
Some("1234")
);
assert_eq!(find_digits("fix/12-something", 3), None);
assert_eq!(find_digits("release/007", 3).as_deref(), Some("007"));
assert_eq!(find_digits("no-digits-here", 3), None);
}
}