1use crate::storage::Storage;
2use crate::timer::TimerManager;
3use anyhow::Result;
4use clap::{Parser, Subcommand};
5use std::time::Duration;
6
7#[derive(Parser)]
9#[command(name = "work-tuimer")]
10#[command(about = "Automatic time tracking with CLI commands and TUI", long_about = None)]
11#[command(version)]
12pub struct Cli {
13 #[command(subcommand)]
14 pub command: Commands,
15}
16
17#[derive(Subcommand)]
19pub enum Commands {
20 Session {
22 #[command(subcommand)]
23 command: SessionCommands,
24 },
25}
26
27#[derive(Subcommand)]
29pub enum SessionCommands {
30 Start {
32 task: String,
34
35 #[arg(short, long)]
37 description: Option<String>,
38 },
39
40 Stop,
42
43 Pause,
45
46 Resume,
48
49 Status,
51}
52
53pub fn handle_command(cmd: Commands, storage: Storage) -> Result<()> {
55 match cmd {
56 Commands::Session { command } => match command {
57 SessionCommands::Start { task, description } => {
58 handle_start(task, description, storage)
59 }
60 SessionCommands::Stop => handle_stop(storage),
61 SessionCommands::Pause => handle_pause(storage),
62 SessionCommands::Resume => handle_resume(storage),
63 SessionCommands::Status => handle_status(storage),
64 },
65 }
66}
67
68fn handle_start(task: String, description: Option<String>, storage: Storage) -> Result<()> {
70 let timer_manager = TimerManager::new(storage);
71
72 let task = task.trim().to_string();
74 if task.is_empty() {
75 return Err(anyhow::anyhow!("Task name cannot be empty"));
76 }
77
78 let timer = timer_manager.start(task, description, None, None)?;
79
80 let start_time = format_time(timer.start_time);
81 println!("✓ Session started");
82 println!(" Task: {}", timer.task_name);
83 if let Some(desc) = &timer.description {
84 println!(" Description: {}", desc);
85 }
86 println!(" Started at: {}", start_time);
87
88 Ok(())
89}
90
91fn handle_stop(storage: Storage) -> Result<()> {
93 let timer_manager = TimerManager::new(storage);
94
95 let timer = timer_manager
97 .status()?
98 .ok_or_else(|| anyhow::anyhow!("No session is running"))?;
99
100 let elapsed = timer_manager.get_elapsed_duration(&timer);
101 let formatted_duration = format_duration(elapsed);
102
103 let start_time = format_time(timer.start_time);
104
105 let record = timer_manager.stop()?;
107
108 let end_time = format!("{:02}:{:02}:{:02}", record.end.hour, record.end.minute, 0);
110
111 println!("✓ Session stopped");
112 println!(" Task: {}", timer.task_name);
113 println!(" Duration: {}", formatted_duration);
114 println!(" Started at: {}", start_time);
115 println!(" Ended at: {}", end_time);
116
117 Ok(())
118}
119
120fn handle_pause(storage: Storage) -> Result<()> {
122 let timer_manager = TimerManager::new(storage);
123
124 let timer = timer_manager
125 .status()?
126 .ok_or_else(|| anyhow::anyhow!("No session is running"))?;
127
128 let _paused_timer = timer_manager.pause()?;
129 let elapsed = timer_manager.get_elapsed_duration(&timer);
130 let formatted_duration = format_duration(elapsed);
131
132 println!("⏸ Session paused");
133 println!(" Task: {}", timer.task_name);
134 println!(" Elapsed: {}", formatted_duration);
135
136 Ok(())
137}
138
139fn handle_resume(storage: Storage) -> Result<()> {
141 let timer_manager = TimerManager::new(storage);
142
143 let timer = timer_manager
144 .status()?
145 .ok_or_else(|| anyhow::anyhow!("No session is running"))?;
146
147 let _resumed_timer = timer_manager.resume()?;
148 let elapsed = timer_manager.get_elapsed_duration(&timer);
149 let formatted_duration = format_duration(elapsed);
150
151 println!("▶ Session resumed");
152 println!(" Task: {}", timer.task_name);
153 println!(" Total elapsed (before pause): {}", formatted_duration);
154
155 Ok(())
156}
157
158fn handle_status(storage: Storage) -> Result<()> {
160 let timer_manager = TimerManager::new(storage);
161
162 match timer_manager.status()? {
163 Some(timer) => {
164 let elapsed = timer_manager.get_elapsed_duration(&timer);
165 let formatted_duration = format_duration(elapsed);
166 let start_time = format_time(timer.start_time);
167
168 println!("⏱ Session Status");
169 println!(" Task: {}", timer.task_name);
170 println!(
171 " Status: {}",
172 match timer.status {
173 crate::timer::TimerStatus::Running => "Running",
174 crate::timer::TimerStatus::Paused => "Paused",
175 crate::timer::TimerStatus::Stopped => "Stopped",
176 }
177 );
178 println!(" Elapsed: {}", formatted_duration);
179 println!(" Started at: {}", start_time);
180 if let Some(desc) = &timer.description {
181 println!(" Description: {}", desc);
182 }
183 }
184 None => {
185 println!("No session is currently running");
186 }
187 }
188
189 Ok(())
190}
191
192fn format_time(dt: time::OffsetDateTime) -> String {
194 format!("{:02}:{:02}:{:02}", dt.hour(), dt.minute(), dt.second())
195}
196
197fn format_duration(duration: Duration) -> String {
199 let total_secs = duration.as_secs();
200 let hours = total_secs / 3600;
201 let minutes = (total_secs % 3600) / 60;
202 let seconds = total_secs % 60;
203
204 if hours > 0 {
205 format!("{}h {:02}m {:02}s", hours, minutes, seconds)
206 } else {
207 format!("{}m {:02}s", minutes, seconds)
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_format_duration_hours_minutes_seconds() {
217 let duration = Duration::from_secs(3661); assert_eq!(format_duration(duration), "1h 01m 01s");
219 }
220
221 #[test]
222 fn test_format_duration_minutes_seconds() {
223 let duration = Duration::from_secs(125); assert_eq!(format_duration(duration), "2m 05s");
225 }
226
227 #[test]
228 fn test_format_duration_seconds_only() {
229 let duration = Duration::from_secs(45);
230 assert_eq!(format_duration(duration), "0m 45s");
231 }
232
233 #[test]
234 fn test_format_duration_zero() {
235 let duration = Duration::from_secs(0);
236 assert_eq!(format_duration(duration), "0m 00s");
237 }
238
239 #[test]
240 fn test_format_time() {
241 use time::macros::datetime;
242 let dt = datetime!(2025-01-15 14:30:45 UTC);
243 assert_eq!(format_time(dt), "14:30:45");
244 }
245
246 #[test]
247 fn test_cli_has_version() {
248 use clap::CommandFactory;
249 let cmd = Cli::command();
250 let version = cmd.get_version();
251 assert!(version.is_some(), "CLI should have version configured");
252 assert_eq!(version.unwrap(), env!("CARGO_PKG_VERSION"));
254 }
255}