Coverage Report

Created: 2023-04-25 07:07

/rust/registry/src/index.crates.io-6f17d22bba15001f/filecheck-0.5.0/src/explain.rs
Line
Count
Source (jump to first uncovered line)
1
//! Explaining how *filecheck* matched or failed to match a file.
2
3
use crate::MatchRange;
4
use std::cmp::min;
5
use std::fmt::{self, Display, Formatter};
6
7
/// Record events during matching.
8
pub trait Recorder {
9
    /// Set the directive we're talking about now.
10
    fn directive(&mut self, dct: usize);
11
12
    /// Matched a positive check directive (check/sameln/nextln/unordered).
13
    fn matched_check(&mut self, regex: &str, matched: MatchRange);
14
15
    /// Matched a `not:` directive. This means the match will fail.
16
    fn matched_not(&mut self, regex: &str, matched: MatchRange);
17
18
    /// Missed a positive check directive. The range given is the range searched for a match.
19
    fn missed_check(&mut self, regex: &str, searched: MatchRange);
20
21
    /// Missed `not:` directive (as intended).
22
    fn missed_not(&mut self, regex: &str, searched: MatchRange);
23
24
    /// The directive defined a variable.
25
    fn defined_var(&mut self, varname: &str, value: &str);
26
}
27
28
/// The null recorder just doesn't listen to anything you say.
29
impl Recorder for () {
30
0
    fn directive(&mut self, _: usize) {}
31
0
    fn matched_check(&mut self, _: &str, _: MatchRange) {}
32
0
    fn matched_not(&mut self, _: &str, _: MatchRange) {}
33
0
    fn defined_var(&mut self, _: &str, _: &str) {}
34
0
    fn missed_check(&mut self, _: &str, _: MatchRange) {}
35
0
    fn missed_not(&mut self, _: &str, _: MatchRange) {}
36
}
37
38
struct Match {
39
    directive: usize,
40
    is_match: bool,
41
    is_not: bool,
42
    regex: String,
43
    range: MatchRange,
44
}
45
46
struct VarDef {
47
    directive: usize,
48
    varname: String,
49
    value: String,
50
}
51
52
/// Record an explanation for the matching process, success or failure.
53
pub struct Explainer<'a> {
54
    text: &'a str,
55
    directive: usize,
56
    matches: Vec<Match>,
57
    vardefs: Vec<VarDef>,
58
}
59
60
impl<'a> Explainer<'a> {
61
0
    pub fn new(text: &'a str) -> Explainer {
62
0
        Explainer {
63
0
            text,
64
0
            directive: 0,
65
0
            matches: Vec::new(),
66
0
            vardefs: Vec::new(),
67
0
        }
68
0
    }
69
70
    /// Finish up after recording all events in a match.
71
0
    pub fn finish(&mut self) {
72
0
        self.matches.sort_by_key(|m| (m.range, m.directive));
73
0
        self.vardefs.sort_by_key(|v| v.directive);
74
0
    }
75
}
76
77
impl<'a> Display for Explainer<'a> {
78
0
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
79
0
        // Offset of beginning of the last line printed.
80
0
        let mut curln = 0;
81
0
        // Offset of beginning of the next line to be printed.
82
0
        let mut nextln = 0;
83
84
0
        for m in &self.matches {
85
            // Emit lines until m.range.0 is visible.
86
0
            while nextln <= m.range.0 && nextln < self.text.len() {
87
0
                let newln = self.text[nextln..]
88
0
                    .find('\n')
89
0
                    .map(|d| nextln + d + 1)
90
0
                    .unwrap_or(self.text.len());
91
0
                assert!(newln > nextln);
92
0
                writeln!(f, "> {}", &self.text[nextln..newln - 1])?;
93
0
                curln = nextln;
94
0
                nextln = newln;
95
            }
96
97
            // Emit ~~~ under the part of the match in curln.
98
0
            if m.is_match {
99
0
                write!(f, "  ")?;
100
0
                let mend = min(m.range.1, nextln - 1);
101
0
                for pos in curln..mend {
102
0
                    if pos < m.range.0 {
103
0
                        write!(f, " ")
104
0
                    } else if pos == m.range.0 {
105
0
                        write!(f, "^")
106
                    } else {
107
0
                        write!(f, "~")
108
0
                    }?;
109
                }
110
0
                writeln!(f)?;
111
0
            }
112
113
            // Emit the match message itself.
114
0
            writeln!(
115
0
                f,
116
0
                "{} #{}{}: {}",
117
0
                if m.is_match { "Matched" } else { "Missed" },
118
                m.directive,
119
0
                if m.is_not { " not" } else { "" },
120
                m.regex
121
0
            )?;
122
123
            // Emit any variable definitions.
124
0
            if let Ok(found) = self
125
0
                .vardefs
126
0
                .binary_search_by_key(&m.directive, |v| v.directive)
127
            {
128
0
                let mut first = found;
129
0
                while first > 0 && self.vardefs[first - 1].directive == m.directive {
130
0
                    first -= 1;
131
0
                }
132
0
                for d in &self.vardefs[first..] {
133
0
                    if d.directive != m.directive {
134
0
                        break;
135
0
                    }
136
0
                    writeln!(f, "Define {}={}", d.varname, d.value)?;
137
                }
138
0
            }
139
        }
140
141
        // Emit trailing lines.
142
0
        for line in self.text[nextln..].lines() {
143
0
            writeln!(f, "> {}", line)?;
144
        }
145
0
        Ok(())
146
0
    }
147
}
148
149
impl<'a> Recorder for Explainer<'a> {
150
0
    fn directive(&mut self, dct: usize) {
151
0
        self.directive = dct;
152
0
    }
153
154
0
    fn matched_check(&mut self, regex: &str, matched: MatchRange) {
155
0
        self.matches.push(Match {
156
0
            directive: self.directive,
157
0
            is_match: true,
158
0
            is_not: false,
159
0
            regex: regex.to_owned(),
160
0
            range: matched,
161
0
        });
162
0
    }
163
164
0
    fn matched_not(&mut self, regex: &str, matched: MatchRange) {
165
0
        self.matches.push(Match {
166
0
            directive: self.directive,
167
0
            is_match: true,
168
0
            is_not: true,
169
0
            regex: regex.to_owned(),
170
0
            range: matched,
171
0
        });
172
0
    }
173
174
0
    fn missed_check(&mut self, regex: &str, searched: MatchRange) {
175
0
        self.matches.push(Match {
176
0
            directive: self.directive,
177
0
            is_match: false,
178
0
            is_not: false,
179
0
            regex: regex.to_owned(),
180
0
            range: searched,
181
0
        });
182
0
    }
183
184
0
    fn missed_not(&mut self, regex: &str, searched: MatchRange) {
185
0
        self.matches.push(Match {
186
0
            directive: self.directive,
187
0
            is_match: false,
188
0
            is_not: true,
189
0
            regex: regex.to_owned(),
190
0
            range: searched,
191
0
        });
192
0
    }
193
194
0
    fn defined_var(&mut self, varname: &str, value: &str) {
195
0
        self.vardefs.push(VarDef {
196
0
            directive: self.directive,
197
0
            varname: varname.to_owned(),
198
0
            value: value.to_owned(),
199
0
        });
200
0
    }
201
}