use super::ops::{Error, capture};
use crate::template::GitRunner;
use std::path::Path;
pub(super) fn of(
ws: &Path,
parent: &str,
target: &str,
git: &dyn GitRunner,
) -> Result<String, Error> {
let message = capture(ws, &["log", "-1", "--format=%B", target], "log body", git)?;
match from_message(&message) {
Some(subject) => Ok(subject),
None => {
let paths = capture(
ws,
&["diff", "--name-only", parent, target],
"diff --name-only",
git,
)?;
Ok(from_paths(&paths))
}
}
}
fn from_message(message: &str) -> Option<String> {
message
.lines()
.map(str::trim)
.find(|line| is_subject(line))
.map(str::to_owned)
}
fn is_subject(line: &str) -> bool {
!line.is_empty()
&& !line.starts_with('#')
&& !line.starts_with("```")
&& !line.chars().all(|c| matches!(c, '-' | '*' | '_' | '='))
}
fn from_paths(paths: &str) -> String {
let paths: Vec<&str> = paths
.lines()
.map(str::trim)
.filter(|p| !p.is_empty())
.collect();
crate::name::pool(&paths)
}
#[cfg(test)]
mod tests {
use super::{from_message, from_paths};
#[test]
fn the_first_line_is_the_subject_when_it_is_one() {
assert_eq!(
from_message("notes: record the lesson\n\nbecause x\n").as_deref(),
Some("notes: record the lesson")
);
}
#[test]
fn ornament_is_skipped_to_the_first_real_line() {
for message in [
"## Final Response\n\nskills/notes: record the correction\n",
"---\n\nskills/notes: record the correction\n",
"```\nskills/notes: record the correction\n```\n",
"\n\n skills/notes: record the correction \n",
] {
assert_eq!(
from_message(message).as_deref(),
Some("skills/notes: record the correction"),
"{message:?}"
);
}
}
#[test]
fn a_message_that_is_only_ornament_answers_nothing() {
for message in ["", "\n\n", "## Final Response\n***\n===\n"] {
assert_eq!(from_message(message), None, "{message:?}");
}
}
#[test]
fn the_fallback_is_what_the_proposal_changes() {
assert_eq!(
from_paths("skills/notes/SKILL.md\nfacts.md\n"),
"skills/notes/SKILL.md, facts.md"
);
assert_eq!(from_paths("\n"), "(none)");
}
}