1use fancy_regex::{Captures, Regex};
4use lazy_static::lazy_static;
5use mdbook::book::{Book, BookItem};
6use mdbook::errors::Result;
7use mdbook::preprocess::{Preprocessor, PreprocessorContext};
8
9const NAME: &str = "mathpunc";
11
12pub struct MathpuncPreprocessor;
14
15lazy_static! {
16 static ref RE: Regex =
18 Regex::new(r"(?<!\\)\$\s*(?<punc>\)?[,,.,;,:,)])").unwrap();
20}
21
22impl MathpuncPreprocessor {
23 pub fn new() -> Self {
24 Self
25 }
26}
27
28impl Preprocessor for MathpuncPreprocessor {
29 fn name(&self) -> &str {
30 NAME
31 }
32
33 fn run(&self, _ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
34 book.for_each_mut(|item: &mut BookItem| {
35 if let BookItem::Chapter(chapter) = item {
36 chapter.content = find_and_replace(&chapter.content);
37 }
38 });
39
40 Ok(book)
41 }
42}
43
44fn find_and_replace(s: &str) -> String {
49 RE.replace_all(s, |caps: &Captures| {
51 match &caps["punc"] {
52 ":" => {
53 r"\!\!:$".to_string()
54 }
55 "):" => {
56 r")\!\!:$".to_string()
57 }
58 _ => {
59 format!("{}$", &caps["punc"])
60 }
61 }
62 }).to_string()
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn basic() {
71 let input = String::from(
72 r"Consider a group $\GG$, of order $p$; and a generator $G$: for example an elliptic curve $E$.",
73 );
74 let output = find_and_replace(&input);
75 let expected = String::from(
76 r"Consider a group $\GG,$ of order $p;$ and a generator $G\!\!:$ for example an elliptic curve $E.$",
77 );
78 assert_eq!(output, expected);
79 }
80
81 #[test]
82 fn escaped_dollar() {
83 let input = String::from(r"This is an escaped dollar \$, don't replace. This as well \& .");
84 let output = find_and_replace(&input);
85 assert_eq!(output, input);
86 }
87
88 #[test]
89 fn whitespaces() {
90 let input = String::from(
91 r"Consider a group $\GG$ , of order $p$ ; and a generator $G$ : for example an elliptic curve $E$ .",
92 );
93 let output = find_and_replace(&input);
94 let expected = String::from(
95 r"Consider a group $\GG,$ of order $p;$ and a generator $G\!\!:$ for example an elliptic curve $E.$",
96 );
97 assert_eq!(output, expected);
98 }
99
100 #[test]
101 fn parenthesis() {
102 let input =
103 String::from(r"Consider a group $\GG$ (of order $p$), and a generator $G$ (of $\GG$).");
104 let output = find_and_replace(&input);
105 let expected =
106 String::from(r"Consider a group $\GG$ (of order $p),$ and a generator $G$ (of $\GG).$");
107 assert_eq!(output, expected);
108 }
109
110 #[test]
111 fn parenthesis_and_colon() {
112 let input =
113 String::from(r"Consider a group $\GG$ (of order $p$):");
114 let output = find_and_replace(&input);
115 let expected =
116 String::from(r"Consider a group $\GG$ (of order $p)\!\!:$");
117 assert_eq!(output, expected);
118 }
119}