]>
git.proxmox.com Git - rustc.git/blob - vendor/snapbox/src/substitutions.rs
3 /// Match pattern expressions, see [`Assert`][crate::Assert]
5 /// Built-in expressions:
6 /// - `...` on a line of its own: match multiple complete lines
7 /// - `[..]`: match multiple characters within a line
8 #[derive(Default, Clone, Debug, PartialEq, Eq)]
9 pub struct Substitutions
{
10 vars
: std
::collections
::BTreeMap
<&'
static str, Cow
<'
static, str>>,
11 unused
: std
::collections
::BTreeSet
<&'
static str>,
15 pub fn new() -> Self {
19 pub(crate) fn with_exe() -> Self {
20 let mut substitutions
= Self::new();
22 .insert("[EXE]", std
::env
::consts
::EXE_SUFFIX
)
27 /// Insert an additional match pattern
29 /// `key` must be enclosed in `[` and `]`.
32 /// let mut subst = snapbox::Substitutions::new();
33 /// subst.insert("[EXE]", std::env::consts::EXE_SUFFIX);
38 value
: impl Into
<Cow
<'
static, str>>,
39 ) -> Result
<(), crate::Error
> {
40 let key
= validate_key(key
)?
;
41 let value
= value
.into();
43 self.unused
.insert(key
);
46 .insert(key
, crate::utils
::normalize_text(value
.as_ref()).into());
51 /// Insert additional match patterns
53 /// keys must be enclosed in `[` and `]`.
56 vars
: impl IntoIterator
<Item
= (&'
static str, impl Into
<Cow
<'
static, str>>)>,
57 ) -> Result
<(), crate::Error
> {
58 for (key
, value
) in vars
{
59 self.insert(key
, value
)?
;
64 /// Apply match pattern to `input`
66 /// If `pattern` matches `input`, then `pattern` is returned.
68 /// Otherwise, `input`, with as many patterns replaced as possible, will be returned.
71 /// let subst = snapbox::Substitutions::new();
72 /// let output = subst.normalize("Hello World!", "Hello [..]!");
73 /// assert_eq!(output, "Hello [..]!");
75 pub fn normalize(&self, input
: &str, pattern
: &str) -> String
{
76 normalize(input
, pattern
, self)
79 fn substitute
<'v
>(&self, value
: &'v
str) -> Cow
<'v
, str> {
80 let mut value
= Cow
::Borrowed(value
);
81 for (var
, replace
) in self.vars
.iter() {
82 debug_assert
!(!replace
.is_empty());
83 value
= Cow
::Owned(value
.replace(replace
.as_ref(), var
));
88 fn clear
<'v
>(&self, pattern
: &'v
str) -> Cow
<'v
, str> {
89 if pattern
.contains('
['
) {
90 let mut pattern
= Cow
::Borrowed(pattern
);
91 for var
in self.unused
.iter() {
92 pattern
= Cow
::Owned(pattern
.replace(var
, ""));
96 Cow
::Borrowed(pattern
)
101 fn validate_key(key
: &'
static str) -> Result
<&'
static str, crate::Error
> {
102 if !key
.starts_with('
['
) || !key
.ends_with('
]'
) {
103 return Err(format
!("Key `{}` is not enclosed in []", key
).into());
106 if key
[1..(key
.len() - 1)]
107 .find(|c
: char| !c
.is_ascii_uppercase())
110 return Err(format
!("Key `{}` can only be A-Z but ", key
).into());
116 fn normalize(input
: &str, pattern
: &str, substitutions
: &Substitutions
) -> String
{
117 if input
== pattern
{
118 return input
.to_owned();
121 let mut normalized
: Vec
<Cow
<str>> = Vec
::new();
122 let input_lines
: Vec
<_
> = crate::utils
::LinesWithTerminator
::new(input
).collect();
123 let pattern_lines
: Vec
<_
> = crate::utils
::LinesWithTerminator
::new(pattern
).collect();
125 let mut input_index
= 0;
126 let mut pattern_index
= 0;
128 let pattern_line
= if let Some(pattern_line
) = pattern_lines
.get(pattern_index
) {
132 input_lines
[input_index
..]
135 .map(|s
| substitutions
.substitute(s
)),
139 let next_pattern_index
= pattern_index
+ 1;
141 let input_line
= if let Some(input_line
) = input_lines
.get(input_index
) {
146 let next_input_index
= input_index
+ 1;
148 if line_matches(input_line
, pattern_line
, substitutions
) {
149 pattern_index
= next_pattern_index
;
150 input_index
= next_input_index
;
151 normalized
.push(Cow
::Borrowed(pattern_line
));
153 } else if is_line_elide(pattern_line
) {
154 let next_pattern_line
: &str =
155 if let Some(pattern_line
) = pattern_lines
.get(next_pattern_index
) {
158 normalized
.push(Cow
::Borrowed(pattern_line
));
161 if let Some(future_input_index
) = input_lines
[input_index
..]
164 .find(|(_
, l
)| **l
== next_pattern_line
)
165 .map(|(i
, _
)| input_index
+ i
)
167 normalized
.push(Cow
::Borrowed(pattern_line
));
168 pattern_index
= next_pattern_index
;
169 input_index
= future_input_index
;
173 input_lines
[input_index
..]
176 .map(|s
| substitutions
.substitute(s
)),
181 // Find where we can pick back up for normalizing
182 for future_input_index
in next_input_index
..input_lines
.len() {
183 let future_input_line
= input_lines
[future_input_index
];
184 if let Some(future_pattern_index
) = pattern_lines
[next_pattern_index
..]
187 .find(|(_
, l
)| **l
== future_input_line
|| is_line_elide(**l
))
188 .map(|(i
, _
)| next_pattern_index
+ i
)
191 input_lines
[input_index
..future_input_index
]
194 .map(|s
| substitutions
.substitute(s
)),
196 pattern_index
= future_pattern_index
;
197 input_index
= future_input_index
;
203 input_lines
[input_index
..]
206 .map(|s
| substitutions
.substitute(s
)),
215 fn is_line_elide(line
: &str) -> bool
{
216 line
== "...\n" || line
== "..."
219 fn line_matches(line
: &str, pattern
: &str, substitutions
: &Substitutions
) -> bool
{
224 let subbed
= substitutions
.substitute(line
);
225 let mut line
= subbed
.as_ref();
227 let pattern
= substitutions
.clear(pattern
);
229 let mut sections
= pattern
.split("[..]").peekable();
230 while let Some(section
) = sections
.next() {
231 if let Some(remainder
) = line
.strip_prefix(section
) {
232 if let Some(next_section
) = sections
.peek() {
233 if next_section
.is_empty() {
235 } else if let Some(restart_index
) = remainder
.find(next_section
) {
236 line
= &remainder
[restart_index
..];
239 return remainder
.is_empty();
258 let actual
= normalize(input
, pattern
, &Substitutions
::new());
259 assert_eq
!(expected
, actual
);
263 fn literals_match() {
264 let input
= "Hello\nWorld";
265 let pattern
= "Hello\nWorld";
266 let expected
= "Hello\nWorld";
267 let actual
= normalize(input
, pattern
, &Substitutions
::new());
268 assert_eq
!(expected
, actual
);
272 fn pattern_shorter() {
273 let input
= "Hello\nWorld";
274 let pattern
= "Hello\n";
275 let expected
= "Hello\nWorld";
276 let actual
= normalize(input
, pattern
, &Substitutions
::new());
277 assert_eq
!(expected
, actual
);
282 let input
= "Hello\n";
283 let pattern
= "Hello\nWorld";
284 let expected
= "Hello\n";
285 let actual
= normalize(input
, pattern
, &Substitutions
::new());
286 assert_eq
!(expected
, actual
);
291 let input
= "Hello\nWorld";
292 let pattern
= "Goodbye\nMoon";
293 let expected
= "Hello\nWorld";
294 let actual
= normalize(input
, pattern
, &Substitutions
::new());
295 assert_eq
!(expected
, actual
);
299 fn middles_diverge() {
300 let input
= "Hello\nWorld\nGoodbye";
301 let pattern
= "Hello\nMoon\nGoodbye";
302 let expected
= "Hello\nWorld\nGoodbye";
303 let actual
= normalize(input
, pattern
, &Substitutions
::new());
304 assert_eq
!(expected
, actual
);
309 let input
= "Hello\nWorld\nGoodbye";
310 let pattern
= "...\nGoodbye";
311 let expected
= "...\nGoodbye";
312 let actual
= normalize(input
, pattern
, &Substitutions
::new());
313 assert_eq
!(expected
, actual
);
317 fn trailing_elide() {
318 let input
= "Hello\nWorld\nGoodbye";
319 let pattern
= "Hello\n...";
320 let expected
= "Hello\n...";
321 let actual
= normalize(input
, pattern
, &Substitutions
::new());
322 assert_eq
!(expected
, actual
);
327 let input
= "Hello\nWorld\nGoodbye";
328 let pattern
= "Hello\n...\nGoodbye";
329 let expected
= "Hello\n...\nGoodbye";
330 let actual
= normalize(input
, pattern
, &Substitutions
::new());
331 assert_eq
!(expected
, actual
);
335 fn post_elide_diverge() {
336 let input
= "Hello\nSun\nAnd\nWorld";
337 let pattern
= "Hello\n...\nMoon";
338 let expected
= "Hello\nSun\nAnd\nWorld";
339 let actual
= normalize(input
, pattern
, &Substitutions
::new());
340 assert_eq
!(expected
, actual
);
344 fn post_diverge_elide() {
345 let input
= "Hello\nWorld\nGoodbye\nSir";
346 let pattern
= "Hello\nMoon\nGoodbye\n...";
347 let expected
= "Hello\nWorld\nGoodbye\n...";
348 let actual
= normalize(input
, pattern
, &Substitutions
::new());
349 assert_eq
!(expected
, actual
);
354 let input
= "Hello\nWorld\nGoodbye\nSir";
355 let pattern
= "Hello\nW[..]d\nGoodbye\nSir";
356 let expected
= "Hello\nW[..]d\nGoodbye\nSir";
357 let actual
= normalize(input
, pattern
, &Substitutions
::new());
358 assert_eq
!(expected
, actual
);
362 fn line_matches_cases() {
366 ("hello", "hello", true),
367 ("hello", "goodbye", false),
368 ("hello", "[..]", true),
369 ("hello", "he[..]", true),
370 ("hello", "go[..]", false),
371 ("hello", "[..]o", true),
372 ("hello", "[..]e", false),
373 ("hello", "he[..]o", true),
374 ("hello", "he[..]e", false),
375 ("hello", "go[..]o", false),
376 ("hello", "go[..]e", false),
378 "hello world, goodbye moon",
379 "hello [..], goodbye [..]",
383 "hello world, goodbye moon",
384 "goodbye [..], goodbye [..]",
388 "hello world, goodbye moon",
389 "goodbye [..], hello [..]",
392 ("hello world, goodbye moon", "hello [..], [..] moon", true),
394 "hello world, goodbye moon",
395 "goodbye [..], [..] moon",
398 ("hello world, goodbye moon", "hello [..], [..] world", false),
400 for (line
, pattern
, expected
) in cases
{
401 let actual
= line_matches(line
, pattern
, &Substitutions
::new());
402 assert_eq
!(expected
, actual
, "line={:?} pattern={:?}", line
, pattern
);
407 fn test_validate_key() {
415 for (key
, expected
) in cases
{
416 let actual
= validate_key(key
).is_ok();
417 assert_eq
!(expected
, actual
, "key={:?}", key
);