aboutsummaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
authorKevin Hoerr <kjhoerr@submelon.dev>2026-04-16 11:37:21 -0400
committerKevin Hoerr <kjhoerr@submelon.dev>2026-04-16 11:37:21 -0400
commit615d44041d35ee1a08ddc66423f67a9d48f53ae9 (patch)
tree9720d6a192e0bae9c7393b8b99ff8ae401ec7f72 /src/main.rs
parent51623bc995c29937bb0a13d5b71ff908be8d1a1c (diff)
downloadequity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.tar.gz
equity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.tar.bz2
equity-tracker-615d44041d35ee1a08ddc66423f67a9d48f53ae9.zip
Switch to distrobox as base for development
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs188
1 files changed, 188 insertions, 0 deletions
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<B: Backend>(term: &mut Terminal<B>, 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::<OverflowError>()
+ .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::<OverflowError>()
+ .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,
+ },
+ );
+ }
+}