1use hash32::{FnvHasher, Hasher};
6use std::fs;
7use std::hash::Hash;
8use std::path::Path;
9
10use super::field::Field;
11use super::game::Game;
12
13pub trait State {}
14
15enum ParserState {
16 Game,
17 Cover,
18 Engine,
19 Setup,
20 Runtime,
21 Store,
22 Hints,
23 Genre,
24 Tags,
25 Year,
26 Dev,
27 Pub,
28 Version,
29 Status,
30 Added,
31 Updated,
32 IgdbId,
33 Error,
34 Recovering,
35}
36
37pub enum ParsingMode {
49 Strict,
50 Relaxed,
51}
52
53pub enum ParserResult {
62 WithError(Vec<Game>, Vec<usize>),
63 WithoutError(Vec<Game>),
64}
65
66impl From<ParserResult> for Vec<Game> {
67 fn from(val: ParserResult) -> Self {
68 match val {
69 ParserResult::WithError(games, _) => games,
70 ParserResult::WithoutError(games) => games,
71 }
72 }
73}
74pub struct Parser {
77 state: ParserState,
78 games: Vec<Game>,
79 current_line: usize,
80 error_lines: Vec<usize>,
81 mode: ParsingMode,
82}
83
84impl Default for Parser {
85 fn default() -> Self {
86 Self {
87 state: ParserState::Game,
88 games: Vec::new(),
89 current_line: 0,
90 error_lines: Vec::new(),
91 mode: ParsingMode::Relaxed,
92 }
93 }
94}
95impl Parser {
96 pub fn new(mode: ParsingMode) -> Self {
98 Self {
99 state: ParserState::Game,
100 games: Vec::new(),
101 current_line: 0,
102 error_lines: Vec::new(),
103 mode,
104 }
105 }
106 pub fn load_from_file(self, file: impl AsRef<Path>) -> Result<ParserResult, std::io::Error> {
108 let file: &Path = file.as_ref();
109 if file.is_file() {
110 let data = fs::read_to_string(file)?;
111 Ok(self.load_from_string(&data))
112 } else {
113 Err(std::io::Error::new(
114 std::io::ErrorKind::InvalidInput,
115 "This is not a file",
116 ))
117 }
118 }
119 pub fn load_from_string(mut self, data: &str) -> ParserResult {
121 for line in data.lines() {
122 self.current_line += 1;
123 self.parse(line);
124 if let ParserState::Error = self.state {
125 self.error_lines.push(self.current_line);
126 if let ParsingMode::Strict = self.mode {
127 break;
128 }
129 };
130 }
131 for game in &mut self.games {
132 let mut fnv = FnvHasher::default();
133 game.added.hash(&mut fnv);
134 game.name.hash(&mut fnv);
135 game.uid = fnv.finish32();
136 }
137 match self.error_lines.is_empty() {
138 false => ParserResult::WithError(self.games, self.error_lines),
139 true => ParserResult::WithoutError(self.games),
140 }
141 }
142 impl_parse![ParserState::Game, Field::Game, name, ParserState::Cover;
143 (ParserState::Cover, Field::Cover, cover, ParserState::Engine);
144 (ParserState::Engine, Field::Engine, engine, ParserState::Setup);
145 (ParserState::Setup, Field::Setup, setup, ParserState::Runtime);
146 (ParserState::Runtime, Field::Runtime, runtime, ParserState::Store);
147 (ParserState::Store, Field::Store, stores, ParserState::Hints);
148 (ParserState::Hints, Field::Hints, hints, ParserState::Genre);
149 (ParserState::Genre, Field::Genres, genres, ParserState::Tags);
150 (ParserState::Tags, Field::Tags, tags, ParserState::Year);
151 (ParserState::Year, Field::Year, year, ParserState::Dev);
152 (ParserState::Dev, Field::Dev, dev, ParserState::Pub);
153 (ParserState::Pub, Field::Publi, publi, ParserState::Version);
154 (ParserState::Version, Field::Version, version, ParserState::Status);
155 (ParserState::Status, Field::Status, status, ParserState::Added);
156 (ParserState::Added, Field::Added, added, ParserState::Updated);
157 (ParserState::Updated, Field::Updated, updated, ParserState::IgdbId);
158 (ParserState::IgdbId, Field::IgdbId, igdb_id, ParserState::Game)
159 ];
160}