update.rs

 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}