1use anyhow::Result;
2use std::path::Path;
3
4use crate::db;
5
6pub struct Opts<'a> {
7 pub title: Option<&'a str>,
8 pub priority: i32,
9 pub task_type: &'a str,
10 pub desc: Option<&'a str>,
11 pub parent: Option<&'a str>,
12 pub labels: Option<&'a str>,
13 pub json: bool,
14}
15
16pub fn run(root: &Path, opts: Opts) -> Result<()> {
17 let title = opts
18 .title
19 .ok_or_else(|| anyhow::anyhow!("title required"))?;
20 let desc = opts.desc.unwrap_or("");
21 let ts = db::now_utc();
22
23 let conn = db::open(root)?;
24
25 let id = match opts.parent {
26 Some(pid) => {
27 let count: i64 =
28 conn.query_row("SELECT COUNT(*) FROM tasks WHERE parent = ?1", [pid], |r| {
29 r.get(0)
30 })?;
31 format!("{pid}.{}", count + 1)
32 }
33 None => db::gen_id(),
34 };
35
36 conn.execute(
37 "INSERT INTO tasks (id, title, description, type, priority, status, parent, created, updated)
38 VALUES (?1, ?2, ?3, ?4, ?5, 'open', ?6, ?7, ?8)",
39 rusqlite::params![
40 id,
41 title,
42 desc,
43 opts.task_type,
44 opts.priority,
45 opts.parent.unwrap_or(""),
46 ts,
47 ts
48 ],
49 )?;
50
51 if let Some(label_str) = opts.labels {
52 for lbl in label_str.split(',') {
53 let lbl = lbl.trim();
54 if !lbl.is_empty() {
55 conn.execute(
56 "INSERT OR IGNORE INTO labels (task_id, label) VALUES (?1, ?2)",
57 [&id, lbl],
58 )?;
59 }
60 }
61 }
62
63 if opts.json {
64 let task = db::Task {
65 id: id.clone(),
66 title: title.to_string(),
67 description: desc.to_string(),
68 task_type: opts.task_type.to_string(),
69 priority: opts.priority,
70 status: "open".to_string(),
71 parent: opts.parent.unwrap_or("").to_string(),
72 created: ts.clone(),
73 updated: ts,
74 };
75 println!("{}", serde_json::to_string(&task)?);
76 } else {
77 let c = crate::color::stdout_theme();
78 println!("{}created{} {id}: {title}", c.green, c.reset);
79 }
80
81 Ok(())
82}