use std::{ffi::OsStr, io::Read, os::unix::ffi::OsStrExt, path::PathBuf}; use sgf::{go, parse_sgf, Game}; use thiserror::Error; #[derive(Error, Debug)] pub enum Error { #[error("Database permission denied")] PermissionDenied, #[error("An IO error occurred: {0}")] IOError(std::io::Error), } impl From for Error { fn from(err: std::io::Error) -> Self { Error::IOError(err) } } #[derive(Debug)] pub struct Database { path: PathBuf, games: Vec, } impl Database { pub fn open_path(path: PathBuf) -> Result { let mut games: Vec = Vec::new(); let extension = PathBuf::from("sgf").into_os_string(); let path_iter = std::fs::read_dir(path.clone())?; for entry in path_iter { match entry { Ok(entry) => { if entry.path().extension() == Some(&extension) { let mut buffer = String::new(); std::fs::File::open(entry.path()) .unwrap() .read_to_string(&mut buffer) .unwrap(); match parse_sgf(&buffer) { Ok(sgfs) => { for sgf in sgfs { match sgf { Game::Go(game) => games.push(game), Game::Unsupported(_) => {} } } } Err(err) => println!("Error parsing {:?}: {:?}", entry.path(), err), } } } Err(err) => println!("failed entry: {:?}", err), } } Ok(Database { path, games }) } pub fn len(&self) -> usize { self.games.len() } pub fn all_games(&self) -> impl Iterator { self.games.iter() } } #[cfg(test)] mod test { use super::*; use cool_asserts::assert_matches; use sgf::Date; #[test] fn it_reads_empty_database() { let db = Database::open_path(PathBuf::from("fixtures/empty_database/")) .expect("database to open"); assert_eq!(db.all_games().count(), 0); } #[test] fn it_reads_five_games_from_database() { let db = Database::open_path(PathBuf::from("fixtures/five_games/")).expect("database to open"); assert_eq!(db.all_games().count(), 5); for game in db.all_games() {} assert_matches!(db.all_games().find(|g| g.info.black_player == Some("Steve".to_owned())), Some(game) => { assert_eq!(game.info.black_player, Some("Steve".to_owned())); assert_eq!(game.info.white_player, Some("Savanni".to_owned())); assert_eq!(game.info.date, vec![Date::Date(chrono::NaiveDate::from_ymd_opt(2023, 4, 19).unwrap())]); assert_eq!(game.info.komi, Some(6.5)); } ); } }