1use anyhow::{bail, Result};
2
3pub fn process(
4 utf8_data: &str,
5 match_against: Vec<String>,
6 enter_pattern: &str,
7 exit_pattern: &str,
8 ignore_pattern: Option<String>,
9) -> Result<String> {
10 let mut matcher = MultilineMatch::new(
11 match_against,
12 enter_pattern.to_owned(),
13 exit_pattern.to_owned(),
14 ignore_pattern,
15 );
16 let mut outputs: Vec<String> = Vec::new();
17 for (i, line) in utf8_data.lines().enumerate() {
18 if let Some(line) = matcher
19 .check_line(line)
20 .map_err(|e| anyhow::anyhow!("parse failed at line {}: {e}", i + 1))?
21 {
22 outputs.push(line);
23 };
24 }
25 Ok(outputs.join("\n"))
26}
27
28#[derive(Debug, Clone)]
29struct MultilineMatch {
30 enter_pattern: String,
31 exit_pattern: String,
32 ignore_pattern: Option<String>,
33 match_against: Vec<String>,
34 default_case_buffer: Vec<String>,
35 state: State,
36}
37
38impl MultilineMatch {
39 fn new(
40 match_against: Vec<String>,
41 enter_pattern: String,
42 exit_pattern: String,
43 ignore_pattern: Option<String>,
44 ) -> Self {
45 Self {
46 enter_pattern,
47 exit_pattern,
48 ignore_pattern,
49 match_against,
50 default_case_buffer: Vec::new(),
51 state: State::Normal,
52 }
53 }
54
55 fn check_line(&mut self, line: &str) -> Result<Option<String>> {
56 let output = match self.check_new_state(line) {
57 Some(new_state) => self.handle_new_state(new_state)?,
58 None => self.handle_normal_line(line),
59 };
60 Ok(output)
61 }
62
63 fn handle_normal_line(&mut self, line: &str) -> Option<String> {
64 match &self.state {
65 State::Normal | State::Matched => Some(line.to_owned()),
66 State::Default => {
67 self.default_case_buffer.push(line.to_owned());
68 None
69 }
70 State::Other | State::Done => None,
71 }
72 }
73
74 fn check_new_state(&self, line: &str) -> Option<NewState> {
75 if let Some(ignore_pattern) = &self.ignore_pattern {
76 if line.contains(ignore_pattern) {
77 return None;
78 }
79 }
80 if let Some((_pat, names)) = line.split_once(&self.enter_pattern) {
81 let names = names.trim();
82 if names.is_empty() {
83 Some(NewState::Enter)
84 } else {
85 let names = names
86 .split_whitespace()
87 .map(std::borrow::ToOwned::to_owned)
88 .collect();
89 Some(NewState::Switch(names))
90 }
91 } else if line.contains(&self.exit_pattern) {
92 Some(NewState::Exit)
93 } else {
94 None
95 }
96 }
97
98 #[allow(clippy::match_same_arms)]
100 fn handle_new_state(&mut self, new_state: NewState) -> Result<Option<String>> {
101 let mut result_value = None;
102 self.state = match (&self.state, new_state) {
103 (State::Normal, NewState::Enter) => State::Default,
105 (State::Default | State::Other, NewState::Switch(names)) => {
107 if self.match_against.is_empty() {
108 result_value = Some(self.default_case_buffer.join("\n"));
110 State::Done
111 } else if self.match_against.iter().any(|m| names.contains(m)) {
112 State::Matched
114 } else {
115 State::Other
117 }
118 }
119 (State::Matched, NewState::Switch(_)) => State::Done,
121 (State::Done, NewState::Switch(_)) => State::Done,
123 (State::Matched | State::Done, NewState::Exit) => {
125 self.default_case_buffer.clear();
126 State::Normal
127 }
128 (State::Other, NewState::Exit) => {
130 result_value = Some(self.default_case_buffer.join("\n"));
131 self.default_case_buffer.clear();
132 State::Normal
133 }
134 (State::Normal, NewState::Switch(_)) => {
136 bail!("cannot start new case: need default first")
137 }
138 (State::Normal, NewState::Exit) => bail!("cannot end match: not in match"),
139 (State::Default, NewState::Enter) => {
140 bail!("cannot start new match: in default of previous match")
141 }
142 (State::Default, NewState::Exit) => bail!("ended match without alternatives"),
143 (State::Other, NewState::Enter) => {
144 bail!("cannot start new match: switching previous match")
145 }
146 (State::Matched, NewState::Enter) => {
147 bail!("cannot start new match: in matched case of previous match")
148 }
149 (State::Done, NewState::Enter) => {
150 bail!("cannot start new match: no exit of previous match")
151 }
152 };
153 Ok(result_value)
154 }
155}
156
157#[derive(Debug, Clone, Copy)]
158enum State {
159 Normal,
161 Default,
163 Matched,
165 Other,
167 Done,
169}
170
171#[derive(Debug)]
172enum NewState {
173 Enter,
175 Switch(Vec<String>),
177 Exit,
179}