1use anyhow::{anyhow, Result};
2use std::path::Path;
3
4use crate::db;
5use crate::editor;
6
7pub struct Opts<'a> {
8 pub status: Option<&'a str>,
9 pub priority: Option<db::Priority>,
10 pub effort: Option<db::Effort>,
11 pub title: Option<&'a str>,
12 pub desc: Option<&'a str>,
13 pub json: bool,
14}
15
16pub fn run(root: &Path, id: &str, opts: Opts) -> Result<()> {
17 let store = db::open(root)?;
18 let task_id = db::resolve_task_id(&store, id, false)?;
19 let ts = db::now_utc();
20
21 let parsed_status = opts.status.map(db::parse_status).transpose()?;
22
23 // If no fields were supplied, open the editor so the user can revise the
24 // task's title and description interactively.
25 let editor_title;
26 let editor_desc;
27 let (title_override, desc_override) = if opts.status.is_none()
28 && opts.priority.is_none()
29 && opts.effort.is_none()
30 && opts.title.is_none()
31 && opts.desc.is_none()
32 {
33 let interactive = std::env::var("TD_FORCE_EDITOR").is_ok()
34 || std::io::IsTerminal::is_terminal(&std::io::stdin());
35 if interactive {
36 // Load the current task so we can pre-populate the template.
37 let task = store
38 .get_task(&task_id, false)?
39 .ok_or_else(|| anyhow!("task not found"))?;
40
41 let template = format!(
42 "{}\n\
43 \n\
44 {}\n\
45 \n\
46 TD: Edit the title and description above. The first line is the\n\
47 TD: title; everything after the blank line is the description.\n\
48 TD: Lines starting with 'TD: ' will be ignored. Saving an empty\n\
49 TD: message aborts the update.",
50 task.title, task.description,
51 );
52
53 let (t, d) = editor::open(&template)?;
54 editor_title = t;
55 editor_desc = d;
56 (Some(editor_title.as_str()), Some(editor_desc.as_str()))
57 } else {
58 return Err(anyhow!(
59 "nothing to update; provide at least one flag or run interactively to open an editor"
60 ));
61 }
62 } else {
63 (opts.title, opts.desc)
64 };
65
66 store.apply_and_persist(|doc| {
67 let tasks = doc.get_map("tasks");
68 let task = db::get_task_map(&tasks, &task_id)?.ok_or_else(|| anyhow!("task not found"))?;
69
70 if let Some(s) = parsed_status {
71 task.insert("status", db::status_label(s))?;
72 }
73 if let Some(p) = opts.priority {
74 task.insert("priority", db::priority_label(p))?;
75 }
76 if let Some(e) = opts.effort {
77 task.insert("effort", db::effort_label(e))?;
78 }
79 if let Some(t) = title_override {
80 task.insert("title", t)?;
81 }
82 if let Some(d) = desc_override {
83 task.insert("description", d)?;
84 }
85 task.insert("updated_at", ts.clone())?;
86 Ok(())
87 })?;
88
89 if opts.json {
90 let task = store
91 .get_task(&task_id, false)?
92 .ok_or_else(|| anyhow!("task not found"))?;
93 println!("{}", serde_json::to_string(&task)?);
94 } else {
95 let c = crate::color::stdout_theme();
96 println!("{}updated{} {}", c.green, c.reset, task_id);
97 }
98
99 Ok(())
100}