use crate::errors::CliError;
use crate::git;
use crate::output::format::OutputFormat;
use comfy_table::{presets::UTF8_FULL_CONDENSED, Cell, Color, Table};
pub fn run(limit: usize, json: bool) -> Result<(), CliError> {
let format = OutputFormat::detect(json);
let config = load_config()?;
let branch = config.branch.as_deref().unwrap_or("autoresearch");
let experiments = git::parse_experiments(branch, limit).unwrap_or_default();
if experiments.is_empty() {
return Err(CliError::NoExperiments(branch.to_string()));
}
match format {
OutputFormat::Json => {
let out = serde_json::json!({
"status": "success",
"data": {
"branch": branch,
"total": experiments.len(),
"experiments": experiments,
}
});
println!("{}", serde_json::to_string_pretty(&out).unwrap());
}
OutputFormat::Table => {
let mut table = Table::new();
table.load_preset(UTF8_FULL_CONDENSED);
table.set_header(vec!["Run", "Hash", "Metric", "Delta", "Status", "Summary"]);
let mut prev_metric: Option<f64> = None;
for exp in &experiments {
let delta = match (exp.metric, prev_metric) {
(Some(current), Some(prev)) => {
let d = current - prev;
if d > 0.0 {
format!("+{:.4}", d)
} else {
format!("{:.4}", d)
}
}
_ => "-".to_string(),
};
let status_cell = match exp.status {
git::ExperimentStatus::Kept => Cell::new("kept").fg(Color::Green),
git::ExperimentStatus::Discarded => Cell::new("discard").fg(Color::Red),
git::ExperimentStatus::Baseline => Cell::new("baseline").fg(Color::Cyan),
git::ExperimentStatus::Unknown => Cell::new("?").fg(Color::DarkGrey),
};
let metric_str = exp
.metric
.map(|m| format!("{:.4}", m))
.unwrap_or_else(|| "-".to_string());
let summary = crate::output::truncate(&exp.summary, 50);
table.add_row(vec![
Cell::new(exp.run),
Cell::new(&exp.short_hash),
Cell::new(&metric_str),
Cell::new(&delta),
status_cell,
Cell::new(&summary),
]);
if exp.metric.is_some() {
prev_metric = exp.metric;
}
}
println!("{table}");
}
}
Ok(())
}
struct Config {
branch: Option<String>,
}
fn load_config() -> Result<Config, CliError> {
let path = std::path::Path::new("autoresearch.toml");
if !path.exists() {
return Ok(Config { branch: None });
}
let content = std::fs::read_to_string(path)?;
let table: toml::Table =
toml::from_str(&content).map_err(|e| CliError::Config(e.to_string()))?;
Ok(Config {
branch: table
.get("branch")
.and_then(|v| v.as_str())
.map(|s| s.to_string()),
})
}