1use anyhow::{bail, Result};
2use std::path::Path;
3
4use crate::cli::DepAction;
5use crate::db;
6
7pub fn run(root: &Path, action: &DepAction, json: bool) -> Result<()> {
8 let conn = db::open(root)?;
9
10 match action {
11 DepAction::Add { child, parent } => {
12 if !db::task_exists(&conn, child)? {
13 bail!("task '{child}' not found");
14 }
15 if !db::task_exists(&conn, parent)? {
16 bail!("task '{parent}' not found");
17 }
18 if db::would_cycle(&conn, parent, child)? {
19 bail!("adding dependency would create a cycle: {child} → {parent} → … → {child}");
20 }
21 conn.execute(
22 "INSERT OR IGNORE INTO blockers (task_id, blocker_id) VALUES (?1, ?2)",
23 [child, parent],
24 )?;
25 conn.execute(
26 "UPDATE tasks SET updated = ?1 WHERE id = ?2",
27 rusqlite::params![db::now_utc(), child],
28 )?;
29 if json {
30 println!("{}", serde_json::json!({"child": child, "blocker": parent}));
31 } else {
32 let c = crate::color::stdout_theme();
33 println!(
34 "{}{child}{} blocked by {}{parent}{}",
35 c.green, c.reset, c.yellow, c.reset
36 );
37 }
38 }
39 DepAction::Rm { child, parent } => {
40 conn.execute(
41 "DELETE FROM blockers WHERE task_id = ?1 AND blocker_id = ?2",
42 [child, parent],
43 )?;
44 conn.execute(
45 "UPDATE tasks SET updated = ?1 WHERE id = ?2",
46 rusqlite::params![db::now_utc(), child],
47 )?;
48 if !json {
49 let c = crate::color::stdout_theme();
50 println!(
51 "{}{child}{} no longer blocked by {}{parent}{}",
52 c.green, c.reset, c.yellow, c.reset
53 );
54 }
55 }
56 DepAction::Tree { id } => {
57 println!("{id}");
58 let mut stmt = conn.prepare("SELECT id FROM tasks WHERE parent = ?1 ORDER BY id")?;
59 let children: Vec<String> = stmt
60 .query_map([id], |r| r.get(0))?
61 .collect::<rusqlite::Result<_>>()?;
62 for child in &children {
63 println!(" {child}");
64 }
65 }
66 }
67
68 Ok(())
69}