bible/src/bible.rs

242 lines
6.2 KiB
Rust

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::<Vec<(usize, &&str)>>();
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/<name>.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::<usize>() {
if let Some(chapter) = book.get_chapter_by_index(chap_num) {
if let Some(verse) = verse {
if let Ok(verse_num) = verse.trim().parse::<usize>() {
// 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::<usize>();
let end = range[1].parse::<usize>();
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<Testament>,
}
#[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)
}
}