Add a TUI to the binary #17

Merged
wojtek merged 2 commits from 14---add-a-tui-to-the-binary into main 2023-04-13 14:10:00 +02:00
6 changed files with 44 additions and 53 deletions
Showing only changes of commit c89fa38d89 - Show all commits

View File

@ -1,6 +1,6 @@
//! Module for interacting with the music library. //! Module for interacting with the music library.
use std::{num::ParseIntError, str::Utf8Error, fmt}; use std::{fmt, num::ParseIntError, str::Utf8Error};
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;

View File

@ -22,7 +22,7 @@ impl TrackSelection {
} }
} }
fn increment(&mut self, tracks: &Vec<Track>) { fn increment(&mut self, tracks: &[Track]) {
if let Some(result) = self.index.checked_add(1) { if let Some(result) = self.index.checked_add(1) {
if result < tracks.len() { if result < tracks.len() {
self.index = result; self.index = result;
@ -30,7 +30,7 @@ impl TrackSelection {
} }
} }
fn decrement(&mut self, _tracks: &Vec<Track>) { fn decrement(&mut self, _tracks: &[Track]) {
if let Some(result) = self.index.checked_sub(1) { if let Some(result) = self.index.checked_sub(1) {
self.index = result; self.index = result;
} }
@ -54,7 +54,7 @@ impl AlbumSelection {
} }
} }
fn increment(&mut self, albums: &Vec<Album>) { fn increment(&mut self, albums: &[Album]) {
if let Some(result) = self.index.checked_add(1) { if let Some(result) = self.index.checked_add(1) {
if result < albums.len() { if result < albums.len() {
self.index = result; self.index = result;
@ -63,7 +63,7 @@ impl AlbumSelection {
} }
} }
fn decrement(&mut self, albums: &Vec<Album>) { fn decrement(&mut self, albums: &[Album]) {
if let Some(result) = self.index.checked_sub(1) { if let Some(result) = self.index.checked_sub(1) {
self.index = result; self.index = result;
self.track = TrackSelection::initialise(&albums[self.index].tracks); self.track = TrackSelection::initialise(&albums[self.index].tracks);
@ -88,7 +88,7 @@ impl ArtistSelection {
} }
} }
fn increment(&mut self, artists: &Vec<Artist>) { fn increment(&mut self, artists: &[Artist]) {
if let Some(result) = self.index.checked_add(1) { if let Some(result) = self.index.checked_add(1) {
if result < artists.len() { if result < artists.len() {
self.index = result; self.index = result;
@ -97,7 +97,7 @@ impl ArtistSelection {
} }
} }
fn decrement(&mut self, artists: &Vec<Artist>) { fn decrement(&mut self, artists: &[Artist]) {
if let Some(result) = self.index.checked_sub(1) { if let Some(result) = self.index.checked_sub(1) {
self.index = result; self.index = result;
self.album = AlbumSelection::initialise(&artists[self.index].albums); self.album = AlbumSelection::initialise(&artists[self.index].albums);
@ -231,7 +231,7 @@ impl App {
} }
fn get_artists(&self) -> &Vec<Artist> { fn get_artists(&self) -> &Vec<Artist> {
&self.collection_manager.get_collection() self.collection_manager.get_collection()
} }
fn get_albums(&self) -> Option<&Vec<Album>> { fn get_albums(&self) -> Option<&Vec<Album>> {
@ -277,20 +277,12 @@ impl App {
} }
pub fn selected_artist(&self) -> Option<usize> { pub fn selected_artist(&self) -> Option<usize> {
if let Some(ref artist_selection) = self.selection.artist { self.selection.artist.as_ref().map(|s| s.index)
Some(artist_selection.index)
} else {
None
}
} }
pub fn selected_album(&self) -> Option<usize> { pub fn selected_album(&self) -> Option<usize> {
if let Some(ref artist_selection) = self.selection.artist { if let Some(ref artist_selection) = self.selection.artist {
if let Some(ref album_selection) = artist_selection.album { artist_selection.album.as_ref().map(|s| s.index)
Some(album_selection.index)
} else {
None
}
} else { } else {
None None
} }
@ -299,11 +291,7 @@ impl App {
pub fn selected_track(&self) -> Option<usize> { pub fn selected_track(&self) -> Option<usize> {
if let Some(ref artist_selection) = self.selection.artist { if let Some(ref artist_selection) = self.selection.artist {
if let Some(ref album_selection) = artist_selection.album { if let Some(ref album_selection) = artist_selection.album {
if let Some(ref track_selection) = album_selection.track { album_selection.track.as_ref().map(|s| s.index)
Some(track_selection.index)
} else {
None
}
} else { } else {
None None
} }

View File

@ -4,17 +4,17 @@ use std::sync::mpsc;
#[derive(Debug)] #[derive(Debug)]
pub enum EventError { pub enum EventError {
SendError(Event), Send(Event),
RecvError, Recv,
IoError(std::io::Error), Io(std::io::Error),
} }
impl fmt::Display for EventError { impl fmt::Display for EventError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Self::SendError(ref e) => write!(f, "failed to send event: {e:?}"), Self::Send(ref e) => write!(f, "failed to send event: {e:?}"),
Self::RecvError => write!(f, "receive event call failed"), Self::Recv => write!(f, "receive event call failed"),
Self::IoError(ref e) => { Self::Io(ref e) => {
write!(f, "an I/O error was triggered during event handling: {e}") write!(f, "an I/O error was triggered during event handling: {e}")
} }
} }
@ -23,13 +23,13 @@ impl fmt::Display for EventError {
impl From<mpsc::SendError<Event>> for EventError { impl From<mpsc::SendError<Event>> for EventError {
fn from(err: mpsc::SendError<Event>) -> EventError { fn from(err: mpsc::SendError<Event>) -> EventError {
EventError::SendError(err.0) EventError::Send(err.0)
} }
} }
impl From<mpsc::RecvError> for EventError { impl From<mpsc::RecvError> for EventError {
fn from(_: mpsc::RecvError) -> EventError { fn from(_: mpsc::RecvError) -> EventError {
EventError::RecvError EventError::Recv
} }
} }
@ -125,14 +125,14 @@ mod tests {
#[test] #[test]
fn errors() { fn errors() {
let send_err = EventError::SendError(Event::Key(KeyEvent { let send_err = EventError::Send(Event::Key(KeyEvent {
code: KeyCode::Up, code: KeyCode::Up,
modifiers: KeyModifiers::empty(), modifiers: KeyModifiers::empty(),
kind: KeyEventKind::Press, kind: KeyEventKind::Press,
state: KeyEventState::NONE, state: KeyEventState::NONE,
})); }));
let recv_err = EventError::RecvError; let recv_err = EventError::Recv;
let io_err = EventError::IoError(io::Error::new(io::ErrorKind::Interrupted, "interrupted")); let io_err = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
assert!(!send_err.to_string().is_empty()); assert!(!send_err.to_string().is_empty());
assert!(!recv_err.to_string().is_empty()); assert!(!recv_err.to_string().is_empty());

View File

@ -3,7 +3,10 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
use super::{app::App, event::{Event, EventReceiver, EventError}}; use super::{
app::App,
event::{Event, EventError, EventReceiver},
};
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait EventHandler { pub trait EventHandler {

View File

@ -4,7 +4,7 @@ use std::thread;
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
use super::event::{EventSender, EventError, Event}; use super::event::{Event, EventError, EventSender};
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait EventListener { pub trait EventListener {
@ -40,7 +40,7 @@ impl EventListener for TuiEventListener {
return err; return err;
} }
} }
Err(err) => return EventError::IoError(err), Err(err) => return EventError::Io(err),
}; };
} }
}) })

View File

@ -19,27 +19,27 @@ use self::ui::Ui;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
CollectionError(String), Collection(String),
IoError(String), Io(String),
EventError(String), Event(String),
ListenerPanic, ListenerPanic,
} }
impl From<collection::Error> for Error { impl From<collection::Error> for Error {
fn from(err: collection::Error) -> Error { fn from(err: collection::Error) -> Error {
Error::CollectionError(err.to_string()) Error::Collection(err.to_string())
} }
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { fn from(err: io::Error) -> Error {
Error::IoError(err.to_string()) Error::Io(err.to_string())
} }
} }
impl From<EventError> for Error { impl From<EventError> for Error {
fn from(err: EventError) -> Error { fn from(err: EventError) -> Error {
Error::EventError(err.to_string()) Error::Event(err.to_string())
} }
} }
@ -159,7 +159,7 @@ impl<B: Backend> Tui<B> {
mod tests { mod tests {
use std::{io, thread}; use std::{io, thread};
use musichoard::collection::{Collection, self}; use musichoard::collection::{self, Collection};
use ratatui::{backend::TestBackend, Terminal}; use ratatui::{backend::TestBackend, Terminal};
use crate::tests::{MockCollectionManager, COLLECTION}; use crate::tests::{MockCollectionManager, COLLECTION};
@ -192,7 +192,7 @@ mod tests {
listener.expect_spawn().return_once(|| { listener.expect_spawn().return_once(|| {
thread::spawn(|| { thread::spawn(|| {
thread::park(); thread::park();
return EventError::IoError(io::Error::new(io::ErrorKind::Interrupted, "unparked")); return EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "unparked"));
}) })
}); });
listener listener
@ -231,13 +231,13 @@ mod tests {
let mut handler = MockEventHandler::new(); let mut handler = MockEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::RecvError)); .return_once(|_| Err(EventError::Recv));
let result = Tui::main(terminal, app, ui, handler, listener); let result = Tui::main(terminal, app, ui, handler, listener);
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
Error::EventError(EventError::RecvError.to_string()) Error::Event(EventError::Recv.to_string())
); );
} }
@ -247,7 +247,7 @@ mod tests {
let app = app(COLLECTION.to_owned()); let app = app(COLLECTION.to_owned());
let ui = Ui::new(); let ui = Ui::new();
let error = EventError::IoError(io::Error::new(io::ErrorKind::Interrupted, "error")); let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error"));
let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| error); let listener_handle: thread::JoinHandle<EventError> = thread::spawn(|| error);
while !listener_handle.is_finished() {} while !listener_handle.is_finished() {}
@ -257,13 +257,13 @@ mod tests {
let mut handler = MockEventHandler::new(); let mut handler = MockEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::RecvError)); .return_once(|_| Err(EventError::Recv));
let result = Tui::main(terminal, app, ui, handler, listener); let result = Tui::main(terminal, app, ui, handler, listener);
assert!(result.is_err()); assert!(result.is_err());
let error = EventError::IoError(io::Error::new(io::ErrorKind::Interrupted, "error")); let error = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "error"));
assert_eq!(result.unwrap_err(), Error::EventError(error.to_string())); assert_eq!(result.unwrap_err(), Error::Event(error.to_string()));
} }
#[test] #[test]
@ -281,7 +281,7 @@ mod tests {
let mut handler = MockEventHandler::new(); let mut handler = MockEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|_| Err(EventError::RecvError)); .return_once(|_| Err(EventError::Recv));
let result = Tui::main(terminal, app, ui, handler, listener); let result = Tui::main(terminal, app, ui, handler, listener);
assert!(result.is_err()); assert!(result.is_err());
@ -292,7 +292,7 @@ mod tests {
fn errors() { fn errors() {
let collection_err: Error = collection::Error::DatabaseError(String::from("")).into(); let collection_err: Error = collection::Error::DatabaseError(String::from("")).into();
let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into(); let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into();
let event_err: Error = EventError::RecvError.into(); let event_err: Error = EventError::Recv.into();
let listener_err = Error::ListenerPanic; let listener_err = Error::ListenerPanic;
assert!(!format!("{:?}", collection_err).is_empty()); assert!(!format!("{:?}", collection_err).is_empty());