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 { todo!() } 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.replace(" ", "").to_lowercase().starts_with(&book.trim().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) } }