#[macro_use]
pub(crate) mod parser_macros;
use crate::models::field::Field;
use crate::Game;
use hash32::{FnvHasher, Hasher};
use std::fs;
use std::hash::Hash;
use std::path::Path;
enum ParserState {
Parsing,
Error,
}
pub enum ParsingMode {
Strict,
Relaxed,
}
pub enum ParserResult {
WithError(Vec<Game>, Vec<usize>),
WithoutError(Vec<Game>),
}
impl From<ParserResult> for Vec<Game> {
fn from(val: ParserResult) -> Self {
match val {
ParserResult::WithError(games, _) => games,
ParserResult::WithoutError(games) => games,
}
}
}
pub struct Parser {
state: ParserState,
games: Vec<Game>,
current_line: usize,
error_lines: Vec<usize>,
mode: ParsingMode,
}
impl Default for Parser {
fn default() -> Self {
Self {
state: ParserState::Parsing,
games: Vec::new(),
current_line: 0,
error_lines: Vec::new(),
mode: ParsingMode::Relaxed,
}
}
}
impl Parser {
pub fn new(mode: ParsingMode) -> Self {
Self {
state: ParserState::Parsing,
games: Vec::new(),
current_line: 0,
error_lines: Vec::new(),
mode,
}
}
pub fn load_from_file(self, file: impl AsRef<Path>) -> Result<ParserResult, std::io::Error> {
let file: &Path = file.as_ref();
if file.is_file() {
let data = fs::read_to_string(file)?;
Ok(self.load_from_string(&data))
} else {
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"This is not a file",
))
}
}
pub fn load_from_string(mut self, data: &str) -> ParserResult {
for line in data.lines() {
self.current_line += 1;
self.parse(line);
if let ParserState::Error = self.state {
self.error_lines.push(self.current_line);
if let ParsingMode::Strict = self.mode {
break;
}
self.state = ParserState::Parsing;
};
}
for game in &mut self.games {
let mut fnv = FnvHasher::default();
let added = game.added.format("%Y-%m-%d").to_string();
Some(added).hash(&mut fnv);
game.name.hash(&mut fnv);
game.uid = fnv.finish32();
}
match self.error_lines.is_empty() {
false => ParserResult::WithError(self.games, self.error_lines),
true => ParserResult::WithoutError(self.games),
}
}
impl_parse![Field::Game, name;
(Field::Cover, cover);
(Field::Engine, engine);
(Field::Setup, setup);
(Field::Runtime, runtime);
(Field::Store, stores);
(Field::Hints, hints);
(Field::Genres, genres);
(Field::Tags, tags);
(Field::Year, year);
(Field::Dev, devs);
(Field::Publi, publis);
(Field::Version, version);
(Field::Status, status);
(Field::Added, added);
(Field::Updated, updated);
(Field::IgdbId, igdb_id)
];
}
#[cfg(test)]
mod game_tests {
use super::*;
#[test]
fn test_from_parse_result_without_error_to_vec() {
let game = Game::new();
let game_bis = Game::new();
let games1 = vec![game, game_bis];
let games2 = games1.clone();
let parse_result = ParserResult::WithoutError(games2);
let games_test: Vec<Game> = parse_result.into();
assert_eq!(games1, games_test);
}
#[test]
fn test_from_parse_result_with_error_to_vec() {
let game = Game::new();
let game_bis = Game::new();
let games1 = vec![game, game_bis];
let games2 = games1.clone();
let parse_result = ParserResult::WithError(games2, vec![]);
let games_test: Vec<Game> = parse_result.into();
assert_eq!(games1, games_test);
}
#[test]
fn load_from_file_fail() {
let re = match Parser::default().load_from_file("nothere") {
Ok(_) => panic!(),
Err(e) => e,
};
let error_type = std::io::ErrorKind::InvalidInput;
assert_eq!(re.kind(), error_type);
}
}