use std::{
io::{BufRead, BufReader},
path::{Path, PathBuf},
process::{Command, Stdio},
sync::mpsc,
thread,
};
use anyhow::Result;
use crate::{ledger::LedgerStore, resolve, reviewer::ReviewQueue};
use super::data::{self, Snapshot};
pub fn remove_queue_item(state_dir: &Path, run_id: &str, sha: &str) -> Result<()> {
let queue = ReviewQueue::new(state_dir);
if !run_id.is_empty() {
queue
.remove_run_id(run_id)
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
} else {
queue
.remove_sha(sha)
.map_err(|error| anyhow::anyhow!(error.to_string()))?;
}
Ok(())
}
pub fn waive_rejection(state_dir: &Path, sha: &str, reason: &str) -> Result<String> {
let store = LedgerStore::new(state_dir);
let entry = resolve::waive_human(&store, sha, reason)?;
Ok(format!("waived {} (human lane)", entry.commit_sha))
}
pub fn petition_rejection(state_dir: &Path, rejection_sha: &str, fix_sha: &str) -> Result<String> {
let store = LedgerStore::new(state_dir);
resolve::petition_fix(&store, state_dir, rejection_sha, fix_sha)
}
pub fn preview_config_edit(snapshot: &Snapshot, path: &str, value: &str) -> Result<String> {
let next = data::apply_config_edit(&snapshot.config_raw, path, value)?;
Ok(data::line_diff(&snapshot.config_raw, &next))
}
pub fn save_config_edit(
config_path: &Path,
snapshot: &Snapshot,
path: &str,
value: &str,
) -> Result<String> {
let next = data::apply_config_edit(&snapshot.config_raw, path, value)?;
data::write_config(config_path, &next)?;
Ok(format!("wrote {}", config_path.display()))
}
pub fn spawn_drain_once(
state_dir: &Path,
config_path: &Path,
tx: mpsc::Sender<DrainEvent>,
) -> Result<thread::JoinHandle<()>> {
let binary = drain_binary();
let state_dir = state_dir.to_path_buf();
let config_path = config_path.to_path_buf();
let binary_display = binary.display().to_string();
let state_display = state_dir.display().to_string();
let config_display = config_path.display().to_string();
let _ = tx.send(DrainEvent::Line(format!(
"$ {binary_display} --state-dir {state_display} --config {config_display} watch --once"
)));
let handle = thread::spawn(move || {
let mut cmd = Command::new(&binary);
cmd.arg("--state-dir")
.arg(&state_dir)
.arg("--config")
.arg(&config_path)
.arg("watch")
.arg("--once")
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = match cmd.spawn() {
Ok(child) => child,
Err(error) => {
let _ = tx.send(DrainEvent::Line(format!("failed to spawn drain: {error}")));
let _ = tx.send(DrainEvent::Done(1));
return;
}
};
let stdout = child.stdout.take();
let stderr = child.stderr.take();
let tx_out = tx.clone();
let out_handle = thread::spawn(move || {
if let Some(out) = stdout {
for line in BufReader::new(out).lines().map_while(Result::ok) {
if tx_out.send(DrainEvent::Line(line)).is_err() {
break;
}
}
}
});
let tx_err = tx.clone();
let err_handle = thread::spawn(move || {
if let Some(err) = stderr {
for line in BufReader::new(err).lines().map_while(Result::ok) {
if tx_err
.send(DrainEvent::Line(format!("[err] {line}")))
.is_err()
{
break;
}
}
}
});
let code = match child.wait() {
Ok(status) => status.code().unwrap_or(1),
Err(error) => {
let _ = tx.send(DrainEvent::Line(format!("wait failed: {error}")));
1
}
};
let _ = out_handle.join();
let _ = err_handle.join();
let _ = tx.send(DrainEvent::Done(code));
});
Ok(handle)
}
fn drain_binary() -> PathBuf {
std::env::current_exe().unwrap_or_else(|_| PathBuf::from("truth-mirror"))
}
#[derive(Debug)]
pub enum DrainEvent {
Line(String),
Done(i32),
}
pub fn resolve_config_path(explicit: Option<&Path>, state_dir: &Path) -> PathBuf {
explicit.map_or_else(
|| crate::config::TruthMirrorConfig::default_path(state_dir),
Path::to_path_buf,
)
}
pub fn validate_and_normalize_edit(path: &str, raw: &str) -> Result<String> {
super::model::validate_config_edit(path, raw).map_err(anyhow::Error::msg)
}
pub fn validate_waiver_reason(raw: &str) -> Result<()> {
let trimmed = raw.trim();
if trimmed.is_empty() {
anyhow::bail!("waiver reason must not be empty");
}
if trimmed.contains("<why this rejection") || trimmed.contains("<what we are choosing") {
anyhow::bail!("replace the template placeholders before submitting the waiver");
}
Ok(())
}