1use std::collections::BTreeMap;
2use std::fmt;
3use std::fmt::Write;
4use std::option::Option;
5use std::str::FromStr;
6
7use super::error::Error;
8
9use crate::escaped::{escape_char, unescape_char};
10
11#[derive(Debug, PartialEq, Default, Clone)]
21pub struct Prefix {
22 pub nick: String,
23 pub user: Option<String>,
24 pub host: Option<String>,
25}
26
27impl Prefix {
28 pub fn new(nick: &str) -> Self {
29 Self::new_with_all(nick, None, None)
30 }
31
32 pub fn new_with_all(nick: &str, user: Option<&str>, host: Option<&str>) -> Self {
33 Prefix {
34 nick: nick.to_string(),
35 user: user.map(|s| s.to_string()),
36 host: host.map(|s| s.to_string()),
37 }
38 }
39}
40
41impl FromStr for Prefix {
42 type Err = Error;
43
44 fn from_str(input: &str) -> Result<Self, Self::Err> {
46 let mut parts = input.splitn(2, '@');
47
48 let rest = parts.next().unwrap_or("");
50 let host = parts.next();
51
52 let mut parts = rest.splitn(2, '!');
53 let nick = parts.next().unwrap_or("").to_string();
54 let user = parts.next();
55
56 Ok(Prefix {
57 nick,
58 user: user.and_then(|s| if s == "" { None } else { Some(s.to_string()) }),
59 host: host.and_then(|s| if s == "" { None } else { Some(s.to_string()) }),
60 })
61 }
62}
63
64impl fmt::Display for Prefix {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 f.write_str(&self.nick)?;
67
68 if let Some(user) = self.user.as_ref() {
69 f.write_char('!')?;
70 f.write_str(&user[..])?;
71 }
72
73 if let Some(host) = self.host.as_ref() {
74 f.write_char('@')?;
75 f.write_str(&host[..])?;
76 }
77
78 Ok(())
79 }
80}
81
82#[derive(Debug, PartialEq, Default, Clone)]
99pub struct Message {
100 pub tags: BTreeMap<String, String>,
101 pub prefix: Option<Prefix>,
102 pub command: String,
103 pub params: Vec<String>,
104}
105
106impl Message {
107 pub fn new(command: String, params: Vec<String>) -> Self {
108 Message {
109 command,
110 params,
111 ..Default::default()
112 }
113 }
114
115 pub fn new_with_all(
116 tags: BTreeMap<String, String>,
117 prefix: Option<Prefix>,
118 command: String,
119 params: Vec<String>,
120 ) -> Self {
121 Message {
122 tags,
123 prefix,
124 command,
125 params,
126 }
127 }
128
129 pub fn new_with_prefix(command: String, params: Vec<String>, prefix: Prefix) -> Self {
130 Message {
131 prefix: Some(prefix),
132 command,
133 params,
134 ..Default::default()
135 }
136 }
137}
138
139fn parse_tags(input: &str) -> Result<BTreeMap<String, String>, Error> {
140 let mut tags = BTreeMap::new();
141
142 for tag_data in input.split(';') {
143 let mut pieces = tag_data.splitn(2, '=');
144 let tag_name = pieces
145 .next()
146 .ok_or_else(|| Error::TagError("missing tag name".to_string()))?;
147 let raw_tag_value = pieces.next().unwrap_or("");
148
149 let mut tag_value = String::new();
150 let mut tag_value_chars = raw_tag_value.chars();
151 while let Some(c) = tag_value_chars.next() {
152 if c == '\\' {
153 if let Some(escaped_char) = tag_value_chars.next() {
154 tag_value.push(unescape_char(escaped_char));
155 }
156 } else {
157 tag_value.push(c);
158 }
159 }
160
161 tags.insert(tag_name.to_string(), tag_value);
162 }
163
164 Ok(tags)
165}
166
167impl FromStr for Message {
168 type Err = Error;
169
170 fn from_str(input: &str) -> Result<Self, Self::Err> {
171 let mut input = input;
175
176 if input.ends_with('\n') {
179 input = &input[..input.len() - 1];
180 }
181 if input.ends_with('\r') {
182 input = &input[..input.len() - 1];
183 }
184
185 let mut tags = BTreeMap::new();
186 let mut prefix = None;
187
188 if input.starts_with('@') {
189 let mut parts = (&input[1..]).splitn(2, ' ');
190 let tag_data = parts
191 .next()
192 .ok_or_else(|| Error::TagError("missing tag data".to_string()))?;
193
194 tags = parse_tags(tag_data)?;
195
196 input = parts.next().unwrap_or("").trim_start_matches(' ');
198 }
199
200 if input.starts_with(':') {
201 let mut parts = (&input[1..]).splitn(2, ' ');
202 prefix = Some(
203 parts
204 .next()
205 .ok_or_else(|| Error::TagError("missing prefix data".to_string()))?
206 .parse()
207 .or_else(|_| Err(Error::TagError("failed to parse prefix data".to_string())))?,
208 );
209
210 input = parts.next().unwrap_or("").trim_start_matches(' ');
212 }
213
214 let mut parts = input.splitn(2, ' ');
215 let command = parts
216 .next()
217 .ok_or_else(|| Error::CommandError("missing command".to_string()))?
218 .to_string();
219
220 input = parts.next().unwrap_or("").trim_start_matches(' ');
222
223 let mut params = Vec::new();
225 while !input.is_empty() {
226 if input.starts_with(':') {
229 params.push(input[1..].to_string());
230 break;
231 }
232
233 let mut parts = input.splitn(2, ' ');
234 if let Some(param) = parts.next() {
235 params.push(param.to_string());
236 }
237
238 input = parts.next().unwrap_or("").trim_start_matches(' ');
240 }
241
242 Ok(Message {
243 tags,
244 prefix,
245 command,
246 params,
247 })
248 }
249}
250
251impl fmt::Display for Message {
252 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253 if !self.tags.is_empty() {
254 f.write_char('@')?;
255
256 for (i, (k, v)) in self.tags.iter().enumerate() {
257 if i != 0 {
260 f.write_char(';')?;
261 }
262
263 f.write_str(k)?;
264 if v.is_empty() {
265 continue;
266 }
267
268 f.write_char('=')?;
269
270 for c in v.chars() {
271 match escape_char(c) {
272 Some(escaped_str) => f.write_str(escaped_str)?,
273 None => f.write_char(c)?,
274 }
275 }
276 }
277
278 f.write_char(' ')?;
279 }
280
281 if let Some(prefix) = &self.prefix {
282 f.write_char(':')?;
283 prefix.fmt(f)?;
284 f.write_char(' ')?;
285 }
286
287 f.write_str(&self.command)?;
288
289 if let Some((last, params)) = self.params.split_last() {
290 for param in params {
291 f.write_char(' ')?;
292 f.write_str(param)?;
293 }
294
295 f.write_str(" :")?;
296 f.write_str(last)?;
297 }
298
299 Ok(())
300 }
301}