bible_lib/lib.rs
1/*
2 Bible Lib
3 .---.
4 '-. | | .-'
5 ___| |___
6 -= [ ] =-
7 `---. .---'
8 __||__ | | __||__
9 '-..-' | | '-..-'
10 || | | ||
11 ||_.-| |-,_||
12 .-"` `"`'` `"-.
13 .' '. Art by Joan Stark
14*/
15
16use std::{collections::HashMap, fmt::Display};
17
18use crate::error::BibleLibError;
19
20pub mod error;
21
22#[cfg(feature = "akjv")]
23const AKJV: &str = include_str!("bible_translations/akjv.txt");
24#[cfg(feature = "asv")]
25const ASV: &str = include_str!("bible_translations/asv.txt");
26#[cfg(feature = "erv")]
27const ERV: &str = include_str!("bible_translations/erv.txt");
28#[cfg(feature = "kjv")]
29const KJV: &str = include_str!("bible_translations/kjv.txt");
30
31/// Different Bible Translations
32/// provided by https://openbible.com/
33/// https://openbible.com/texts.htm
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum Translation {
36 /// American King James Version
37 #[cfg(feature = "akjv")]
38 AmericanKingJames,
39 /// American Standard Version
40 #[cfg(feature = "asv")]
41 AmericanStandard,
42 /// English Revised Version
43 #[cfg(feature = "erv")]
44 EnglishedRevised,
45 /// King James Version
46 #[cfg(feature = "kjv")]
47 KingJames,
48 /// For custom translations,
49 /// each line must be a verse formatted as: `Book Chapter:Verse Content`
50 /// See bible_translations/ for examples
51 ///
52 /// `name` is strictly for display purposes
53 ///
54 /// note: other translations are included in the binary at compile time,
55 /// but custom translations are read from the filesystem at runtime
56 Custom { name: String, path: String }
57}
58
59impl Translation {
60 #[doc(hidden)]
61 fn get_text(&self) -> Result<String, BibleLibError> {
62 match self {
63
64 Self::AmericanKingJames => {
65 Ok(AKJV.to_string())
66 }
67 Self::AmericanStandard => {
68 Ok(ASV.to_string())
69 }
70 Self::EnglishedRevised => {
71 Ok(ERV.to_string())
72 }
73 Self::KingJames => {
74 Ok(KJV.to_string())
75 }
76 Self::Custom { path, .. } => {
77 // ensure the file exists
78 if !std::path::Path::new(path).exists() {
79 return Err(BibleLibError::InvalidCustomTranslationFile);
80 }
81
82 // read the file and return the content
83 let result = std::fs::read_to_string(path);
84 match result {
85 Ok(content) => Ok(content),
86 Err(e) => Err(BibleLibError::IOError(e))
87 }
88 }
89 }
90 }
91}
92
93#[cfg(any(feature = "akjv", feature = "asv", feature = "erv", feature = "kjv"))]
94impl Default for Translation {
95 #[cfg(feature = "akjv")]
96 fn default() -> Self {
97 Self::AmericanKingJames
98 }
99 #[cfg(all(not(feature = "akjv"), feature = "asv"))]
100 fn default() -> Self {
101 Self::AmericanStandard
102 }
103 #[cfg(all(not(feature = "akjv"), not(feature = "asv"), feature = "erv"))]
104 fn default() -> Self {
105 Self::EnglishedRevised
106 }
107 #[cfg(all(not(feature = "akjv"), not(feature = "asv"), not(feature = "erv"), feature = "kjv"))]
108 fn default() -> Self {
109 Self::KingJames
110 }
111}
112
113impl Display for Translation {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 match self {
116 #[cfg(feature = "akjv")]
117 Self::AmericanKingJames => write!(f, "American King James Version"),
118 #[cfg(feature = "asv")]
119 Self::AmericanStandard => write!(f, "American Standard Version"),
120 #[cfg(feature = "erv")]
121 Self::EnglishedRevised => write!(f, "English Revised Version"),
122 #[cfg(feature = "kjv")]
123 Self::KingJames => write!(f, "King James Version"),
124 Self::Custom { name, .. } => write!(f, "Custom Translation: {}", name),
125 }
126 }
127}
128
129/// Struct representing a Bible verse lookup
130/// `book` is not case-sensitive
131/// `thru_verse` is optional and used for verse ranges like `John 3:16-18`
132/// # Example
133/// ```
134/// use bible_lib::{Bible, BibleLookup, Translation};
135///
136/// // get the bible translation
137/// let bible = Bible::new(Translation::KingJames).unwrap();
138/// // create a lookup for John 3:16
139/// let lookup = BibleLookup::new("John", 3, 16);
140/// // get the verse text
141/// let verse = bible.get_verse(lookup, false).unwrap();
142///
143/// // print the verse text
144/// println!("John 3:16: {}", verse);
145/// ```
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct BibleLookup {
148 pub book: String,
149 pub chapter: u32,
150 pub verse: u32,
151 pub thru_verse: Option<u32>,
152}
153
154impl BibleLookup {
155 /// Create a new BibleLookup instance (single verse)
156 /// `book` is not case-sensitive
157 /// # Example
158 /// ```
159 /// use bible_lib::BibleLookup;
160 ///
161 /// // create a lookup for John 3:16
162 /// let lookup = BibleLookup::new("John", 3, 16);
163 /// ```
164 pub fn new<S: Into<String>>(book: S, chapter: u32, verse: u32) -> Self {
165 let book = book.into();
166 let book = book.to_lowercase();
167 Self {
168 book,
169 chapter,
170 verse,
171 thru_verse: None,
172 }
173 }
174
175 /// Create a new BibleLookup instance (verse range)
176 /// # Example
177 /// ```
178 /// use bible_lib::BibleLookup;
179 ///
180 /// // create a lookup for Luke 23:39-43
181 /// let lookup = BibleLookup::new_range("Luke", 23, 39, 43);
182 /// ```
183 pub fn new_range<S: Into<String>>(book: S, chapter: u32, verse: u32, thru_verse: u32) -> Self {
184 let book = book.into();
185 let book = book.to_lowercase();
186 Self {
187 book,
188 chapter,
189 verse,
190 thru_verse: Some(thru_verse),
191 }
192 }
193
194 /// Detect Bible verses in a string
195 /// Requires the `detection` feature to be enabled
196 /// Can return multiple verses if more than one is found
197 /// # Example
198 /// ```
199 /// use bible_lib::{Bible, Translation, BibleLookup};
200 ///
201 /// // get the bible translation
202 /// let bible = Bible::new(Translation::default()).unwrap();
203 ///
204 /// // create the string to look for verses in
205 /// let text = "Show me John 3:16";
206 /// // detect verses in the string
207 /// let verses = BibleLookup::detect_from_string(text);
208 ///
209 /// // iterate through the found verses and print them
210 /// for verse in verses {
211 /// // get the verse text
212 /// let verse_text = bible.get_verse(verse.clone()).unwrap();
213 /// // print the verse text
214 /// println!("Found verse: {} - {}", verse, verse_text);
215 /// }
216 /// ```
217 #[cfg(feature = "detection")]
218 pub fn detect_from_string<S: Into<String>>(lookup: S) -> Vec<Self> {
219 let mut verses = Vec::new();
220
221 let lookup = lookup.into();
222 let text = lookup.to_lowercase();
223
224 //let regex = regex::Regex::new(r"\b(?:genesis|exodus|leviticus|numbers|deuteronomy|joshua|judges|ruth|1\s?samuel|2\s?samuel|1\s?kings|2\s?kings|1\s?chronicles|2\s?chronicles|ezra|nehemiah|esther|job|psalms|proverbs|ecclesiastes|song\sof\ssolomon|isaiah|jeremiah|lamentations|ezekiel|daniel|hosea|joel|amos|obadiah|jonah|micah|nahum|habakkuk|zephaniah|haggai|zechariah|malachi|matthew|mark|luke|john|acts|romans|1\s?corinthians|2\s?corinthians|galatians|ephesians|philippians|colossians|1\s?thessalonians|2\s?thessalonians|1\s?timothy|2\s?timothy|titus|philemon|hebrews|james|1\s?peter|2\s?peter|1\s?john|2\s?john|3\s?john|jude|revelation)\s+\d+:\d+\b").unwrap();
225 let regex = regex::Regex::new(r"\b(?:genesis|exodus|leviticus|numbers|deuteronomy|joshua|judges|ruth|1\s?samuel|2\s?samuel|1\s?kings|2\s?kings|1\s?chronicles|2\s?chronicles|ezra|nehemiah|esther|job|psalms|proverbs|ecclesiastes|song\sof\ssolomon|isaiah|jeremiah|lamentations|ezekiel|daniel|hosea|joel|amos|obadiah|jonah|micah|nahum|habakkuk|zephaniah|haggai|zechariah|malachi|matthew|mark|luke|john|acts|romans|1\s?corinthians|2\s?corinthians|galatians|ephesians|philippians|colossians|1\s?thessalonians|2\s?thessalonians|1\s?timothy|2\s?timothy|titus|philemon|hebrews|james|1\s?peter|2\s?peter|1\s?john|2\s?john|3\s?john|jude|revelation)\s+\d+:\d+(?:-\d+)?\b").unwrap();
226
227 for instance in regex.find_iter(&text) {
228 let instance = instance.as_str();
229 // to handle cases like `1 samuel` and `Song of Solomon`, split by ':' first and then split by whitespace
230 let mut parts = instance.split(':');
231 // split the first part by whitespace
232 let book_chapter = parts.next().unwrap().split_whitespace();
233 let count = book_chapter.clone().count();
234 let chapter = book_chapter.clone().last().unwrap().parse::<u32>().unwrap();
235 let book = book_chapter.take(count - 1).collect::<Vec<&str>>().join(" ").to_lowercase();
236
237 // handle cases where the verse is a range (i.e. `1-3`)
238 let verse_part = parts.next().unwrap();
239 if verse_part.contains('-') {
240 let verse_split = verse_part.split('-');
241 let verse = verse_split.clone().next().unwrap().parse::<u32>().unwrap();
242 let thru_verse = verse_split.clone().last().unwrap().parse::<u32>().unwrap();
243 verses.push(BibleLookup {
244 book,
245 chapter,
246 verse,
247 thru_verse: Some(thru_verse),
248 });
249 } else {
250 let verse = verse_part.parse::<u32>().unwrap();
251 verses.push(BibleLookup {
252 book,
253 chapter,
254 verse,
255 thru_verse: None,
256 });
257 }
258 }
259
260 verses
261 }
262
263 /// Capitalize the first letter of each word in the book name
264 /// Handles cases like `1 samuel` and `song of solomon`
265 /// This is used because book names are stored in lowercase for easier lookup
266 /// # Example
267 /// ```
268 /// use bible_lib::BibleLookup;
269 ///
270 /// // capitalize book names
271 /// let book1 = BibleLookup::capitalize_book(&"john".to_string());
272 /// let book2 = BibleLookup::capitalize_book(&"1 samuel".to_string());
273 ///
274 /// // print the capitalized book names
275 /// println!("Capitalized Book 1: {}", book1); // John
276 /// println!("Capitalized Book 2: {}", book2); // 1 Samuel
277 ///
278 /// ```
279 pub fn capitalize_book(name: &String) -> String {
280 // capitalize the first letter of each word in the book name
281 // Split the input string by whitespace into words
282 name.split_whitespace()
283 // For each word, apply the following transformation
284 .map(|word| {
285 // Convert the word into characters
286 let mut chars = word.chars();
287 // If there's a first character, convert it to uppercase and concatenate it with the rest of the characters
288 if let Some(first_char) = chars.next() {
289 if first_char.is_numeric() {
290 // If the first character is numeric, leave it unchanged
291 first_char.to_string() + &chars.collect::<String>()
292 } else {
293 // If the first character is not numeric, capitalize it
294 first_char.to_uppercase().chain(chars).collect::<String>()
295 }
296 } else {
297 // If the word is empty, return an empty string
298 String::new()
299 }
300 })
301 // Collect the transformed words back into a single string, separated by whitespace
302 .collect::<Vec<String>>().join(" ")
303 }
304}
305
306impl Display for BibleLookup {
307 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308 if let Some(thru_verse) = self.thru_verse {
309 write!(f, "{} {}:{}-{}", Self::capitalize_book(&self.book), self.chapter, self.verse, thru_verse)
310 } else {
311 write!(f, "{} {}:{}", Self::capitalize_book(&self.book), self.chapter, self.verse)
312 }
313 }
314}
315
316/// Main Bible struct
317/// Stores the verses of the Bible for interfacing
318/// # Example
319/// ```
320/// use bible_lib::{Bible, Translation, BibleLookup};
321///
322/// // get the bible translation
323/// let bible = Bible::new(Translation::AmericanStandard).unwrap();
324///
325/// // create a lookup for John 3:16
326/// let lookup = BibleLookup::new("John", 3, 16);
327/// // get the verse text
328/// let verse = bible.get_verse(lookup, false).unwrap();
329///
330/// // print the verse text
331/// println!("John 3:16: {}", verse);
332/// ```
333#[derive(Debug, Clone)]
334pub struct Bible {
335 translation: Translation,
336 pub verses: HashMap<String /* Book */,
337 HashMap<u32 /* Chapter */,
338 HashMap<u32 /* Verse */, String /* Text */>>>,
339}
340
341impl Bible {
342
343 #[doc(hidden)]
344 fn parse_text(lines: &String) -> HashMap<String, HashMap<u32, HashMap<u32, String>>> {
345 let mut verses = HashMap::new();
346
347 for line in lines.lines() {
348 // to handle cases like `1 samuel` and `Song of Solomon`, split by ':' first and then split by whitespace
349 let mut parts = line.split(':');
350 // split the first part by whitespace
351 let book_chapter = parts.next().unwrap().split_whitespace();
352 let count = book_chapter.clone().count();
353 let chapter = book_chapter.clone().last().unwrap().parse::<u32>().unwrap();
354 let book = book_chapter.take(count - 1).collect::<Vec<&str>>().join(" ").to_lowercase();
355
356 let verse_text = parts.next().unwrap().split_whitespace();
357 let verse = verse_text.clone().next().unwrap().parse::<u32>().unwrap();
358 let text = verse_text.clone().skip(1).collect::<Vec<&str>>().join(" ");
359
360 if !verses.contains_key(&book) {
361 verses.insert(book.to_string(), HashMap::new());
362 }
363 if !verses.get_mut(&book).unwrap().contains_key(&chapter) {
364 verses.get_mut(&book).unwrap().insert(chapter, HashMap::new());
365 }
366 verses.get_mut(&book).unwrap().get_mut(&chapter).unwrap().insert(verse, text.to_string());
367 }
368
369 verses
370 }
371
372 /// Create a new Bible instance with the specified translation
373 pub fn new(translation: Translation) -> Result<Self, BibleLibError> {
374 let text = translation.get_text()?;
375 let verses = Self::parse_text(&text);
376 Ok(Self {
377 translation,
378 verses,
379 })
380 }
381
382 /// Get the current translation of the Bible instance
383 pub fn get_translation(&self) -> &Translation {
384 &self.translation
385 }
386
387 #[doc(hidden)]
388 fn replace_superscript(s: String) -> String {
389 s.chars().map(|c| {
390 match c {
391 '0' => '⁰',
392 '1' => '¹',
393 '2' => '²',
394 '3' => '³',
395 '4' => '⁴',
396 '5' => '⁵',
397 '6' => '⁶',
398 '7' => '⁷',
399 '8' => '⁸',
400 '9' => '⁹',
401 _ => c,
402 }
403 }).collect()
404 }
405
406 /// Get the text of a verse or range of verses
407 /// `use_superscripts` adds superscript verse numbers for better readability
408 /// Returns an error if the verse or chapter is not found
409 /// # Example
410 /// ```
411 /// use bible_lib::{Bible, BibleLookup, Translation};
412 ///
413 /// // get the bible translation
414 /// let bible = Bible::new(Translation::AmericanStandard).unwrap();
415 /// // create a lookup for John 3:16
416 /// let lookup = BibleLookup::new("John", 3, 16);
417 /// // get the verse text
418 /// let verse = bible.get_verse(lookup, false).unwrap();
419 ///
420 /// // print the verse text
421 /// println!("John 3:16: {}", verse);
422 /// ```
423 pub fn get_verse(&self, lookup: BibleLookup, use_superscripts: bool) -> Result<String, BibleLibError> {
424 // multiple verse lookup
425 if let Some(thru_verse) = lookup.thru_verse {
426 let mut verse_text = String::new();
427
428 // iterate through the verses
429 for verse in lookup.verse..=thru_verse {
430 let Some(chapters) = self.verses.get(&lookup.book) else {
431 return Err(BibleLibError::BookNotFound);
432 };
433 let Some(verses) = chapters.get(&lookup.chapter) else {
434 return Err(BibleLibError::ChapterNotFound);
435 };
436 let Some(text) = verses.get(&verse) else {
437 return Err(BibleLibError::VerseNotFound);
438 };
439
440 if use_superscripts {
441 verse_text.push_str(&format!("{}{} ", Self::replace_superscript(verse.to_string()), text));
442 } else {
443 verse_text.push_str(text);
444 }
445 }
446 return Ok(verse_text.trim().to_string());
447 }
448
449 // single verse lookup
450 let Some(chapters) = self.verses.get(&lookup.book) else {
451 return Err(BibleLibError::BookNotFound);
452 };
453 let Some(verses) = chapters.get(&lookup.chapter) else {
454 return Err(BibleLibError::ChapterNotFound);
455 };
456 let Some(text) = verses.get(&lookup.verse) else {
457 return Err(BibleLibError::VerseNotFound);
458 };
459
460 if use_superscripts {
461 Ok(format!("{}{}", Self::replace_superscript(lookup.verse.to_string()), text))
462 } else {
463 Ok(text.to_string())
464 }
465 }
466
467 /// Get the text of an entire chapter as a string
468 /// `use_superscripts` adds superscript verse numbers for better readability
469 /// Returns an error if the chapter is not found
470 /// # Example
471 /// ```
472 /// use bible_lib::{Bible, BibleLookup, Translation};
473 ///
474 /// // get the bible translation
475 /// let bible = Bible::new(Translation::EnglishedRevised).unwrap();
476 /// // get the text of Isaiah chapter 53
477 /// let chapter_text = bible.get_chapter("Isaiah", 53, true).unwrap();
478 ///
479 /// // print the chapter text
480 /// println!("Isaiah 53: {}", chapter_text);
481 /// ```
482 pub fn get_chapter(&self, book: &str, chapter: u32, use_superscripts: bool) -> Result<String, BibleLibError> {
483 let mut chapter_text = String::new();
484 // sort the verses by verse number
485 let Some(chapters) = self.verses.get(book) else {
486 return Err(BibleLibError::BookNotFound);
487 };
488 let Some(verses) = chapters.get(&chapter) else {
489 return Err(BibleLibError::ChapterNotFound);
490 };
491 let mut verses = verses.iter().collect::<Vec<(&u32, &String)>>();
492 verses.sort_by(|a, b| a.0.cmp(b.0));
493 for (verse, text) in verses {
494 let verse_designation = Self::replace_superscript(verse.to_string());
495 if use_superscripts {
496 chapter_text.push_str(&format!("{}{} ", verse_designation, text));
497 } else {
498 chapter_text.push_str(&format!("{} ", text));
499 }
500 }
501 Ok(chapter_text)
502 }
503
504 /// Get a list of all books in the Bible
505 /// # Example
506 /// ```
507 /// use bible_lib::{Bible, Translation};
508 ///
509 /// // get the bible translation
510 /// let bible = Bible::new(Translation::default()).unwrap();
511 ///
512 /// // get the list of books
513 /// let books = bible.get_books();
514 /// // print the list of books
515 /// println!("Books in the Bible: {:?}", books);
516 /// ```
517 pub fn get_books(&self) -> Vec<String> {
518 self.verses.keys().map(|s| s.to_string()).collect()
519 }
520
521 /// Get a list of all books in the Bible, sorted in canonical order
522 /// # Example
523 /// ```
524 /// use bible_lib::{Bible, Translation};
525 ///
526 /// // get the bible translation
527 /// let bible = Bible::new(Translation::default()).unwrap();
528 ///
529 /// // get the list of books
530 /// let books = bible.get_sorted_books();
531 /// // print the list of books
532 /// println!("Books in the Bible: {:?}", books);
533 /// ```
534 pub fn get_sorted_books(&self) -> Vec<String> {
535 let mut books = self.get_books();
536 let canonical_order = vec![
537 "genesis", "exodus", "leviticus", "numbers", "deuteronomy",
538 "joshua", "judges", "ruth", "1 samuel", "2 samuel",
539 "1 kings", "2 kings", "1 chronicles", "2 chronicles",
540 "ezra", "nehemiah", "esther", "job", "psalms", "psalm", // double entry for psalms because some translations use singular
541 "proverbs", "ecclesiastes", "song of solomon",
542 "isaiah", "jeremiah", "lamentations", "ezekiel",
543 "daniel", "hosea", "joel", "amos", "obadiah",
544 "jonah", "micah", "nahum", "habakkuk", "zephaniah",
545 "haggai", "zechariah", "malachi", "matthew",
546 "mark", "luke", "john", "acts", "romans",
547 "1 corinthians", "2 corinthians", "galatians",
548 "ephesians", "philippians", "colossians",
549 "1 thessalonians", "2 thessalonians",
550 "1 timothy", "2 timothy", "titus",
551 "philemon", "hebrews", "james",
552 "1 peter", "2 peter", "1 john",
553 "2 john", "3 john", "jude",
554 "revelation"
555 ];
556 books.sort_by_key(|book| {
557 canonical_order.iter().position(|&b| b == book.as_str()).unwrap_or(usize::MAX)
558 });
559 books
560 }
561
562 /// Get a list of all chapters in a book
563 /// # Example
564 /// ```
565 /// use bible_lib::{Bible, Translation};
566 ///
567 /// // get the bible translation
568 /// let bible = Bible::new(Translation::default()).unwrap();
569 ///
570 /// // get the list of chapters in Revelation
571 /// let chapters = bible.get_chapters("Revelation").unwrap();
572 /// // print the list of chapters
573 /// println!("Chapters in Revelation: {:?}", chapters);
574 /// ```
575 pub fn get_chapters(&self, book: &str) -> Result<Vec<u32>, BibleLibError> {
576 if let Some(chapters) = self.verses.get(book).map(|chapters| chapters.keys().map(|c| *c).collect()) {
577 Ok(chapters)
578 } else {
579 Err(BibleLibError::BookNotFound)
580 }
581 }
582
583 /// Get a list of all verses in a chapter of a book
584 /// # Example
585 /// ```
586 /// use bible_lib::{Bible, Translation};
587 ///
588 /// // get the bible translation
589 /// let bible = Bible::new(Translation::default()).unwrap();
590 ///
591 /// // get the list of verses in John chapter 3
592 /// let verses = bible.get_verses("John", 3).unwrap();
593 /// // print the list of verses
594 /// println!("Verses in John 3: {:?}", verses);
595 /// ```
596 pub fn get_verses(&self, book: &str, chapter: u32) -> Result<Vec<u32>, BibleLibError> {
597 if let Some(verses) = self.verses.get(book)
598 .and_then(|chapters| chapters.get(&chapter))
599 .map(|verses| verses.keys().map(|v| *v).collect()) {
600 Ok(verses)
601 } else {
602 Err(BibleLibError::ChapterNotFound)
603 }
604 }
605
606 /// Get the maximum verse number in a chapter of a book
607 pub fn get_max_verse(&self, book: &str, chapter: u32) -> Result<u32, BibleLibError> {
608 if let Some(verses) = self.verses.get(book)
609 .and_then(|chapters| chapters.get(&chapter)) {
610 if let Some(max_verse) = verses.keys().max() {
611 Ok(*max_verse)
612 } else {
613 Err(BibleLibError::ChapterNotFound)
614 }
615 } else {
616 Err(BibleLibError::ChapterNotFound)
617 }
618 }
619
620 /// Get the maximum chapter number in a chapter of a book
621 pub fn get_max_chapter(&self, book: &str) -> Result<u32, BibleLibError> {
622 if let Some(chapters) = self.verses.get(book) {
623 if let Some(max_chapter) = chapters.keys().max() {
624 Ok(*max_chapter)
625 } else {
626 Err(BibleLibError::BookNotFound)
627 }
628 } else {
629 Err(BibleLibError::BookNotFound)
630 }
631 }
632
633 /// Get a random verse from the Bible
634 /// Requires the `random` feature to be enabled
635 /// # Example
636 /// ```
637 /// use bible_lib::{Bible, Translation};
638 ///
639 /// // get the bible translation
640 /// let bible = Bible::new(Translation::default()).unwrap();
641 ///
642 /// // get a random verse
643 /// let random_verse = bible.random_verse();
644 /// // get the verse text
645 /// let verse_text = bible.get_verse(random_verse.clone(), false).unwrap();
646 /// // print the random verse
647 /// println!("Random Verse: {} - {}", random_verse, verse_text);
648 /// ```
649 #[cfg(feature = "random")]
650 pub fn random_verse(&self) -> BibleLookup {
651 use rand::seq::IteratorRandom;
652 let mut rng = rand::rng();
653 let book = self.verses.keys().choose(&mut rng).unwrap().to_string();
654 let chapters = self.verses.get(&book).unwrap();
655 let chapter = chapters.keys().choose(&mut rng).unwrap().to_owned();
656 let verses = chapters.get(&chapter).unwrap();
657 let verse = verses.keys().choose(&mut rng).unwrap().to_owned();
658 BibleLookup {
659 book,
660 chapter,
661 verse,
662 thru_verse: None,
663 }
664 }
665
666}