use std::fmt::Write;
use serde_json::Value;
use super::history::Dead;
use super::{filter, json_line, task_json, Catalog, Entry, Flags, Style};
use crate::task::Task;
enum Row<'a> {
Live(&'a Entry),
Dead(&'a Dead),
}
impl Row<'_> {
fn id(&self) -> &str {
match self {
Row::Live(e) => &e.id,
Row::Dead(d) => &d.id,
}
}
fn task(&self) -> &Task {
match self {
Row::Live(e) => &e.task,
Row::Dead(d) => &d.task,
}
}
}
pub(crate) fn render_list(cat: &Catalog, dead: &[Dead], flags: &Flags, style: &Style) -> String {
let mut rows: Vec<Row> = Vec::new();
if flags.reach.live() {
rows.extend(
cat.entries()
.iter()
.filter(|e| flags.status.is_none_or(|want| cat.status(e) == want))
.filter(|e| filter::matches(&e.task, e.task.updated, flags))
.map(Row::Live),
);
}
if flags.reach.dead() {
rows.extend(
dead.iter().filter(|d| filter::matches(&d.task, d.retired_at, flags)).map(Row::Dead),
);
}
rows.sort_by(|a, b| order_key(a).cmp(&order_key(b)));
render(cat, &rows, flags, style)
}
fn order_key<'a>(r: &'a Row) -> (bool, i64, i64, &'a str) {
let t = r.task();
(t.priority.is_none(), t.priority.unwrap_or(0), t.created, r.id())
}
fn render(cat: &Catalog, rows: &[Row], flags: &Flags, style: &Style) -> String {
if flags.json {
let arr: Vec<Value> = rows.iter().map(|r| task_json(r.id(), r.task())).collect();
return json_line(&Value::Array(arr));
}
let mut out = String::new();
for r in rows {
out.push_str(&line(&badge(cat, r, style), r.id(), r.task()));
}
out
}
fn badge(cat: &Catalog, r: &Row, style: &Style) -> String {
match r {
Row::Live(e) => style.badge(cat.status(e)),
Row::Dead(_) => style.retired_badge(),
}
}
fn line(badge: &str, id: &str, task: &Task) -> String {
let mut row = format!("{badge} {id} {}", task.title);
if let Some(p) = task.priority {
let _ = write!(row, " p{p}");
}
if let Some(c) = &task.claimant {
let _ = write!(row, " @{c}");
}
row.push('\n');
row
}
impl Catalog {
pub(crate) fn entries(&self) -> &[Entry] {
&self.entries
}
}
#[cfg(test)]
#[path = "list_tests.rs"]
mod tests;