333 lines
8.9 KiB
Rust
333 lines
8.9 KiB
Rust
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<String> {
|
|
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::<Vec<(usize, &&str)>>();
|
|
|
|
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::<usize>() {
|
|
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<String, 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_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::<usize>(), en.parse::<usize>()) {
|
|
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::<usize>() {
|
|
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<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")]
|
|
/// Genesis is book 1
|
|
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)
|
|
}
|
|
}
|