Skip to main content

sift_queue/cli/
formatters.rs

1use crate::queue::Item;
2
3/// Print a one-line summary for an item (used by `sq list`).
4/// Format: {id}  [{status}]  {title_or_description}  {source_types}  {created_at}
5pub fn print_item_summary(item: &Item) {
6    // Tally source types
7    let mut type_counts: Vec<(String, usize)> = Vec::new();
8    for source in &item.sources {
9        if let Some(entry) = type_counts.iter_mut().find(|(t, _)| *t == source.type_) {
10            entry.1 += 1;
11        } else {
12            type_counts.push((source.type_.clone(), 1));
13        }
14    }
15    let source_types: String = type_counts
16        .iter()
17        .map(|(t, c)| {
18            if *c > 1 {
19                format!("{}:{}", t, c)
20            } else {
21                t.clone()
22            }
23        })
24        .collect::<Vec<_>>()
25        .join(",");
26
27    let label = match (&item.title, &item.description) {
28        (Some(t), _) => t.clone(),
29        (None, Some(d)) => d.clone(),
30        (None, None) => String::new(),
31    };
32
33    let priority = item
34        .priority
35        .map(|value| format!("  [priority:{}]", value))
36        .unwrap_or_default();
37
38    println!(
39        "{}  [{}]{}  {}  {}  {}",
40        item.id, item.status, priority, label, source_types, item.created_at
41    );
42}
43
44/// Print detailed view for an item (used by `sq show`).
45pub fn print_item_detail(item: &Item) {
46    println!("Item: {}", item.id);
47    if let Some(ref title) = item.title {
48        println!("Title: {}", title);
49    }
50    if let Some(ref description) = item.description {
51        println!("Description: {}", description);
52    }
53    println!("Status: {}", item.status);
54    if let Some(priority) = item.priority {
55        println!("Priority: {}", priority);
56    }
57    println!("Created: {}", item.created_at);
58    println!("Updated: {}", item.updated_at);
59
60    if !item.blocked_by.is_empty() {
61        println!("Blocked by: {}", item.blocked_by.join(", "));
62    }
63
64    if let serde_json::Value::Object(ref map) = item.metadata {
65        if !map.is_empty() {
66            println!("Metadata:");
67            for (k, v) in map {
68                println!("  {}: {}", k, v);
69            }
70        }
71    }
72
73    println!("Sources: ({})", item.sources.len());
74    for (i, source) in item.sources.iter().enumerate() {
75        print_source(source, i);
76    }
77}
78
79/// Print a single source entry.
80fn print_source(source: &crate::queue::Source, index: usize) {
81    let location = if let Some(ref path) = source.path {
82        path.clone()
83    } else if source.content.is_some() {
84        "[inline]".to_string()
85    } else {
86        "[empty]".to_string()
87    };
88
89    println!("  [{}] {}: {}", index, source.type_, location);
90
91    if let (Some(ref content), None) = (&source.content, &source.path) {
92        let lines: Vec<&str> = content.lines().collect();
93        let preview: Vec<&str> = lines.iter().take(3).copied().collect();
94        let preview_str = preview.join("\n      ");
95        println!("      {}", preview_str);
96        if lines.len() > 3 {
97            println!("      ...");
98        }
99    }
100}