1use ronami_core::types::{User, UserId};
6
7#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
12 without using its output does nothing useful"]
13pub fn bold(s: &str) -> String {
14 format!("<b>{s}</b>")
15}
16
17#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
22 without using its output does nothing useful"]
23pub fn blockquote(s: &str) -> String {
24 format!("<blockquote>{s}</blockquote>")
25}
26
27#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
32 without using its output does nothing useful"]
33pub fn expandable_blockquote(s: &str) -> String {
34 format!("<blockquote expandable>{s}</blockquote>")
35}
36
37#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
42 without using its output does nothing useful"]
43pub fn italic(s: &str) -> String {
44 format!("<i>{s}</i>")
45}
46
47#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
52 without using its output does nothing useful"]
53pub fn underline(s: &str) -> String {
54 format!("<u>{s}</u>")
55}
56
57#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
62 without using its output does nothing useful"]
63pub fn strike(s: &str) -> String {
64 format!("<s>{s}</s>")
65}
66
67#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
71 without using its output does nothing useful"]
72pub fn link(url: &str, text: &str) -> String {
73 format!("<a href=\"{}\">{}</a>", escape(url).replace('"', """), escape(text))
74}
75
76#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
78 without using its output does nothing useful"]
79pub fn user_mention(user_id: UserId, text: &str) -> String {
80 link(format!("tg://user?id={user_id}").as_str(), text)
81}
82
83#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
87 without using its output does nothing useful"]
88pub fn code_block(code: &str) -> String {
89 format!("<pre>{}</pre>", escape(code))
90}
91
92#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
96 without using its output does nothing useful"]
97pub fn code_block_with_lang(code: &str, lang: &str) -> String {
98 format!(
99 "<pre><code class=\"language-{}\">{}</code></pre>",
100 escape(lang).replace('"', """),
101 escape(code)
102 )
103}
104
105#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
109 without using its output does nothing useful"]
110pub fn code_inline(s: &str) -> String {
111 format!("<code>{}</code>", escape(s))
112}
113
114#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
122 without using its output does nothing useful"]
123pub fn escape(s: &str) -> String {
124 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
125 match c {
126 '&' => s.push_str("&"),
127 '<' => s.push_str("<"),
128 '>' => s.push_str(">"),
129 c => s.push(c),
130 }
131 s
132 })
133}
134
135#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
136 without using its output does nothing useful"]
137pub fn user_mention_or_link(user: &User) -> String {
138 match user.mention() {
139 Some(mention) => mention,
140 None => link(user.url().as_str(), &user.full_name()),
141 }
142}
143
144#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
146 without using its output does nothing useful"]
147pub fn date_time(text: &str, unix_time: i64, format: Option<&str>) -> String {
148 let escaped_text = escape(text);
149 match format {
150 Some(fmt) => {
151 format!("<tg-time unix=\"{unix_time}\" format=\"{fmt}\">{escaped_text}</tg-time>")
152 }
153 None => format!("<tg-time unix=\"{unix_time}\">{escaped_text}</tg-time>"),
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn test_bold() {
163 assert_eq!(bold(" foobar "), "<b> foobar </b>");
164 assert_eq!(bold(" <i>foobar</i> "), "<b> <i>foobar</i> </b>");
165 assert_eq!(bold("<s>(`foobar`)</s>"), "<b><s>(`foobar`)</s></b>");
166 }
167
168 #[test]
169 fn test_italic() {
170 assert_eq!(italic(" foobar "), "<i> foobar </i>");
171 assert_eq!(italic(" <b>foobar</b> "), "<i> <b>foobar</b> </i>");
172 assert_eq!(italic("<s>(`foobar`)</s>"), "<i><s>(`foobar`)</s></i>");
173 }
174
175 #[test]
176 fn test_underline() {
177 assert_eq!(underline(" foobar "), "<u> foobar </u>");
178 assert_eq!(underline(" <b>foobar</b> "), "<u> <b>foobar</b> </u>");
179 assert_eq!(underline("<s>(`foobar`)</s>"), "<u><s>(`foobar`)</s></u>");
180 }
181
182 #[test]
183 fn test_strike() {
184 assert_eq!(strike(" foobar "), "<s> foobar </s>");
185 assert_eq!(strike(" <b>foobar</b> "), "<s> <b>foobar</b> </s>");
186 assert_eq!(strike("<b>(`foobar`)</b>"), "<s><b>(`foobar`)</b></s>");
187 }
188
189 #[test]
190 fn test_link() {
191 assert_eq!(
192 link("https://www.google.com/?q=foo&l=ru", "<google>"),
193 "<a href=\"https://www.google.com/?q=foo&l=ru\"><google></a>",
194 );
195 }
196
197 #[test]
198 fn test_user_mention() {
199 assert_eq!(
200 user_mention(UserId(123_456_789), "<pwner666>"),
201 "<a href=\"tg://user?id=123456789\"><pwner666></a>",
202 );
203 }
204
205 #[test]
206 fn test_code_block() {
207 assert_eq!(
208 code_block("<p>pre-'formatted'\n & fixed-width \\code `block`</p>"),
209 "<pre><p>pre-'formatted'\n & fixed-width \\code `block`</p></pre>"
210 );
211 }
212
213 #[test]
214 fn test_code_block_with_lang() {
215 assert_eq!(
216 code_block_with_lang(
217 "<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
218 "<html>\"",
219 ),
220 concat!(
221 "<pre><code class=\"language-<html>"\">",
222 "<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
223 "</code></pre>",
224 )
225 );
226 }
227
228 #[test]
229 fn test_code_inline() {
230 assert_eq!(
231 code_inline("<span class=\"foo\">foo & bar</span>"),
232 "<code><span class=\"foo\">foo & bar</span></code>",
233 );
234 }
235
236 #[test]
237 fn test_escape() {
238 assert_eq!(
239 escape(" <title>Foo & Bar</title> "),
240 " <title>Foo & Bar</title> "
241 );
242 assert_eq!(escape("<p>你好 & 再見</p>"), "<p>你好 & 再見</p>");
243 assert_eq!(escape("'foo\""), "'foo\"");
244 }
245
246 #[test]
247 fn user_mention_link() {
248 let user_with_username = User {
249 id: UserId(0),
250 is_bot: false,
251 first_name: "".to_string(),
252 last_name: None,
253 username: Some("abcd".to_string()),
254 language_code: None,
255 is_premium: false,
256 added_to_attachment_menu: false,
257 can_manage_bots: false,
258 supports_guest_queries: false,
259 supports_join_request_queries: false,
260 };
261 assert_eq!(user_mention_or_link(&user_with_username), "@abcd");
262 let user_without_username = User {
263 id: UserId(123_456_789),
264 is_bot: false,
265 first_name: "Name".to_string(),
266 last_name: None,
267 username: None,
268 language_code: None,
269 is_premium: false,
270 added_to_attachment_menu: false,
271 can_manage_bots: false,
272 supports_guest_queries: false,
273 supports_join_request_queries: false,
274 };
275 assert_eq!(
276 user_mention_or_link(&user_without_username),
277 r#"<a href="tg://user/?id=123456789">Name</a>"#
278 )
279 }
280}