mod activity;
mod command_log;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, MutexGuard, PoisonError};
use crate::config::UserConfig;
use crate::harness::Choice;
use crate::issue::{IssueUrl, Repo};
use crate::progress;
use activity::Kind;
pub enum Begin<'a> {
Run(&'a IssueUrl),
ChildRun(&'a str),
ArchitectRun,
PickupRun,
}
#[derive(Clone, Copy)]
pub enum Work<'a> {
Run(&'a IssueUrl),
SpecRun(&'a IssueUrl),
PickupRun(&'a IssueUrl),
ArchitectRun(&'a Repo),
}
#[derive(Clone, Copy)]
pub enum Pass {
ArchitectRun,
PickupRun,
}
struct Record {
logs_dir: Option<PathBuf>,
child: bool,
started: bool,
}
static RECORD: Mutex<Record> = Mutex::new(Record {
logs_dir: None,
child: false,
started: false,
});
pub fn begin(begin: Begin) {
match begin {
Begin::Run(issue) => command_log::begin(format_args!("starting on {}", issue.url), false),
Begin::ChildRun(stamp) => {
record().child = true;
command_log::begin_child(stamp);
}
Begin::ArchitectRun => command_log::begin("Architect run starting", true),
Begin::PickupRun => command_log::begin("Pickup run starting", true),
}
}
pub fn configured(config: &UserConfig) {
record().logs_dir = Some(config.logs_dir.clone());
if !config.quiet_skips {
command_log::show_held();
}
}
pub fn started(work: Work, harness: &Choice) {
if !record().take_start() {
return;
}
let root = work.root();
command_log::keep(work.command_log(&root, command_log::stamp()));
progress::step(format_args!("sessions run on {harness}"));
activity::start(&root, work.kind(), work.issue(), harness);
}
pub fn skipped(pass: Pass, repo: &Repo, reason: impl Display) {
let kind = match pass {
Pass::ArchitectRun => Kind::ArchitectRun,
Pass::PickupRun => Kind::PickupRun,
};
activity::skip(&root(repo), kind, reason);
}
pub fn ended(outcome: impl Display) {
command_log::show_held();
activity::end(outcome);
}
pub fn show_held() {
command_log::show_held();
}
pub fn root(repo: &Repo) -> PathBuf {
let record = record();
let logs_dir = record
.logs_dir
.as_deref()
.expect("the root of a repository's logs is asked for before the User config is loaded");
root_under(logs_dir, repo)
}
pub fn stamp() -> &'static str {
command_log::stamp()
}
pub fn command_log_path() -> Option<PathBuf> {
command_log::path()
}
pub fn eprint(line: &str) {
command_log::eprint(line);
}
pub fn print(line: &str) {
command_log::print(line);
}
fn root_under(logs_dir: &Path, repo: &Repo) -> PathBuf {
logs_dir.join(&repo.owner).join(&repo.name)
}
impl Record {
fn take_start(&mut self) -> bool {
let first = !self.started;
self.started = true;
first && !self.child
}
}
impl Work<'_> {
fn root(self) -> PathBuf {
match self {
Work::Run(issue) | Work::SpecRun(issue) | Work::PickupRun(issue) => root(&issue.repo()),
Work::ArchitectRun(repo) => root(repo),
}
}
fn kind(self) -> Kind {
match self {
Work::Run(_) => Kind::Run,
Work::SpecRun(_) => Kind::SpecRun,
Work::PickupRun(_) => Kind::PickupRun,
Work::ArchitectRun(_) => Kind::ArchitectRun,
}
}
fn issue(self) -> Option<u64> {
match self {
Work::Run(issue) | Work::SpecRun(issue) | Work::PickupRun(issue) => Some(issue.number),
Work::ArchitectRun(_) => None,
}
}
fn command_log(self, root: &Path, stamp: &str) -> PathBuf {
let (folder, prefix) = match self {
Work::Run(issue) | Work::SpecRun(issue) => ("issue", format!("{}-", issue.number)),
Work::PickupRun(issue) => ("pickup", format!("{}-", issue.number)),
Work::ArchitectRun(_) => ("architect", String::new()),
};
root.join("commands")
.join(folder)
.join(format!("{prefix}{stamp}.log"))
}
}
fn record() -> MutexGuard<'static, Record> {
RECORD.lock().unwrap_or_else(PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::*;
const STAMP: &str = "20261003T120000-0400";
fn issue(number: u64) -> IssueUrl {
IssueUrl::parse(&format!("https://github.com/acme/widgets/issues/{number}")).unwrap()
}
#[test]
fn each_kind_of_works_command_log_is_in_its_commands_folder_with_the_stamp() {
let root = Path::new("/logs/acme/widgets");
let issue = issue(7);
let repo = issue.repo();
for (work, path) in [
(
Work::Run(&issue),
"commands/issue/7-20261003T120000-0400.log",
),
(
Work::SpecRun(&issue),
"commands/issue/7-20261003T120000-0400.log",
),
(
Work::PickupRun(&issue),
"commands/pickup/7-20261003T120000-0400.log",
),
(
Work::ArchitectRun(&repo),
"commands/architect/20261003T120000-0400.log",
),
] {
assert_eq!(work.command_log(root, STAMP), root.join(path));
}
}
#[test]
fn the_root_of_a_repositorys_logs_is_named_for_its_owner_and_name() {
assert_eq!(
root_under(Path::new("/logs"), &issue(7).repo()),
Path::new("/logs/acme/widgets")
);
}
#[test]
fn only_the_first_work_started_is_recorded() {
let mut record = Record {
logs_dir: None,
child: false,
started: false,
};
assert!(record.take_start());
assert!(!record.take_start());
}
#[test]
fn a_child_run_records_no_work_it_take_start() {
let mut record = Record {
logs_dir: None,
child: true,
started: false,
};
assert!(!record.take_start());
assert!(!record.take_start());
}
}