use inline_colorization::*; use std::{fmt::Display, fs, path::PathBuf}; use quick_xml::de::from_str; use serde::Deserialize; pub const BOOKS_IN_ORDER: [&str; 66] = [ "Genesis", "Exodus", "Leviticus", "Numbers", "Deuteronomy", "Joshua", "Judges", "Ruth", "1 Samuel", "2 Samuel", "1 Kings", "2 Kings", "1 Chronicles", "2 Chronicles", "Ezra", "Nehemiah", "Esther", "Job", "Psalms", "Proverbs", "Ecclesiastes", "Song of Solomon", "Isaiah", "Jeremiah", "Lamentations", "Ezekiel", "Daniel", "hosea", "Joel", "Amos", "Obadiah", "Jonah", "Micah", "Nahum", "Habakkuk", "Zephaniah", "Haggai", "Zechariah", "Malachi", "Matthew", "Mark", "Luke", "John", "Acts", "Romans", "1 Corinthians", "2 Corinthians", "Galations", "Ephesians", "Philippians", "Colossians", "1 Thessalonians", "2 Thessalonians", "1 Timothy", "2 Timothy", "Titus", "Philemon", "Hebrews", "James", "1 Peter", "2 Peter", "1 John", "2 John", "3 John", "Jude", "Revelation", ]; pub fn get(query: &str, location: &str, files: Vec) { // expecting query to be in format: // Gen 1:2 // Gen 1:2-5 // Gen 1 // The book name needs to be just long enough to be unique let mut splits = location.split(':'); let chapter = splits.next(); let verse = splits.next(); // ########################################### // Figure out what book they are talking about let res = BOOKS_IN_ORDER .iter() .enumerate() .filter(|(_, book)| book.to_lowercase().contains(&query.to_lowercase())) .collect::>(); let (book_idx, book_name) = match res.len() { 1 => res[0], 2.. => { eprintln!("Err: Ambigious input '{query}', could be any of:"); for (_, i) in &res { eprintln!("\t{i}"); } return; } _ => { eprintln!("'{query}' is not a book"); return; } }; // Load Bibles into memory let bibles = files .iter() .filter_map(|path| fs::read_to_string(path).ok()) .filter_map(|contents| from_str::(&contents).ok()) .collect::>(); // Select the book in each Bible let books = bibles .iter() // Book are 1 indexed in the xml spec .map(|bible| (bible, bible.get_book_by_index(book_idx + 1))) .filter(|(_,e)| e.is_some()) .map(|(bible,book)| (bible,book.unwrap())) .collect::>(); // Select the chapter in each Bible let chapters = if let Some(chapter) = chapter { if let Ok(ch_num) = chapter.parse::() { books .iter() .map(|(bible,book)| (bible, book, book.get_chapter_by_index(ch_num))) .filter(|(_,_,book)| book.is_some()) .map(|(bible,book,chapter)| (*bible,*book,chapter.unwrap())) .collect::>() } else { return; } } else { return; }; // Get the verse in each Bible match verse { Some(verse) => { let mut splits = verse.split("-"); let start = splits.next(); let end = splits.next(); match (start, end) { // range of verses (Some(sn), Some(en)) => { if let (Ok(start), Ok(end)) = (sn.parse::(), en.parse::()) { for num in start..=end { for (bible, _book, chapter) in &chapters { if let Some(verse) = chapter.get_verse_by_index(num) { println!( "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}", bible.translation_name, chapter.number, verse.number ); } } } } } // only one verse (Some(ver_num), None) => { if let Ok(num) = ver_num.parse::() { for (bible, _book, chapter) in chapters { if let Some(verse) = chapter.get_verse_by_index(num) { println!( "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}", bible.translation_name, chapter.number, verse.number ); } } } } _ => { // couldn't parse verse return; } } } None => { // only chapter for (bible, _book, chapter) in chapters { println!( "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:", bible.translation_name, chapter.number ); print!("{}", chapter); } } } } impl Bible { fn get_book_by_index(&self, idx: usize) -> Option<&Book> { for i in &self.testaments { for book in &i.books { if book.number == idx { return Some(&book); } } } None } } impl Book { fn get_chapter_by_index(&self, idx: usize) -> Option<&Chapter> { for chap in &self.chapters { if chap.number == idx { return Some(&chap); } } None } } impl Chapter { fn get_verse_by_index(&self, idx: usize) -> Option<&Verse> { for verse in &self.verses { if verse.number == idx { return Some(&verse); } } None } } #[derive(Deserialize)] struct Bible { #[serde(rename = "@translation")] translation_name: String, #[serde(rename = "testament")] testaments: Vec, } #[derive(Deserialize)] struct Testament { #[serde(rename = "@name")] // yes, this would make a nice enum _testament: String, #[serde(rename = "book")] books: Vec, } #[derive(Deserialize)] struct Book { #[serde(rename = "@number")] number: usize, #[serde(rename = "chapter")] chapters: Vec, } #[derive(Deserialize)] struct Chapter { #[serde(rename = "@number")] number: usize, #[serde(rename = "verse")] verses: Vec, } impl Display for Chapter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for v in &self.verses { writeln!( f, "{style_bold}{style_underline}{}{style_reset}: {}", v.number, v )?; } Ok(()) } } #[derive(Deserialize)] struct Verse { #[serde(rename = "@number")] number: usize, #[serde(rename = "$text")] text: String, } impl Display for Verse { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.text) } }