use std::{fmt::Display, fs::read_to_string}; use inline_colorization::*; use quick_xml::de::from_str; use serde::Deserialize; 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, loc: &str) { // expecting query to be in format: // Gen 1:2 // Gen 1:2-5 // Genesis 1 // 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 found_book = 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; } }; let file = read_to_string("/usr/local/bible/EnglishNASBBible.xml").expect( "Failed to get the bible xml file. It's expect to be at:\n/usr/local/bible/.xml\n\n", ); let bible: Bible = from_str(&file).unwrap(); // Book are 1 indexed in the xml spec if let Some(book) = bible.get_book_by_index(found_book.0 + 1) { // Figure out what verse they want let mut splits = loc.split(':'); let chatper = splits.next(); let verse = splits.next(); if let Some(chapter) = chatper { if let Ok(chap_num) = chapter.trim().parse::() { if let Some(chapter) = book.get_chapter_by_index(chap_num) { if let Some(verse) = verse { if let Ok(verse_num) = verse.trim().parse::() { // return specific verse if let Some(verse) = chapter.get_verse_by_index(verse_num) { println!("{style_underline}{style_bold}{} {}:{}{style_reset}: {}", found_book.1, chapter.number, verse.number, verse); } } else { // might have input a verse range let range: Vec<&str> = verse.split("-").collect(); if range.len() == 2 { let start = range[0].parse::(); let end = range[1].parse::(); if let (Ok(s), Ok(e)) = (start,end) { println!("{style_underline}{style_bold}{} {}:{}-{}{style_reset}:", found_book.1, chapter.number, s, e); for verse_idx in s..=e { if let Some(v) = chapter.get_verse_by_index(verse_idx) { println!("{style_underline}{style_bold}{}{style_reset}: {}", v.number, v); } } } } } } else { println!("{style_bold}{style_underline}{} {}{style_reset}:", found_book.1, 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")] _version: 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) } }