use super::metadata::RefsLookup;
use crate::commands::issue::epic::render::{epic_number_of, phase_number_of};
pub(crate) const GITHUB_TITLE_LIMIT: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TitleDecision {
Unchanged,
Retitle(String),
Warn(String),
Unknown(String),
}
pub(crate) fn phase_tag(issue_title: &str) -> Option<String> {
let epic = epic_number_of(issue_title)?;
let phase = phase_number_of(issue_title)?;
Some(format!("[EPIC_{epic} PHASE_{phase}]"))
}
pub(crate) fn conventional_prefix_len(title: &str) -> Option<usize> {
let type_len = title.bytes().take_while(u8::is_ascii_lowercase).count();
if type_len == 0 {
return None;
}
let rest = &title[type_len..];
if !rest.starts_with('(') {
return separator_len(rest).map(|n| type_len + n);
}
rest.match_indices(')')
.filter(|(i, _)| *i >= 2)
.find_map(|(i, _)| separator_len(&rest[i + 1..]).map(|n| type_len + i + 1 + n))
}
fn separator_len(s: &str) -> Option<usize> {
if s.starts_with("!: ") {
Some(3)
} else if s.starts_with(": ") {
Some(2)
} else {
None
}
}
pub(crate) fn decide(title: &str, refs: &RefsLookup<'_>) -> TitleDecision {
let title = title.trim();
if title.contains("[EPIC_") {
return TitleDecision::Unchanged;
}
let tag = match refs {
RefsLookup::Absent => return TitleDecision::Unchanged,
RefsLookup::Found(issue) => match phase_tag(&issue.title) {
Some(tag) => tag,
None => return TitleDecision::Unchanged,
},
RefsLookup::Unreadable(number) => {
return match conventional_prefix_len(title) {
Some(_) => TitleDecision::Unknown(format!(
"title (issue #{number} could not be read, so its [EPIC_N PHASE_M] tag \
is unknown)"
)),
None => TitleDecision::Unchanged,
};
}
};
let Some(at) = conventional_prefix_len(title) else {
return TitleDecision::Warn(format!(
"title not tagged {tag}: \"{title}\" does not match `<type>(<scope>): <subject>`"
));
};
let tagged = format!("{}{tag} {}", &title[..at], &title[at..]);
if tagged.chars().count() > GITHUB_TITLE_LIMIT {
return TitleDecision::Warn(format!(
"title not tagged {tag}: the tagged title would pass GitHub's \
{GITHUB_TITLE_LIMIT}-character limit"
));
}
TitleDecision::Retitle(tagged)
}
#[cfg(test)]
#[path = "phase_title_tests.rs"]
mod tests;