sift_queue/cli/commands/
collect.rs1use crate::collect::{detect_format, render_title, Format};
2use crate::queue::{NewItem, Queue, Source};
3use crate::CollectArgs;
4use anyhow::Result;
5use std::io::{IsTerminal, Read};
6use std::path::PathBuf;
7
8pub 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 metadata = match &args.metadata {
63 Some(json_str) => match serde_json::from_str(json_str) {
64 Ok(v) => v,
65 Err(e) => {
66 eprintln!("Error: Invalid JSON for metadata: {}", e);
67 return Ok(1);
68 }
69 },
70 None => serde_json::Value::Object(serde_json::Map::new()),
71 };
72
73 let blocked_by: Vec<String> = match &args.blocked_by {
74 Some(ids) => ids
75 .split(',')
76 .map(|s| s.trim().to_string())
77 .filter(|s| !s.is_empty())
78 .collect(),
79 None => Vec::new(),
80 };
81
82 let mut new_items = Vec::with_capacity(grouped.len());
83 for grouped_item in &grouped {
84 let title = match render_title(
85 args.title.as_deref(),
86 args.title_template.as_deref(),
87 grouped_item,
88 ) {
89 Ok(title) => title,
90 Err(err) => {
91 eprintln!("Error: {}", err);
92 return Ok(1);
93 }
94 };
95
96 new_items.push(NewItem {
97 sources: vec![
98 Source {
99 type_: "file".to_string(),
100 path: Some(grouped_item.filepath.clone()),
101 content: None,
102 session_id: None,
103 },
104 Source {
105 type_: "text".to_string(),
106 path: None,
107 content: Some(grouped_item.text.clone()),
108 session_id: None,
109 },
110 ],
111 title: Some(title),
112 description: args.description.clone(),
113 metadata: metadata.clone(),
114 session_id: None,
115 blocked_by: blocked_by.clone(),
116 });
117 }
118
119 let queue = Queue::new(queue_path);
120 let items = queue.push_many_with_description(new_items)?;
121
122 if args.json {
123 let json_values: Vec<serde_json::Value> = items.iter().map(|i| i.to_json_value()).collect();
124 let json = serde_json::to_string_pretty(&json_values)?;
125 println!("{}", json);
126 } else {
127 for item in &items {
128 println!("{}", item.id);
129 }
130 eprintln!("Added {} item(s)", items.len());
131 }
132
133 Ok(0)
134}