use std::fmt::Display;
use chrono::Local;
use crate::logs;
const MAX_DETAIL: usize = 100;
const PREFIX: &str = "thirdshift: ";
const STAMP: &str = "%H:%M:%S";
pub fn step(message: impl Display) {
logs::eprint(&stamped(message));
}
pub fn warn(error: &anyhow::Error, warning: std::fmt::Arguments) {
step(format_args!("{error:#}"));
step(format_args!("warning: {warning}"));
}
pub fn stamped(message: impl Display) -> String {
progress_line(Local::now().format(STAMP), message)
}
#[derive(Debug, PartialEq, Eq)]
pub enum ChildLine<'a> {
Own(&'a str),
Relayed,
Continuation,
}
impl<'a> ChildLine<'a> {
pub fn of(line: &'a str) -> Self {
let Some(rest) = line.strip_prefix(PREFIX) else {
return ChildLine::Continuation;
};
let message = split_stamp(rest).map_or(rest, |(_, message)| message);
let from_its_own_child = message
.strip_prefix('#')
.and_then(|rest| rest.split_once(": "))
.is_some_and(|(number, _)| {
!number.is_empty() && number.bytes().all(|byte| byte.is_ascii_digit())
});
if from_its_own_child {
ChildLine::Relayed
} else {
ChildLine::Own(message)
}
}
}
pub fn relay(number: u64, line: &str) -> ChildLine<'_> {
logs::eprint(&relayed(number, line));
ChildLine::of(line)
}
pub fn relayed(number: u64, line: &str) -> String {
let rest = line.strip_prefix(PREFIX).unwrap_or(line);
match split_stamp(rest) {
Some((time, message)) => progress_line(time, format_args!("#{number}: {message}")),
None => stamped(format_args!("#{number}: {rest}")),
}
}
pub(crate) fn progress_line(time: impl Display, message: impl Display) -> String {
format!("{PREFIX}{time} {message}")
}
fn split_stamp(line: &str) -> Option<(&str, &str)> {
let (time, message) = line.split_at_checked(8)?;
let message = message.strip_prefix(' ')?;
let is_stamp = time.bytes().enumerate().all(|(at, byte)| match at {
2 | 5 => byte == b':',
_ => byte.is_ascii_digit(),
});
is_stamp.then_some((time, message))
}
pub(crate) fn bash(command: &str) -> String {
let actions: Vec<&str> = ["commit", "push"]
.into_iter()
.filter(|subcommand| runs_git(command, subcommand))
.collect();
if actions.is_empty() {
format!("$ {}", shorten(command.lines().next().unwrap_or("")))
} else {
actions.join(" and ")
}
}
fn runs_git(command: &str, subcommand: &str) -> bool {
command.split(['\n', ';', '&', '|']).any(|part| {
let mut words = part.split_whitespace();
words.next() == Some("git") && words.next() == Some(subcommand)
})
}
pub(crate) fn shorten(text: &str) -> String {
match text.char_indices().nth(MAX_DETAIL) {
Some((cut, _)) => format!("{}…", &text[..cut]),
None => text.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_stamped_line_splits_into_its_time_and_message() {
assert_eq!(
split_stamp("12:14:49 pushing issue-7"),
Some(("12:14:49", "pushing issue-7"))
);
}
#[test]
fn a_line_without_a_stamp_does_not_split() {
for line in [
"pushing issue-7",
"12:14:49",
"12:14:49pushing",
"12-14-49 pushing",
"1:14:49 pushing",
"#21: 12:14:49 pushing",
"é2:14:49 pushing",
] {
assert_eq!(split_stamp(line), None, "{line}");
}
}
#[test]
fn a_relayed_line_keeps_its_time_under_the_issues_number() {
assert_eq!(
relayed(21, "thirdshift: 12:14:49 could not push issue-21"),
"thirdshift: 12:14:49 #21: could not push issue-21"
);
}
#[test]
fn a_relayed_line_with_no_time_is_stamped_now() {
let line = relayed(21, "hint: fetch first");
let (_, message) = split_stamp(line.strip_prefix(PREFIX).unwrap()).unwrap();
assert_eq!(message, "#21: hint: fetch first");
}
#[test]
fn a_childs_line_that_starts_with_the_prefix_is_its_own_progress_line() {
assert_eq!(
ChildLine::of("thirdshift: 12:14:49 could not push issue-21"),
ChildLine::Own("could not push issue-21")
);
assert_eq!(
ChildLine::of(&stamped("#21 failed: claude exited 1")),
ChildLine::Own("#21 failed: claude exited 1")
);
}
#[test]
fn a_childs_line_without_the_prefix_continues_the_one_before() {
for line in [
"hint: fetch first",
"12:14:49 thirdshift: pushing",
" thirdshift: 12:14:49 pushing",
"#8: claude exited 1",
"",
] {
assert_eq!(ChildLine::of(line), ChildLine::Continuation, "{line}");
}
}
#[test]
fn a_line_a_child_relayed_is_not_its_own_progress_line() {
for line in [
"thirdshift: 12:14:49 claude exited 1",
"hint: fetch first",
"thirdshift: 12:14:49 #9: relayed before",
] {
assert_eq!(
ChildLine::of(&relayed(8, line)),
ChildLine::Relayed,
"{line}"
);
}
}
}