create.rs

 1use anyhow::{anyhow, Result};
 2use std::path::Path;
 3
 4use crate::db;
 5use crate::editor;
 6use crate::ops;
 7
 8pub struct Opts<'a> {
 9    pub title: Option<&'a str>,
10    pub priority: db::Priority,
11    pub effort: db::Effort,
12    pub task_type: &'a str,
13    pub desc: Option<&'a str>,
14    pub parent: Option<&'a str>,
15    pub labels: Option<&'a str>,
16    pub json: bool,
17}
18
19/// Template shown in the editor when the user runs `td create` without a title.
20const TEMPLATE: &str = "
21TD: Please provide the task title on the first line, and an optional
22TD: description below. Lines starting with 'TD: ' will be ignored.
23TD: An empty message aborts.";
24
25pub fn run(root: &Path, opts: Opts) -> Result<()> {
26    // If neither title nor description were supplied, try to open an editor.
27    // We treat the presence of TD_FORCE_EDITOR as an explicit interactive
28    // signal (used by tests); otherwise we check whether stdin is a tty.
29    let (title_owned, desc_owned);
30    let (title, desc) = if opts.title.is_none() && opts.desc.is_none() {
31        let interactive = std::env::var("TD_FORCE_EDITOR").is_ok()
32            || std::io::IsTerminal::is_terminal(&std::io::stdin());
33        if interactive {
34            let (t, d) = editor::open(TEMPLATE)?;
35            title_owned = t;
36            desc_owned = d;
37            (title_owned.as_str(), desc_owned.as_str())
38        } else {
39            return Err(anyhow!(
40                "title required; provide it as a positional argument or run interactively to open an editor"
41            ));
42        }
43    } else {
44        (
45            opts.title.ok_or_else(|| anyhow!("title required"))?,
46            opts.desc.unwrap_or(""),
47        )
48    };
49
50    let store = db::open(root)?;
51
52    let parent = if let Some(raw) = opts.parent {
53        Some(db::resolve_task_id(&store, raw, false)?)
54    } else {
55        None
56    };
57
58    let labels = opts
59        .labels
60        .map(|s| {
61            s.split(',')
62                .map(str::trim)
63                .filter(|l| !l.is_empty())
64                .map(String::from)
65                .collect()
66        })
67        .unwrap_or_default();
68
69    let task = ops::create_task(
70        &store,
71        ops::CreateOpts {
72            title: title.to_owned(),
73            description: desc.to_owned(),
74            task_type: opts.task_type.to_owned(),
75            priority: opts.priority,
76            effort: opts.effort,
77            parent,
78            labels,
79        },
80    )?;
81
82    if opts.json {
83        println!("{}", serde_json::to_string(&task)?);
84    } else {
85        let c = crate::color::stdout_theme();
86        println!("{}created{} {}: {}", c.green, c.reset, task.id, task.title);
87    }
88
89    Ok(())
90}