use crate::game_of_life::opts::Opts; use crate::game_of_life::universe::Universe; use clap::{Error, ErrorKind}; use regex::Regex; #[cfg(test)] use mocktopus::macros::mockable; /// Generate a new Universe/Board with a parsed local file or remote URL/file /// /// If a URL is provided as an input (starting with http/ftp) the URL/File will be downloaded /// and parsed to generate a seeding input for the simulation /// otherwise, it'll be assumed that it's a path to a local file that will be parsed, and /// used as the seeding input #[cfg_attr(test, mockable)] pub(crate) fn new(opts: Opts) -> Result { let input = opts.input.clone().unwrap(); match Regex::new("^(ht|f)tp(s)?://*").unwrap().is_match(&input) { true => generate_url_data(opts), false => generate_file_data(opts), } } #[cfg(test)] mod new { use super::*; use mocktopus::mocking::*; fn universe_mock(opts: Opts) -> Universe { Universe { opts, current: vec![vec![]], future: vec![vec![]], } } fn opts(input: Option) -> Opts { Opts { input, width: 13, height: 13, seed: 1337, live_cell: 'L', dead_cell: 'D', delay: 13, } } #[test] fn with_url() { let opts = opts(Some(String::from("https://example.com"))); generate_file_data.mock_safe(|_| panic!()); generate_url_data.mock_safe(|opts| MockResult::Return(Ok(universe_mock(opts)))); let universe = new(opts.clone()).unwrap(); assert_eq!(universe, universe_mock(opts)); } #[test] fn with_file() { let opts = opts(Some(String::from("./example.txt"))); generate_file_data.mock_safe(|opts| MockResult::Return(Ok(universe_mock(opts)))); generate_url_data.mock_safe(|_| panic!()); let universe = new(opts.clone()).unwrap(); assert_eq!(universe, universe_mock(opts)); } } /// Reads a local file and parses it, converting alpha-numerical chars into living cells /// and generate a new universe instance in the initial state from file #[cfg_attr(test, mockable)] fn generate_file_data(opts: Opts) -> Result { let file = match std::fs::read_to_string(opts.input.clone().unwrap()) { Ok(s) => s, Err(e) => { return Err(Error::with_description( format!("{}\n", e.to_string()), ErrorKind::Io, )) } }; let data: Vec> = file .split("\n") .map(|s| { s.to_string() .chars() .map(|c| c.is_ascii_alphanumeric()) .collect() }) .collect(); Ok(populate(opts, data)) } #[cfg(test)] mod generate_file_data { use super::*; use std::env; use std::fs::File; use std::io::Write; fn opts(input: Option) -> Opts { Opts { input, width: 4, height: 3, seed: 1337, live_cell: 'L', dead_cell: 'D', delay: 13, } } #[test] fn file_exists() { let temp_file = env::temp_dir().join("file.txt"); let path = temp_file.clone().into_os_string().into_string().unwrap(); let mut file = File::create(temp_file).unwrap(); writeln!(&mut file, "--Learning Rust--").unwrap(); writeln!(&mut file, "--First attempt--").unwrap(); let opts = opts(Some(path)); let universe = generate_file_data(opts.clone()).unwrap(); assert_eq!( universe, Universe { opts, future: vec![vec![false; 4]; 3], current: vec![ vec![false, false, true, true], vec![false, false, true, true], vec![false, false, false, false], ], } ) } #[test] fn file_does_not_exist() { assert!(generate_file_data(opts(Some(String::from("./not-real.txt")),)).is_err()); } } /// Reads a URL and parses it, converting alpha-numerical chars into living cells /// in a newly generated universe simulation #[cfg_attr(test, mockable)] fn generate_url_data(_opts: Opts) -> Result { unimplemented!() } /// Return a universe struct with populated data from the data param /// /// The data param is reshaped to fit with the opts width/height (extended or shrinked) fn populate(opts: Opts, data: Vec>) -> Universe { let (width, height) = (opts.width as usize, opts.height as usize); let mut current = vec![vec![false; width]; height]; for i in 0..height { for j in 0..width { current[i][j] = data .get(i) .unwrap_or(&vec![]) .get(j) .unwrap_or(&false) .clone(); } } Universe { opts, current, future: vec![vec![false; width]; height], } } #[cfg(test)] mod populate { use super::*; fn opts(width: isize, height: isize) -> Opts { Opts { width, height, seed: 1337, input: None, live_cell: 'L', dead_cell: 'D', delay: 13, } } #[test] fn expand_data() { let data = vec![vec![true, false, true]; 3]; let opts = opts(5, 5); let universe = populate(opts.clone(), data); assert_eq!( universe, Universe { opts, future: vec![vec![false; 5]; 5], current: vec![ vec![true, false, true, false, false], vec![true, false, true, false, false], vec![true, false, true, false, false], vec![false, false, false, false, false], vec![false, false, false, false, false], ] } ) } #[test] fn shrink_data() { let data = vec![vec![true, false, true]; 3]; let opts = opts(2, 2); let universe = populate(opts.clone(), data); assert_eq!( universe, Universe { opts, future: vec![vec![false; 2]; 2], current: vec![vec![true, false], vec![true, false]] } ) } }