/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 | | } |