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}