added bible support

This commit is contained in:
Rushmore75 2025-03-25 15:19:42 -06:00
parent 6fdf966e13
commit 55c3e5b175
4 changed files with 250 additions and 25 deletions

3
.gitmodules vendored
View File

@ -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

1
Holy-Bible-XML-Format Submodule

@ -0,0 +1 @@
Subproject commit 69ac8b94c9781f25483905ee08192ee4bf982317

241
src/bible.rs Normal file
View File

@ -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::<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.parse::<usize>() {
if let Some(chapter) = book.get_chapter_by_index(chap_num) {
if let Some(verse) = verse {
if let Ok(verse_num) = verse.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!("{}", 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 {
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)
}
}

View File

@ -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::<Vec<String>>();
if let Some(query) = arg.get(1) {
if query.starts_with("H") | query.starts_with("h") {
@ -13,27 +12,8 @@ fn main() {
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);
}
}
}