Skip to main content

sift_queue/cli/commands/
edit.rs

1use crate::queue::{Queue, Source, UpdateAttrs, VALID_STATUSES};
2use crate::EditArgs;
3use anyhow::Result;
4use std::path::PathBuf;
5
6/// Execute the `sq edit` command.
7pub 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    // Status
30    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    // Title
44    if let Some(ref title) = args.set_title {
45        attrs.title = Some(title.clone());
46        has_changes = true;
47    }
48
49    // Metadata
50    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    // Blocked by
64    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    // Source modifications
75    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        // Remove sources (sort indices in reverse to preserve correctness)
88        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        // Add new sources
100        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        // Convert back to Source structs
122        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}