bat_impl/
line_range.rs

1use crate::error::*;
2
3#[derive(Debug, Clone)]
4pub struct LineRange {
5    lower: usize,
6    upper: usize,
7}
8
9impl Default for LineRange {
10    fn default() -> LineRange {
11        LineRange {
12            lower: usize::min_value(),
13            upper: usize::max_value(),
14        }
15    }
16}
17
18impl LineRange {
19    pub fn new(from: usize, to: usize) -> Self {
20        LineRange {
21            lower: from,
22            upper: to,
23        }
24    }
25
26    pub fn from(range_raw: &str) -> Result<LineRange> {
27        LineRange::parse_range(range_raw)
28    }
29
30    fn parse_range(range_raw: &str) -> Result<LineRange> {
31        let mut new_range = LineRange::default();
32
33        if range_raw.bytes().next().ok_or("Empty line range")? == b':' {
34            new_range.upper = range_raw[1..].parse()?;
35            return Ok(new_range);
36        } else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
37            new_range.lower = range_raw[..range_raw.len() - 1].parse()?;
38            return Ok(new_range);
39        }
40
41        let line_numbers: Vec<&str> = range_raw.split(':').collect();
42        match line_numbers.len() {
43            1 => {
44                new_range.lower = line_numbers[0].parse()?;
45                new_range.upper = new_range.lower;
46                Ok(new_range)
47            }
48            2 => {
49                new_range.lower = line_numbers[0].parse()?;
50                let first_byte = line_numbers[1].bytes().next();
51
52                new_range.upper = if first_byte == Some(b'+') {
53                    let more_lines = &line_numbers[1][1..]
54                        .parse()
55                        .map_err(|_| "Invalid character after +")?;
56                    new_range.lower + more_lines
57                } else if first_byte == Some(b'-') {
58                    // this will prevent values like "-+5" even though "+5" is valid integer
59                    if line_numbers[1][1..].bytes().next() == Some(b'+') {
60                        return Err("Invalid character after -".into());
61                    }
62                    let prior_lines = &line_numbers[1][1..]
63                        .parse()
64                        .map_err(|_| "Invalid character after -")?;
65                    let prev_lower = new_range.lower;
66                    new_range.lower = new_range.lower.saturating_sub(*prior_lines);
67                    prev_lower
68                } else {
69                    line_numbers[1].parse()?
70                };
71
72                Ok(new_range)
73            }
74            _ => Err(
75                "Line range contained more than one ':' character. Expected format: 'N' or 'N:M'"
76                    .into(),
77            ),
78        }
79    }
80
81    pub(crate) fn is_inside(&self, line: usize) -> bool {
82        line >= self.lower && line <= self.upper
83    }
84}
85
86#[test]
87fn test_parse_full() {
88    let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
89    assert_eq!(40, range.lower);
90    assert_eq!(50, range.upper);
91}
92
93#[test]
94fn test_parse_partial_min() {
95    let range = LineRange::from(":50").expect("Shouldn't fail on test!");
96    assert_eq!(usize::min_value(), range.lower);
97    assert_eq!(50, range.upper);
98}
99
100#[test]
101fn test_parse_partial_max() {
102    let range = LineRange::from("40:").expect("Shouldn't fail on test!");
103    assert_eq!(40, range.lower);
104    assert_eq!(usize::max_value(), range.upper);
105}
106
107#[test]
108fn test_parse_single() {
109    let range = LineRange::from("40").expect("Shouldn't fail on test!");
110    assert_eq!(40, range.lower);
111    assert_eq!(40, range.upper);
112}
113
114#[test]
115fn test_parse_fail() {
116    let range = LineRange::from("40:50:80");
117    assert!(range.is_err());
118    let range = LineRange::from("40::80");
119    assert!(range.is_err());
120    let range = LineRange::from(":40:");
121    assert!(range.is_err());
122}
123
124#[test]
125fn test_parse_plus() {
126    let range = LineRange::from("40:+10").expect("Shouldn't fail on test!");
127    assert_eq!(40, range.lower);
128    assert_eq!(50, range.upper);
129}
130
131#[test]
132fn test_parse_plus_fail() {
133    let range = LineRange::from("40:+z");
134    assert!(range.is_err());
135    let range = LineRange::from("40:+-10");
136    assert!(range.is_err());
137    let range = LineRange::from("40:+");
138    assert!(range.is_err());
139}
140
141#[test]
142fn test_parse_minus_success() {
143    let range = LineRange::from("40:-10").expect("Shouldn't fail on test!");
144    assert_eq!(30, range.lower);
145    assert_eq!(40, range.upper);
146}
147
148#[test]
149fn test_parse_minus_edge_cases_success() {
150    let range = LineRange::from("5:-4").expect("Shouldn't fail on test!");
151    assert_eq!(1, range.lower);
152    assert_eq!(5, range.upper);
153    let range = LineRange::from("5:-5").expect("Shouldn't fail on test!");
154    assert_eq!(0, range.lower);
155    assert_eq!(5, range.upper);
156    let range = LineRange::from("5:-100").expect("Shouldn't fail on test!");
157    assert_eq!(0, range.lower);
158    assert_eq!(5, range.upper);
159}
160
161#[test]
162fn test_parse_minus_fail() {
163    let range = LineRange::from("40:-z");
164    assert!(range.is_err());
165    let range = LineRange::from("40:-+10");
166    assert!(range.is_err());
167    let range = LineRange::from("40:-");
168    assert!(range.is_err());
169}
170
171#[derive(Copy, Clone, Debug, PartialEq)]
172pub enum RangeCheckResult {
173    // Within one of the given ranges
174    InRange,
175
176    // Before the first range or within two ranges
177    BeforeOrBetweenRanges,
178
179    // Line number is outside of all ranges and larger than the last range.
180    AfterLastRange,
181}
182
183#[derive(Debug, Clone)]
184pub struct LineRanges {
185    ranges: Vec<LineRange>,
186    largest_upper_bound: usize,
187}
188
189impl LineRanges {
190    pub fn none() -> LineRanges {
191        LineRanges::from(vec![])
192    }
193
194    pub fn all() -> LineRanges {
195        LineRanges::from(vec![LineRange::default()])
196    }
197
198    pub fn from(ranges: Vec<LineRange>) -> LineRanges {
199        let largest_upper_bound = ranges
200            .iter()
201            .map(|r| r.upper)
202            .max()
203            .unwrap_or(usize::max_value());
204        LineRanges {
205            ranges,
206            largest_upper_bound,
207        }
208    }
209
210    pub(crate) fn check(&self, line: usize) -> RangeCheckResult {
211        if self.ranges.iter().any(|r| r.is_inside(line)) {
212            RangeCheckResult::InRange
213        } else if line < self.largest_upper_bound {
214            RangeCheckResult::BeforeOrBetweenRanges
215        } else {
216            RangeCheckResult::AfterLastRange
217        }
218    }
219}
220
221impl Default for LineRanges {
222    fn default() -> Self {
223        Self::all()
224    }
225}
226
227#[derive(Debug, Clone)]
228pub struct HighlightedLineRanges(pub LineRanges);
229
230impl Default for HighlightedLineRanges {
231    fn default() -> Self {
232        HighlightedLineRanges(LineRanges::none())
233    }
234}
235
236#[cfg(test)]
237fn ranges(rs: &[&str]) -> LineRanges {
238    LineRanges::from(rs.iter().map(|r| LineRange::from(r).unwrap()).collect())
239}
240
241#[test]
242fn test_ranges_simple() {
243    let ranges = ranges(&["3:8"]);
244
245    assert_eq!(RangeCheckResult::BeforeOrBetweenRanges, ranges.check(2));
246    assert_eq!(RangeCheckResult::InRange, ranges.check(5));
247    assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
248}
249
250#[test]
251fn test_ranges_advanced() {
252    let ranges = ranges(&["3:8", "11:20", "25:30"]);
253
254    assert_eq!(RangeCheckResult::BeforeOrBetweenRanges, ranges.check(2));
255    assert_eq!(RangeCheckResult::InRange, ranges.check(5));
256    assert_eq!(RangeCheckResult::BeforeOrBetweenRanges, ranges.check(9));
257    assert_eq!(RangeCheckResult::InRange, ranges.check(11));
258    assert_eq!(RangeCheckResult::BeforeOrBetweenRanges, ranges.check(22));
259    assert_eq!(RangeCheckResult::InRange, ranges.check(28));
260    assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(31));
261}
262
263#[test]
264fn test_ranges_open_low() {
265    let ranges = ranges(&["3:8", ":5"]);
266
267    assert_eq!(RangeCheckResult::InRange, ranges.check(1));
268    assert_eq!(RangeCheckResult::InRange, ranges.check(3));
269    assert_eq!(RangeCheckResult::InRange, ranges.check(7));
270    assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
271}
272
273#[test]
274fn test_ranges_open_high() {
275    let ranges = ranges(&["3:", "2:5"]);
276
277    assert_eq!(RangeCheckResult::BeforeOrBetweenRanges, ranges.check(1));
278    assert_eq!(RangeCheckResult::InRange, ranges.check(3));
279    assert_eq!(RangeCheckResult::InRange, ranges.check(5));
280    assert_eq!(RangeCheckResult::InRange, ranges.check(9));
281}
282
283#[test]
284fn test_ranges_all() {
285    let ranges = LineRanges::all();
286
287    assert_eq!(RangeCheckResult::InRange, ranges.check(1));
288}
289
290#[test]
291fn test_ranges_none() {
292    let ranges = LineRanges::none();
293
294    assert_ne!(RangeCheckResult::InRange, ranges.check(1));
295}