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