clock_timer/
lib.rs

1#[cfg(target_arch = "wasm32")]
2pub mod wasm;
3
4/// Module for countdown timer functionalities.
5
6pub mod timer {
7    use std::{io::Write, thread, time::Duration};
8
9    pub trait TimerTrait {
10        fn new(hours: u32, minutes: u32, seconds: u32) -> Result<TimerStruct, &'static str>;
11        fn start_timer<W: Write>(&self, writer: &mut W);
12    }
13
14    /// Represents a countdown timer.
15    #[derive(Clone, Copy, Debug)]
16    pub struct TimerStruct {
17        /// The total duration of the timer in seconds.
18        pub duration: u32,
19        /// The initial hours component of the timer.
20        pub hours: u32,
21        /// The initial minutes component of the timer.
22        pub minutes: u32,
23        /// The initial seconds component of the timer.
24        pub seconds: u32,
25    }
26
27    impl TimerTrait for TimerStruct {
28        /// Creates a new `TimerStruct` instance.
29        ///
30        /// # Arguments
31        ///
32        /// * `hours` - The hours component of the timer duration.
33        /// * `minutes` - The minutes component of the timer duration.
34        /// * `seconds` - The seconds component of the timer duration.
35        ///
36        /// # Returns
37        ///
38        /// * `Ok(TimerStruct)` if the total duration calculated from `hours`, `minutes`,
39        ///   and `seconds` is greater than 0.
40        /// * `Err("Duration need to be 1 or more seconds.")` if the total duration is 0.
41        ///
42        /// # Examples
43        ///
44        /// ```
45        /// use your_crate_name::TimerStruct; // Replace your_crate_name with your actual crate name
46        ///
47        /// let timer = TimerStruct::new(0, 1, 30).expect("Failed to create timer"); // 1 minute 30 seconds
48        /// let invalid_timer = TimerStruct::new(0, 0, 0); // This will return an Err
49        /// ```
50        fn new(hours: u32, minutes: u32, seconds: u32) -> Result<TimerStruct, &'static str> {
51            let duration = (hours * 3600) + (minutes * 60) + seconds;
52
53            if duration == 0 {
54                return Err("Duration need to be 1 or more seconds.");
55            }
56
57            Ok(TimerStruct {
58                duration,
59                hours,
60                minutes,
61                seconds,
62            })
63        }
64
65        /// Starts the countdown timer.
66        ///
67        /// The timer will print the remaining time to the provided writer every second,
68        /// overwriting the previous line. When the timer reaches 0, it prints the final
69        /// `0:0:0` with a newline and stops.
70        ///
71        /// # Arguments
72        ///
73        /// * `writer` - A mutable reference to any type that implements the `std::io::Write`
74        ///              trait (e.g., `&mut std::io::Stdout`).
75        ///
76        /// # Examples
77        ///
78        /// ```
79        /// use clock-timer::TimerStruct; // Replace your_crate_name with your actual crate name
80        /// use std::io::{self, stdout};
81        ///
82        /// let timer = TimerStruct::new(0, 0, 5).unwrap(); // 5-second timer
83        /// let mut writer = stdout();
84        /// timer.start_timer(&mut writer);
85        /// println!("Timer finished!");
86        /// ```
87        fn start_timer<W: Write>(&self, writer: &mut W) {
88            let mut current_duration = self.duration;
89            let one_second = Duration::from_secs(1);
90
91            loop {
92                // Calculate display components from the current total duration
93                let display_hours = current_duration / 3600;
94                let remaining_seconds_after_hours = current_duration % 3600;
95                let display_minutes = remaining_seconds_after_hours / 60;
96                let display_seconds = remaining_seconds_after_hours % 60;
97
98                let time_display_string =
99                    format!("{}:{}:{}", display_hours, display_minutes, display_seconds);
100
101                if current_duration == 0 {
102                    // If duration is 0, this is the final display. Print with a newline and break.
103                    writeln!(writer, "{}", time_display_string).unwrap();
104                    break;
105                } else {
106                    // For all other durations, print with a carriage return to overwrite the line.
107                    write!(writer, "{}\r", time_display_string).unwrap();
108                    writer.flush().unwrap(); // Ensure the output is flushed immediately
109                }
110
111                thread::sleep(one_second);
112                current_duration -= 1;
113            }
114        }
115    }
116}
117
118/// Re-exports `TimerStruct` from the `timer` module for easier access.
119pub use timer::TimerStruct;
120
121/// Module for stopwatch functionalities.
122pub mod stopwatch {
123    #[cfg(not(target_arch = "wasm32"))]
124    use ctrlc;
125    use std::{
126        io::Write,
127        process,
128        sync::{
129            Arc,
130            atomic::{AtomicU32, Ordering},
131        },
132        thread,
133        time::Duration,
134    };
135
136    pub trait StopwatchTrait<T>
137    where
138        T: Fn(u32) + std::marker::Send + Copy + 'static,
139    {
140        fn new(operation_on_stop: T) -> StopwatchStruct<T>;
141        fn start_stopwatch<W: Write>(&mut self, writer: &mut W);
142    }
143
144    /// Represents the current status of the stopwatch.
145    #[derive(Clone, Debug)]
146    pub enum StopwatchStatus {
147        /// The stopwatch is currently stopped.
148        Stopped,
149        /// The stopwatch is currently running.
150        Running,
151    }
152
153    /// Represents a stopwatch that measures elapsed time.
154    ///
155    /// It takes a generic type `T` which must be a closure that accepts a `u32`
156    /// (the final `current_time` when the stopwatch stops).
157    #[derive(Debug, Clone)]
158    pub struct StopwatchStruct<T>
159    where
160        T: Fn(u32) + std::marker::Send + Copy + 'static,
161    {
162        /// The current elapsed time in seconds.
163        pub current_time: u32,
164        /// The current status of the stopwatch (Running or Stopped).
165        pub status: StopwatchStatus,
166        /// A closure that will be executed when the stopwatch is stopped.
167        /// It receives the final `current_time` as an argument.
168        pub operation_on_stop: T,
169    }
170
171    impl<T> StopwatchStruct<T>
172    where
173        T: Fn(u32) + std::marker::Send + Copy + 'static,
174    {
175        /// Creates a new `StopwatchStruct` instance.
176        ///
177        /// # Arguments
178        ///
179        /// * `operation_on_stop` - A closure that will be called when the stopwatch status
180        ///   is set to `Stopped`. It receives the total elapsed time in seconds as its argument.
181        ///
182        /// # Returns
183        ///
184        /// A new `StopwatchStruct` initialized with `current_time` at 0 and `status` as `Running`.
185        ///
186        /// # Examples
187        ///
188        /// ```
189        /// use clock-timer::stopwatch::{StopwatchStruct, StopwatchStatus}; // Replace clock-timer
190        ///
191        /// let mut stopwatch = StopwatchStruct::new(|time| {
192        ///     println!("Stopwatch stopped at {} seconds.", time);
193        /// });
194        /// ```
195        pub fn new(operation_on_stop: T) -> StopwatchStruct<T> {
196            StopwatchStruct {
197                current_time: 0,
198                status: StopwatchStatus::Running,
199                operation_on_stop,
200            }
201        }
202
203        /// Starts the stopwatch.
204        ///
205        /// The stopwatch will increment its `current_time` every second and print the elapsed time
206        /// to the provided writer, overwriting the previous line.
207        ///
208        /// The timer can be stopped in two ways:
209        /// 1.  Pressing `Ctrl+C`. This will execute the `operation_on_stop` closure and exit the process.
210        /// 2.  Programmatically by setting the `status` field to `StopwatchStatus::Stopped`. This will
211        ///     stop the loop and execute the `operation_on_stop` closure.
212        ///
213        /// # Arguments
214        ///
215        /// * `writer` - A mutable reference to any type that implements the `std::io::Write`
216        ///              trait (e.g., `&mut std::io::Stdout`).
217        ///
218        /// # Examples
219        ///
220        /// ```no_run
221        /// use your_crate_name::stopwatch::{StopwatchStruct, StopwatchStatus}; // Replace your_crate_name
222        /// use std::{io::stdout, thread, time::Duration};
223        ///
224        /// // This stopwatch will be stopped by another thread after 5 seconds.
225        /// let mut stopwatch = StopwatchStruct::new(|time| {
226        ///     println!("\nStopwatch finished at {} seconds!", time);
227        /// });
228        ///
229        /// let mut stopwatch_clone = stopwatch.clone();
230        /// thread::spawn(move || {
231        ///     thread::sleep(Duration::from_secs(5));
232        ///     stopwatch_clone.status = StopwatchStatus::Stopped;
233        /// });
234        ///
235        /// stopwatch.start_timer(&mut stdout());
236        /// println!("Stopwatch loop ended.");
237        /// ```
238        pub fn start_stopwatch<W: Write>(&mut self, writer: &mut W) {
239            // Share the current time with the Ctrl-C handler using an Arc<AtomicU32>.
240            // This is necessary because the handler has a 'static lifetime and needs
241            // access to the time, which is being mutated in the loop.
242            let shared_time = Arc::new(AtomicU32::new(self.current_time));
243            let time_for_handler = shared_time.clone();
244
245            // The operation_on_stop closure has the `Copy` trait, so we can create a
246            // copy to move into the 'static Ctrl-C handler.
247            let op_on_stop = self.operation_on_stop;
248
249            // Set the Ctrl-C handler. This closure is executed when the user presses Ctrl-C.
250            #[cfg(not(target_arch = "wasm32"))]
251            ctrlc::set_handler(move || {
252                // Load the current elapsed time from the shared atomic variable.
253                let final_time = time_for_handler.load(Ordering::SeqCst);
254                // Print a newline to avoid the shell prompt overwriting the final time.
255                println!();
256                // Execute the user-provided closure with the final time.
257                (op_on_stop)(final_time);
258                // Exit the process.
259                process::exit(0);
260            })
261            .expect("Error setting Ctrl-C handler");
262
263            loop {
264                // Check for a programmatic stop condition (e.g., set by `stop_timer`).
265                if let StopwatchStatus::Stopped = self.status {
266                    break;
267                }
268
269                let current_seconds = shared_time.load(Ordering::SeqCst);
270
271                let hours = current_seconds / 3600;
272                let minutes = (current_seconds % 3600) / 60;
273                let seconds = current_seconds % 60;
274
275                let output_format = format!("{}:{}:{}", hours, minutes, seconds);
276
277                // Write the formatted time. The carriage return `\r` moves the cursor
278                // to the beginning of the line, so the next write overwrites the current one.
279                write!(writer, "{}\r", output_format).unwrap();
280                writer.flush().unwrap();
281
282                thread::sleep(Duration::from_secs(1));
283
284                // Atomically increment the time for thread-safety.
285                shared_time.fetch_add(1, Ordering::SeqCst);
286            }
287
288            // This block is only reached on a programmatic stop. Ctrl-C exits the process directly.
289            // Update the struct's time to the final value from the shared atomic.
290            self.current_time = shared_time.load(Ordering::SeqCst);
291
292            // Print a final newline to ensure the shell prompt doesn't overwrite the last display.
293            writeln!(writer).unwrap();
294
295            // Execute the on-stop operation.
296            (self.operation_on_stop)(self.current_time);
297        }
298    }
299}