bible/src/texts/bible.rs
2025-03-29 11:43:47 -06:00

317 lines
8.3 KiB
Rust

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<String> {
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<String> {
// 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::<Vec<(usize, &&str)>>();
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::<Vec<(&Bible,&Book)>>();
// Select the chapter in each Bible
let chapters = if let Some(chapter) = chapter {
if let Ok(ch_num) = chapter.parse::<usize>() {
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::<Vec<(&Bible, &Book, &Chapter)>>()
} 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::<usize>(), en.parse::<usize>()) {
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::<usize>() {
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<Testament>,
}
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<Book>,
}
#[derive(Deserialize)]
struct Book {
#[serde(rename = "@number")]
number: usize,
#[serde(rename = "chapter")]
chapters: Vec<Chapter>,
}
#[derive(Deserialize)]
struct Chapter {
#[serde(rename = "@number")]
number: usize,
#[serde(rename = "verse")]
verses: Vec<Verse>,
}
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)
}
}