From 615d44041d35ee1a08ddc66423f67a9d48f53ae9 Mon Sep 17 00:00:00 2001 From: Kevin Hoerr Date: Thu, 16 Apr 2026 11:37:21 -0400 Subject: Switch to distrobox as base for development --- src/main.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bb63c86 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,188 @@ +mod app; +mod ui; + +use app::App; +use miette::{IntoDiagnostic, Result, miette}; +use ratatui::{Terminal, backend::Backend}; + +use ratatui::crossterm::{ + event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind, + }, + execute, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, +}; +use ratatui::prelude::CrosstermBackend; + +#[cfg(not(tarpaulin_include))] +fn main() -> Result<()> { + enable_raw_mode().into_diagnostic()?; + let mut stdout = std::io::stdout(); + + execute!(stdout, EnterAlternateScreen, EnableMouseCapture).into_diagnostic()?; + + let backend = CrosstermBackend::new(stdout); + let mut term = Terminal::new(backend).into_diagnostic()?; + + let mut app = App::default(); + app.init_context()?; + + // could be error, so don't unwrap; let term restore first + let run = run_app(&mut term, &mut app); + + // restore terminal + disable_raw_mode().into_diagnostic()?; + execute!( + term.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + ) + .into_diagnostic()?; + term.show_cursor().into_diagnostic()?; + run?; + Ok(()) +} + +#[cfg(not(tarpaulin_include))] +fn run_app(term: &mut Terminal, app: &mut App) -> Result<()> { + while !app.get_exit_status() { + term.draw(|frame| ui::draw(frame, app)) + .map_err(|_| miette!("Unrecoverable error occurred"))?; + + match event::read().into_diagnostic()? { + // it's important to check that the event is a key press event as + // crossterm also emits key release and repeat events on Windows. + Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + // if error occurred during input processing, set error in the app state + match handle_key_event(app, key_event) { + Err(e) => app.set_error(e)?, + _ => {} + } + } + _ => {} + }; + } + Ok(()) +} + +/// Process user input and run associated app functions +fn handle_key_event(app: &mut App, key_event: KeyEvent) -> Result<()> { + match key_event.code { + KeyCode::Char('q') => app.exit()?, + KeyCode::Left => app.decrement_counter()?, + KeyCode::Right => app.increment_counter()?, + _ => {} + }; + // any new key event, we should clear the existing error + app.clear_error()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + + use super::*; + use app::OverflowError; + + #[test] + fn handle_key_event_inc_dec() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let res = handle_key_event(&mut app, KeyCode::Right.into()); + assert!(res.is_ok()); + assert_eq!(app.display_counter(), "1"); + + let res = handle_key_event(&mut app, KeyCode::Left.into()); + assert!(res.is_ok()); + assert_eq!(app.display_counter(), "0"); + } + + #[test] + fn handle_key_event_inc_n() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let n = 8; + + for i in 1..=n { + let res = handle_key_event(&mut app, KeyCode::Right.into()); + assert!(res.is_ok()); + assert_eq!(app.display_counter(), i.to_string()); + } + assert_eq!(app.display_counter(), n.to_string()); + } + + #[test] + fn handle_key_event_inc_n_dec() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let n = 8; + + for i in 1..=n { + let res = handle_key_event(&mut app, KeyCode::Right.into()); + assert!(res.is_ok()); + assert_eq!(app.display_counter(), i.to_string()); + } + assert_eq!(app.display_counter(), n.to_string()); + + let res = handle_key_event(&mut app, KeyCode::Left.into()); + assert!(res.is_ok()); + assert_eq!(app.display_counter(), (n - 1).to_string()); + } + + #[test] + fn handle_key_event_exit() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let res = handle_key_event(&mut app, KeyCode::Char('q').into()); + assert!(res.is_ok()); + assert!(app.get_exit_status()); + } + + #[test] + fn handle_key_event_dec_error() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + let res = handle_key_event(&mut app, KeyCode::Left.into()); + + assert!(res.is_err()); + let res_err = res.unwrap_err(); + let casted_val = res_err + .downcast_ref::() + .expect("something bad occurred"); + + let expected_err_msg: String = "Cannot count below 0".into(); + assert_eq!(casted_val._msg, expected_err_msg); + assert_eq!( + *casted_val, + OverflowError { + _msg: expected_err_msg, + }, + ); + } + + #[test] + fn handle_key_event_inc_error() { + let mut app = App::default(); + assert!(app.init_context().is_ok()); + // no pub set_counter exposed, so we manually set this + for _ in 0..u8::MAX { + assert!(app.increment_counter().is_ok()); + } + let res = handle_key_event(&mut app, KeyCode::Right.into()); + + assert!(res.is_err()); + let res_err = res.unwrap_err(); + let casted_val = res_err + .downcast_ref::() + .expect("something bad occurred"); + + let expected_err_msg: String = "Cannot count above u8::MAX".into(); + assert_eq!(casted_val._msg, expected_err_msg); + assert_eq!( + *casted_val, + OverflowError { + _msg: expected_err_msg, + }, + ); + } +} -- cgit v1.3