use std::fs::File; use std::io; use std::io::BufRead; use std::path::Path; pub mod cafeteria; pub mod printing_department; pub mod trash_compactor; pub trait Solver { fn get_puzzle_index(&self) -> u8; fn get_puzzle_name(&self) -> &str; fn get_input_filename(&self) -> &str; fn get_part1(&self) -> u64; fn get_part2(&self) -> u64; fn process_data(&mut self, lines: io::Lines>); } pub fn run(mut solver: Box) { println!( "--- Day {}: {} ---", solver.get_puzzle_index(), solver.get_puzzle_name() ); // The "../../data" and "../../../data" paths are useful for running the binary from // "target/release/" directory with "data/" directory in package root or parent directory. let paths = vec!["./data", "../data", "../../data", "../../../data"]; match read_data_file(solver.get_input_filename(), &paths) { Ok(lines) => { solver.process_data(lines); print_result(solver); } Err(error) => eprintln!("{}", error), }; } fn print_result(solver: Box) { println!( "Part 1: {:?}\nPart 2: {:?}\n", solver.get_part1(), solver.get_part2() ); } fn read_data_file( filename: T, search_paths: &Vec, ) -> Result>, String> where T: AsRef, { for path in search_paths { if let Ok(lines) = read_lines(path.as_ref().join(&filename)) { return Ok(lines); } } let message = search_paths .iter() .map(|path| path.as_ref().join(&filename).display().to_string()) .collect::>() .join("\n"); Err(format!( "Cannot open puzzle input file, searched these paths:\n{}", message )) } // This code is taken from https://doc.rust-lang.org/stable/rust-by-example/std_misc/file/read_lines.html fn read_lines(filename: T) -> io::Result>> where T: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) }