whistle_proxy_rule_parser/
markdown_values.rs1use nom::{
3 branch::alt,
4 bytes::complete::{is_not, tag, take},
5 combinator::{map, not},
6 multi::{many0, many1},
7 sequence::{delimited, preceded, terminated, tuple},
8 IResult,
9};
10
11pub type MarkdownText = Vec<MarkdownInline>;
12
13#[derive(Clone, Debug, PartialEq)]
14pub enum Markdown {
15 Line(MarkdownText),
16 Codeblock(String, String),
17}
18
19#[derive(Clone, Debug, PartialEq)]
20pub enum MarkdownInline {
21 Plaintext(String),
22}
23
24pub fn parse_markdown(i: &str) -> IResult<&str, Vec<Markdown>> {
25 many1(alt((
26 map(parse_code_block, |e| {
27 Markdown::Codeblock(e.0.to_string(), e.1.to_string())
28 }),
29 map(parse_markdown_text, |e| Markdown::Line(e)),
30 )))(i)
31}
32
33fn parse_plaintext(i: &str) -> IResult<&str, String> {
34 map(
35 many1(preceded(not(alt((tag("```"), tag("\n")))), take(1u8))),
36 |vec| vec.join(""),
37 )(i)
38}
39
40fn parse_markdown_inline(i: &str) -> IResult<&str, MarkdownInline> {
41 alt((map(parse_plaintext, |s| MarkdownInline::Plaintext(s)),))(i)
42}
43
44fn parse_markdown_text(i: &str) -> IResult<&str, MarkdownText> {
45 terminated(many0(parse_markdown_inline), tag("\n"))(i)
46}
47
48fn parse_code_block(i: &str) -> IResult<&str, (String, &str)> {
49 tuple((parse_code_block_lang, parse_code_block_body))(i)
50}
51
52fn parse_code_block_body(i: &str) -> IResult<&str, &str> {
53 delimited(tag("\n"), is_not("```"), tag("```"))(i)
54}
55
56fn parse_code_block_lang(i: &str) -> IResult<&str, String> {
57 alt((
58 preceded(tag("```"), parse_plaintext),
59 map(tag("```"), |_| "__UNKNOWN__".to_string()),
60 ))(i)
61}
62
63pub fn into_parts(md_arr: Vec<Markdown>) -> (String, Vec<(String, String)>) {
71 let mut lines = String::new();
72 let mut codes = vec![];
73 md_arr.iter().for_each(|m| {
74 match m {
75 Markdown::Line(v) => {
76 if v.is_empty() {
77 lines.push('\n');
78 return;
79 }
80 match &v[0] {
81 MarkdownInline::Plaintext(s) => {
82 lines.push_str(&s);
83 lines.push('\n');
84 }
85 }
86 }
87 Markdown::Codeblock(name, value) => {
88 codes.push((name.to_owned(), value.to_owned()));
89 }
90 }
91 });
92 (lines, codes)
93}
94
95#[cfg(test)]
96mod test {
97 use super::*;
98 #[test]
99 fn test_markdown() {
100 let input = r#"
101# oijsdf
102**bold text**
103```rust
104fn main() {
105 println!("Hello, world!");
106}
107```
108**bold**
109```js
110console.log(1234)
111```
112`inline code`
113"#;
114 assert_eq!(
115 parse_markdown(input),
116 Ok((
117 "",
118 vec![
119 Markdown::Line(vec![]),
120 Markdown::Line(vec![MarkdownInline::Plaintext("# oijsdf".into())]),
121 Markdown::Line(vec![MarkdownInline::Plaintext("**bold text**".into())]),
122 Markdown::Codeblock(
123 "rust".into(),
124 "fn main() {\n println!(\"Hello, world!\");\n}\n".into()
125 ),
126 Markdown::Line(vec![]),
127 Markdown::Line(vec![MarkdownInline::Plaintext("**bold**".into())]),
128 Markdown::Codeblock("js".into(), "console.log(1234)\n".into()),
129 Markdown::Line(vec![]),
130 Markdown::Line(vec![MarkdownInline::Plaintext("`inline code`".into())])
131 ]
132 ))
133 );
134
135 assert_eq!(into_parts(parse_markdown(input).unwrap().1), (
136 "\n# oijsdf\n**bold text**\n\n**bold**\n\n`inline code`\n".into(),
137 vec![("rust".into(), "fn main() {\n println!(\"Hello, world!\");\n}\n".into()), ("js".into(), "console.log(1234)\n".into())]
138 ));
139
140 }
141}