1use std::{
2 collections::{BTreeSet, HashMap},
3 sync::{Arc, Mutex, OnceLock},
4};
5
6use grok::Grok;
7use serde::Serialize;
8
9use crate::failure::OutputPatternMatchFailure;
10use crate::{
11 failure::{OutputMatchTraceNode, PatternTraceNote},
12 script::{IfCondition, ScriptLocation, ScriptRunContext},
13};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct Line {
17 pub number: usize,
18 pub text: String,
19}
20
21#[derive(Clone)]
22pub struct Lines {
23 lines: Arc<Vec<String>>,
24 current_line: usize,
25 ignored_patterns: OutputPatterns,
26 negative_disabled: bool,
27 rejected_patterns: OutputPatterns,
28}
29
30impl std::fmt::Debug for Lines {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(
33 f,
34 "Lines {{ ignored: {} pattern(s), rejected: {} pattern(s) }}",
35 self.ignored_patterns.len(),
36 self.rejected_patterns.len()
37 )
38 }
39}
40
41impl<'s> IntoIterator for &'s Lines {
42 type Item = &'s String;
43 type IntoIter = std::slice::Iter<'s, String>;
44
45 fn into_iter(self) -> Self::IntoIter {
46 self.lines.iter()
47 }
48}
49
50impl std::fmt::Display for Lines {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "{}", self.lines[self.current_line..].join("\n"))
53 }
54}
55
56impl Lines {
57 pub fn new(lines: Vec<String>) -> Self {
58 Self {
59 lines: Arc::new(lines),
60 current_line: 0,
61 ignored_patterns: Default::default(),
62 negative_disabled: false,
63 rejected_patterns: Default::default(),
64 }
65 }
66
67 pub fn is_exhausted(&self) -> bool {
68 self.current_line >= self.lines.len()
69 }
70
71 pub fn next_line(&self) -> Option<Line> {
72 if self.current_line < self.lines.len() {
73 Some(Line {
74 number: self.current_line,
75 text: self.lines[self.current_line].clone(),
76 })
77 } else {
78 None
79 }
80 }
81
82 pub fn next(
83 &self,
84 context: OutputMatchContext,
85 ) -> Result<(Option<Line>, Lines), OutputPatternMatchFailure> {
86 let mut next = self.clone();
87 'outer: while next.current_line < next.lines.len() {
88 if !self.negative_disabled {
89 let ignore_check = next.without_negatives();
90 for ignored_pattern in &*next.ignored_patterns {
91 if let Ok(next_next) =
92 ignored_pattern.matches(context.ignore(), ignore_check.clone())
93 {
94 next = next_next.with_negatives();
95 continue 'outer;
96 }
97 }
98 for rejected_pattern in &*next.rejected_patterns {
99 if rejected_pattern
100 .matches(context.ignore(), ignore_check.clone())
101 .is_ok()
102 {
103 return Err(OutputPatternMatchFailure {
104 location: rejected_pattern.location.clone(),
105 pattern_type: "reject",
106 output_line: next.next_line(),
107 });
108 }
109 }
110 }
111 let line = Line {
112 number: next.current_line,
113 text: next.lines[next.current_line].clone(),
114 };
115 next.current_line += 1;
116 return Ok((Some(line), next));
117 }
118 Ok((None, next))
119 }
120
121 pub fn with_ignore(&self, ignore: &OutputPatterns) -> Self {
122 let mut ignored_patterns = self.ignored_patterns.clone();
123 ignored_patterns.extend(ignore);
124 Self {
125 ignored_patterns,
126 ..self.clone()
127 }
128 }
129
130 pub fn with_reject(&self, reject: &OutputPatterns) -> Self {
131 let mut rejected_patterns = self.rejected_patterns.clone();
132 rejected_patterns.extend(reject);
133 Self {
134 rejected_patterns,
135 ..self.clone()
136 }
137 }
138
139 fn without_negatives(&self) -> Self {
140 Self {
141 negative_disabled: true,
142 ..self.clone()
143 }
144 }
145
146 fn with_negatives(&self) -> Self {
147 Self {
148 negative_disabled: false,
149 ..self.clone()
150 }
151 }
152
153 pub fn into_inner(self) -> Vec<String> {
154 Arc::unwrap_or_clone(self.lines).split_off(self.current_line)
155 }
156
157 pub fn is_empty(&self) -> bool {
158 self.lines.is_empty()
159 }
160}
161
162#[derive(Clone, Default, Debug, Serialize)]
163
164pub struct OutputPatterns {
165 patterns: Arc<Vec<OutputPattern>>,
166}
167
168impl OutputPatterns {
169 pub fn new(patterns: Vec<OutputPattern>) -> Self {
170 Self {
171 patterns: Arc::new(patterns),
172 }
173 }
174
175 pub fn is_empty(&self) -> bool {
176 self.patterns.is_empty()
177 }
178
179 pub fn len(&self) -> usize {
180 self.patterns.len()
181 }
182
183 pub fn extend(&mut self, patterns: &OutputPatterns) {
184 if self.is_empty() {
185 self.patterns = patterns.patterns.clone();
186 return;
187 }
188 let new_patterns = std::mem::take(&mut self.patterns);
189 let mut new_patterns = Arc::unwrap_or_clone(new_patterns);
190 new_patterns.extend(patterns.patterns.iter().cloned());
191 self.patterns = Arc::new(new_patterns);
192 }
193}
194
195impl std::ops::Deref for OutputPatterns {
196 type Target = Vec<OutputPattern>;
197 fn deref(&self) -> &Self::Target {
198 &self.patterns
199 }
200}
201
202#[derive(Clone)]
203pub struct OutputPattern {
204 pub location: ScriptLocation,
205 pub pattern: OutputPatternType,
206 pub ignore: OutputPatterns,
207 pub reject: OutputPatterns,
208}
209
210impl Serialize for OutputPattern {
211 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
212 where
213 S: serde::Serializer,
214 {
215 self.pattern.serialize(serializer)
216 }
217}
218
219impl std::fmt::Debug for OutputPattern {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 write!(f, "{:?}", self.pattern)
222 }
223}
224
225impl OutputPattern {
226 pub fn new_sequence(location: ScriptLocation, mut patterns: Vec<OutputPattern>) -> Self {
227 if patterns.len() == 1 {
228 patterns.remove(0)
229 } else {
230 Self {
231 pattern: OutputPatternType::Sequence(patterns),
232 ignore: Default::default(),
233 reject: Default::default(),
234 location: location.clone(),
235 }
236 }
237 }
238
239 pub fn prepare(&self, grok: &Grok) -> Result<(), OutputPatternPrepareError> {
240 for pattern in &*self.ignore.patterns {
241 pattern.prepare(grok)?
242 }
243 for pattern in &*self.reject.patterns {
244 pattern.prepare(grok)?
245 }
246 match &self.pattern {
247 OutputPatternType::Pattern(pattern) => {
248 pattern
249 .prepare(grok)
250 .map_err(|e| OutputPatternPrepareError {
251 location: self.location.clone(),
252 pattern: pattern.pattern.clone(),
253 error: e,
254 })?
255 }
256 OutputPatternType::Sequence(patterns) => {
257 for pattern in patterns {
258 pattern.prepare(grok)?;
259 }
260 }
261 OutputPatternType::Unordered(patterns) => {
262 for pattern in patterns {
263 pattern.prepare(grok)?;
264 }
265 }
266 OutputPatternType::Choice(patterns) => {
267 for pattern in patterns {
268 pattern.prepare(grok)?;
269 }
270 }
271 OutputPatternType::If(_, pattern) => pattern.prepare(grok)?,
272 OutputPatternType::Not(pattern) => pattern.prepare(grok)?,
273 OutputPatternType::Any(pattern) => pattern.prepare(grok)?,
274 OutputPatternType::Repeat(pattern) => pattern.prepare(grok)?,
275 OutputPatternType::Optional(pattern) => pattern.prepare(grok)?,
276 OutputPatternType::Literal(_) => {}
277 OutputPatternType::End | OutputPatternType::None => {}
278 }
279 Ok(())
280 }
281
282 pub fn matches(
283 &self,
284 context: OutputMatchContext,
285 output: Lines,
286 ) -> Result<Lines, OutputPatternMatchFailure> {
287 if self.ignore.is_empty() && self.reject.is_empty() {
288 self.pattern.matches(&self.location, context, output)
289 } else {
290 let output = output.with_ignore(&self.ignore).with_reject(&self.reject);
291 self.pattern.matches(&self.location, context, output)
292 }
293 }
294
295 pub fn min_matches(&self) -> usize {
297 self.pattern.min_matches()
298 }
299
300 pub fn max_matches(&self) -> usize {
302 self.pattern.max_matches()
303 }
304}
305
306#[derive(thiserror::Error, Debug)]
307#[error("pattern {pattern} at line {location} failed to compile: {error}")]
308pub struct OutputPatternPrepareError {
309 pub location: ScriptLocation,
310 pub pattern: String,
311 pub error: grok::Error,
312}
313
314#[derive(Clone)]
315pub enum OutputPatternType {
316 End,
318 None,
320 Any(Box<OutputPattern>),
322 Literal(String),
324 Pattern(Arc<GrokPattern>),
326 Repeat(Box<OutputPattern>),
328 Optional(Box<OutputPattern>),
330 Unordered(Vec<OutputPattern>),
332 Choice(Vec<OutputPattern>),
334 Sequence(Vec<OutputPattern>),
336 Not(Box<OutputPattern>),
338 If(IfCondition, Box<OutputPattern>),
340}
341
342impl Serialize for OutputPatternType {
343 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
344 where
345 S: serde::Serializer,
346 {
347 match self {
348 OutputPatternType::Literal(literal) => {
349 serializer.serialize_str(&format!("! {literal}"))
350 }
351 OutputPatternType::Pattern(pattern) => {
352 serializer.serialize_str(&format!("? {}", pattern.pattern))
353 }
354 OutputPatternType::Repeat(pattern) => {
355 HashMap::from([("repeat", &pattern)]).serialize(serializer)
356 }
357 OutputPatternType::Optional(pattern) => {
358 HashMap::from([("optional", &pattern)]).serialize(serializer)
359 }
360 OutputPatternType::Unordered(patterns) => {
361 HashMap::from([("unordered", &patterns)]).serialize(serializer)
362 }
363 OutputPatternType::Choice(patterns) => {
364 HashMap::from([("choice", &patterns)]).serialize(serializer)
365 }
366 OutputPatternType::Sequence(patterns) => {
367 HashMap::from([("sequence", &patterns)]).serialize(serializer)
368 }
369 OutputPatternType::Not(pattern) => {
370 HashMap::from([("not", &pattern)]).serialize(serializer)
371 }
372 OutputPatternType::Any(pattern) => {
373 HashMap::from([("any", &pattern)]).serialize(serializer)
374 }
375 OutputPatternType::If(condition, pattern) => {
376 #[derive(Serialize)]
377 struct If<'a> {
378 condition: &'a IfCondition,
379 pattern: &'a OutputPattern,
380 }
381 If { condition, pattern }.serialize(serializer)
382 }
383 OutputPatternType::End => serializer.serialize_str("end"),
384 OutputPatternType::None => serializer.serialize_str("none"),
385 }
386 }
387}
388
389impl OutputPatternType {
390 pub fn min_matches(&self) -> usize {
392 match self {
393 OutputPatternType::None => 0,
394 OutputPatternType::Literal(_) => 1,
395 OutputPatternType::Pattern(_) => 1,
396 OutputPatternType::Repeat(pattern) => pattern.min_matches(),
397 OutputPatternType::Optional(_) => 0,
398 OutputPatternType::Unordered(patterns) => {
399 patterns.iter().map(|p| p.min_matches()).sum()
400 }
401 OutputPatternType::Choice(patterns) => {
402 patterns.iter().map(|p| p.min_matches()).min().unwrap_or(0)
403 }
404 OutputPatternType::Sequence(patterns) => patterns.iter().map(|p| p.min_matches()).sum(),
405 OutputPatternType::Not(_) => 0,
406 OutputPatternType::Any(pattern) => pattern.min_matches(),
407 OutputPatternType::If(_, _) => 0,
408 OutputPatternType::End => 0,
409 }
410 }
411
412 pub fn max_matches(&self) -> usize {
414 fn saturating_iter_sum<I>(iter: I) -> usize
415 where
416 I: IntoIterator<Item = usize>,
417 {
418 iter.into_iter()
419 .reduce(|n, i| n.saturating_add(i))
420 .unwrap_or(0)
421 }
422
423 match self {
424 OutputPatternType::None => 0,
425 OutputPatternType::Literal(_) => 1,
426 OutputPatternType::Pattern(_) => 1,
427 OutputPatternType::Repeat(pattern) => {
428 if pattern.max_matches() == 0 {
429 0
430 } else {
431 usize::MAX
432 }
433 }
434 OutputPatternType::Optional(pattern) => pattern.max_matches(),
435 OutputPatternType::Unordered(patterns) => {
436 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
437 }
438 OutputPatternType::Choice(patterns) => {
439 patterns.iter().map(|p| p.max_matches()).max().unwrap_or(0)
440 }
441 OutputPatternType::Sequence(patterns) => {
442 saturating_iter_sum(patterns.iter().map(|p| p.max_matches()))
443 }
444 OutputPatternType::Not(_) => 0,
445 OutputPatternType::Any(_) => usize::MAX,
446 OutputPatternType::If(_, pattern) => pattern.max_matches(),
447 OutputPatternType::End => 0,
448 }
449 }
450
451 pub fn keyword(&self) -> &'static str {
452 match self {
453 OutputPatternType::Literal(_) => "\"...\"",
454 OutputPatternType::Pattern(_) => "? ...",
455 OutputPatternType::Repeat(_) => "repeat",
456 OutputPatternType::Optional(_) => "optional",
457 OutputPatternType::Unordered(_) => "unordered",
458 OutputPatternType::Choice(_) => "choice",
459 OutputPatternType::Sequence(_) => "sequence",
460 OutputPatternType::Not(_) => "not",
461 OutputPatternType::Any(_) => "*",
462 OutputPatternType::If(_, _) => "if",
463 OutputPatternType::End => "end",
464 OutputPatternType::None => "none",
465 }
466 }
467
468 pub fn is_container(&self) -> bool {
469 match self {
470 OutputPatternType::Literal(_) => false,
471 OutputPatternType::Pattern(_) => false,
472 OutputPatternType::Repeat(_) => true,
473 OutputPatternType::Optional(_) => true,
474 OutputPatternType::Unordered(_) => true,
475 OutputPatternType::Choice(_) => true,
476 OutputPatternType::Sequence(_) => true,
477 OutputPatternType::Not(_) => true,
478 OutputPatternType::Any(_) => false,
479 OutputPatternType::If(_, _) => true,
480 OutputPatternType::End => false,
481 OutputPatternType::None => false,
482 }
483 }
484
485 pub fn trace_string(&self) -> String {
486 use std::fmt::Write;
487 let mut out = String::new();
488 _ = match self {
489 OutputPatternType::Any(pattern) => {
490 if let OutputPatternType::End = pattern.pattern {
491 write!(out, "*")
492 } else {
493 write!(out, "* ... {}", pattern.pattern.trace_string())
494 }
495 }
496 OutputPatternType::End => {
497 write!(out, "<eof>")
498 }
499 _ if self.is_container() => {
500 write!(out, "{} {{ ... }}", self.keyword())
501 }
502 _ => {
503 write!(out, "{:?}", self)
504 }
505 };
506 out
507 }
508}
509
510impl Default for OutputPatternType {
511 fn default() -> Self {
512 Self::Sequence(vec![])
513 }
514}
515
516impl std::fmt::Debug for OutputPatternType {
517 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518 match self {
519 OutputPatternType::Literal(literal) => write!(f, "{literal:?}"),
520 OutputPatternType::Pattern(pattern) => write!(f, "Pattern({pattern:?})"),
521 OutputPatternType::Repeat(pattern) => write!(f, "Repeat({pattern:?})"),
522 OutputPatternType::Optional(pattern) => write!(f, "Optional({pattern:?})"),
523 OutputPatternType::Unordered(patterns) => write!(f, "Unordered({patterns:?})"),
524 OutputPatternType::Choice(patterns) => write!(f, "Choice({patterns:?})"),
525 OutputPatternType::Sequence(patterns) => write!(f, "Sequence({patterns:?})"),
526 OutputPatternType::Not(pattern) => write!(f, "Not({pattern:?})"),
527 OutputPatternType::Any(until) => write!(f, "Any({until:?})"),
528 OutputPatternType::If(condition, pattern) => {
529 write!(f, "If({condition:?}, {pattern:?})")
530 }
531 OutputPatternType::End => write!(f, "End"),
532 OutputPatternType::None => write!(f, "None"),
533 }
534 }
535}
536
537#[derive(Serialize, derive_more::Debug)]
538#[debug("/{pattern:?}/")]
539pub struct GrokPattern {
540 pattern: String,
541 aliases: Vec<String>,
542 #[serde(skip)]
543 grok: OnceLock<grok::Pattern>,
544}
545
546impl GrokPattern {
547 pub fn compile(line: &str, escape_non_grok: bool) -> Result<Self, String> {
548 use grok::parser::GrokPatternError;
549 let mut test_pattern = String::new();
550 let mut final_pattern = String::new();
551 let mut aliases = vec![];
552 for bit in grok::parser::grok_split(line) {
553 match bit {
554 grok::parser::GrokComponent::RegularExpression { string, .. } => {
555 if escape_non_grok {
556 for char in string.chars() {
557 if char.is_ascii() && !char.is_alphanumeric() {
558 test_pattern.push('\\');
559 test_pattern.push(char);
560 final_pattern.push('\\');
561 final_pattern.push(char);
562 } else {
563 test_pattern.push(char);
564 final_pattern.push(char);
565 }
566 }
567 } else {
568 test_pattern.push_str(string);
569 final_pattern.push_str(string);
570 }
571 }
572 grok::parser::GrokComponent::GrokPattern { pattern, alias, .. } => {
573 test_pattern.push('.');
574 final_pattern.push_str(pattern);
575 if !alias.is_empty() {
576 aliases.push(alias.to_string());
577 }
578 }
579 grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidCharacter(
580 c,
581 )) => {
582 return Err(format!("Invalid character in pattern: {c:?}"));
583 }
584 grok::parser::GrokComponent::PatternError(GrokPatternError::InvalidPattern) => {
585 return Err("Invalid grok pattern".to_string());
586 }
587 grok::parser::GrokComponent::PatternError(
588 GrokPatternError::InvalidPatternDefinition,
589 ) => {
590 return Err("Invalid grok pattern definition".to_string());
591 }
592 }
593 }
594
595 test_pattern.push('$');
596 final_pattern.push('$');
597
598 _ = Grok::empty()
599 .compile(&test_pattern, false)
600 .map_err(|e| e.to_string())?;
601
602 Ok(Self {
603 pattern: final_pattern,
604 aliases,
605 grok: OnceLock::new(),
606 })
607 }
608
609 pub fn prepare(&self, grok: &Grok) -> Result<(), grok::Error> {
610 if self.grok.get().is_none() {
613 let pattern = grok.compile(&self.pattern, false)?;
614 self.grok.get_or_init(move || pattern);
615 }
616 Ok(())
617 }
618
619 pub fn matches<'a>(&'a self, text: &'a str) -> Option<grok::Matches<'a>> {
620 let pattern_ref = self.grok.get().expect("grok pattern not compiled");
621 pattern_ref.match_against(text)
622 }
623}
624
625#[derive(Debug, Default)]
626struct OutputMatchTraceCollector {
627 root: Vec<OutputMatchTraceNode>,
628 path: Vec<usize>,
631}
632
633fn resolve_trace_node_mut<'a>(
634 root: &'a mut Vec<OutputMatchTraceNode>,
635 path: &[usize],
636 idx: usize,
637) -> &'a mut OutputMatchTraceNode {
638 let mut cur = root;
639 for &p in path {
640 cur = &mut cur[p].children;
641 }
642 &mut cur[idx]
643}
644
645impl OutputMatchTraceCollector {
646 fn navigate_mut<'a>(&'a mut self) -> &'a mut Vec<OutputMatchTraceNode> {
647 let mut cur = &mut self.root;
648 for &idx in &self.path {
649 cur = &mut cur[idx].children;
650 }
651 cur
652 }
653
654 fn composite_pattern_begin(&mut self, pattern: OutputPatternType, ignore: bool) {
655 let list = self.navigate_mut();
656 let idx = list.len();
657 list.push(OutputMatchTraceNode {
658 ignore,
659 pattern,
660 succeeded: false,
661 output_line: None,
662 note: None,
663 children: Vec::new(),
664 });
665 self.path.push(idx);
666 }
667
668 fn composite_pattern_end(
669 &mut self,
670 succeeded: bool,
671 output_line: Option<Line>,
672 note: Option<PatternTraceNote>,
673 ) {
674 let idx = self
675 .path
676 .pop()
677 .expect("composite_pattern_end without composite_pattern_begin");
678 let node = resolve_trace_node_mut(&mut self.root, &self.path, idx);
679 node.succeeded = succeeded;
680 node.output_line = output_line;
681 node.note = note;
682 }
683
684 fn pop_traces_before_last(&mut self, count: usize) {
685 let trace = self.navigate_mut().pop().expect("no trace to pop");
686 for _ in 0..count {
687 self.navigate_mut().pop().expect("no trace to pop");
688 }
689 self.navigate_mut().push(trace);
690 }
691
692 fn leaf_pattern(&mut self, node: OutputMatchTraceNode) {
693 let list = self.navigate_mut();
694 list.push(node);
695 }
696
697 fn take_root(&mut self) -> Vec<OutputMatchTraceNode> {
698 self.path.clear();
699 std::mem::take(&mut self.root)
700 }
701}
702
703#[derive(Debug, Clone)]
704pub struct OutputMatchContext<'s> {
705 trace: Arc<Mutex<OutputMatchTraceCollector>>,
706 ignore: bool,
707 expectations: Arc<Mutex<HashMap<String, String>>>,
708 script_context: &'s ScriptRunContext,
709}
710
711struct RawPatternOk {
713 lines: Lines,
714 matched_line_if_ok: Option<Line>,
715 note: Option<PatternTraceNote>,
717}
718
719struct RawPatternErr {
721 failure: OutputPatternMatchFailure,
722 note: Option<PatternTraceNote>,
723}
724
725impl From<OutputPatternMatchFailure> for RawPatternErr {
726 fn from(failure: OutputPatternMatchFailure) -> Self {
727 Self {
728 failure,
729 note: None,
730 }
731 }
732}
733
734type RawPatternMatch = Result<RawPatternOk, RawPatternErr>;
736
737fn raw_ok(lines: Lines, matched_line_if_ok: Option<Line>) -> RawPatternMatch {
738 Ok(RawPatternOk {
739 lines,
740 matched_line_if_ok,
741 note: None,
742 })
743}
744
745fn raw_err(failure: OutputPatternMatchFailure, note: Option<PatternTraceNote>) -> RawPatternMatch {
746 Err(RawPatternErr { failure, note })
747}
748
749fn raw_into_public(raw: RawPatternMatch) -> Result<Lines, OutputPatternMatchFailure> {
750 raw.map(|ok| ok.lines).map_err(|e| e.failure)
751}
752
753fn record_leaf_pattern(
754 context: &OutputMatchContext<'_>,
755 pattern: OutputPatternType,
756 raw: &RawPatternMatch,
757) {
758 let node = match raw {
759 Ok(ok) => OutputMatchTraceNode {
760 ignore: context.ignore,
761 pattern,
762 succeeded: true,
763 output_line: ok.matched_line_if_ok.clone(),
764 note: ok.note.clone(),
765 children: Vec::new(),
766 },
767 Err(err) => OutputMatchTraceNode {
768 ignore: context.ignore,
769 pattern,
770 succeeded: false,
771 output_line: err.failure.output_line.clone(),
772 note: err.note.clone(),
773 children: Vec::new(),
774 },
775 };
776 context.trace.lock().unwrap().leaf_pattern(node);
777}
778
779fn finish_composite_pattern(context: &OutputMatchContext<'_>, raw: &RawPatternMatch) {
780 let (succeeded, output_line, note) = match raw {
781 Ok(ok) => (true, ok.matched_line_if_ok.clone(), ok.note.clone()),
782 Err(err) => (false, err.failure.output_line.clone(), err.note.clone()),
783 };
784 context
785 .trace
786 .lock()
787 .unwrap()
788 .composite_pattern_end(succeeded, output_line, note);
789}
790
791impl<'s> OutputMatchContext<'s> {
792 pub fn new(script_context: &'s ScriptRunContext) -> Self {
793 Self {
794 trace: Default::default(),
795 ignore: false,
796 script_context,
797 expectations: Default::default(),
798 }
799 }
800
801 pub fn descend(&self) -> Self {
803 Self {
804 trace: self.trace.clone(),
805 ignore: self.ignore,
806 script_context: self.script_context,
807 expectations: self.expectations.clone(),
808 }
809 }
810
811 pub fn composite_pattern_begin(&self, pattern: OutputPatternType) {
812 self.trace
813 .lock()
814 .unwrap()
815 .composite_pattern_begin(pattern, self.ignore);
816 }
817
818 pub fn ignore(&self) -> Self {
819 Self {
820 trace: self.trace.clone(),
821 ignore: true,
822 script_context: self.script_context,
823 expectations: self.expectations.clone(),
824 }
825 }
826
827 pub fn traces(&self) -> Vec<OutputMatchTraceNode> {
828 self.trace.lock().unwrap().take_root()
829 }
830
831 pub fn expect(&self, key: &str, value: String) {
832 self.expectations
833 .lock()
834 .unwrap()
835 .insert(key.to_string(), value);
836 }
837
838 pub fn expects(&self) -> HashMap<String, String> {
839 self.expectations.lock().unwrap().clone()
840 }
841}
842
843impl OutputPatternType {
844 pub fn matches(
845 &self,
846 location: &ScriptLocation,
847 context: OutputMatchContext,
848 output: Lines,
849 ) -> Result<Lines, OutputPatternMatchFailure> {
850 match self {
851 OutputPatternType::None
852 | OutputPatternType::Literal(_)
853 | OutputPatternType::Pattern(_)
854 | OutputPatternType::End => {
855 let raw = self.raw_matches(location, &context, output);
856 record_leaf_pattern(&context, self.clone(), &raw);
857 raw_into_public(raw)
858 }
859 _ => {
860 context.composite_pattern_begin(self.clone());
861 let raw = self.raw_matches(location, &context, output);
862 finish_composite_pattern(&context, &raw);
863 raw_into_public(raw)
864 }
865 }
866 }
867
868 fn raw_matches(
869 &self,
870 location: &ScriptLocation,
871 context: &OutputMatchContext<'_>,
872 mut output: Lines,
873 ) -> RawPatternMatch {
874 match self {
875 OutputPatternType::None => raw_ok(output, None),
876 OutputPatternType::Literal(literal) => {
877 let (line, next) = output.next(context.clone()).map_err(RawPatternErr::from)?;
878 let Some(line) = line else {
879 return raw_err(
880 OutputPatternMatchFailure {
881 location: location.clone(),
882 pattern_type: "literal",
883 output_line: None,
884 },
885 None,
886 );
887 };
888 let text = line.text.trim_end();
889 if text == literal
890 || (line.text.contains('\x1b')
891 && fast_strip_ansi::strip_ansi_string(&line.text).as_ref() == literal)
892 {
893 raw_ok(next, Some(line.clone()))
894 } else {
895 raw_err(
896 OutputPatternMatchFailure {
897 location: location.clone(),
898 pattern_type: "literal",
899 output_line: Some(line.clone()),
900 },
901 None,
902 )
903 }
904 }
905 OutputPatternType::Pattern(pattern) => {
906 let (line, next) = output.next(context.clone()).map_err(RawPatternErr::from)?;
907 let Some(line) = line else {
908 return raw_err(
909 OutputPatternMatchFailure {
910 location: location.clone(),
911 pattern_type: "pattern",
912 output_line: None,
913 },
914 None,
915 );
916 };
917 let mut text = line.text.clone();
918 let mut res = pattern.matches(&text);
919 if res.is_none() {
920 if text.contains('\x1b') {
922 text = fast_strip_ansi::strip_ansi_string(&text).into_owned();
923 res = pattern.matches(&text);
924 }
925 }
926 match res {
927 None => raw_err(
928 OutputPatternMatchFailure {
929 location: location.clone(),
930 pattern_type: "pattern",
931 output_line: Some(line.clone()),
932 },
933 None,
934 ),
935 Some(matches) => {
936 for alias in &pattern.aliases {
937 if let Some(value) = matches.get(alias) {
938 let existing = context
939 .expectations
940 .lock()
941 .unwrap()
942 .insert(alias.clone(), value.to_string());
943 if let Some(existing) = existing
944 && existing != value
945 {
946 return raw_err(
947 OutputPatternMatchFailure {
948 location: location.clone(),
949 pattern_type: "pattern",
950 output_line: Some(line.clone()),
951 },
952 Some(PatternTraceNote::AliasMismatch(
953 existing,
954 value.to_string(),
955 )),
956 );
957 }
958 }
959 }
960 raw_ok(next, Some(line.clone()))
961 }
962 }
963 }
964 OutputPatternType::Sequence(patterns) => {
965 for pattern in patterns {
966 output = pattern
967 .matches(context.descend(), output)
968 .map_err(RawPatternErr::from)?;
969 }
970 raw_ok(output, None)
971 }
972 OutputPatternType::Repeat(pattern) => {
973 let mut output = pattern
974 .matches(context.descend(), output)
975 .map_err(RawPatternErr::from)?;
976 loop {
977 match pattern.matches(context.descend(), output.clone()) {
978 Ok(new_rest) => output = new_rest,
979 Err(_) => break,
980 }
981 }
982 raw_ok(output, None)
983 }
984 OutputPatternType::Optional(pattern) => {
985 let lines = match pattern.matches(context.descend(), output.clone()) {
986 Ok(v) => v,
987 Err(_) => output,
988 };
989 raw_ok(lines, None)
990 }
991 OutputPatternType::Unordered(patterns) => {
992 let mut not_found = (0..patterns.len()).collect::<BTreeSet<_>>();
993 'outer: while !not_found.is_empty() {
994 let mut cleanup = 0;
995 for idx in ¬_found {
996 let idx = *idx;
997 match patterns[idx].matches(context.descend(), output.clone()) {
998 Ok(v) => {
999 not_found.remove(&idx);
1000 output = v;
1001 context
1002 .trace
1003 .lock()
1004 .unwrap()
1005 .pop_traces_before_last(cleanup);
1006 continue 'outer;
1007 }
1008 Err(_) => {
1009 cleanup += 1;
1010 }
1011 }
1012 }
1013 return raw_err(
1014 OutputPatternMatchFailure {
1015 location: location.clone(),
1016 pattern_type: "unordered",
1017 output_line: output.next_line(),
1018 },
1019 None,
1020 );
1021 }
1022 raw_ok(output, None)
1023 }
1024 OutputPatternType::Choice(patterns) => {
1025 for pattern in patterns {
1026 if let Ok(v) = pattern.matches(context.descend(), output.clone()) {
1027 return Ok(RawPatternOk {
1028 lines: v,
1029 matched_line_if_ok: None,
1030 note: None,
1031 });
1032 }
1033 }
1034 raw_err(
1035 OutputPatternMatchFailure {
1036 location: location.clone(),
1037 pattern_type: "choice",
1038 output_line: output.next_line(),
1039 },
1040 None,
1041 )
1042 }
1043 OutputPatternType::Not(pattern) => {
1044 if pattern.matches(context.descend(), output.clone()).is_err() {
1045 raw_ok(output, None)
1046 } else {
1047 raw_err(
1048 OutputPatternMatchFailure {
1049 location: location.clone(),
1050 pattern_type: "not",
1051 output_line: output.next_line(),
1052 },
1053 None,
1054 )
1055 }
1056 }
1057 OutputPatternType::Any(until) => loop {
1058 match until.matches(context.descend(), output.clone()) {
1059 Ok(v) => {
1060 output = v;
1061 break raw_ok(output, None);
1062 }
1063 Err(e) => match output.next(context.clone()) {
1064 Err(failure) => break Err(failure.into()),
1065 Ok((Some(_), next)) => output = next,
1066 Ok((None, _)) => break Err(e.into()),
1067 },
1068 }
1069 },
1070 OutputPatternType::If(condition, pattern) => {
1071 let branch_met = condition.matches(context.script_context);
1072 let branch_note = if branch_met {
1073 PatternTraceNote::IfConditionMet(condition.clone())
1074 } else {
1075 PatternTraceNote::IfConditionSkipped(condition.clone())
1076 };
1077 let inner = if branch_met {
1078 pattern.matches(context.clone(), output.clone())
1079 } else {
1080 Ok(output)
1081 };
1082 inner
1083 .map(|lines| RawPatternOk {
1084 lines,
1085 matched_line_if_ok: None,
1086 note: Some(branch_note.clone()),
1087 })
1088 .map_err(|failure| RawPatternErr {
1089 failure,
1090 note: Some(branch_note),
1091 })
1092 }
1093 OutputPatternType::End => {
1094 let (line, next) = output.next(context.clone()).map_err(RawPatternErr::from)?;
1095 if let Some(line) = line {
1096 raw_err(
1097 OutputPatternMatchFailure {
1098 location: location.clone(),
1099 pattern_type: "end",
1100 output_line: Some(line),
1101 },
1102 None,
1103 )
1104 } else {
1105 raw_ok(next, None)
1106 }
1107 }
1108 }
1109 }
1110}
1111
1112#[derive(Debug)]
1113pub enum PatternResult {
1114 Matches,
1115 MatchesFailure,
1116 ExpectedFailure,
1117 Mismatch(OutputPatternMatchFailure, String),
1118}