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