Skip to main content

sift_queue/cli/commands/
list.rs

1use crate::cli::formatters;
2use crate::queue::{Item, Queue};
3use crate::ListArgs;
4use anyhow::Result;
5use std::collections::HashSet;
6use std::io::Write;
7use std::path::PathBuf;
8use std::process::{Command, Stdio};
9
10/// Execute the `sq list` command.
11pub fn execute(args: &ListArgs, queue_path: PathBuf) -> Result<i32> {
12    let queue = Queue::new(queue_path);
13
14    let mut items: Vec<Item> = if args.ready {
15        queue.ready()
16    } else if let Some(status) = args.status.as_deref() {
17        queue.filter(Some(status))
18    } else if args.all {
19        queue.filter(None)
20    } else {
21        queue
22            .all()
23            .into_iter()
24            .filter(|item| item.status != "closed")
25            .collect()
26    };
27
28    // Apply jq filter
29    if let Some(ref filter_expr) = args.filter {
30        let expr = format!("[.[] | {}]", filter_expr);
31        match jq_filter(&items, &expr) {
32            Some(filtered) => items = filtered,
33            None => return Ok(1),
34        }
35    }
36
37    // Apply jq sort
38    if let Some(ref sort_path) = args.sort {
39        let expr = format!("sort_by({} // infinite)", sort_path);
40        match jq_filter(&items, &expr) {
41            Some(sorted) => items = sorted,
42            None => return Ok(1),
43        }
44    } else {
45        items.sort_by_key(|item| {
46            (
47                item.priority.unwrap_or(5),
48                item.created_at.clone(),
49                item.id.clone(),
50            )
51        });
52    }
53
54    // Apply reverse
55    if args.reverse {
56        items.reverse();
57    }
58
59    if args.json {
60        let values: Vec<serde_json::Value> =
61            items.iter().map(|i: &Item| i.to_json_value()).collect();
62        let json = serde_json::to_string_pretty(&values)?;
63        println!("{}", json);
64    } else if items.is_empty() {
65        eprintln!("No items found");
66    } else {
67        let pending_ids: HashSet<String> = queue
68            .filter(Some("pending"))
69            .iter()
70            .map(|i| i.id.clone())
71            .collect();
72        for item in &items {
73            formatters::print_item_summary(item, Some(&pending_ids));
74        }
75        eprintln!("{} item(s)", items.len());
76    }
77
78    Ok(0)
79}
80
81/// Run a jq expression on items, returning parsed results or None on error.
82fn jq_filter(items: &[Item], expr: &str) -> Option<Vec<Item>> {
83    let json_values: Vec<serde_json::Value> =
84        items.iter().map(|i: &Item| i.to_json_value()).collect();
85    let json = serde_json::to_string(&json_values).ok()?;
86
87    let mut child = Command::new("jq")
88        .arg("-e")
89        .arg(expr)
90        .stdin(Stdio::piped())
91        .stdout(Stdio::piped())
92        .stderr(Stdio::piped())
93        .spawn()
94        .map_err(|e| {
95            eprintln!("Error: Failed to run jq: {}", e);
96        })
97        .ok()?;
98
99    if let Some(ref mut stdin) = child.stdin {
100        stdin.write_all(json.as_bytes()).ok()?;
101    }
102    // Close stdin
103    drop(child.stdin.take());
104
105    let output = child.wait_with_output().ok()?;
106
107    if !output.status.success() {
108        let stderr = String::from_utf8_lossy(&output.stderr);
109        eprintln!("Error: Filter failed: {}", stderr.trim());
110        return None;
111    }
112
113    let parsed: Vec<serde_json::Value> = serde_json::from_slice(&output.stdout).ok()?;
114    Some(
115        parsed
116            .into_iter()
117            .filter_map(|v| serde_json::from_value::<Item>(v).ok())
118            .collect(),
119    )
120}