tvrank 0.3.0

Query and sort information about movies and series
Documentation
#![warn(clippy::all)]

use derive_more::Display;
use enum_utils::FromStr;
use std::fmt;

#[derive(Debug, Display, FromStr, PartialEq, Eq, Hash, Clone, Copy)]
#[display(fmt = "{}")]
pub enum Genre {
  Action = 0,
  Adult = 1,
  Adventure = 2,
  Animation = 3,
  Biography = 4,
  Comedy = 5,
  Crime = 6,
  Documentary = 7,
  Drama = 8,
  Family = 9,
  Fantasy = 10,
  #[display(fmt = "Film-Noir")]
  #[enumeration(rename = "Film-Noir")]
  FilmNoir = 11,
  #[display(fmt = "Game-Show")]
  #[enumeration(rename = "Game-Show")]
  GameShow = 12,
  History = 13,
  Horror = 14,
  Music = 15,
  Musical = 16,
  Mystery = 17,
  News = 18,
  #[display(fmt = "Reality-TV")]
  #[enumeration(rename = "Reality-TV")]
  RealityTv = 19,
  Romance = 20,
  #[display(fmt = "Sci-Fi")]
  #[enumeration(rename = "Sci-Fi")]
  SciFi = 21,
  Short = 22,
  Sport = 23,
  #[display(fmt = "Talk-Show")]
  #[enumeration(rename = "Talk-Show")]
  TalkShow = 24,
  Thriller = 25,
  War = 26,
  Western = 27,
}

impl Genre {
  pub(crate) const fn max() -> u8 {
    Self::Western as u8
  }

  pub(crate) const unsafe fn from(value: u8) -> Self {
    std::mem::transmute(value)
  }
}

#[derive(PartialEq, Eq, Default, Clone, Copy)]
pub struct Genres(u32);

impl Genres {
  pub(crate) fn add(&mut self, genre: Genre) {
    let index = genre as u8;
    self.0 |= 1 << index;
  }

  pub fn iter(&self) -> GenresIter {
    GenresIter::new(*self)
  }

  fn get(&self, index: u8) -> Option<Genre> {
    if index > Genre::max() {
      panic!("Genre index `{}` out of range (max: {})", index, Genre::max());
    }

    if (self.0 >> index) & 1 == 1 {
      Some(unsafe { Genre::from(index) })
    } else {
      None
    }
  }
}

impl From<Genres> for u32 {
  fn from(genres: Genres) -> Self {
    genres.0
  }
}

impl From<u32> for Genres {
  fn from(values: u32) -> Self {
    Genres(values)
  }
}

impl fmt::Debug for Genres {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    <Self as fmt::Display>::fmt(self, f)
  }
}

impl fmt::Display for Genres {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    let mut first = true;

    for genre in self.iter() {
      if first {
        write!(f, "{}", genre)?;
        first = false;
      } else {
        write!(f, ", {}", genre)?;
      }
    }

    Ok(())
  }
}

pub struct GenresIter {
  genres: Genres,
  index: u8,
}

impl GenresIter {
  pub fn new(genres: Genres) -> Self {
    Self { genres, index: 0 }
  }
}

impl Iterator for GenresIter {
  type Item = Genre;

  fn next(&mut self) -> Option<Self::Item> {
    loop {
      if self.index > Genre::max() {
        return None;
      }

      if let Some(genre) = self.genres.get(self.index) {
        self.index += 1;
        return Some(genre);
      }

      self.index += 1;
    }
  }
}

#[cfg(test)]
mod tests {
  use super::Genre;
  use super::Genres;

