use inline_colorization::*; use std::fmt::Display; 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 search_for_word(word: &str, from_files: &Bible) -> Vec { let mut found = Vec::new(); for test in &from_files.testaments { for book in &test.books { for chapter in &book.chapters { for verse in &chapter.verses { if verse.text.to_lowercase().contains(&word.to_lowercase()) { found.push(format!("{} {}:{}", BOOKS_IN_ORDER[book.number-1], chapter.number, verse.number)); } } } } } found } pub fn get(book: &str, chap_and_ver: &str, bibles: Vec<&Bible>) -> Option { // 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 = chap_and_ver.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(|(_, lbook)| lbook .to_lowercase() .starts_with(&book .trim() .replace(" ", "-") .to_lowercase() ) ) .collect::>(); let (book_idx, book_name) = match res.len() { 1 => res[0], 2.. => { eprintln!("Err: Ambigious input '{book}', could be any of:"); for (_, i) in &res { eprintln!("\t{i}"); } return None; } _ => { eprintln!("'{book}' is not a book"); return None; } }; // 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 None; } } else { return None; }; // 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::()) { let mut buf = String::new(); for num in start..=end { for (bible, _book, chapter) in &chapters { if let Some(verse) = chapter.get_verse_by_index(num) { buf += &format!( "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n", bible.translation_name, chapter.number, verse.number ); } } } return Some(buf); } return None; } // only one verse (Some(ver_num), None) => { if let Ok(num) = ver_num.parse::() { let mut buf = String::new(); for (bible, _book, chapter) in chapters { if let Some(verse) = chapter.get_verse_by_index(num) { buf += &format!( "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}: {verse}\n", bible.translation_name, chapter.number, verse.number ); } } return Some(buf); } return None; } _ => { // couldn't parse verse return None; } } } None => { // only chapter let mut buf = String::new(); for (bible, _, chapter) in chapters { buf += &format!( "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:\n", bible.translation_name, chapter.number ); buf += &format!("{}", chapter); } return Some(buf); } } } 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)] pub struct Bible { #[serde(rename = "@translation")] translation_name: String, #[serde(rename = "testament")] testaments: Vec, } impl Display for Bible { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}",self.translation_name) } } #[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) } }