Skip to main content

sift_queue/cli/commands/
collect.rs

1use crate::collect::{detect_format, render_title, Format};
2use crate::queue::{parse_priority_value, NewItem, Queue, Source};
3use crate::CollectArgs;
4use anyhow::Result;
5use std::io::{IsTerminal, Read};
6use std::path::PathBuf;
7
8/// Execute the `sq collect` command.
9pub fn execute(args: &CollectArgs, queue_path: PathBuf) -> Result<i32> {
10    if !args.by_file {
11        eprintln!("Error: collect requires a split mode (currently only --by-file is supported)");
12        return Ok(1);
13    }
14
15    if args.title.is_some() && args.title_template.is_some() {
16        eprintln!("Error: --title and --title-template are mutually exclusive");
17        return Ok(1);
18    }
19
20    if std::io::stdin().is_terminal() {
21        eprintln!("Error: sq collect --by-file expects piped stdin");
22        eprintln!("Try: rg --json PATTERN | sq collect --by-file");
23        return Ok(1);
24    }
25
26    let mut input = String::new();
27    std::io::stdin().read_to_string(&mut input)?;
28
29    if input.trim().is_empty() {
30        eprintln!("Error: no stdin input received");
31        eprintln!("Try: rg --json PATTERN | sq collect --by-file");
32        return Ok(1);
33    }
34
35    let format = match args.stdin_format.as_deref() {
36        Some("rg-json") => Format::RgJson,
37        Some(other) => {
38            eprintln!("Error: Unsupported stdin format: {}", other);
39            eprintln!("Currently supported: rg --json");
40            return Ok(1);
41        }
42        None => match detect_format(&input) {
43            Some(format) => format,
44            None => {
45                eprintln!("Error: could not detect a supported stdin format");
46                eprintln!("Currently supported: rg --json");
47                return Ok(1);
48            }
49        },
50    };
51
52    let grouped = match format {
53        Format::RgJson => match crate::collect::rg::parse_json(&input) {
54            Ok(items) => items,
55            Err(err) => {
56                eprintln!("Error: {}", err);
57                return Ok(1);
58            }
59        },
60    };
61
62    let priority = match &args.priority {
63        Some(value) => match parse_priority_value(value) {
64            Ok(priority) => Some(priority),
65            Err(err) => {
66                eprintln!("Error: {}", err);
67                return Ok(1);
68            }
69        },
70        None => None,
71    };
72
73    let metadata = match &args.metadata {
74        Some(json_str) => match serde_json::from_str(json_str) {
75            Ok(v) => v,
76            Err(e) => {
77                eprintln!("Error: Invalid JSON for metadata: {}", e);
78                return Ok(1);
79            }
80        },
81        None => serde_json::Value::Object(serde_json::Map::new()),
82    };
83
84    let blocked_by: Vec<String> = match &args.blocked_by {
85        Some(ids) => ids
86            .split(',')
87            .map(|s| s.trim().to_string())
88            .filter(|s| !s.is_empty())
89            .collect(),
90        None => Vec::new(),
91    };
92
93    let mut new_items = Vec::with_capacity(grouped.len());
94    for grouped_item in &grouped {
95        let title = match render_title(
96            args.title.as_deref(),
97            args.title_template.as_deref(),
98            grouped_item,
99        ) {
100            Ok(title) => title,
101            Err(err) => {
102                eprintln!("Error: {}", err);
103                return Ok(1);
104            }
105        };
106
107        new_items.push(NewItem {
108            sources: vec![
109                Source {
110                    type_: "file".to_string(),
111                    path: Some(grouped_item.filepath.clone()),
112                    content: None,
113                        },
114                Source {
115                    type_: "text".to_string(),
116                    path: None,
117                    content: Some(grouped_item.text.clone()),
118                        },
119            ],
120            title: Some(title),
121            description: args.description.clone(),
122            priority,
123            metadata: metadata.clone(),
124            blocked_by: blocked_by.clone(),
125        });
126    }
127
128    let queue = Queue::new(queue_path);
129    let items = queue.push_many_with_description(new_items)?;
130
131    if args.json {
132        let json_values: Vec<serde_json::Value> = items.iter().map(|i| i.to_json_value()).collect();
133        let json = serde_json::to_string_pretty(&json_values)?;
134        println!("{}", json);
135    } else {
136        for item in &items {
137            println!("{}", item.id);
138        }
139        eprintln!("Added {} item(s)", items.len());
140    }
141
142    Ok(0)
143}