pobsd_parser/
field.rs

1use super::split_line::split_line;
2use crate::store_links::{StoreLink, StoreLinks};
3use std::fmt;
4
5/* ------------------------ FIELD ENUM -----------------------*/
6/// The Field enum is a representations of a line
7/// in the database.
8/// Each type of line is represented by a variant (see
9/// below).
10/// The Unknown variant is used to represent lines
11/// that were not parsed correctly.
12#[derive(PartialEq, Eq, Debug, Clone)]
13pub enum Field {
14    /// Store the result of a Game line of the database
15    Game(Option<String>),
16    /// Store the result of a Cover line of the database
17    Cover(Option<String>),
18    /// Store the result of a Engine line of the database
19    Engine(Option<String>),
20    /// Store the result of a Setup line of the database
21    Setup(Option<String>),
22    /// Store the result of a Runtime line of the database
23    Runtime(Option<String>),
24    /// Store the result of a Hints line of the database
25    Hints(Option<String>),
26    /// Store the result of a Dev line of the database
27    Dev(Option<String>),
28    /// Store the result of a Pub line of the database
29    Publi(Option<String>),
30    /// Store the result of a Version line of the database
31    Version(Option<String>),
32    /// Store the result of a Status line of the database
33    Status(Option<String>),
34    /// Store the result of a Store line of the database
35    /// Stores are stored in a vector
36    Store(Option<StoreLinks>),
37    /// Store the result of a Genre line of the database
38    /// Genres are stored in a vector
39    Genres(Option<Vec<String>>),
40    /// Store the result of a Tag line of the database
41    /// Tags are stored in a vector
42    Tags(Option<Vec<String>>),
43    /// Store the result of a Year line of the database
44    Year(Option<String>),
45    /// When the game was added
46    Added(Option<String>),
47    /// When the game was last updated
48    Updated(Option<String>),
49    /// The id of the game in the IGDB database
50    IgdbId(Option<String>),
51    /// Store the result of a unknown line of the database
52    /// The left hand side and the right hand side (if
53    /// any) are stores separately.
54    Unknown(Option<String>),
55}
56
57impl fmt::Display for Field {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Field::Game(name) => match name {
61                Some(name) => write!(f, "Game\t{}", name),
62                None => write!(f, "Game"),
63            },
64            Field::Cover(name) => match name {
65                Some(name) => write!(f, "Cover\t{}", name),
66                None => write!(f, "Cover"),
67            },
68            Field::Engine(name) => match name {
69                Some(name) => write!(f, "Engine\t{}", name),
70                None => write!(f, "Engine"),
71            },
72            Field::Setup(name) => match name {
73                Some(name) => write!(f, "Setup\t{}", name),
74                None => write!(f, "Setup"),
75            },
76            Field::Runtime(name) => match name {
77                Some(name) => write!(f, "Runtime\t{}", name),
78                None => write!(f, "Runtime"),
79            },
80            Field::Hints(name) => match name {
81                Some(name) => write!(f, "Hints\t{}", name),
82                None => write!(f, "Hints"),
83            },
84            Field::Dev(name) => match name {
85                Some(name) => write!(f, "Dev\t{}", name),
86                None => write!(f, "Dev"),
87            },
88            Field::Publi(name) => match name {
89                Some(name) => write!(f, "Pub\t{}", name),
90                None => write!(f, "Pub"),
91            },
92            Field::Version(name) => match name {
93                Some(name) => write!(f, "Version\t{}", name),
94                None => write!(f, "Version"),
95            },
96            Field::Status(name) => match name {
97                Some(name) => write!(f, "Status\t{}", name),
98                None => write!(f, "Status"),
99            },
100            Field::Store(name) => match name {
101                Some(StoreLinks(name)) => {
102                    write!(
103                        f,
104                        "Store\t{}",
105                        name.iter()
106                            .map(|a| a.url.to_string())
107                            .collect::<Vec<String>>()
108                            .join(" ")
109                    )
110                }
111                None => write!(f, "Store"),
112            },
113            Field::Genres(name) => match name {
114                Some(name) => write!(f, "Genre\t{}", name.join(", ")),
115                None => write!(f, "Genre"),
116            },
117            Field::Tags(name) => match name {
118                Some(name) => write!(f, "Tags\t{}", name.join(", ")),
119                None => write!(f, "Tags"),
120            },
121            Field::Year(name) => match name {
122                Some(name) => write!(f, "Year\t{}", name),
123                None => write!(f, "Year"),
124            },
125            Field::Added(name) => match name {
126                Some(name) => write!(f, "Added\t{}", name),
127                None => write!(f, "Added"),
128            },
129            Field::Updated(name) => match name {
130                Some(name) => write!(f, "Updated\t{}", name),
131                None => write!(f, "Updated"),
132            },
133            Field::IgdbId(name) => match name {
134                Some(name) => write!(f, "IgdbId\t{}", name),
135                None => write!(f, "IgdbId"),
136            },
137            Field::Unknown(field) => match field {
138                Some(field) => {
139                    write!(f, "Unknown field {}", field)
140                }
141                None => {
142                    write!(f, "Unexpected patern")
143                }
144            },
145        }
146    }
147}
148
149impl Field {
150    /// Convert a line of the database into a Field enum
151    /// (see exemple above).
152    pub fn from(line: &str) -> Self {
153        // Split the line in a left and right hand sides
154        let (left, right) = split_line(line);
155        // Use the left hand side to discriminate between single and multiple item lines
156        if let Some(left) = left {
157            match left {
158                "Game" => match right {
159                    Some(right) => Field::Game(Some(right.into())),
160                    None => Field::Game(None),
161                },
162                "Cover" => match right {
163                    Some(right) => Field::Cover(Some(right.into())),
164                    None => Field::Cover(None),
165                },
166                "Engine" => match right {
167                    Some(right) => Field::Engine(Some(right.into())),
168                    None => Field::Engine(None),
169                },
170                "Setup" => match right {
171                    Some(right) => Field::Setup(Some(right.into())),
172                    None => Field::Setup(None),
173                },
174                "Runtime" => match right {
175                    Some(right) => Field::Runtime(Some(right.into())),
176                    None => Field::Runtime(None),
177                },
178                "Hints" => match right {
179                    Some(right) => Field::Hints(Some(right.into())),
180                    None => Field::Hints(None),
181                },
182                "Dev" => match right {
183                    Some(right) => Field::Dev(Some(right.into())),
184                    None => Field::Dev(None),
185                },
186                "Pub" => match right {
187                    Some(right) => Field::Publi(Some(right.into())),
188                    None => Field::Publi(None),
189                },
190                "Version" => match right {
191                    Some(right) => Field::Version(Some(right.into())),
192                    None => Field::Version(None),
193                },
194                "Status" => match right {
195                    Some(right) => Field::Status(Some(right.into())),
196                    None => Field::Status(None),
197                },
198                // Store does not use the same separator than Genre and Tags
199                "Store" => match right {
200                    Some(right) => {
201                        let mut items: Vec<StoreLink> = Vec::new();
202                        for item in right.split(' ') {
203                            let store = StoreLink::from(item.trim());
204                            items.push(store);
205                        }
206                        Field::Store(Some(StoreLinks(items)))
207                    }
208                    None => Field::Store(None),
209                },
210                "Genre" => match right {
211                    Some(right) => {
212                        let mut items: Vec<String> = Vec::new();
213                        for item in right.split(',') {
214                            items.push(item.trim().into());
215                        }
216                        Field::Genres(Some(items))
217                    }
218                    None => Field::Genres(None),
219                },
220                "Tags" => match right {
221                    Some(right) => {
222                        let mut items: Vec<String> = Vec::new();
223                        for item in right.split(',') {
224                            items.push(item.trim().into());
225                        }
226                        Field::Tags(Some(items))
227                    }
228                    None => Field::Tags(None),
229                },
230                "Year" => match right {
231                    Some(right) => Field::Year(Some(right.into())),
232                    None => Field::Year(None),
233                },
234                "Added" => match right {
235                    Some(right) => Field::Added(Some(right.into())),
236                    None => Field::Added(Some("1970/01/01".into())),
237                },
238                "Updated" => match right {
239                    Some(right) => Field::Updated(Some(right.into())),
240                    None => Field::Updated(None),
241                },
242                "IgdbId" => match right {
243                    Some(right) => Field::IgdbId(Some(right.into())),
244                    None => Field::IgdbId(None),
245                },
246                _ => Field::Unknown(Some(left.into())),
247            }
248        } else {
249            Field::Unknown(None)
250        }
251    }
252}
253
254#[cfg(test)]
255mod field_tests {
256    use super::*;
257    #[test]
258    fn test_from_game_line() {
259        let input = "Game\tToto";
260        let field = Field::from(&input);
261        assert_eq!(Field::Game(Some("Toto".into())), field);
262        assert_eq!(format!("{}", field), input);
263        let input = "Game";
264        let field = Field::from(&input);
265        assert_eq!(Field::Game(None), field);
266        assert_eq!(format!("{}", field), input);
267    }
268    #[test]
269    fn test_from_cover_line() {
270        let input = "Cover\tToto";
271        let field = Field::from(&input);
272        assert_eq!(Field::Cover(Some("Toto".into())), field);
273        assert_eq!(format!("{}", field), input);
274        let input = "Cover";
275        let field = Field::from(&input);
276        assert_eq!(Field::Cover(None), field);
277        assert_eq!(format!("{}", field), input);
278    }
279    #[test]
280    fn test_from_engine_line() {
281        let input = "Engine\tToto";
282        let field = Field::from(&input);
283        assert_eq!(Field::Engine(Some("Toto".into())), field);
284        assert_eq!(format!("{}", field), input);
285        let input = "Engine";
286        let field = Field::from(&input);
287        assert_eq!(Field::Engine(None), field);
288        assert_eq!(format!("{}", field), input);
289    }
290    #[test]
291    fn test_from_setup_line() {
292        let input = "Setup\tToto";
293        let field = Field::from(&input);
294        assert_eq!(Field::Setup(Some("Toto".into())), field);
295        assert_eq!(format!("{}", field), input);
296        let input = "Setup";
297        let field = Field::from(&input);
298        assert_eq!(Field::Setup(None), field);
299        assert_eq!(format!("{}", field), input);
300    }
301    #[test]
302    fn test_from_runtime_line() {
303        let input = "Runtime\tToto";
304        let field = Field::from(&input);
305        assert_eq!(Field::Runtime(Some("Toto".into())), field);
306        assert_eq!(format!("{}", field), input);
307        let input = "Runtime";
308        let field = Field::from(&input);
309        assert_eq!(Field::Runtime(None), field);
310        assert_eq!(format!("{}", field), input);
311    }
312    #[test]
313    fn test_from_hints_line() {
314        let input = "Hints\tToto";
315        let field = Field::from(&input);
316        assert_eq!(Field::Hints(Some("Toto".into())), field);
317        assert_eq!(format!("{}", field), input);
318        let input = "Hints";
319        let field = Field::from(&input);
320        assert_eq!(Field::Hints(None), field);
321        assert_eq!(format!("{}", field), input);
322    }
323    #[test]
324    fn test_from_dev_line() {
325        let input = "Dev\tToto";
326        let field = Field::from(&input);
327        assert_eq!(Field::Dev(Some("Toto".into())), field);
328        assert_eq!(format!("{}", field), input);
329        let input = "Dev";
330        let field = Field::from(&input);
331        assert_eq!(Field::Dev(None), field);
332        assert_eq!(format!("{}", field), input);
333    }
334    #[test]
335    fn test_from_publi_line() {
336        let input = "Pub\tToto";
337        let field = Field::from(&input);
338        assert_eq!(Field::Publi(Some("Toto".into())), field);
339        assert_eq!(format!("{}", field), input);
340        let input = "Pub";
341        let field = Field::from(&input);
342        assert_eq!(Field::Publi(None), field);
343        assert_eq!(format!("{}", field), input);
344    }
345    #[test]
346    fn test_from_version_line() {
347        let input = "Version\tToto";
348        let field = Field::from(&input);
349        assert_eq!(Field::Version(Some("Toto".into())), field);
350        assert_eq!(format!("{}", field), input);
351        let input = "Version";
352        let field = Field::from(&input);
353        assert_eq!(Field::Version(None), field);
354        assert_eq!(format!("{}", field), input);
355    }
356    #[test]
357    fn test_from_status_line() {
358        let input = "Status\tToto";
359        let field = Field::from(&input);
360        assert_eq!(Field::Status(Some("Toto".into())), field);
361        assert_eq!(format!("{}", field), input);
362        let input = "Status";
363        let field = Field::from(&input);
364        assert_eq!(Field::Status(None), field);
365        assert_eq!(format!("{}", field), input);
366    }
367    #[test]
368    fn test_from_store_line() {
369        let input = "Store\tfirst second";
370        let field = Field::from(&input);
371        assert_eq!(
372            Field::Store(Some(StoreLinks(vec![
373                StoreLink::from("first"),
374                StoreLink::from("second")
375            ]))),
376            field
377        );
378        assert_eq!(format!("{}", field), input);
379        let input = "Store";
380        let field = Field::from(&input);
381        assert_eq!(Field::Store(None), field);
382        assert_eq!(format!("{}", field), input);
383    }
384    #[test]
385    fn test_from_genre_line() {
386        let input = "Genre\tfirst, second";
387        let field = Field::from(&input);
388        assert_eq!(
389            Field::Genres(Some(vec!["first".into(), "second".into()])),
390            field
391        );
392        assert_eq!(format!("{}", field), input);
393        let input = "Genre";
394        let field = Field::from(&input);
395        assert_eq!(Field::Genres(None), field);
396        assert_eq!(format!("{}", field), input);
397    }
398    #[test]
399    fn test_from_tag_line() {
400        let input = "Tags\tfirst, second";
401        let field = Field::from(&input);
402        assert_eq!(
403            Field::Tags(Some(vec!["first".into(), "second".into()])),
404            field
405        );
406        assert_eq!(format!("{}", field), input);
407        let input = "Tags";
408        let field = Field::from(&input);
409        assert_eq!(Field::Tags(None), field);
410        assert_eq!(format!("{}", field), input);
411    }
412    #[test]
413    fn test_from_year_line() {
414        let input = "Year\t1980";
415        let field = Field::from(&input);
416        assert_eq!(Field::Year(Some("1980".into())), field);
417        assert_eq!(format!("{}", field), input);
418        let input = "Year";
419        let field = Field::from(&input);
420        assert_eq!(Field::Year(None), field);
421        assert_eq!(format!("{}", field), input);
422    }
423    #[test]
424    fn test_from_unknown_field() {
425        let input = "Let's not\tpanic";
426        let field = Field::from(&input);
427        assert_eq!(Field::Unknown(Some("Let's not".into())), field);
428        assert_eq!(
429            format!("{}", field),
430            format!("Unknown field {}", "Let's not")
431        );
432    }
433    #[test]
434    fn test_from_unknown_field_with_notab() {
435        let input = "Let's not";
436        let field = Field::from(&input);
437        assert_eq!(Field::Unknown(Some("Let's not".into())), field);
438        assert_eq!(format!("{}", field), format!("Unknown field {}", input));
439    }
440    #[test]
441    fn test_from_igdb_id_line() {
442        let input = "IgdbId\t12";
443        let field = Field::from(&input);
444        assert_eq!(Field::IgdbId(Some("12".into())), field);
445        assert_eq!(format!("{}", field), input);
446        let input = "IgdbId";
447        let field = Field::from(&input);
448        assert_eq!(Field::IgdbId(None), field);
449        assert_eq!(format!("{}", field), input);
450    }
451}