use crate::Channel;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Emph {
Bold,
Italic,
Strike,
Code,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Vocab {
pub channel: Channel,
}
impl Vocab {
pub fn new(channel: Channel) -> Self {
Self { channel }
}
pub fn tables_native(&self) -> bool {
matches!(self.channel, Channel::SlackMarkdown)
}
pub fn is_plain(&self) -> bool {
self.channel == Channel::Plain
}
pub fn open(&self, e: Emph) -> &'static str {
match (self.channel, e) {
(Channel::TelegramHtml, Emph::Bold) => "<b>",
(Channel::TelegramHtml, Emph::Italic) => "<i>",
(Channel::TelegramHtml, Emph::Strike) => "<s>",
(Channel::TelegramHtml, Emph::Code) => "<code>",
(Channel::Plain, _) => "",
(_, Emph::Strike) => "~~",
(_, Emph::Bold) => "**",
(_, Emph::Italic) => "*",
(_, Emph::Code) => "`",
}
}
pub fn close(&self, e: Emph) -> &'static str {
match (self.channel, e) {
(Channel::TelegramHtml, Emph::Bold) => "</b>",
(Channel::TelegramHtml, Emph::Italic) => "</i>",
(Channel::TelegramHtml, Emph::Strike) => "</s>",
(Channel::TelegramHtml, Emph::Code) => "</code>",
_ => self.open(e),
}
}
pub fn escape_char(&self, c: char, out: &mut String) {
match (self.channel, c) {
(Channel::TelegramHtml, '&') => out.push_str("&"),
(Channel::TelegramHtml, '<') => out.push_str("<"),
(Channel::TelegramHtml, '>') => out.push_str(">"),
_ => out.push(c),
}
}
pub fn escape(&self, s: &str, out: &mut String) {
if self.channel != Channel::TelegramHtml {
out.push_str(s);
return;
}
if !s.contains(['&', '<', '>']) {
out.push_str(s);
return;
}
for c in s.chars() {
self.escape_char(c, out);
}
}
pub fn link(&self, text: &str, url: &str, out: &mut String) {
match self.channel {
Channel::TelegramHtml => {
let markup = escaped_len(url) + "<a href=\"\"></a>".len();
if markup >= self.channel.limit() {
out.push_str(text);
if !url.is_empty() && !escaped_eq(text, url) {
out.push_str(" (");
self.escape(url, out);
out.push(')');
}
return;
}
out.push_str("<a href=\"");
for c in url.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
_ => out.push(c),
}
}
out.push_str("\">");
out.push_str(text);
out.push_str("</a>");
}
Channel::Plain => {
out.push_str(text);
if !url.is_empty() && url != text {
out.push_str(" (");
out.push_str(url);
out.push(')');
}
}
_ => {
if text == url && url.contains("://") {
out.push('<');
out.push_str(url);
out.push('>');
return;
}
out.push('[');
out.push_str(text);
out.push_str("](");
out.push_str(url);
out.push(')');
}
}
}
pub fn literal(&self, c: char, out: &mut String) {
match self.channel {
Channel::TelegramHtml | Channel::Plain => self.escape_char(c, out),
_ if matches!(
c,
'*' | '_' | '~' | '`' | '\\' | '[' | ']' | '(' | ')' | '#' | '>' | '|' | '-'
| '+' | '.' | '!'
) =>
{
out.push('\\');
out.push(c);
}
_ => self.escape_char(c, out),
}
}
pub fn bullet(&self) -> &'static str {
match self.channel {
Channel::SlackMarkdown => "- ",
_ => "• ",
}
}
pub fn quote_prefix(&self) -> &'static str {
match self.channel {
Channel::TelegramHtml => "",
_ => "> ",
}
}
pub fn quote_open(&self) -> &'static str {
match self.channel {
Channel::TelegramHtml => "<blockquote>",
_ => "",
}
}
pub fn quote_close(&self) -> &'static str {
match self.channel {
Channel::TelegramHtml => "</blockquote>",
_ => "",
}
}
pub fn rule(&self) -> &'static str {
match self.channel {
Channel::TelegramHtml | Channel::Plain => "──────────",
_ => "---",
}
}
pub fn max_heading(&self) -> usize {
match self.channel {
Channel::SlackMarkdown => 3,
Channel::TelegramHtml | Channel::Plain => 0,
}
}
pub fn verbatim_open(&self, info: &str, out: &mut String) {
match self.channel {
Channel::TelegramHtml => {
out.push_str("<pre>");
if !info.is_empty() {
out.push_str("<code class=\"language-");
self.escape(info, out);
out.push_str("\">");
}
}
Channel::Plain => {}
_ => {
out.push_str("```");
out.push_str(info);
}
}
}
pub fn verbatim_body_newline(&self) -> bool {
!matches!(self.channel, Channel::TelegramHtml | Channel::Plain)
}
pub fn verbatim_close(&self, info: &str, out: &mut String) {
match self.channel {
Channel::TelegramHtml => {
if !info.is_empty() {
out.push_str("</code>");
}
out.push_str("</pre>");
}
Channel::Plain => {}
_ => out.push_str("\n```"),
}
}
}
fn escaped_len(url: &str) -> usize {
url.chars()
.map(|c| match c {
'&' => 5,
'<' | '>' => 4,
'"' => 6,
_ => 1,
})
.sum()
}
fn escaped_eq(text: &str, url: &str) -> bool {
let mut t = text.chars();
for c in url.chars() {
let escaped = match c {
'&' => "&",
'<' => "<",
'>' => ">",
'"' => """,
_ => {
if t.next() != Some(c) {
return false;
}
continue;
}
};
for e in escaped.chars() {
if t.next() != Some(e) {
return false;
}
}
}
t.next().is_none()
}