hash_tag/
lib.rs

1use wasm_bindgen::prelude::*;
2
3fn get_lines(md: &str) -> Vec<String> {
4    let mut lines: Vec<String> = Vec::new();
5    let mut line = String::from("");
6    for c in md.chars() {
7        if c == '\n' {
8            if !line.is_empty() {
9                lines.push(line.clone());
10            }
11            line.clear();
12        } else {
13            line.push(c);
14        }
15    }
16
17    if !line.is_empty() {
18        lines.push(line);
19    }
20
21    lines
22}
23
24fn close_list_tags(tag_ul: &mut bool, tag_ol: &mut bool, html: &mut String) {
25    if *tag_ul {
26        html.push_str("</ul>");
27        *tag_ul = false;
28    }
29    if *tag_ol {
30        html.push_str("</ol>");
31        *tag_ol = false;
32    }
33}
34
35fn process_line(l: &str) -> String {
36    let mut chars = l.chars().peekable();
37    let mut line = String::from("");
38
39    let mut tag_code = false;
40    let mut tag_b = false;
41    let mut tag_i = false;
42
43    while let Some(c) = chars.next() {
44        if c == '`' {
45            tag_code = !tag_code;
46            if tag_code {
47                line.push_str("<code>");
48            } else {
49                line.push_str("</code>");
50            }
51        } else if c == '*' {
52            if chars.peek() == Some(&'*') {
53                chars.next();
54                tag_b = !tag_b;
55                if tag_b {
56                    line.push_str("<b>");
57                } else {
58                    line.push_str("</b>");
59                }
60            }
61        } else if c == '_' {
62            tag_i = !tag_i;
63            if tag_i {
64                line.push_str("<i>");
65            } else {
66                line.push_str("</i>");
67            }
68        } else if c == '[' {
69            let mut text = String::new();
70            while let Some(&next) = chars.peek() {
71                if next == ']' {
72                    chars.next();
73                    break;
74                }
75                text.push(next);
76                chars.next();
77            }
78
79            if chars.peek() == Some(&'(') {
80                chars.next();
81                let mut url = String::new();
82                while let Some(&next) = chars.peek() {
83                    if next == ')' {
84                        chars.next();
85                        break;
86                    }
87                    url.push(next);
88                    chars.next();
89                }
90
91                if !url.is_empty() {
92                    line.push_str(&format!(
93                        "<a class=\"link\" href=\"{url}\" target=\"_blank\">{text}</a>"
94                    ));
95                } else {
96                    line.push('[');
97                    line.push_str(&text);
98                    line.push(']');
99                }
100            } else {
101                line.push('[');
102                line.push_str(&text);
103                line.push(']');
104            }
105        } else {
106            line.push(c);
107        }
108    }
109
110    line.to_string()
111}
112
113#[wasm_bindgen]
114pub fn parse(md: String) -> String {
115    let mut html = String::from("");
116
117    let lines: Vec<String> = get_lines(&md);
118
119    let mut tag_pre = false;
120    let mut tag_ul = false;
121    let mut tag_ol = false;
122
123    for l in lines.iter() {
124        for (j, c) in l.chars().enumerate() {
125            if j == 0 && c == '#' {
126                // heading
127                close_list_tags(&mut tag_ul, &mut tag_ol, &mut html);
128                let count_hash = l.chars().filter(|&c| c == '#').count();
129                let line = process_line(&l[count_hash + 1..]);
130                let line = format!("<h{count_hash}>{line}</h{count_hash}>");
131                html.push_str(&line);
132            } else if j == 0 && c == '>' {
133                // blockquote
134                close_list_tags(&mut tag_ul, &mut tag_ol, &mut html);
135                let line = process_line(&l[2..]);
136                let line = format!("<blockquote>{line}</blockquote>");
137                html.push_str(&line);
138            } else if j == 0 && c == '-' {
139                // unordered list
140                if l.trim().chars().all(|ch| ch == '-') {
141                    html.push_str("<hr>");
142                    break;
143                }
144                if !tag_ul {
145                    html.push_str("<ul>");
146                }
147                tag_ul = true;
148                let line = process_line(&l[2..]);
149                let line = format!("<li>{line}</li>");
150                html.push_str(&line);
151            } else if j == 0 && c.is_ascii_digit() {
152                // ordered list
153                if !tag_ol {
154                    html.push_str("<ol>");
155                }
156                tag_ol = true;
157                let line = process_line(&l[3..]);
158                let line = format!("<li>{line}</li>");
159                html.push_str(&line);
160            } else if j == 0 && c == '`' {
161                // codeblock
162                close_list_tags(&mut tag_ul, &mut tag_ol, &mut html);
163                let count_backtick = l.chars().filter(|&c| c == '`').count();
164                if count_backtick == 3 {
165                    tag_pre = !tag_pre;
166                    if tag_pre {
167                        html.push_str("<pre>");
168                    } else {
169                        html.push_str("</pre>");
170                    }
171                } else if count_backtick % 2 == 0 {
172                    let line = process_line(l);
173                    let line = format!("<p>{line}</p>");
174                    html.push_str(&line);
175                }
176            } else if j == 0 && tag_pre {
177                // paragraph
178                let line = format!("<code>{l}</code>\n");
179                html.push_str(&line);
180            } else if j == 0 && !tag_pre {
181                // paragraph
182                close_list_tags(&mut tag_ul, &mut tag_ol, &mut html);
183                let line = process_line(l);
184                let line = format!("<p>{line}</p>");
185                html.push_str(&line);
186            }
187        }
188    }
189
190    html
191}