sift_queue/cli/commands/
edit.rs1use crate::queue::{Queue, Source, UpdateAttrs, VALID_STATUSES};
2use crate::EditArgs;
3use anyhow::Result;
4use std::path::PathBuf;
5
6pub fn execute(args: &EditArgs, queue_path: PathBuf) -> Result<i32> {
8 let queue = Queue::new(queue_path);
9
10 let id = match &args.id {
11 Some(ref id) => id.as_str(),
12 None => {
13 eprintln!("Error: Item ID is required");
14 return Ok(1);
15 }
16 };
17
18 let item = match queue.find(id) {
19 Some(item) => item,
20 None => {
21 eprintln!("Error: Item not found: {}", id);
22 return Ok(1);
23 }
24 };
25
26 let mut attrs = UpdateAttrs::default();
27 let mut has_changes = false;
28
29 if let Some(ref status) = args.set_status {
31 if !VALID_STATUSES.contains(&status.as_str()) {
32 eprintln!(
33 "Error: Invalid status: {}. Valid: {}",
34 status,
35 VALID_STATUSES.join(", ")
36 );
37 return Ok(1);
38 }
39 attrs.status = Some(status.clone());
40 has_changes = true;
41 }
42
43 if let Some(ref title) = args.set_title {
45 attrs.title = Some(title.clone());
46 has_changes = true;
47 }
48
49 if let Some(ref json_str) = args.set_metadata {
51 match serde_json::from_str::<serde_json::Value>(json_str) {
52 Ok(v) => {
53 attrs.metadata = Some(v);
54 has_changes = true;
55 }
56 Err(e) => {
57 eprintln!("Error: Invalid JSON for metadata: {}", e);
58 return Ok(1);
59 }
60 }
61 }
62
63 if let Some(ref ids_str) = args.set_blocked_by {
65 let blocked_by: Vec<String> = ids_str
66 .split(',')
67 .map(|s: &str| s.trim().to_string())
68 .filter(|s: &String| !s.is_empty())
69 .collect();
70 attrs.blocked_by = Some(blocked_by);
71 has_changes = true;
72 }
73
74 let has_source_adds = !args.add_diff.is_empty()
76 || !args.add_file.is_empty()
77 || !args.add_text.is_empty()
78 || !args.add_directory.is_empty()
79 || !args.add_transcript.is_empty();
80 let has_source_removes = !args.rm_source.is_empty();
81
82 if has_source_adds || has_source_removes {
83 has_changes = true;
84 let mut sources: Vec<serde_json::Value> =
85 item.sources.iter().map(|s| s.to_json_value()).collect();
86
87 let mut rm_indices: Vec<usize> = args.rm_source.clone();
89 rm_indices.sort();
90 rm_indices.reverse();
91 for index in rm_indices {
92 if index < sources.len() {
93 sources.remove(index);
94 } else {
95 eprintln!("Warning: Source index {} out of range", index);
96 }
97 }
98
99 for path in &args.add_diff {
101 sources.push(source_value("diff", Some(path.as_str()), None));
102 }
103 for path in &args.add_file {
104 sources.push(source_value("file", Some(path.as_str()), None));
105 }
106 for text in &args.add_text {
107 sources.push(source_value("text", None, Some(text.as_str())));
108 }
109 for path in &args.add_directory {
110 sources.push(source_value("directory", Some(path.as_str()), None));
111 }
112 for path in &args.add_transcript {
113 sources.push(source_value("transcript", Some(path.as_str()), None));
114 }
115
116 if sources.is_empty() {
117 eprintln!("Error: Cannot remove all sources");
118 return Ok(1);
119 }
120
121 let new_sources: Vec<Source> = sources
123 .into_iter()
124 .filter_map(|v| serde_json::from_value(v).ok())
125 .collect();
126 attrs.sources = Some(new_sources);
127 }
128
129 if !has_changes {
130 eprintln!("Error: No changes specified");
131 return Ok(1);
132 }
133
134 match queue.update(id, attrs)? {
135 Some(updated) => {
136 println!("{}", updated.id);
137 eprintln!("Updated item {}", updated.id);
138 Ok(0)
139 }
140 None => {
141 eprintln!("Error: Item not found: {}", id);
142 Ok(1)
143 }
144 }
145}
146
147fn source_value(
148 type_: &str,
149 path: Option<&str>,
150 content: Option<&str>,
151) -> serde_json::Value {
152 let mut map = serde_json::Map::new();
153 map.insert(
154 "type".to_string(),
155 serde_json::Value::String(type_.to_string()),
156 );
157 if let Some(p) = path {
158 map.insert(
159 "path".to_string(),
160 serde_json::Value::String(p.to_string()),
161 );
162 }
163 if let Some(c) = content {
164 map.insert(
165 "content".to_string(),
166 serde_json::Value::String(c.to_string()),
167 );
168 }
169 serde_json::Value::Object(map)
170}