1use std::{
2 collections::{HashMap, HashSet},
3 sync::{Arc, Mutex, OnceLock},
4};
5
6use grok::Grok;
7use serde::Serialize;
8
9use crate::script::{IfCondition, ScriptLocation, ScriptRunContext};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct Line {
13 pub number: usize,
14 pub text: String,
15}
16
17#[derive(Clone)]
18pub struct Lines {
19 lines: Arc<Vec<String>>,
20 current_line: usize,
21 ignored_patterns: OutputPatterns,
22 negative_disabled: bool,
23 rejected_patterns: OutputPatterns,
24}
25
26impl std::fmt::Debug for Lines {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(
29 f,
30 "Lines {{ ignored: {} pattern(s), rejected: {} pattern(s) }}",
31 self.ignored_patterns.len(),
32 self.rejected_patterns.len()
33 )
34 }
35}
36
37impl<'s> IntoIterator for &'s Lines {
38 type Item = &'s String;
39 type IntoIter = std::slice::Iter<'s, String>;
40
41 fn into_iter(self) -> Self::IntoIter {
42 self.lines.iter()
43 }
44}
45
46impl std::fmt::Display for Lines {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 write!(f, "{}", self.lines[self.current_line..].join("\n"))
49 }
50}
51
52impl Lines {
53 pub fn new(lines: Vec<String>) -> Self {
54 Self {
55 lines: Arc::new(lines),
56 current_line: 0,
57 ignored_patterns: Default::default(),
58 negative_disabled: false,
59 rejected_patterns: Default::default(),
60 }
61 }
62
63 pub fn is_exhausted(&self) -> bool {
64 self.current_line >= self.lines.len()
65 }
66
67 pub fn next(
68 &self,
69 context: OutputMatchContext,
70 ) -> Result<(Option<Line>, Lines), OutputPatternMatchFailure> {
71 let mut next = self.clone();
72 'outer: while next.current_line < next.lines.len() {
73 if !self.negative_disabled {
74 let ignore_check = next.without_negatives();
75 for ignored_pattern in &*next.ignored_patterns {
76 if let Ok(next_next) =
77 ignored_pattern.matches(context.ignore(), ignore_check.clone())
78 {
79 next = next_next.with_negatives();
80 continue 'outer;
81 }
82 }
83 for rejected_pattern in &*next.rejected_patterns {
84 if let Ok(_) = rejected_pattern.matches(context.ignore(), ignore_check.clone())
85 {
86 return Err(OutputPatternMatchFailure {
87 location: rejected_pattern.location.clone(),
88 pattern_type: "reject",
89 output_line: None,
90 });
91 }
92 }
93 }
94 let line = Line {
95 number: next.current_line,
96 text: next.lines[next.current_line].clone(),
97 };
98 next.current_line += 1;
99 return Ok((Some(line), next));
100 }
101 Ok((None, next))
102 }
103
104 pub fn with_ignore(&self, ignore: &OutputPatterns) -> Self {
105 let mut ignored_patterns = self.ignored_patterns.clone();
106 ignored_patterns.extend(&ignore);
107 Self {
108 ignored_patterns,
109 ..self.clone()
110 }
111 }
112
113 pub fn with_reject(&self, reject: &OutputPatterns) -> Self {
114 let mut rejected_patterns = self.rejected_patterns.clone();
115 rejected_patterns.extend(&reject);
116 Self {
117 rejected_patterns,
118 ..self.clone()
119 }
120 }
121
122 fn without_negatives(&self) -> Self {
123 Self {
124 negative_disabled: true,
125 ..self.clone()
126 }
127 }
128
129 fn with_negatives(&self) -> Self {
130 Self {
131 negative_disabled: false,
132 ..self.clone()
133 }
134 }
135
136 pub fn into_inner(self) -> Vec<String> {
137 Arc::unwrap_or_clone(self.lines).split_off(self.current_line)
138 }
139
140 pub fn is_empty(&self) -> bool {
141 self.lines.is_empty()
142 }
143}
144
145#[derive(Clone, Default, Debug, Serialize)]
146
147pub struct OutputPatterns {
148 patterns: Arc<Vec<OutputPattern>>,
149}
150
151impl OutputPatterns {
152 pub fn new(patterns: Vec<OutputPattern>) -> Self {
153 Self {
154 patterns: Arc::new(patterns),
155 }
156 }
157
158 pub fn is_empty(&self) -> bool {
159 self.patterns.is_empty()
160 }
161
162 pub fn len(&self) -> usize {
163 self.patterns.len()
164 }
165
166 pub fn extend(&mut self, patterns: &OutputPatterns) {
167 if self.is_empty() {
168 self.patterns = patterns.patterns.clone();
169 return;
170 }
171 let new_patterns = std::mem::take(&mut self.patterns);
172 let mut new_patterns = Arc::unwrap_or_clone(new_patterns);
173 new_patterns.extend(patterns.patterns.iter().cloned());
174 self.patterns = Arc::new(new_patterns);
175 }
176}
177
178impl std::ops::Deref for OutputPatterns {
179 type Target = Vec<OutputPattern>;
180 fn deref(&self) -> &Self::Target {
181 &*self.patterns
182 }
183}
184
185#[derive(Clone)]
186pub struct OutputPattern {
187 pub location: ScriptLocation,
188 pub pattern: OutputPatternType,
189 pub ignore: OutputPatterns,
190 pub reject: OutputPatterns,
191}
192
193impl Serialize for OutputPattern {
194 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195 where
196 S: serde::Serializer,
197 {
198 self.pattern.serialize(serializer)
199 }
200}
201
202impl std::fmt::Debug for OutputPattern {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "{:?}", self.pattern)
205 }
206}
207
208impl OutputPattern {
209 pub fn new_sequence(location: ScriptLocation, mut patterns: Vec<OutputPattern>) -> Self {
210 if patterns.len() == 1 {
211 patterns.remove(0)
212 } else {
213 Self {
214 pattern: OutputPatternType::Sequence(patterns),
215 ignore: Default::default(),
216 reject: Default::default(),
217 location: location.clone(),
218 }
219 }
220 }
221
222 pub fn prepare(&self, grok: &Grok) -> Result<(), OutputPatternPrepareError> {
223 for pattern in &*self.ignore.patterns {
224 pattern.prepare(grok)?
225 }
226 for pattern in &*self.reject.patterns {
227 pattern.prepare(grok)?
228 }
229 match &self.pattern {
230 OutputPatternType::Pattern(pattern) => {
231 pattern
232 .prepare(grok)
233 .map_err(|e| OutputPatternPrepareError {
234 location: self.location.clone(),
235 pattern: pattern.pattern.clone(),
236 error: e,
237 })?
238 }
239 OutputPatternType::Sequence(patterns) => {
240 for pattern in patterns {
241 pattern.prepare(grok)?;
242 }
243 }
244 OutputPatternType::Unordered(patterns) => {
245 for pattern in patterns {
246 pattern.prepare(grok)?;
247 }
248 }
249 OutputPatternType::Choice(patterns) => {
250 for pattern in patterns {
251 pattern.prepare(grok)?;
252 }
253 }
254 OutputPatternType::If(_, pattern) => pattern.prepare(grok)?,
255 OutputPatternType::Any(pattern) => pattern.prepare(grok)?,
256 OutputPatternType::Repeat(pattern) => pattern.prepare(grok)?,
257 OutputPatternType::Optional(pattern) => pattern.prepare(grok)?,
258 OutputPatternType::Literal(_) => {}
259 OutputPatternType::End | OutputPatternType::None => {}
260 }
261 Ok(())
262 }
263
264 pub fn matches(
265 &self,
266 context: OutputMatchContext,
267 output: Lines,
268 ) -> Result<Lines, OutputPatternMatchFailure> {
269 if self.ignore.is_empty() && self.reject.is_empty() {
270 self.pattern.matches(&self.location, context, output)
271 } else {
272 let output = output.with_ignore(&self.ignore).with_reject(&self.reject);
273 self.pattern.matches(&self.location, context, output)
274 }
275 }
276
277 pub fn min_matches(&self) -> usize {
279 self.pattern.min_matches()
280 }
281
282 pub fn max_matches(&self) -> usize {
284 self.pattern.max_matches()
285 }
286}
287
288#[derive(thiserror::Error, Debug)]
289#[error("pattern {pattern} at line {location} failed to compile: {error}")]
290pub struct OutputPatternPrepareError {
291 pub location: ScriptLocation,
292 pub pattern: String,
293 pub error: grok::Error,
294}
295
296#[derive(Clone)]
297pub enum OutputPatternType {
298 End,
300 None,
302 Any(Box<OutputPattern>),
304 Literal(String),
306 Pattern(Arc<GrokPattern>),
308 Repeat(Box<OutputPattern>),
310 Optional(Box<OutputPattern>),
312 Unordered(Vec<OutputPattern>),
314 Choice(Vec<OutputPattern>),
316 Sequence(Vec<OutputPattern>),
318 If(IfCondition, Box<OutputPattern>),
320}
321
322impl Serialize for OutputPatternType {
323 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
324 where
325 S: serde::Serializer,
326 {
327 match self {
328 OutputPatternType::Literal(literal) => {
329 serializer.serialize_str(&format!("! {literal}"))
330 }
331 OutputPatternType::Pattern(pattern) => {
332 serializer.serialize_str(&format!("? {}", pattern.pattern))
333 }
334 OutputPatternType::Repeat(pattern) => {
335 HashMap::from([("repeat", &pattern)]).serialize(serializer)
336 }
337 OutputPatternType::Optional(pattern) => {
338 HashMap::from([("optional", &pattern)]).serialize(serializer)
339 }
340 OutputPatternType::Unordered(patterns) => {
341 HashMap::from([("unordered", &patterns)]).serialize(serializer)
342 }
343 OutputPatternType::Choice(patterns) => {
344 HashMap::from([("choice", &patterns)]).serialize(serializer)
345 }
346 OutputPatternType::Sequence(patterns) => {
347 HashMap::from([("sequence", &patterns)]).serialize(serializer)
348 }
349 OutputPatternType::Any(pattern) => {
350 HashMap::from([("any", &pattern)]).serialize(serializer)
351 }
352 OutputPatternType::If(condition, pattern) => {
353 #[derive(Serialize)]
354 struct If<'a> {
355 condition: &'a IfCondition,
356 pattern: &'a OutputPattern,
357 }
358 If { condition, pattern }.serialize(serializer)
359 }
360 OutputPatternType::End => serializer.serialize_str("end"),
361 OutputPatternType::None => serializer.serialize_str("none"),
362 }
363 }
364}
365
366impl OutputPatternType {
367 pub fn min_matches(&self) -> usize {
369 match self {
370 OutputPatternType::None => 0,
371 OutputPatternType::Literal(_) => 1,
372 OutputPatternType::Pattern(_) => 1,
373 OutputPatternType::Repeat(pattern) => pattern.min_matches(),
374 OutputPatternType::Optional(_) => 0,
375 OutputPatternType::Unordered(patterns) => {
376 patterns.iter().map(|p| p.min_matches()).sum()
377 }
378 OutputPatternType::Choice(patterns) => {
379 patterns.iter().map(|p| p.min_matches()).min().unwrap_or(0)
380 }
381 OutputPatternType::Sequence(patterns) => patterns.iter().map(|p| p.min_matches()).sum(),
382 OutputPatternType::Any(pattern) => pattern.min_matches(),
383 OutputPatternType::If(_, _) => 0,
384 OutputPatternType::End => 0,
385 }
386 }
387
388 pub fn max_matches(&self) -> usize {
390 fn saturating_iter_sum<I>(iter: I) -> usize
391 where
392 I: IntoIterator<Item = usize>,
393 {
394 iter.into_iter()
395 .reduce(|n, i| n.saturating_add(i))
396 .unwrap_or(0)
397 }
398
399 match self {
400 OutputPatternType::None => 0,
401 OutputPatternType::Literal(_) => 1,
402 OutputPatternType::Pattern(_) => 1,
403 OutputPatternType::Repeat(pattern) => {
404 if pattern.max_matches() == 0 {
405 0
406 } else {
407 usize::MAX
408 }
409 }
410 OutputPatternType::Optional(pattern) => pattern.max_matches(),
411 OutputPatternType::Unordered(patterns) => {
412 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
413 }
414 OutputPatternType::Choice(patterns) => {
415 patterns.iter().map(|p| p.max_matches()).max().unwrap_or(0)
416 }
417 OutputPatternType::Sequence(patterns) => {
418 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
419 }
420 OutputPatternType::Any(_) => usize::MAX,
421 OutputPatternType::If(_, pattern) => pattern.max_matches(),
422 OutputPatternType::End => 0,
423 }
424 }
425}
426
427impl Default for OutputPatternType {
428 fn default() -> Self {
429 Self::Sequence(vec![])
430 }
431}
432
433impl std::fmt::Debug for OutputPatternType {
434 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
435 match self {
436 OutputPatternType::Literal(literal) => write!(f, "Literal({literal})"),
437 OutputPatternType::Pattern(pattern) => write!(f, "Pattern({pattern:?})"),
438 OutputPatternType::Repeat(pattern) => write!(f, "Repeat({pattern:?})"),
439 OutputPatternType::Optional(pattern) => write!(f, "Optional({pattern:?})"),
440 OutputPatternType::Unordered(patterns) => write!(f, "Unordered({patterns:?})"),
441 OutputPatternType::Choice(patterns) => write!(f, "Choice({patterns:?})"),
442 OutputPatternType::Sequence(patterns) => write!(f, "Sequence({patterns:?})"),
443 OutputPatternType::Any(until) => write!(f, "Any({until:?})"),
444 OutputPatternType::If(condition, pattern) => {
445 write!(f, "If({condition:?}, {pattern:?})")
446 }
447 OutputPatternType::End => write!(f, "End"),
448 OutputPatternType::None => write!(f, "None"),
449 }
450 }
451}
452
453#[derive(Serialize, derive_more::Debug)]
454#[debug("/{pattern:?}/")]
455pub struct GrokPattern {
456 pattern: String,
457 aliases: Vec<String>,
458 #[serde(skip)]
459 grok: OnceLock<grok::Pattern>,
460}
461
462impl GrokPattern {
463 pub fn compile(line: &str, escape_non_grok: bool) -> Result<Self, String> {
464 use grok::parser::GrokPatternError;
465 let mut test_pattern = String::new();
466 let mut final_pattern = String::new();
467 let mut aliases = vec![];
468 for bit in grok::parser::grok_split(line) {
469 match bit {
470 grok::parser::GrokComponent::RegularExpression { string, .. } => {
471 if escape_non_grok {
472 for char in string.chars() {
473 if char.is_ascii() && !char.is_alphanumeric() {
474 test_pattern.push('\\');
475 test_pattern.push(char);
476 final_pattern.push('\\');
477 final_pattern.push(char);
478 } else {
479 test_pattern.push(char);
480 final_pattern.push(char);
481 }
482 }
483 } else {
484 test_pattern.push_str(string);
485 final_pattern.push_str(string);
486 }
487 }
488 grok::parser::GrokComponent::GrokPattern { pattern, alias, .. } => {
489 test_pattern.push_str(".");
490 final_pattern.push_str(pattern);
491 if !alias.is_empty() {
492 aliases.push(alias.to_string());
493 }
494 }
495 grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidCharacter(
496 c,
497 )) => {
498 return Err(format!("Invalid character in pattern: {:?}", c));
499 }
500 grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidPattern) => {
501 return Err("Invalid grok pattern".to_string());
502 }
503 grok::parser::GrokComponent::PatternError(
504 GrokPatternError::InvalidPatternDefinition,
505 ) => {
506 return Err("Invalid grok pattern definition".to_string());
507 }
508 }
509 }
510
511 test_pattern.push('$');
512 final_pattern.push('$');
513
514 _ = Grok::empty()
515 .compile(&test_pattern, false)
516 .map_err(|e| e.to_string())?;
517
518 Ok(Self {
519 pattern: final_pattern,
520 aliases,
521 grok: OnceLock::new(),
522 })
523 }
524
525 pub fn prepare(&self, grok: &Grok) -> Result<(), grok::Error> {
526 if self.grok.get().is_none() {
529 let pattern = grok.compile(&self.pattern, false)?;
530 self.grok.get_or_init(move || pattern);
531 }
532 Ok(())
533 }
534
535 pub fn matches<'a>(&'a self, text: &'a str) -> Option<grok::Matches<'a>> {
536 let pattern_ref = self.grok.get().expect("grok pattern not compiled");
537 return pattern_ref.match_against(text);
538 }
539}
540
541#[derive(Clone, Debug, thiserror::Error, derive_more::Display, PartialEq, Eq)]
542#[display("pattern {pattern_type} at line {location} does not match output line {:?}", output_line.as_ref().map(|l| l.text.clone()).unwrap_or("<eof>".to_string()))]
543pub struct OutputPatternMatchFailure {
544 pub location: ScriptLocation,
545 pub pattern_type: &'static str,
546 pub output_line: Option<Line>,
547}
548
549#[derive(Debug, Clone)]
550pub struct OutputMatchContext<'s> {
551 depth: usize,
552 trace: Arc<Mutex<Vec<String>>>,
553 ignore: bool,
554 expectations: Arc<Mutex<HashMap<String, String>>>,
555 script_context: &'s ScriptRunContext,
556}
557
558impl<'s> OutputMatchContext<'s> {
559 pub fn new(script_context: &'s ScriptRunContext) -> Self {
560 Self {
561 depth: 0,
562 trace: Default::default(),
563 ignore: false,
564 script_context,
565 expectations: Default::default(),
566 }
567 }
568
569 pub fn descend(&self) -> Self {
570 Self {
571 depth: self.depth + 1,
572 trace: self.trace.clone(),
573 ignore: self.ignore,
574 script_context: self.script_context,
575 expectations: self.expectations.clone(),
576 }
577 }
578
579 pub fn ignore(&self) -> Self {
580 Self {
581 depth: self.depth,
582 trace: self.trace.clone(),
583 ignore: true,
584 script_context: self.script_context,
585 expectations: self.expectations.clone(),
586 }
587 }
588
589 pub fn trace(&self, line: &str) {
590 let ignore = if self.ignore { "-" } else { "" };
591 self.trace.lock().unwrap().push(format!(
592 "{:indent$}{ignore}{}",
593 "",
594 line,
595 indent = self.depth * 2
596 ));
597 }
598
599 pub fn traces(&self) -> Vec<String> {
600 std::mem::take(&mut self.trace.lock().unwrap())
601 }
602
603 pub fn expect(&self, key: &str, value: String) {
604 self.expectations
605 .lock()
606 .unwrap()
607 .insert(key.to_string(), value);
608 }
609
610 pub fn expects(&self) -> HashMap<String, String> {
611 self.expectations.lock().unwrap().clone()
612 }
613}
614
615impl OutputPatternType {
616 pub fn matches<'s>(
617 &self,
618 location: &ScriptLocation,
619 context: OutputMatchContext,
620 mut output: Lines,
621 ) -> Result<Lines, OutputPatternMatchFailure> {
622 context.trace(&format!("matching {:?}", self));
623 match self {
624 OutputPatternType::None => Ok(output),
625 OutputPatternType::Literal(literal) => {
626 let (line, next) = output.next(context.clone())?;
627 if let Some(line) = line {
628 if &line.text.trim_end() == literal {
629 context.trace(&format!("literal match: {:?} == {literal:?}", line.text));
630 Ok(next)
631 } else {
632 context.trace(&format!(
633 "literal FAILED match: {:?} == {literal:?}",
634 line.text
635 ));
636 Err(OutputPatternMatchFailure {
637 location: location.clone(),
638 pattern_type: "literal",
639 output_line: Some(line),
640 })
641 }
642 } else {
643 Err(OutputPatternMatchFailure {
644 location: location.clone(),
645 pattern_type: "literal",
646 output_line: None,
647 })
648 }
649 }
650 OutputPatternType::Pattern(pattern) => {
651 let (line, next) = output.next(context.clone())?;
652 if let Some(line) = line {
653 let text = line.text.clone();
654 let res = pattern.matches(&text);
655 if let Some(matches) = res {
656 for alias in &pattern.aliases {
657 if let Some(value) = matches.get(&alias) {
658 let existing = context
659 .expectations
660 .lock()
661 .unwrap()
662 .insert(alias.clone(), value.to_string());
663 if let Some(existing) = existing {
664 if existing != value {
665 context.trace(&format!(
666 "pattern alias FAILED match: {existing:?} != {value:?}",
667 ));
668 return Err(OutputPatternMatchFailure {
669 location: location.clone(),
670 pattern_type: "pattern",
671 output_line: Some(line),
672 });
673 }
674 }
675 }
676 }
677 context.trace(&format!("pattern match: {:?} =~ {pattern:?}", line.text));
678 Ok(next)
679 } else {
680 context.trace(&format!(
681 "pattern FAILED match: {:?} =~ {pattern:?}",
682 line.text
683 ));
684 Err(OutputPatternMatchFailure {
685 location: location.clone(),
686 pattern_type: "pattern",
687 output_line: Some(line),
688 })
689 }
690 } else {
691 Err(OutputPatternMatchFailure {
692 location: location.clone(),
693 pattern_type: "pattern",
694 output_line: None,
695 })
696 }
697 }
698 OutputPatternType::Sequence(patterns) => {
699 for pattern in patterns {
700 match pattern.matches(context.descend(), output) {
701 Ok(v) => {
702 output = v;
703 }
704 Err(e) => {
705 return Err(e);
706 }
707 }
708 }
709 Ok(output)
710 }
711 OutputPatternType::Repeat(pattern) => {
712 let mut output = pattern.matches(context.descend(), output)?;
714 loop {
716 match pattern.matches(context.descend(), output.clone()) {
717 Ok(new_rest) => {
718 output = new_rest;
719 }
720 Err(_) => break Ok(output),
721 }
722 }
723 }
724 OutputPatternType::Optional(pattern) => {
725 match pattern.matches(context.descend(), output.clone()) {
727 Ok(v) => Ok(v),
728 Err(_) => Ok(output),
729 }
730 }
731 OutputPatternType::Unordered(patterns) => {
732 let mut not_found = (0..patterns.len()).collect::<HashSet<_>>();
734 'outer: while !not_found.is_empty() {
735 for pattern in ¬_found {
736 let pattern = *pattern;
737 match patterns[pattern].matches(context.descend(), output.clone()) {
738 Ok(v) => {
739 not_found.remove(&pattern);
740 output = v;
741 continue 'outer;
742 }
743 Err(_) => {
744 continue;
745 }
746 }
747 }
748 return Err(OutputPatternMatchFailure {
749 location: location.clone(),
750 pattern_type: "unordered",
751 output_line: None,
752 });
753 }
754 Ok(output)
755 }
756 OutputPatternType::Choice(patterns) => {
757 for pattern in patterns {
758 if let Ok(v) = pattern.matches(context.descend(), output.clone()) {
759 return Ok(v);
760 }
761 }
762 Err(OutputPatternMatchFailure {
763 location: location.clone(),
764 pattern_type: "choice",
765 output_line: None,
766 })
767 }
768 OutputPatternType::Any(until) => {
769 loop {
770 match until.matches(context.descend(), output.clone()) {
771 Ok(v) => {
772 output = v;
773 break Ok(output);
774 }
775 Err(e) => {
776 let (line, next) = output.next(context.clone())?;
778 if line.is_some() {
779 output = next;
780 continue;
781 } else {
782 break Err(e);
783 }
784 }
785 }
786 }
787 }
788 OutputPatternType::If(condition, pattern) => {
789 if condition.matches(context.script_context) {
790 context.trace(&format!("if match: {:?}", condition));
791 pattern.matches(context.clone(), output.clone())
792 } else {
793 context.trace(&format!("if FAILED match: {:?}", condition));
794 Ok(output)
795 }
796 }
797 OutputPatternType::End => {
798 let (line, next) = output.next(context)?;
799 if let Some(line) = line {
800 Err(OutputPatternMatchFailure {
801 location: location.clone(),
802 pattern_type: "end",
803 output_line: Some(line),
804 })
805 } else {
806 Ok(next)
807 }
808 }
809 }
810 }
811}
812
813#[derive(Debug)]
814pub enum PatternResult {
815 Matches,
816 MatchesFailure,
817 ExpectedFailure,
818 Mismatch(OutputPatternMatchFailure, String),
819}