tbot/markup/
code_block.rs

1use super::{html, markdown_v2, Nesting};
2use std::{
3    fmt::{self, Formatter, Write},
4    ops::Deref,
5};
6
7/// Formats a block of code. Can be created with [`code_block`].
8///
9/// [`code_block`]: ./fn.code_block.html
10#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
11#[must_use = "formatters need to be formatted with `markdown_v2` or `html`"]
12pub struct CodeBlock<C, L = &'static str> {
13    code: C,
14    language: Option<L>,
15}
16
17impl<C, L: Deref<Target = str>> CodeBlock<C, L> {
18    /// Defines the langauge of the code block.
19    ///
20    /// # Panics
21    ///
22    /// Panics if the language contains a line break or a quote.
23    pub fn language(mut self, language: L) -> Self {
24        if (&*language).contains('\n') {
25            panic!(
26                "[tbot] A code block's language may not contain line breaks: {}",
27                &*language,
28            );
29        }
30
31        if (&*language).contains('"') {
32            panic!(
33                "[tbot] A code block's language may not contain quotes: {}",
34                &*language,
35            );
36        }
37
38        self.language = Some(language);
39        self
40    }
41}
42
43/// Formats a block of code.
44pub fn code_block<I, T, L>(code: I) -> CodeBlock<I, L>
45where
46    for<'a> &'a I: IntoIterator<Item = &'a T>,
47    T: Deref<Target = str>,
48    L: Deref<Target = str>,
49{
50    CodeBlock {
51        code,
52        language: None,
53    }
54}
55
56impl<I, T, L> markdown_v2::Formattable for CodeBlock<I, L>
57where
58    for<'a> &'a I: IntoIterator<Item = &'a T>,
59    T: Deref<Target = str>,
60    L: Deref<Target = str>,
61{
62    fn format(&self, formatter: &mut Formatter, _: Nesting) -> fmt::Result {
63        formatter.write_str("```")?;
64        if let Some(language) = &self.language {
65            language
66                .deref()
67                .chars()
68                .map(|x| {
69                    if markdown_v2::ESCAPED_CODE_CHARACTERS.contains(&x) {
70                        formatter.write_char('\\')?;
71                    }
72                    formatter.write_char(x)
73                })
74                .collect::<Result<(), _>>()?;
75        }
76        formatter.write_char('\n')?;
77
78        (&self.code)
79            .into_iter()
80            .flat_map(|x| x.deref().chars())
81            .map(|x| {
82                if markdown_v2::ESCAPED_CODE_CHARACTERS.contains(&x) {
83                    formatter.write_char('\\')?;
84                }
85                formatter.write_char(x)
86            })
87            .collect::<Result<(), _>>()?;
88        formatter.write_str("\n```")
89    }
90}
91
92impl<I, T, L> html::Formattable for CodeBlock<I, L>
93where
94    for<'a> &'a I: IntoIterator<Item = &'a T>,
95    T: Deref<Target = str>,
96    L: Deref<Target = str>,
97{
98    fn format(
99        &self,
100        formatter: &mut Formatter,
101        nesting: Nesting,
102    ) -> fmt::Result {
103        formatter.write_str("<pre>")?;
104
105        if let Some(language) = &self.language {
106            write!(formatter, "<code class=\"language-{}\">", &**language)?;
107        }
108
109        (&self.code)
110            .into_iter()
111            .map(|x| html::Formattable::format(&&**x, formatter, nesting))
112            .collect::<Result<(), _>>()?;
113
114        if self.language.is_some() {
115            formatter.write_str("</code>")?;
116        }
117
118        formatter.write_str("</pre>")
119    }
120}