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