Skip to main content

sel/matcher/
lines.rs

1//! Line-number matcher built from sorted, merged ranges.
2
3use super::Matcher;
4use crate::selector::{LineSpec, Selector};
5use crate::{Line, MatchInfo};
6
7/// Matches lines whose 1-indexed number falls in a set of merged ranges.
8pub struct LineMatcher {
9    /// Sorted, non-overlapping, inclusive `(start, end)` ranges (1-indexed).
10    ranges: Vec<(u64, u64)>,
11}
12
13impl LineMatcher {
14    /// Build from a `Selector::LineNumbers`. Panics on other variants.
15    pub fn from_selector(sel: &Selector) -> Self {
16        let normalized = sel.normalize();
17        let specs: &[LineSpec] = match &normalized {
18            Selector::LineNumbers(s) => s,
19            Selector::All => &[],
20            Selector::Positions(_) => {
21                panic!("LineMatcher::from_selector called with positional selector")
22            }
23        };
24        let ranges = specs
25            .iter()
26            .map(|s| match s {
27                LineSpec::Single(n) => (*n as u64, *n as u64),
28                LineSpec::Range(a, b) => (*a as u64, *b as u64),
29            })
30            .collect();
31        Self { ranges }
32    }
33}
34
35impl Matcher for LineMatcher {
36    fn match_line(&mut self, line: &Line) -> MatchInfo {
37        let hit = self
38            .ranges
39            .iter()
40            .any(|&(a, b)| line.no >= a && line.no <= b);
41        MatchInfo {
42            hit,
43            ..MatchInfo::default()
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    fn mk_line(n: u64) -> Line {
53        Line::new(n, Vec::new())
54    }
55
56    #[test]
57    fn single_line_hits_only_that_line() {
58        let sel = Selector::parse("5").unwrap();
59        let mut m = LineMatcher::from_selector(&sel);
60        assert!(!m.match_line(&mk_line(4)).hit);
61        assert!(m.match_line(&mk_line(5)).hit);
62        assert!(!m.match_line(&mk_line(6)).hit);
63    }
64
65    #[test]
66    fn range_hits_inclusive() {
67        let sel = Selector::parse("10-12").unwrap();
68        let mut m = LineMatcher::from_selector(&sel);
69        assert!(!m.match_line(&mk_line(9)).hit);
70        assert!(m.match_line(&mk_line(10)).hit);
71        assert!(m.match_line(&mk_line(11)).hit);
72        assert!(m.match_line(&mk_line(12)).hit);
73        assert!(!m.match_line(&mk_line(13)).hit);
74    }
75
76    #[test]
77    fn mixed_list_merges_ranges() {
78        let sel = Selector::parse("1,5,10-15,14").unwrap();
79        let mut m = LineMatcher::from_selector(&sel);
80        assert!(m.match_line(&mk_line(1)).hit);
81        assert!(!m.match_line(&mk_line(2)).hit);
82        assert!(m.match_line(&mk_line(5)).hit);
83        assert!(m.match_line(&mk_line(12)).hit);
84        assert!(m.match_line(&mk_line(15)).hit);
85        assert!(!m.match_line(&mk_line(16)).hit);
86    }
87}