Implement HTML utils, refactor markdown utils with macroses

This commit is contained in:
Sergey Levitin 2020-01-03 18:01:34 +03:00
parent 2a66a84454
commit a2879b8ea3
2 changed files with 51 additions and 29 deletions

View file

@ -1,3 +1,4 @@
//! Utils for working with the [HTML message style.](https://core.telegram.org/bots/api#html-style)
use std::string::String;
// Escapes the string to be shown "as is" within the Telegram HTML message style.
@ -9,6 +10,42 @@ pub fn escape(s: &str) -> String {
.replace(">", ">")
}
pub fn bold(s: &str) -> String {
format!("<b>{}</b>", s)
}
pub fn italic(s: &str) -> String {
format!("<i>{}</i>", s)
}
pub fn underline(s: &str) -> String {
format!("<u>{}</u>", s)
}
pub fn strike(s: &str) -> String {
format!("<s>{}</s>", s)
}
pub fn link(url: &str, text: &str) -> String {
format!("<a href=\"{}\">{}</a>", escape(url), text)
}
pub fn user_mention(user_id: i32, text: &str) -> String {
link(format!("tg://user?id={}", user_id).as_str(), text)
}
pub fn code_block(code: &str) -> String {
format!("<pre>\n{}\n</pre>", escape(code))
}
pub fn code_block_with_lang(code: &str, lang: &str) -> String {
format!("<pre><code class=\"language-{}\">\n{}\n</code></pre>", escape(lang), escape(code))
}
pub fn code_inline(s: &str) -> String {
format!("<code>{}</code>", escape(s))
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -6,7 +6,7 @@ use std::string::String;
/// Passed string will not be automatically escaped
/// because it can contain nested markup.
pub fn bold(s: &str) -> String {
wrap(s, "*", "*")
format!("*{}*", s)
}
/// Applies the italic font style to the string.
@ -15,9 +15,9 @@ pub fn bold(s: &str) -> String {
/// because it can contain nested markup.
pub fn italic(s: &str) -> String {
if s.starts_with("__") && s.ends_with("__") {
return wrap(&s[..s.len() - 1], "_", r"\r__");
return format!(r"_{}\r__", &s[..s.len() - 1])
}
wrap(s, "_", "_")
format!("_{}_", s)
}
/// Applies the underline font style to the string.
@ -30,25 +30,22 @@ pub fn underline(s: &str) -> String {
// so instead of ___italic underline___ we should use ___italic underline_\r__,
// where \r is a character with code 13, which will be ignored.
if s.starts_with("_") && s.ends_with("_") {
return wrap(s, "__", r"\r__");
return format!(r"__{}\r__", s)
}
wrap(s, "__", "__")
format!("__{}__", s)
}
/// Applies the strikethrough font style to the string.
/// Passed string will not be automatically escaped
/// because it can contain nested markup.
pub fn strike(s: &str) -> String {
wrap(s, "~", "~")
format!("~{}~", s)
}
/// Builds an inline link with an anchor.
/// Escapes `)` and ``` characters inside the link url.
pub fn link(url: &str, text: &str) -> String {
let mut out = String::with_capacity(url.len() + text.len() + 4);
out.push_str(wrap(text, "[", "]").as_str());
out.push_str(wrap(escape_link_url(url).as_str(), "(", ")").as_str());
out
format!("[{}]({})", text, escape_link_url(url))
}
/// Builds an inline user mention link with an anchor.
@ -58,23 +55,19 @@ pub fn user_mention(user_id: i32, text: &str) -> String {
/// Formats the code block. Escapes ``` and `\` characters inside the block.
pub fn code_block(code: &str) -> String {
code_block_with_lang(code, "")
format!("```\n{}\n```", escape_code(code))
}
/// Formats the code block with a specific language syntax.
/// Escapes ``` and `\` characters inside the block.
pub fn code_block_with_lang(code: &str, lang: &str) -> String {
wrap(
escape_code(code).as_str(),
format!("```{}\n", lang).as_str(),
"\n```",
)
format!("```{}\n{}\n```", escape(lang), escape_code(code))
}
/// Formats the string as an inline code.
/// Escapes ``` and `\` characters inside the block.
pub fn code_inline(s: &str) -> String {
wrap(escape_code(s).as_str(), "`", "`")
format!("`{}`", escape_code(s))
}
/// Escapes all markdown special characters in the passed string.
@ -98,24 +91,16 @@ pub fn escape(s: &str) -> String {
.replace("!", r"\!")
}
/// Escapes all markdown special characters specific for the inline link URL (``` and `)`)
/// Escapes all markdown special characters specific for the inline link URL (``` and `)`).
pub fn escape_link_url(s: &str) -> String {
s.replace("`", r"\`").replace(")", r"\)")
}
/// Escapes all markdown special characters specific for the code block (``` and `\`)
/// Escapes all markdown special characters specific for the code block (``` and `\`).
pub fn escape_code(s: &str) -> String {
s.replace(r"\", r"\\").replace("`", r"\`")
}
fn wrap(s: &str, left: &str, right: &str) -> String {
let mut out = String::with_capacity(left.len() + s.len() + right.len());
out.push_str(left);
out.push_str(s);
out.push_str(right);
out
}
#[cfg(test)]
mod tests {
use super::*;
@ -183,9 +168,9 @@ mod tests {
assert_eq!(
code_block_with_lang(
"pre-'formatted'\nfixed-width \\code `block`",
"python"
"[python]"
),
"```python\npre-'formatted'\nfixed-width \\\\code \\`block\\`\n```"
"```\\[python\\]\npre-'formatted'\nfixed-width \\\\code \\`block\\`\n```"
);
}