1#[derive(Debug, PartialEq, Eq)]
8enum SendItem {
9 Index(usize),
10 Str(String),
11}
12
13impl SendItem {
14 fn min_size(&self) -> usize {
15 match self {
16 Self::Index(_) => 0,
17 Self::Str(s) => s.len(),
18 }
19 }
20}
21
22#[derive(Debug)]
56pub struct PatternLine {
57 line: Vec<SendItem>,
58 min_size: usize,
59}
60
61impl PatternLine {
62 pub fn new(template: String, patterns: Vec<String>) -> Self {
66 let line = Self::_from(patterns, Vec::from([SendItem::Str(template)]));
67 Self {
68 min_size: line.iter().map(SendItem::min_size).sum(),
69 line,
70 }
71 }
72 fn _from(mut patterns: Vec<String>, acc: Vec<SendItem>) -> Vec<SendItem> {
73 match patterns.pop() {
74 None => acc,
75 Some(pattern) => {
76 let enclosed_pattern = format!("<{pattern}>");
77 let acc = acc
78 .into_iter()
79 .flat_map(|item| match &item {
80 SendItem::Index(_) => vec![item],
81 SendItem::Str(str) => match str.find(&enclosed_pattern) {
82 Some(i) => {
83 let pattern_index = patterns.len();
84 let mut ret = vec![];
85
86 let (left, mid) = str.split_at(i);
87 if !left.is_empty() {
88 ret.push(SendItem::Str(left.into()))
89 }
90
91 ret.push(SendItem::Index(pattern_index));
92
93 if mid.len() > enclosed_pattern.len() {
94 let (_, right) = mid.split_at(enclosed_pattern.len());
95 ret.push(SendItem::Str(right.into()))
96 }
97
98 ret
99 }
100 None => vec![item],
101 },
102 })
103 .collect();
104 Self::_from(patterns, acc)
105 }
106 }
107 }
108
109 pub fn line(&self, match_: Vec<String>) -> String {
110 let mut res = String::with_capacity(self.min_size);
111 for item in &self.line {
112 match item {
113 SendItem::Index(i) => {
114 if let Some(element) = match_.get(*i) {
115 res.push_str(element);
116 }
117 }
118 SendItem::Str(str) => res.push_str(str),
119 }
120 }
121 res
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::line::{PatternLine, SendItem};
128
129 #[test]
130 fn line_0_pattern() {
131 let msg = "my message".to_string();
132 let line = PatternLine::new(msg.clone(), vec![]);
133 assert_eq!(line.line, vec![SendItem::Str(msg.clone())]);
134 assert_eq!(line.min_size, msg.len());
135 assert_eq!(line.line(vec![]), msg.clone());
136 }
137
138 #[test]
139 fn line_1_pattern() {
140 let patterns = vec![
141 "ignored".into(),
142 "oh".into(),
143 "ignored".into(),
144 "my".into(),
145 "test".into(),
146 ];
147
148 let matches = vec!["yay", "oh", "my", "test", "<oh>", "<my>", "<test>"];
149
150 let tests = [
151 (
152 "<oh> my test",
153 1,
154 vec![SendItem::Index(1), SendItem::Str(" my test".into())],
155 vec![
156 ("yay", "yay my test"),
157 ("oh", "oh my test"),
158 ("my", "my my test"),
159 ("test", "test my test"),
160 ("<oh>", "<oh> my test"),
161 ("<my>", "<my> my test"),
162 ("<test>", "<test> my test"),
163 ],
164 ),
165 (
166 "oh <my> test",
167 3,
168 vec![
169 SendItem::Str("oh ".into()),
170 SendItem::Index(3),
171 SendItem::Str(" test".into()),
172 ],
173 vec![
174 ("yay", "oh yay test"),
175 ("oh", "oh oh test"),
176 ("my", "oh my test"),
177 ("test", "oh test test"),
178 ("<oh>", "oh <oh> test"),
179 ("<my>", "oh <my> test"),
180 ("<test>", "oh <test> test"),
181 ],
182 ),
183 (
184 "oh my <test>",
185 4,
186 vec![SendItem::Str("oh my ".into()), SendItem::Index(4)],
187 vec![
188 ("yay", "oh my yay"),
189 ("oh", "oh my oh"),
190 ("my", "oh my my"),
191 ("test", "oh my test"),
192 ("<oh>", "oh my <oh>"),
193 ("<my>", "oh my <my>"),
194 ("<test>", "oh my <test>"),
195 ],
196 ),
197 ];
198
199 for (msg, index, expected_pl, lines) in tests {
200 let pattern_line = PatternLine::new(msg.to_string(), patterns.clone());
201 assert_eq!(pattern_line.line, expected_pl);
202
203 for (match_element, line) in lines {
204 for match_default in &matches {
205 let mut match_ = vec![
206 match_default.to_string(),
207 match_default.to_string(),
208 match_default.to_string(),
209 match_default.to_string(),
210 match_default.to_string(),
211 ];
212 match_[index] = match_element.to_string();
213 assert_eq!(
214 pattern_line.line(match_.clone()),
215 line,
216 "match: {match_:?}, pattern_line: {pattern_line:?}"
217 );
218 }
219 }
220 }
221 }
222
223 #[test]
224 fn line_2_pattern() {
225 let pattern_line = PatternLine::new("<a> ; <b>".into(), vec!["a".into(), "b".into()]);
226
227 let matches = ["a", "b", "ab", "<a>", "<b>"];
228 for a in &matches {
229 for b in &matches {
230 assert_eq!(
231 pattern_line.line(vec![a.to_string(), b.to_string()]),
232 format!("{a} ; {b}"),
233 );
234 }
235 }
236 }
237}