query_range/range/
utility.rs

1use std::ops::Range;
2use num::{PrimInt};
3
4// Utilities ------------------------------------------------------------------------------------- /
5
6/// Converts a string to title case (first letter capitalized and all the rest lower-case).
7pub fn to_title_case(content: &str) -> String {
8    if content.len() > 1 {
9        format!("{}{}", &content[0..1].to_uppercase(), &content[1..].to_lowercase())
10    } else if content.len() == 1 {
11        content[0..1].to_uppercase()
12    } else {
13        String::new()
14    }
15}
16
17/// Gets first range of given query in given content.
18pub fn get_range(query: &str, content: &str) -> Option<Range<usize>> {
19    let possible_start = content.find(query);
20    if let Some(start) = possible_start {
21        let end: usize = start + query.len();
22        if end > content.len() {
23            None
24        } else {
25            Some(start..end)
26        }
27    } else {
28        None
29    }
30}
31
32/// Enum to specify the direction of a shift, up or down with amount (magnitude).
33pub enum Shift<T> where T: PrimInt {
34    /// Shifts a number/range *up* by specified amount.
35    Up(T),
36    /// Shifts a number/range *down* by specified amount.
37    Down(T),
38}
39
40impl<T> Shift<T> where T: PrimInt {
41
42    /// Applies the shift to a single number, producing a new number shifted by that amount.
43    /// Returns `None` if any overflow occurs.
44    fn apply_to_number(&self, number: T) -> Option<T> {
45        match self {
46            Shift::Up(amount) => number.checked_add(amount),
47            Shift::Down(amount) => number.checked_sub(amount),
48        }
49    }
50
51    /// Applies the shift to a range, producing a new range shifted by that amount.
52    /// Returns `None` if any overflow occurs.
53    fn apply_to_range(&self, range: Range<T>) -> Option<Range<T>> {
54        let Range { start, end  } = range;
55        let (new_start, new_end) = match self {
56            Shift::Up(amount) => (start.checked_add(amount), end.checked_add(amount)),
57            Shift::Down(amount) => (start.checked_sub(amount), end.checked_sub(amount)),
58        };
59        if let (Some(new_start), Some(new_end)) = (new_start, new_end) {
60            Some(new_start..new_end)
61        } else {
62            None
63        }
64    }
65}
66
67/// Creates a new range with the start and end values shifted by the given amount.
68///
69/// ## Example:
70/// ```
71/// use query_range::{shift_range, Shift};
72///
73/// let test_str = "this is a test";
74///
75/// let range = 0..5;
76/// assert_eq!(shift_range(range, Shift::Up(2)), Some(2..7));
77///
78/// let range = 0..5;
79/// assert_eq!(shift_range(range, Shift::Up(20)), Some(20..25));
80/// ```
81pub fn shift_range<T>(range: Range<T>, shift: Shift<T>) -> Option<Range<T>> where T: PrimInt {
82    shift.apply_to_range(range)
83}
84
85/// Creates a new range with the start and end values shifted by the given amount *and* checks that
86/// the range is valid in the given content. If new range falls outside of the given content, `None`
87/// will be returned.
88///
89/// ## Example:
90/// ```
91/// use query_range::{shift_range_in_content, Shift};
92///
93/// let test_str = "this is a test";
94///
95/// let range = 0..5;
96/// assert_eq!(shift_range_in_content(range, Shift::Up(2), test_str), Some(2..7));
97///
98/// let range = 0..5;
99/// assert_eq!(shift_range_in_content(range, Shift::Up(20), test_str), None);
100/// ```
101pub fn shift_range_in_content<T>(range: Range<T>, shift: Shift<T>, content: &str) -> Option<Range<T>> where T: PrimInt {
102    let new_range = shift_range(range, shift);
103    if let Some(new_range) = new_range {
104        if is_within(content, &new_range) {
105            Some(new_range)
106        } else {
107            None
108        }
109    } else {
110        None
111    }
112}
113
114/// Checks if a closed range exists in given string content.
115///
116/// ## Example:
117/// ```
118/// use query_range::is_within;
119///
120/// let test_str = "this is a test";
121///
122/// let range = 0..2;
123/// assert_eq!(is_within(test_str, &range), true);
124///
125/// let range02 = 20..25;
126/// assert_eq!(is_within(test_str, &range02), false);
127/// ```
128pub fn is_within<T>(content: &str, range: &Range<T>) -> bool where T: PrimInt {
129    let range_end = range.end.to_usize();
130    if let Some(range_end) = range_end {
131        if range_end > content.len() {
132            false
133        } else {
134            true
135        }
136    } else {
137        false
138    }
139}
140
141// Tests ----------------------------------------------------------------------------------------- /
142
143#[cfg(test)]
144mod test {
145    use super::*;
146
147    #[test]
148    fn can_convert_to_title_case() {
149        assert_eq!(to_title_case("fooBarBaz"), "Foobarbaz");
150        assert_eq!(to_title_case("f"), "F");
151        assert_eq!(to_title_case(""), "");
152    }
153
154    #[test]
155    fn can_apply_a_shift_to_a_number() {
156        let number = 5;
157        let shift = Shift::Up(2);
158        assert_eq!(shift.apply_to_number(number), Some(7));
159        let number = 8;
160        let shift = Shift::Down(3);
161        assert_eq!(shift.apply_to_number(number), Some(5));
162    }
163
164    #[test]
165    fn can_apply_a_shift_to_a_range() {
166        let range = 0..5;
167        let shift = Shift::Up(2);
168        assert_eq!(shift.apply_to_range(range), Some(2..7));
169        let range = 4..7;
170        let shift = Shift::Down(3);
171        assert_eq!(shift.apply_to_range(range), Some(1..4));
172    }
173
174    #[test]
175    fn can_shift_a_range() {
176        let range = 0..5;
177        let new_range = shift_range(range, Shift::Up(2));
178        assert_eq!(new_range, Some(2..7));
179        assert_ne!(shift_range(new_range.unwrap(), Shift::Down(3)), Some(0..1));
180    }
181
182    #[test]
183    fn is_within_is_true_when_range_is_within() {
184        let test_str = "012345";
185        let range = 0..2;
186        assert_eq!(is_within(test_str, &range), true);
187    }
188
189    #[test]
190    fn is_within_is_false_when_range_end_is_longer_than_string() {
191        let test_str = "012345";
192        let range = 2..7;
193        assert_eq!(is_within(test_str, &range), false);
194    }
195}