1use crate::{
2 model::{due_date::Due, item::Item, Model},
3 storage::{
4 config_manager::{Auth, ConfigManager},
5 model_manager::ModelManager,
6 },
7 sync::{client::Client, Request, ResourceType},
8};
9use anyhow::{anyhow, Result};
10use chrono::{Local, NaiveDateTime};
11use clap::{Parser, Subcommand};
12use std::io::{self, Write};
13
14#[derive(Parser, Clone)]
15#[command(author)]
16pub struct Args {
17 #[command(subcommand)]
18 pub command: Option<Command>,
19
20 #[arg(long = "sync-url-override", hide = true)]
22 pub sync_url_override: Option<String>,
23
24 #[arg(long = "local-dir-override", hide = true)]
26 pub local_dir_override: Option<String>,
27
28 #[arg(long = "date-time-override", hide = true)]
30 pub datetime_override: Option<NaiveDateTime>,
31}
32
33#[derive(Subcommand, Clone)]
34pub enum Command {
35 #[command(name = "add")]
37 AddTodo {
38 todo: String,
40
41 #[arg(long, short)]
43 due: Option<String>,
44
45 #[arg(long = "no-sync", short)]
47 no_sync: bool,
48 },
49
50 #[command(name = "complete")]
52 CompleteTodo {
53 number: usize,
55
56 #[arg(long = "no-sync", short)]
58 no_sync: bool,
59 },
60
61 #[command(name = "list")]
63 ListInbox,
64
65 #[command(name = "set-token")]
67 SetApiToken {
68 token: String,
70 },
71
72 #[command()]
74 Sync {
75 #[arg(long, short)]
77 incremental: bool,
78 },
79}
80
81pub fn complete_item(number: usize, model: &mut Model) -> Result<()> {
85 let inbox_items = model.get_inbox_items(true);
87 let num_items = inbox_items.len();
88
89 let item = inbox_items.get(number - 1).ok_or_else(|| {
90 anyhow!(
91 "'{number}' is outside of the valid range. Pass a number between 1 and {num_items}.",
92 )
93 })?;
94 let content = item.content.clone();
95
96 model.mark_item(&item.id.clone(), true);
97 println!("'{content}' marked complete.");
98
99 Ok(())
100}
101
102pub async fn handle_command(
106 command: &Command,
107 args: Args,
108 model_manager: ModelManager<'_>,
109 client: Result<Client>,
110 config_manager: ConfigManager<'_>,
111) -> Result<()> {
112 match command {
113 Command::AddTodo { todo, no_sync, due } => {
114 let today = args
116 .datetime_override
117 .unwrap_or(Local::now().naive_local())
118 .date();
119 let due_date = due.as_ref().and_then(|due| {
120 Due::parse_from_str(due, today).and_then(|(date, range)| {
121 if range == (0..due.len()) {
123 Some(date)
124 } else {
125 None
126 }
127 })
128 });
129
130 let mut model = model_manager.read_model()?;
131 model.add_item_to_inbox(todo, due_date);
132 println!("'{todo}' added to inbox.");
133 if !no_sync {
134 sync(&mut model, &client?, true).await?;
135 }
136 model_manager.write_model(&model)?;
137 }
138
139 Command::CompleteTodo { number, no_sync } => {
140 let mut model = model_manager.read_model()?;
141 complete_item(*number, &mut model)?;
142 if !no_sync {
143 sync(&mut model, &client?, true).await?;
144 }
145 model_manager.write_model(&model)?;
146 }
147
148 Command::ListInbox => {
149 let model = model_manager.read_model()?;
150 let inbox_items = model.get_inbox_items(true);
151
152 if inbox_items.is_empty() {
153 println!("Your inbox is empty.");
154 } else {
155 println!("Inbox: ");
156 for (index, Item { content, .. }) in inbox_items.iter().enumerate() {
157 println!("[{}] {content}", index + 1);
158 }
159 }
160 }
161
162 Command::SetApiToken { token } => {
163 config_manager.write_auth_config(&Auth {
164 api_token: token.clone(),
165 })?;
166 println!("Stored API token.");
167 }
168
169 Command::Sync { incremental } => {
170 let mut model = model_manager.read_model()?;
171 sync(&mut model, &client?, *incremental).await?;
172 model_manager.write_model(&model)?;
173 }
174 };
175
176 Ok(())
177}
178
179pub async fn sync(model: &mut Model, client: &Client, incremental: bool) -> Result<()> {
184 let sync_token = if incremental {
185 model.sync_token.clone()
186 } else {
187 "*".to_string()
188 };
189
190 let request = Request {
191 sync_token,
192 resource_types: ResourceType::all(),
193 commands: model.commands.clone(),
194 };
195
196 print!("Syncing... ");
197 io::stdout().flush()?;
198
199 let response = client.make_request(&request).await?;
200 println!("Done.");
201
202 model.update(response);
204
205 Ok(())
206}