sift_queue/cli/commands/
add.rs1use crate::cli::help::{HelpDoc, HelpSection};
2use crate::queue::{parse_priority_value, Queue, Source};
3use crate::AddArgs;
4use anyhow::Result;
5use clap::builder::{StyledStr, Styles};
6use std::io::Read;
7use std::path::PathBuf;
8
9pub fn after_help(styles: &Styles) -> StyledStr {
10 HelpDoc::new()
11 .section(
12 HelpSection::new("Task content:")
13 .text("Provide at least one of --title, --description, or a source."),
14 )
15 .section(
16 HelpSection::new("Dependencies:")
17 .text("Use --blocked-by <id1,id2> to declare blockers when creating an item."),
18 )
19 .render(styles)
20}
21
22pub fn execute(args: &AddArgs, queue_path: PathBuf) -> Result<i32> {
24 let queue = Queue::new(queue_path);
25
26 let mut sources: Vec<Source> = Vec::new();
27
28 for path in &args.diff {
29 sources.push(Source {
30 type_: "diff".to_string(),
31 path: Some(path.clone()),
32 content: None,
33 });
34 }
35
36 for path in &args.file {
37 sources.push(Source {
38 type_: "file".to_string(),
39 path: Some(path.clone()),
40 content: None,
41 });
42 }
43
44 for text in &args.text {
45 sources.push(Source {
46 type_: "text".to_string(),
47 path: None,
48 content: Some(text.clone()),
49 });
50 }
51
52 for path in &args.directory {
53 sources.push(Source {
54 type_: "directory".to_string(),
55 path: Some(path.clone()),
56 content: None,
57 });
58 }
59
60 if let Some(ref stdin_type) = args.stdin {
61 let mut content = String::new();
62 std::io::stdin().read_to_string(&mut content)?;
63 sources.push(Source {
64 type_: stdin_type.clone(),
65 path: None,
66 content: Some(content),
67 });
68 }
69
70 let priority = match &args.priority {
71 Some(value) => match parse_priority_value(value) {
72 Ok(priority) => Some(priority),
73 Err(err) => {
74 eprintln!("Error: {}", err);
75 return Ok(1);
76 }
77 },
78 None => None,
79 };
80
81 let has_source = !sources.is_empty();
82 let has_description = args.description.is_some();
83 let has_title = args.title.is_some();
84
85 if !has_source && !has_description && !has_title {
86 eprintln!("Error: At least one of --description, --title, or a source is required");
87 eprintln!("Use --diff, --file, --text, --directory, --stdin, --description, or --title");
88 return Ok(1);
89 }
90
91 let metadata = match &args.metadata {
92 Some(json_str) => match serde_json::from_str::<serde_json::Value>(json_str) {
93 Ok(v) => {
94 if !v.is_object() {
95 eprintln!("Error: --metadata must be a JSON object");
96 return Ok(1);
97 }
98 v
99 }
100 Err(e) => {
101 eprintln!("Error: Invalid JSON for metadata: {}", e);
102 return Ok(1);
103 }
104 },
105 None => serde_json::Value::Object(serde_json::Map::new()),
106 };
107
108 let blocked_by: Vec<String> = match &args.blocked_by {
109 Some(ids) => ids
110 .split(',')
111 .map(|s| s.trim().to_string())
112 .filter(|s| !s.is_empty())
113 .collect(),
114 None => Vec::new(),
115 };
116
117 let item = queue.push(
118 sources,
119 args.title.clone(),
120 args.description.clone(),
121 priority,
122 metadata,
123 blocked_by,
124 )?;
125
126 if args.json {
127 let item = queue.item_with_computed_status(item);
128 let json = serde_json::to_string_pretty(&item.to_json_value())?;
129 println!("{}", json);
130 } else {
131 println!("{}", item.id);
132 eprintln!(
133 "Added item {} with {} source(s)",
134 item.id,
135 item.sources.len()
136 );
137 }
138
139 Ok(0)
140}