From 55c3e5b1754d3d6341dfcadd22232594f5a4a23f Mon Sep 17 00:00:00 2001 From: Rushmore75 Date: Tue, 25 Mar 2025 15:19:42 -0600 Subject: [PATCH] added bible support --- .gitmodules | 3 + Holy-Bible-XML-Format | 1 + src/bible.rs | 241 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 30 +----- 4 files changed, 250 insertions(+), 25 deletions(-) create mode 160000 Holy-Bible-XML-Format create mode 100644 src/bible.rs diff --git a/.gitmodules b/.gitmodules index 3a1572e..d852986 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "HebrewLexicon"] path = HebrewLexicon url = https://github.com/openscriptures/HebrewLexicon.git +[submodule "Holy-Bible-XML-Format"] + path = Holy-Bible-XML-Format + url = https://github.com/Beblia/Holy-Bible-XML-Format.git diff --git a/Holy-Bible-XML-Format b/Holy-Bible-XML-Format new file mode 160000 index 0000000..69ac8b9 --- /dev/null +++ b/Holy-Bible-XML-Format @@ -0,0 +1 @@ +Subproject commit 69ac8b94c9781f25483905ee08192ee4bf982317 diff --git a/src/bible.rs b/src/bible.rs new file mode 100644 index 0000000..53b1f3c --- /dev/null +++ b/src/bible.rs @@ -0,0 +1,241 @@ +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.parse::() { + if let Some(chapter) = book.get_chapter_by_index(chap_num) { + if let Some(verse) = verse { + if let Ok(verse_num) = verse.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!("{}", 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 { + writeln!(f, "{style_bold}{style_underline}{}{style_reset}", self.number)?; + for v in &self.verses { + writeln!(f, "|\t{}", 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) + } +} + diff --git a/src/main.rs b/src/main.rs index ea46c7c..ade4dc8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ -use inline_colorization::*; -use std::{env, io::{self, Write}}; +use std::env; mod strong; +mod bible; fn main() { - let arg = env::args().collect::>(); if let Some(query) = arg.get(1) { if query.starts_with("H") | query.starts_with("h") { @@ -12,28 +11,9 @@ fn main() { print!("{found}"); return; } - } - } - - // No input args, go into interactive mode - let stdin = io::stdin(); - let mut stdout = io::stdout(); - loop { - print!("{color_bright_white}{style_bold}Search>{style_reset} "); - let _ = stdout.flush(); - - let mut buf = String::new(); - stdin.read_line(&mut buf).unwrap(); - let search = buf.trim().to_string(); - - if search.starts_with("H") { - if let Some(entry) = strong::get(&search) { - println!(""); - print!("{}", entry); - println!(""); - } - } else { - println!("Not sure what you're looking for..."); + } + if let Some(second_query) = arg.get(2) { + bible::get(query,second_query); } } }