1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! MarkdownV2 markup utilities.

use super::Nesting;
use crate::types::parameters::Text;
use std::{
    fmt::{self, Display, Formatter, Write},
    ops::Deref,
};

/// Characters that need to be escaped to be interpreted as text.
pub const ESCAPED_TEXT_CHARACTERS: [char; 19] = [
    '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{',
    '}', '.', '!', '\\',
];

/// Characters that need to be escaped to be interpreted as code.
pub const ESCAPED_CODE_CHARACTERS: [char; 2] = ['`', '\\'];

/// Characters that need to be escaped to be interpreted as a link.
pub const ESCAPED_LINK_CHARACTERS: [char; 2] = [')', '\\'];

/// Represents a value that can be formatted for MarkdownV2.
pub trait Formattable {
    // This is not meant to be public, thus relying on it may break you code
    // at any time
    #[doc(hidden)]
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result;
}

impl_primitives!(Formattable);
impl_tuples!(Formattable);

impl Formattable for char {
    fn format(&self, formatter: &mut Formatter, _: Nesting) -> fmt::Result {
        if ESCAPED_TEXT_CHARACTERS.contains(self) {
            formatter.write_char('\\')?;
        }

        formatter.write_char(*self)
    }
}

impl Formattable for &'_ str {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.chars()
            .try_for_each(|character| character.format(formatter, nesting))
    }
}

impl Formattable for String {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.as_str().format(formatter, nesting)
    }
}

impl<T: Formattable> Formattable for &'_ [T] {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.iter().try_for_each(|x| x.format(formatter, nesting))
    }
}

impl<T: Formattable> Formattable for Vec<T> {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.as_slice().format(formatter, nesting)
    }
}

impl<T: Formattable + ?Sized> Formattable for Box<T> {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.deref().format(formatter, nesting)
    }
}

/// Represents MarkdownV2 text. Can be created with [`markdown_v2`].
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[must_use = "MarkdownV2 needs to be turned into a `Text` instance"]
pub struct MarkdownV2<T>(T);

struct Displayable<T>(MarkdownV2<T>);

/// Creates MarkdownV2 text.
pub fn markdown_v2<T: Formattable>(content: T) -> MarkdownV2<T> {
    MarkdownV2(content)
}

impl<T: Formattable> Formattable for MarkdownV2<T> {
    fn format(
        &self,
        formatter: &mut Formatter,
        nesting: Nesting,
    ) -> fmt::Result {
        self.0.format(formatter, nesting)
    }
}

impl<T: Formattable> Display for Displayable<T> {
    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
        self.0.format(formatter, Nesting::default())
    }
}

impl<T: Formattable> From<MarkdownV2<T>> for Text {
    fn from(markup: MarkdownV2<T>) -> Self {
        let message = Displayable(markup).to_string();
        Self::with_markdown_v2(message)
    }
}