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