sift_queue/cli/
formatters.rs1use crate::queue::Item;
2use std::collections::HashSet;
3
4pub fn print_item_summary(item: &Item, pending_ids: Option<&HashSet<String>>) {
7 let display_status = resolve_display_status(item, pending_ids);
8
9 let mut type_counts: Vec<(String, usize)> = Vec::new();
11 for source in &item.sources {
12 if let Some(entry) = type_counts.iter_mut().find(|(t, _)| *t == source.type_) {
13 entry.1 += 1;
14 } else {
15 type_counts.push((source.type_.clone(), 1));
16 }
17 }
18 let source_types: String = type_counts
19 .iter()
20 .map(|(t, c)| {
21 if *c > 1 {
22 format!("{}:{}", t, c)
23 } else {
24 t.clone()
25 }
26 })
27 .collect::<Vec<_>>()
28 .join(",");
29
30 let label = match (&item.title, &item.description) {
31 (Some(t), _) => t.clone(),
32 (None, Some(d)) => d.clone(),
33 (None, None) => String::new(),
34 };
35
36 println!(
37 "{} [{}] {} {} {}",
38 item.id, display_status, label, source_types, item.created_at
39 );
40}
41
42pub fn print_item_detail(item: &Item) {
44 println!("Item: {}", item.id);
45 if let Some(ref title) = item.title {
46 println!("Title: {}", title);
47 }
48 if let Some(ref description) = item.description {
49 println!("Description: {}", description);
50 }
51 println!("Status: {}", item.status);
52 println!("Created: {}", item.created_at);
53 println!("Updated: {}", item.updated_at);
54 println!(
55 "Session: {}",
56 item.session_id.as_deref().unwrap_or("none")
57 );
58
59 if !item.blocked_by.is_empty() {
60 println!("Blocked by: {}", item.blocked_by.join(", "));
61 }
62
63 if let Some(ref wt) = item.worktree {
64 let branch = wt.branch.as_deref().unwrap_or("");
65 let path = wt.path.as_deref().unwrap_or("");
66 println!("Worktree: {} {}", branch, path);
67 }
68
69 if let serde_json::Value::Object(ref map) = item.metadata {
70 if !map.is_empty() {
71 println!("Metadata:");
72 for (k, v) in map {
73 println!(" {}: {}", k, v);
74 }
75 }
76 }
77
78 println!("Sources: ({})", item.sources.len());
79 for (i, source) in item.sources.iter().enumerate() {
80 print_source(source, i);
81 }
82}
83
84fn print_source(source: &crate::queue::Source, index: usize) {
86 let location = if let Some(ref path) = source.path {
87 path.clone()
88 } else if source.content.is_some() {
89 "[inline]".to_string()
90 } else {
91 "[empty]".to_string()
92 };
93
94 println!(" [{}] {}: {}", index, source.type_, location);
95
96 if let (Some(ref content), None) = (&source.content, &source.path) {
97 let lines: Vec<&str> = content.lines().collect();
98 let preview: Vec<&str> = lines.iter().take(3).copied().collect();
99 let preview_str = preview.join("\n ");
100 println!(" {}", preview_str);
101 if lines.len() > 3 {
102 println!(" ...");
103 }
104 }
105}
106
107fn resolve_display_status(item: &Item, pending_ids: Option<&HashSet<String>>) -> String {
109 if !item.pending() || !item.blocked() {
110 return item.status.clone();
111 }
112 match pending_ids {
113 None => "blocked".to_string(),
114 Some(ids) => {
115 if item.blocked_by.iter().any(|id| ids.contains(id)) {
116 "blocked".to_string()
117 } else {
118 item.status.clone()
119 }
120 }
121 }
122}