use crate::actions::verbs::{Outcome, collect, run_logged};
use crate::cli_outbound::Cli;
use crate::git_tree::{Agent, AgentState};
use crate::opslog::Origin;
use std::io;
use std::path::Path;
const DELETE: &str = "delete";
const CHILDREN: &str = "--children";
const DRY_RUN: &str = "--dry-run";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentConfirmation {
pub name: String,
pub live: Vec<String>,
}
impl AgentConfirmation {
pub fn refused(&self) -> bool {
!self.live.is_empty()
}
pub fn subtree_armed(&self, typed: &str) -> bool {
!self.refused() && typed.trim() == self.name
}
}
pub fn confirmation(root: &str, agents: &[Agent]) -> AgentConfirmation {
let live = crate::nav::convs::members(agents, root)
.iter()
.filter_map(|r| agents.get(r.index))
.filter(|a| matches!(a.state, AgentState::Live | AgentState::InFlight) || a.state_uncertain)
.map(|a| a.agent_id.clone())
.collect();
AgentConfirmation {
name: crate::nav::convs::display_name_of(agents, root),
live,
}
}
pub fn confirmation_of_rows(rows: &[crate::nav::convs::ConvRow], root: &str) -> AgentConfirmation {
let at = rows.iter().position(|r| r.root_id == root);
AgentConfirmation {
name: crate::nav::convs::selection(rows, root).name,
live: at
.map(|at| {
crate::nav::convs::census::subtree(rows, at)
.filter(|r| {
matches!(r.state, AgentState::Live | AgentState::InFlight) || r.uncertain
})
.map(|r| r.root_id.clone())
.collect()
})
.unwrap_or_default(),
}
}
pub fn live_refusal(live: &[String]) -> String {
format!(
"refused \u{2014} live: {} \u{2014} stop them first",
live.join(", ")
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Census {
pub descendants: Vec<String>,
pub pending_deposits: usize,
}
pub fn parse_report(line: &str) -> Option<Census> {
let (_, rest) = line.split_once("; descendants: ")?;
let (desc, pending) = rest.split_once("; pending deposits: ")?;
let pending_deposits = pending.trim().parse().ok()?;
let descendants = match desc.split_once(" (") {
Some((_, named)) => named
.strip_suffix(')')?
.split(", ")
.map(str::to_owned)
.collect(),
None => Vec::new(),
};
Some(Census {
descendants,
pending_deposits,
})
}
pub fn census(litany: &Cli, ws: &Path, root: &str) -> Result<Census, String> {
let ws_s = ws.to_string_lossy().into_owned();
let outcome = collect(litany.run_in(ws, &[DELETE, &ws_s, root, CHILDREN, DRY_RUN]))
.map_err(|e| e.to_string())?;
if !outcome.ok() {
return Err(outcome.stderr.trim().to_owned());
}
let line = outcome.stdout.trim();
parse_report(line).ok_or_else(|| format!("unrecognized delete report: {line}"))
}
pub fn spawn(
litany: &Cli,
state_root: &Path,
ts: &str,
ws: &Path,
root: &str,
children: bool,
) -> io::Result<Outcome> {
let ws_s = ws.to_string_lossy();
let mut args = vec![DELETE, ws_s.as_ref(), root];
if children {
args.push(CHILDREN);
}
run_logged(litany, state_root, ts, ws, &args, Origin::Conversation)
}
#[cfg(test)]
mod tests;