est/parsers/
surround.rs

1use std::borrow::{Borrow, Cow};
2use crate::parsers::scripture::RE;
3
4use crate::{
5    locales::{nwt_en::Site, BibleError},
6    url::Url,
7};
8
9use super::scripture::Bible;
10
11/// _ScriptSlice_ type describes as a tuple the begining and ending index plus one for a scripture found in a string.
12///
13/// Take the following string as an example, "Scripture Mathrew 3:16."
14/// The `ScriptSlice` will be as follows:
15/// - Start = 10
16/// - End = 22 (21 + 1)
17pub type ScriptSlice = (Start, End);
18type Start = usize;
19type End = usize;
20
21/// The _ScriptureCollection_ is a vector contain scruptures. TODO: Fix docs
22pub type ScriptureCollection = Vec<String>;
23
24#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy)]
25/// _Elements_ is a struct that descibes the _elements_ that will be placed before and after a scripture.
26/// For example, if the elements, `<strong>` and `</strong>` were to surround a scripture then `<strong>` would be
27/// the prefix with a prefix length of `5` and `</strong>` would be the postfix with a length of `6`. 
28/// The prefix and postfix lengths are important to correctly calculate the insertion of the lements before 
29/// and after the scripture(s).
30struct Elements<'a> {
31    /// The optional prefix string that will be inserted before a scripture.
32    prefix: Option<&'a str>,
33    /// The lenth of chareters in the prefix.
34    prefix_len: Option<usize>,
35    /// The optional postfix string that will be inserted after a scripture.
36    postfix: Option<&'a str>,
37    /// The lenth of chareters in the postfix.
38    postfix_len: Option<usize>,
39}
40#[derive(Debug,PartialEq, Eq, PartialOrd, Ord, Clone)]
41/// _Locations_ contains the start and end indexes of all the scriptures found in the string.
42pub struct Locations{
43    /// The start and end indexes of all the scriptures found in the string passed in.
44    pub slices: Vec<ScriptSlice>,
45    /// The original string that was passed in.
46    pub string: String,
47}
48
49
50
51#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
52/// The _Script_ struct describes properties needed for sucessfully wrapping a scripture with _elements_.
53pub struct Script<'a> {
54    /// The _elements_ that will wrap around the scripture. i.e. the _prefix_ and _postfix_.
55    elements: Elements<'a>,
56    /// Both the start and end (+1) index for the scripture(s) within the string to be able to locate where the scriptures are found within the string.
57    slices: Vec<ScriptSlice>,
58    /// The full string that was passed into the library that contains the scripture.
59    string: String,
60}
61
62#[allow(unused_variables)]
63impl<'a> Script<'a> {
64    /// Find all _potential_ scriptures in a string and return the beginning index and length.
65    /// Will accept `&str` or `String` types.
66    pub(crate) fn new<S>(text: S) -> Self
67    where
68        S: Into<String> + Clone,
69    {
70        let re: &regex::Regex = &RE;
71
72        let text_to_str: Cow<str> = Cow::from(text.clone().into());
73        let matches = re.find_iter(Cow::borrow(&text_to_str));
74
75        let mut scrip_slices: Vec<ScriptSlice> = Vec::new();
76        for script in matches {
77            scrip_slices.push((script.start(), script.end()));
78        }
79
80        Self {
81            string: text.into(),
82            slices: scrip_slices,
83            elements: Elements {
84                ..Default::default()
85            },
86        }
87    }
88
89    /// The prefix to be added before the scripture.
90    pub fn prefix(mut self, prefix: &'a str) -> Self {
91        self.elements.prefix = Some(prefix);
92        self
93    }
94
95    /// The postfix to be added after the scripture.
96    pub fn postfix(mut self, postfix: &'a str) -> Self {
97        self.elements.postfix = Some(postfix);
98        self
99    }
100
101    #[allow(dead_code)]
102    fn is_prefix(&self) -> bool {
103        self.elements.prefix.is_some()
104    }
105
106    #[allow(dead_code)]
107    fn is_postfix(&self) -> bool {
108        self.elements.postfix.is_some()
109    }
110
111    /// The surround method adds a prefix and postfix when the corresponding methods are used.
112    /// `surround()` does not verify if a captured _scripture_ is valid.
113    pub(crate) fn surround(mut self) -> Self {
114        // .rev method is used to avoid dealing with the changing size of the string.
115        for item in self.slices.iter().rev() {
116            self.string.insert_str(
117                item.0 + (item.1 - item.0),
118                self.elements
119                    .postfix
120                    .map_or("", |postfix_value| postfix_value),
121            );
122
123            self.string.insert_str(
124                item.0,
125                self.elements.prefix.map_or("", |prefix_value| prefix_value),
126            );
127        }
128
129        self
130    }
131
132    /// Returns the original string with URL markup for all scriptures.
133    pub(crate) fn url(mut self, site: &Site) -> Result<Self, BibleError> {
134        // .rev() method is used to avoid dealing with the changing size of the string as new characters are added.
135        for (start, end) in self.slices.iter().rev() {
136            let verse_slice: String = self.get_from_slice(&(*start, *end));
137            let bible: Bible = Bible::parse(verse_slice.as_str())?;
138            let url: String = site.get_url(&bible)?;
139
140            self.string
141                .insert_str(*start + (*end - *start), format!("]({})", url).as_str());
142
143            self.string.insert(*start, '[');
144        }
145
146        Ok(self)
147    }
148
149    /// Returns the text field of the Script struct.
150    pub(crate) fn get_text(self) -> String {
151        self.string
152    }
153
154    // Returns a `String` to avoid borrowing headaches
155    pub(crate) fn get_from_slice(&self, slice: &(usize, usize)) -> String {
156        let clone_obj: Script<'a> = self.clone();
157        let text: String = clone_obj.get_text();
158
159        text[slice.0..slice.1].to_owned()
160    }
161
162    /// Returns a collection of scriptures found in the string passed in.
163    pub(crate) fn get_scriptures(&self) -> Result<ScriptureCollection, BibleError> {
164        let mut scripture_list:Vec<String>  = Vec::new();
165
166        for i in self.slices.iter() {
167            let scripture_str: &str = self.string.get(i.0..i.1).unwrap();
168
169            // We need to validate if the found slice contains a valid Bible book name.
170            if Bible::parse(scripture_str).is_ok() {
171                scripture_list.push(scripture_str.to_string());
172            }
173        }
174
175        Ok(scripture_list)
176    }
177
178    /// Returns the index of the start and end of each scripture found and also the original string.
179    pub(crate) fn get_locations(&self) -> Locations {
180        Locations { slices: self.slices.clone(), string: self.string.clone() }
181    }
182
183}
184
185#[cfg(test)]
186mod test {
187    use super::*;
188    use pretty_assertions::assert_eq;
189
190    #[test]
191    fn is_prefix() {
192        let text: &str = "Testing";
193        let result_true: Script = Script::new(text).prefix("true");
194        let result_false: Script = Script::new(text); // Defaults to `None`.
195
196        assert!(result_true.is_prefix());
197        assert!(!result_false.is_prefix());
198    }
199
200    #[test]
201    fn is_postfix() {
202        let text: &str = "Testing";
203        let result_true: Script = Script::new(text).postfix("true");
204        let result_false: Script = Script::new(text); // Defaults to `None`.
205
206        assert!(result_true.is_postfix());
207        assert!(!result_false.is_postfix());
208    }
209
210    #[test]
211    fn find_slice_1() {
212        let text: &str = "A popular scripture is John 3:16.";
213        let expect: Vec<(usize, usize)> = vec![(23, 32)];
214        let result: Script = Script::new(text);
215        assert_eq!(result.slices, expect);
216    }
217
218    #[test]
219    fn find_slice_2() {
220        let text: &str = "John 3:16 and Matthew 24:14";
221        let expect: Vec<(usize, usize)> = vec![(0, 9), (14, 27)];
222        let result: Script = Script::new(text);
223        assert_eq!(result.slices, expect);
224    }
225
226    #[test]
227    fn find_slice_3() {
228        let text: &str = "John 3:16, Mathew 24:14, and Psalms 83:18 are commonly used.";
229        let result: Script = Script::new(text);
230        assert_eq!(result.slices, vec![(0, 9), (11, 23), (29, 41)]);
231    }
232
233    #[test]
234    fn single_scripture() {
235        let text: &str = "John 3:16";
236        let expect: &str = "[John 3:16]";
237        let got: String = Script::new(text)
238            .prefix("[")
239            .postfix("]")
240            .surround()
241            .get_text();
242        assert_eq!(got, expect);
243    }
244
245    #[test]
246    // Tests if an empty "" is added if prefix is `None`.
247    fn add_element_prefix_single_none() {
248        let text: &str = "Another popular scripture is John 3:16, it's quoted often.";
249        let expect: &str = "Another popular scripture is John 3:16, it's quoted often.";
250        let result: String = Script::new(text).surround().get_text();
251        assert_eq!(result, expect)
252    }
253
254    #[test]
255    fn add_element_prefix_single() {
256        let text: &str = "Another popular scripture is John 3:16, it's quoted often.";
257        let expect: &str = "Another popular scripture is [John 3:16, it's quoted often.";
258        let result: String = Script::new(text).prefix("[").surround().get_text();
259        assert_eq!(result, expect);
260    }
261
262    #[test]
263    fn add_element_prefix_single_to_string() {
264        let text: String = "Another popular scripture is John 3:16, it's quoted often.".to_string();
265        let expect: &str = "Another popular scripture is [John 3:16, it's quoted often.";
266        let result: String = Script::new(text).prefix("[").surround().get_text();
267        assert_eq!(result, expect);
268    }
269
270    #[test]
271    // Tests to make sure the prefix len is taken into consideration for each scripture found.
272    fn add_element_prefix_multi() {
273        let text: &str =
274            "Two popular scripture are John 3:16 and Matthew 24:14. They are quoted often.";
275        let expect: &str = "Two popular scripture are [prefix]John 3:16 and [prefix]Matthew 24:14. They are quoted often.";
276        let got: String = Script::new(text).prefix("[prefix]").surround().get_text();
277        assert_eq!(got, expect)
278    }
279
280    #[test]
281    fn add_element_postfix_single() {
282        let text: &str = "Another popular scripture is John 3:16, it's quoted often.";
283        let expect: &str = "Another popular scripture is John 3:16[postfix], it's quoted often.";
284        let got: String = Script::new(text).postfix("[postfix]").surround().get_text();
285        assert_eq!(got, expect)
286    }
287
288    #[test]
289    fn add_element_postfix_multi() {
290        let text: &str =
291            "Two popular scriptures are John 3:16 and Mathew 24:14. They are quoted often.";
292        let expect: &str = "Two popular scriptures are John 3:16[postfix] and Mathew 24:14[postfix]. They are quoted often.";
293        let got: String = Script::new(text).postfix("[postfix]").surround().get_text();
294        assert_eq!(got, expect)
295    }
296
297    #[test]
298    fn single_url() {
299        let text: &str = "A popular scriptures is John 3:16. It is quoted often.";
300        let expect: String = "A popular scriptures is [John 3:16](https://www.jw.org/en/library/bible/study-bible/books/john/3/#v43003016). It is quoted often.".to_string();
301        let got: String = Script::new(text).url(&Site::JwOrg).unwrap().get_text();
302        assert_eq!(got, expect)
303    }
304
305    #[test]
306    fn single_url_abbr() {
307        let text: &str = "A popular scriptures is Joh 3:16. It is quoted often.";
308        let expect: String = "A popular scriptures is [Joh 3:16](https://www.jw.org/en/library/bible/study-bible/books/john/3/#v43003016). It is quoted often.".to_string();
309        let got: String = Script::new(text).url(&Site::JwOrg).unwrap().get_text();
310        assert_eq!(got, expect)
311    }
312
313    #[test]
314    fn get_scripture() {
315        let text: &str =
316            "Two popular scriptures are John 3:16 and Matthew 24:14. They are quoted often.";
317        let expect: Vec<&str> = vec!["John 3:16", "Matthew 24:14"];
318        let got: Vec<String> = Script::new(text).get_scriptures().unwrap();
319        assert_eq!(got, expect)
320    }
321
322    #[test]
323    fn get_from_slice() {
324        let text: &str =
325            "Two popular scriptures are John 3:16 and Matthew 24:14. They are quoted often.";
326        let expect = "popular".to_string();
327        let got: String = Script::new(text).get_from_slice(&(4, 11));
328        assert_eq!(got, expect)
329    }
330
331    #[test]
332    fn get_locations1(){
333        let text: &str = "John 3:16 is well known.";
334        let expect: Locations = Locations{ slices: vec![(0, 9)], string: text.into() };
335        let got: Locations = Script::new(text).get_locations();
336        assert_eq!(got, expect);
337    }
338
339    #[test]
340    fn get_locations2(){
341        let text: &str = "John 3:16 is well known and if you know it, then it's easy to remember Timothy 3:16, another important scripture.";
342        let expect: Locations = Locations{ slices: vec![(0, 9), (71,83)], string: text.into() };
343        let got: Locations = Script::new(text).get_locations();
344        assert_eq!(got, expect);
345    }
346}