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