#![allow(dead_code)]
use std::io::{self, Write};
use std::path::Path;
use clap::Args as ClapArgs;
use crate::error::CliError;
use crate::event_log;
use crate::events::ErrorKind;
use crate::run_state::{self, NodeStatus, PipeState};
use crate::volume;
#[derive(Debug, ClapArgs)]
pub struct Args {
pub run_id: Option<String>,
}
pub fn run(args: &Args) -> Result<(), CliError> {
let cwd = std::env::current_dir()
.map_err(|e| CliError::Io(format!("cannot determine current directory: {e}")))?;
run_at_root(&cwd, args, &mut io::stdout())
}
pub fn run_at_root(start: &Path, args: &Args, out: &mut dyn Write) -> Result<(), CliError> {
let root = volume::find_omne_root(start).ok_or(CliError::NotAVolume)?;
match &args.run_id {
Some(run_id) => show_run(&root, run_id, out),
None => show_all(&root, out),
}
}
fn show_all(root: &Path, out: &mut dyn Write) -> Result<(), CliError> {
let run_ids = event_log::enumerate_runs(root)?;
if run_ids.is_empty() {
writeln!(out, "No runs found.").map_err(io_err)?;
return Ok(());
}
for run_id in &run_ids {
let events = match event_log::read_run(root, run_id) {
Ok(ev) => ev,
Err(e) => {
writeln!(out, "{run_id} (read error: {e})").map_err(io_err)?;
continue;
}
};
let summary = run_state::summarize(run_id, &events);
let state_str = match &summary.state {
PipeState::Running => "running",
PipeState::Completed => "completed",
PipeState::Aborted { .. } => "aborted",
};
let orphan_marker = if summary.is_orphan { " [ORPHAN?]" } else { "" };
let progress = if summary.node_count > 0 {
format!(" ({}/{})", summary.completed_count, summary.node_count)
} else {
String::new()
};
writeln!(
out,
"{run_id} {pipe} {state_str}{progress}{orphan_marker} {ts}",
pipe = summary.pipe,
ts = summary.last_ts,
)
.map_err(io_err)?;
}
Ok(())
}
fn show_run(root: &Path, run_id: &str, out: &mut dyn Write) -> Result<(), CliError> {
if !event_log::run_exists(root, run_id) {
return Err(CliError::RunNotFound(run_id.to_string()));
}
let events = event_log::read_run(root, run_id)?;
let state = run_state::derive(run_id, &events);
let pipe_status = match &state.state {
PipeState::Running => "running".to_string(),
PipeState::Completed => "completed".to_string(),
PipeState::Aborted { reason } => format!("aborted: {reason}"),
};
let orphan_marker = if state.is_orphan { " [ORPHAN?]" } else { "" };
writeln!(out, "run: {}", state.run_id).map_err(io_err)?;
writeln!(out, "pipe: {}", state.pipe).map_err(io_err)?;
writeln!(out, "status: {pipe_status}{orphan_marker}").map_err(io_err)?;
writeln!(out, "last: {}", state.last_ts).map_err(io_err)?;
if !state.nodes.is_empty() {
writeln!(out).map_err(io_err)?;
writeln!(out, "nodes:").map_err(io_err)?;
for node in &state.nodes {
let (glyph, detail) = match &node.status {
NodeStatus::Pending => ("\u{2022}", String::new()), NodeStatus::Running => ("\u{25b6}", String::new()), NodeStatus::Completed => ("\u{2713}", String::new()), NodeStatus::Failed { kind, message } => {
let kind_str = error_kind_label(*kind);
let msg = match message {
Some(m) => format!(" {kind_str}: {m}"),
None => format!(" {kind_str}"),
};
("\u{2717}", msg) }
};
let kind_tag = node
.kind
.map(|k| format!(" [{}]", node_kind_label(k)))
.unwrap_or_default();
writeln!(out, " {glyph} {id}{kind_tag}{detail}", id = node.id).map_err(io_err)?;
}
}
Ok(())
}
fn node_kind_label(kind: crate::events::NodeKind) -> &'static str {
match kind {
crate::events::NodeKind::Command => "command",
crate::events::NodeKind::Prompt => "prompt",
crate::events::NodeKind::Bash => "bash",
crate::events::NodeKind::Loop => "loop",
}
}
fn error_kind_label(kind: ErrorKind) -> &'static str {
match kind {
ErrorKind::HostMissing => "host_missing",
ErrorKind::Timeout => "timeout",
ErrorKind::Blocked => "blocked",
ErrorKind::GateFailed => "gate_failed",
ErrorKind::GateTimeout => "gate_timeout",
ErrorKind::Crash => "crash",
ErrorKind::MaxIterationsExceeded => "max_iterations_exceeded",
}
}
fn io_err(e: io::Error) -> CliError {
CliError::Io(format!("stdout write failed: {e}"))
}