Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,14 @@ async fn run_job_command(config_path: &str, command: JobCommand) -> Result<()> {
}
JobCommand::List => {
let catalog = jobs::Catalog::load(&cfg)?;
let mut rows = Vec::new();
for job in catalog.jobs.values() {
println!("{}\tvalid\t{}", job.name, job.backend.as_str());
rows.push((job.name.as_str(), "valid", job.backend.as_str()));
}
for error in catalog.errors {
println!("{}\tinvalid\t{}", error.name, error.message);
for error in &catalog.errors {
rows.push((error.name.as_str(), "invalid", error.message.as_str()));
}
print_job_list(&rows);
Ok(())
}
JobCommand::Show(name) => {
Expand Down Expand Up @@ -338,6 +340,49 @@ async fn run_job_command(config_path: &str, command: JobCommand) -> Result<()> {
}
}

fn print_job_list(rows: &[(&str, &str, &str)]) {
let rows = rows
.iter()
.map(|(name, status, detail)| {
(
escape_table_cell(name),
escape_table_cell(status),
escape_table_cell(detail),
)
})
.collect::<Vec<_>>();
let name_width = rows
.iter()
.map(|(name, _, _)| name.len())
.max()
.unwrap_or(0)
.max("NAME".len());
let status_width = rows
.iter()
.map(|(_, status, _)| status.len())
.max()
.unwrap_or(0)
.max("STATUS".len());

println!(
"{:<name_width$} {:<status_width$} BACKEND / ERROR",
"NAME", "STATUS"
);
println!(
"{:<name_width$} {:<status_width$} {}",
"-".repeat(name_width),
"-".repeat(status_width),
"-".repeat("BACKEND / ERROR".len())
);
for (name, status, detail) in rows {
println!("{name:<name_width$} {status:<status_width$} {detail}");
}
}

fn escape_table_cell(value: &str) -> String {
value.chars().flat_map(|c| c.escape_default()).collect()
}

fn report_invalid_jobs(cfg: &config::Config) -> Result<()> {
let catalog = jobs::Catalog::load(cfg)?;
for error in catalog.errors {
Expand Down
27 changes: 26 additions & 1 deletion tests/manual_job_crash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ printf '%s\n' '{"type":"thread.started","thread_id":"cli-thread"}'

let list = run_cli(binary, &config, &["job", "list"]);
assert!(list.status.success());
assert!(stdout(&list).contains("crash-test\tvalid\tcodex"));
let list_output = stdout(&list);
assert!(list_output.contains("NAME STATUS BACKEND / ERROR"));
assert!(list_output.contains("crash-test valid codex"));

let show = run_cli(binary, &config, &["job", "show", "crash-test"]);
assert!(show.status.success());
Expand Down Expand Up @@ -78,6 +80,29 @@ printf '%s\n' '{"type":"thread.started","thread_id":"cli-thread"}'
assert!(stdout(&invalid).contains("INVALID\tinvalid"));
assert!(String::from_utf8_lossy(&invalid.stderr).contains("1 invalid job(s)"));

let list_with_invalid = run_cli(binary, &config, &["job", "list"]);
assert!(list_with_invalid.status.success());
assert!(stdout(&list_with_invalid)
.lines()
.any(|line| line.ends_with("invalid job must start with a +++ frontmatter delimiter")));

std::fs::write(jobs.join("bad\nname.md"), "not a runbook").unwrap();
let list_with_malformed_name = run_cli(binary, &config, &["job", "list"]);
assert!(list_with_malformed_name.status.success());
let malformed_output = stdout(&list_with_malformed_name);
assert_eq!(malformed_output.lines().count(), 5);
assert!(malformed_output.contains(r"bad\nname.md invalid "));

std::fs::remove_file(jobs.join("crash-test.md")).unwrap();
std::fs::remove_file(jobs.join("invalid.md")).unwrap();
std::fs::remove_file(jobs.join("bad\nname.md")).unwrap();
let empty_list = run_cli(binary, &config, &["job", "list"]);
assert!(empty_list.status.success());
assert_eq!(
stdout(&empty_list),
"NAME STATUS BACKEND / ERROR\n---- ------ ---------------\n"
);

let _ = std::fs::remove_dir_all(root);
}

Expand Down
Loading