Skip to main content

sift_queue/cli/commands/
add.rs

1use 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
22/// Execute the `sq add` command.
23pub 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}