1use teloxide_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 italic(s: &str) -> String {
34 format!("<i>{s}</i>")
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 underline(s: &str) -> String {
44 format!("<u>{s}</u>")
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 strike(s: &str) -> String {
54 format!("<s>{s}</s>")
55}
56
57#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
61 without using its output does nothing useful"]
62pub fn link(url: &str, text: &str) -> String {
63 format!("<a href=\"{}\">{}</a>", escape(url), escape(text))
64}
65
66#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
68 without using its output does nothing useful"]
69pub fn user_mention(user_id: UserId, text: &str) -> String {
70 link(format!("tg://user?id={user_id}").as_str(), text)
71}
72
73#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
77 without using its output does nothing useful"]
78pub fn code_block(code: &str) -> String {
79 format!("<pre>{}</pre>", escape(code))
80}
81
82#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
86 without using its output does nothing useful"]
87pub fn code_block_with_lang(code: &str, lang: &str) -> String {
88 format!(
89 "<pre><code class=\"language-{}\">{}</code></pre>",
90 escape(lang).replace('"', """),
91 escape(code)
92 )
93}
94
95#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
99 without using its output does nothing useful"]
100pub fn code_inline(s: &str) -> String {
101 format!("<code>{}</code>", escape(s))
102}
103
104#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
112 without using its output does nothing useful"]
113pub fn escape(s: &str) -> String {
114 s.chars().fold(String::with_capacity(s.len()), |mut s, c| {
115 match c {
116 '&' => s.push_str("&"),
117 '<' => s.push_str("<"),
118 '>' => s.push_str(">"),
119 c => s.push(c),
120 }
121 s
122 })
123}
124
125#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \
126 without using its output does nothing useful"]
127pub fn user_mention_or_link(user: &User) -> String {
128 match user.mention() {
129 Some(mention) => mention,
130 None => link(user.url().as_str(), &user.full_name()),
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_bold() {
140 assert_eq!(bold(" foobar "), "<b> foobar </b>");
141 assert_eq!(bold(" <i>foobar</i> "), "<b> <i>foobar</i> </b>");
142 assert_eq!(bold("<s>(`foobar`)</s>"), "<b><s>(`foobar`)</s></b>");
143 }
144
145 #[test]
146 fn test_italic() {
147 assert_eq!(italic(" foobar "), "<i> foobar </i>");
148 assert_eq!(italic(" <b>foobar</b> "), "<i> <b>foobar</b> </i>");
149 assert_eq!(italic("<s>(`foobar`)</s>"), "<i><s>(`foobar`)</s></i>");
150 }
151
152 #[test]
153 fn test_underline() {
154 assert_eq!(underline(" foobar "), "<u> foobar </u>");
155 assert_eq!(underline(" <b>foobar</b> "), "<u> <b>foobar</b> </u>");
156 assert_eq!(underline("<s>(`foobar`)</s>"), "<u><s>(`foobar`)</s></u>");
157 }
158
159 #[test]
160 fn test_strike() {
161 assert_eq!(strike(" foobar "), "<s> foobar </s>");
162 assert_eq!(strike(" <b>foobar</b> "), "<s> <b>foobar</b> </s>");
163 assert_eq!(strike("<b>(`foobar`)</b>"), "<s><b>(`foobar`)</b></s>");
164 }
165
166 #[test]
167 fn test_link() {
168 assert_eq!(
169 link("https://www.google.com/?q=foo&l=ru", "<google>"),
170 "<a href=\"https://www.google.com/?q=foo&l=ru\"><google></a>",
171 );
172 }
173
174 #[test]
175 fn test_user_mention() {
176 assert_eq!(
177 user_mention(UserId(123_456_789), "<pwner666>"),
178 "<a href=\"tg://user?id=123456789\"><pwner666></a>",
179 );
180 }
181
182 #[test]
183 fn test_code_block() {
184 assert_eq!(
185 code_block("<p>pre-'formatted'\n & fixed-width \\code `block`</p>"),
186 "<pre><p>pre-'formatted'\n & fixed-width \\code `block`</p></pre>"
187 );
188 }
189
190 #[test]
191 fn test_code_block_with_lang() {
192 assert_eq!(
193 code_block_with_lang(
194 "<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
195 "<html>\"",
196 ),
197 concat!(
198 "<pre><code class=\"language-<html>"\">",
199 "<p>pre-'formatted'\n & fixed-width \\code `block`</p>",
200 "</code></pre>",
201 )
202 );
203 }
204
205 #[test]
206 fn test_code_inline() {
207 assert_eq!(
208 code_inline("<span class=\"foo\">foo & bar</span>"),
209 "<code><span class=\"foo\">foo & bar</span></code>",
210 );
211 }
212
213 #[test]
214 fn test_escape() {
215 assert_eq!(
216 escape(" <title>Foo & Bar</title> "),
217 " <title>Foo & Bar</title> "
218 );
219 assert_eq!(escape("<p>你好 & 再見</p>"), "<p>你好 & 再見</p>");
220 assert_eq!(escape("'foo\""), "'foo\"");
221 }
222
223 #[test]
224 fn user_mention_link() {
225 let user_with_username = User {
226 id: UserId(0),
227 is_bot: false,
228 first_name: "".to_string(),
229 last_name: None,
230 username: Some("abcd".to_string()),
231 language_code: None,
232 is_premium: false,
233 added_to_attachment_menu: false,
234 };
235 assert_eq!(user_mention_or_link(&user_with_username), "@abcd");
236 let user_without_username = User {
237 id: UserId(123_456_789),
238 is_bot: false,
239 first_name: "Name".to_string(),
240 last_name: None,
241 username: None,
242 language_code: None,
243 is_premium: false,
244 added_to_attachment_menu: false,
245 };
246 assert_eq!(
247 user_mention_or_link(&user_without_username),
248 r#"<a href="tg://user/?id=123456789">Name</a>"#
249 )
250 }
251}