rumdl_lib/utils/
fence_language.rs1use crate::config::MarkdownFlavor;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum FenceLanguage {
7 Language,
8 CodeSyntax,
10 Missing,
11 Exempt,
13}
14
15pub fn is_superfences_attribute(info: &str) -> bool {
16 ["title=", "hl_lines=", "linenums=", ".", "#"]
17 .iter()
18 .any(|prefix| info.starts_with(prefix))
19}
20
21pub fn classify(info: &str, flavor: MarkdownFlavor) -> FenceLanguage {
22 let info = info.trim_matches([' ', '\t']);
25 if flavor.supports_myst_directives() && info.starts_with('{') && info.contains('}') {
26 let name = info[1..].split('}').next().unwrap_or("");
27 if name.chars().next().is_some_and(|c| c.is_alphabetic() || c == '_') {
28 return if matches!(name, "code" | "code-block" | "sourcecode" | "code-cell") {
29 FenceLanguage::CodeSyntax
30 } else {
31 FenceLanguage::Exempt
32 };
33 }
34 }
35 if flavor.is_pandoc_compatible() && crate::utils::pandoc::is_pandoc_raw_block_lang(info) {
36 return FenceLanguage::Exempt;
37 }
38 if (flavor.is_pandoc_compatible() && crate::utils::pandoc::is_pandoc_code_class_attr(info))
39 || (flavor == MarkdownFlavor::Quarto && info.starts_with('{') && info.ends_with('}'))
40 {
41 return FenceLanguage::CodeSyntax;
42 }
43 if flavor == MarkdownFlavor::MkDocs && is_superfences_attribute(info) {
44 FenceLanguage::Exempt
45 } else if info.is_empty() || is_superfences_attribute(info) || (info.starts_with('{') && info.ends_with('}')) {
46 FenceLanguage::Missing
47 } else {
48 FenceLanguage::Language
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn flavor_constructs_are_exempt_but_unknown_plain_labels_are_languages() {
58 for (flavor, info) in [
59 (MarkdownFlavor::Pandoc, "{=html}"),
60 (MarkdownFlavor::MyST, "{note}"),
61 (MarkdownFlavor::MkDocs, "title=\"example\""),
62 ] {
63 assert_eq!(classify(info, flavor), FenceLanguage::Exempt);
64 }
65 for (flavor, info) in [
66 (MarkdownFlavor::Quarto, "{python}"),
67 (MarkdownFlavor::Quarto, "{{python}}"),
68 (MarkdownFlavor::Pandoc, "{.python}"),
69 (MarkdownFlavor::MyST, "{code-cell} python"),
70 ] {
71 assert_eq!(classify(info, flavor), FenceLanguage::CodeSyntax);
72 }
73 assert_eq!(classify("pyhton", MarkdownFlavor::Standard), FenceLanguage::Language);
74 for info in ["", "{}", "{python}", "title=example"] {
75 assert_eq!(classify(info, MarkdownFlavor::Standard), FenceLanguage::Missing);
76 }
77 }
78
79 #[test]
80 fn unicode_space_is_part_of_the_info_string() {
81 assert_eq!(classify("\u{a0}", MarkdownFlavor::Standard), FenceLanguage::Language);
82 assert_eq!(classify(" \t", MarkdownFlavor::Standard), FenceLanguage::Missing);
83 }
84}