  #[test]
  fn test_genre_value() {
    assert_eq!(Genre::Action as u8, 0);
    assert_eq!(Genre::Adult as u8, 1);
    assert_eq!(Genre::Adventure as u8, 2);
    assert_eq!(Genre::Animation as u8, 3);
    assert_eq!(Genre::Biography as u8, 4);
    assert_eq!(Genre::Comedy as u8, 5);
    assert_eq!(Genre::Crime as u8, 6);
    assert_eq!(Genre::Documentary as u8, 7);
    assert_eq!(Genre::Drama as u8, 8);
    assert_eq!(Genre::Family as u8, 9);
    assert_eq!(Genre::Fantasy as u8, 10);
    assert_eq!(Genre::FilmNoir as u8, 11);
    assert_eq!(Genre::GameShow as u8, 12);
    assert_eq!(Genre::History as u8, 13);
    assert_eq!(Genre::Horror as u8, 14);
    assert_eq!(Genre::Music as u8, 15);
    assert_eq!(Genre::Musical as u8, 16);
    assert_eq!(Genre::Mystery as u8, 17);
    assert_eq!(Genre::News as u8, 18);
    assert_eq!(Genre::RealityTv as u8, 19);
    assert_eq!(Genre::Romance as u8, 20);
    assert_eq!(Genre::SciFi as u8, 21);
    assert_eq!(Genre::Short as u8, 22);
    assert_eq!(Genre::Sport as u8, 23);
    assert_eq!(Genre::TalkShow as u8, 24);
    assert_eq!(Genre::Thriller as u8, 25);
    assert_eq!(Genre::War as u8, 26);
    assert_eq!(Genre::Western as u8, 27);
  }

  #[test]
  fn test_genre_from_u8() {
    assert_eq!(Genre::Action, unsafe { Genre::from(0) });
    assert_eq!(Genre::Adult, unsafe { Genre::from(1) });
    assert_eq!(Genre::Adventure, unsafe { Genre::from(2) });
    assert_eq!(Genre::Animation, unsafe { Genre::from(3) });
    assert_eq!(Genre::Biography, unsafe { Genre::from(4) });
    assert_eq!(Genre::Comedy, unsafe { Genre::from(5) });
    assert_eq!(Genre::Crime, unsafe { Genre::from(6) });
    assert_eq!(Genre::Documentary, unsafe { Genre::from(7) });
    assert_eq!(Genre::Drama, unsafe { Genre::from(8) });
    assert_eq!(Genre::Family, unsafe { Genre::from(9) });
    assert_eq!(Genre::Fantasy, unsafe { Genre::from(10) });
    assert_eq!(Genre::FilmNoir, unsafe { Genre::from(11) });
    assert_eq!(Genre::GameShow, unsafe { Genre::from(12) });
    assert_eq!(Genre::History, unsafe { Genre::from(13) });
    assert_eq!(Genre::Horror, unsafe { Genre::from(14) });
    assert_eq!(Genre::Music, unsafe { Genre::from(15) });
    assert_eq!(Genre::Musical, unsafe { Genre::from(16) });
    assert_eq!(Genre::Mystery, unsafe { Genre::from(17) });
    assert_eq!(Genre::News, unsafe { Genre::from(18) });
    assert_eq!(Genre::RealityTv, unsafe { Genre::from(19) });
    assert_eq!(Genre::Romance, unsafe { Genre::from(20) });
    assert_eq!(Genre::SciFi, unsafe { Genre::from(21) });
    assert_eq!(Genre::Short, unsafe { Genre::from(22) });
    assert_eq!(Genre::Sport, unsafe { Genre::from(23) });
    assert_eq!(Genre::TalkShow, unsafe { Genre::from(24) });
    assert_eq!(Genre::Thriller, unsafe { Genre::from(25) });
    assert_eq!(Genre::War, unsafe { Genre::from(26) });
    assert_eq!(Genre::Western, unsafe { Genre::from(27) });
  }

  #[test]
  fn test_genre_max() {
    assert_eq!(Genre::max(), Genre::Western as u8);
  }

  #[test]
  #[should_panic]
  fn test_genres_max() {
    Genres::default().get(Genre::max() + 1);
  }

  fn make_genres() -> Genres {
    let mut genres = Genres::default();
    genres.add(Genre::Adventure);
    genres.add(Genre::Music);
    genres.add(Genre::War);
    genres
  }

  #[test]
  fn test_genres() {
    let genres = make_genres();
    assert_eq!(genres.get(2), Some(Genre::Adventure));
    assert_eq!(genres.get(3), None);
    assert_eq!(genres.get(15), Some(Genre::Music));
    assert_eq!(genres.get(17), None);
    assert_eq!(genres.get(26), Some(Genre::War));
    assert_eq!(genres.get(27), None);
  }

  #[test]
  fn test_genres_iter() {
    let genres = make_genres();
    let mut iter = genres.iter();
    assert_eq!(iter.next(), Some(Genre::Adventure));
    assert_eq!(iter.next(), Some(Genre::Music));
    assert_eq!(iter.next(), Some(Genre::War));
    assert_eq!(iter.next(), None);
  }
}