update.rs

  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}