sift_queue/cli/commands/
collect.rs1use 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
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 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}