Add a TUI to the binary #17
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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 {
|
||||||
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user