use inline_colorization::*; use regex::Regex; 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(); let regex = match Regex::new(&word.to_lowercase()) { Ok(re) => re, Err(err) => { eprintln!("{}", err); #[cfg(debug_assertions)] todo!("IDK what to do yet if building regex engine fails"); #[cfg(not(debug_assertions))] return Vec::new() }, }; for test in &from_files.testaments { for book in &test.books { for chapter in &book.chapters { for verse in &chapter.verses { if regex.is_match(&verse.text.to_lowercase()) { found.push(format!("{} {}:{}", BOOKS_IN_ORDER[book.number-1], chapter.number, verse.number)); } } } } } found } fn parse_book(book: &str) -> Result<(usize, &str), String> { // Go thru the list of books and see which // match the passed book str 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() { // there is one option for what book it is 1 => res[0], // there are >1 options for what book it is 2.. => { eprintln!("Err: Ambigious input '{book}', could be any of:"); for (_, i) in &res { eprintln!("\t{i}"); } return Err(format!("Ambigious input '{book}'")); } _ => { eprintln!("'{book}' is not a book"); return Err(format!("'{book}' is not a book")); } }; Ok((book_idx, book_name)) } fn parse_chapter<'a>(chapter_num: &str, book: &'a Book) -> Result<&'a Chapter, String> { let chapter = if let Ok(idx) = chapter_num.parse::() { if let Some(chapter) = book.get_chapter_by_index(idx) { chapter } else { return Err(format!("Chapter {idx} doesn't exist in book {}", BOOKS_IN_ORDER[book.number-1])); } } else { return Err(format!("Chapter number could not be parsed from {chapter_num}")); }; Ok(chapter) } pub fn get(book: &str, chap_and_ver: &str, bible: &Bible) -> Result { // 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_num = splits.next(); let verse_num = splits.next(); // ########################################### // Figure out what book they are talking about let (book_idx, book_name) = parse_book(book)?; let book = match bible.get_book_by_index(book_idx+1) { Some(s) => s, None => return Err(format!("Selected Bible {} doesn't contain book {book_name}", bible.translation_name)), }; // Figure out what chapter they are talking about let chapter = if let Some(ch) = chapter_num { parse_chapter(ch, book)? } else { return Err("No chapter number was passed".to_string()); }; // Get the verse in each Bible match verse_num { 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(); let translation = &bible.translation_name; buf += &format!("{style_bold}[{translation}] {style_underline}{book_name}:{start}-{end}{style_reset}:\n"); for num in start..=end { if let Some(verse) = chapter.get_verse_by_index(num) { buf += &format!( "{}: {verse}\n", verse.number ); } } return Ok(buf); } return Err(format!("Could not parse '{sn}' or '{en}' into numbers")); } // only one verse (Some(ver_num), None) => { if let Ok(num) = ver_num.parse::() { let mut buf = String::new(); if let Some(verse) = chapter.get_verse_by_index(num) { buf += &format!( "{style_bold}[{}] {style_underline}{book_name} {}:{}{style_reset}:\n{verse}\n", bible.translation_name, chapter.number, verse.number ); } return Ok(buf); } return Err(format!("Could not parse '{ver_num}' into a number")); } _ => { // couldn't parse verse return Err(format!("Couldn't parse '{:?}' or '{:?}' into verse numbers", start, end)); } } } None => { // only chapter let mut buf = String::new(); buf += &format!( "{style_bold}[{}] {style_underline}{book_name} {}{style_reset}:\n", bible.translation_name, chapter.number ); buf += &format!("{}", chapter); return Ok(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")] pub 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")] /// Genesis is book 1 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) } }