1 pub fn parse(source: &str) -> String {
3 let lines = source.split("\n");
4 let mut is_preformatted = false;
6 let mut html:String = "".to_owned();
7 let mut current_line_type: Option<LineType> = None;
9 let mut heading_stack: Vec<u8> = Vec::new();
11 let mut line_type = LineType::Blank;
12 if line.char_indices().count() > 2 {
13 let mut end = line.len();
14 if line.char_indices().count() > 3 {
15 end = line.char_indices().map(|(i, _)| i).nth(3).unwrap();
17 line_type = identify_line(&line[..end], is_preformatted);
20 LineType::PreformattedToggle => is_preformatted = !is_preformatted,
22 // Close previous block if needed
23 if let Some(line) = ¤t_line_type {
24 if line != &line_type && is_block(line) {
25 html.push_str(get_line_closer(line));
30 if is_block(&line_type) {
31 if let Some(line) = ¤t_line_type {
32 if line != &line_type {
33 html.push_str(get_line_opener(&line_type));
36 html.push_str(get_line_opener(&line_type));
39 let line_content = get_partial_line_content(&line_type, line);
40 html.push_str(&line_content);
42 html.push_str(&get_heading_wrapper(&mut heading_stack, &line_type));
43 html.push_str(&get_full_line_content(&line_type, line));
45 current_line_type = Some(line_type);
49 if let Some(line) = ¤t_line_type {
51 html.push_str(get_line_closer(line));
54 html.push_str(&close_heading_wrapper(&mut heading_stack));
58 fn is_block(line_type: &LineType) -> bool {
59 return match line_type {
60 LineType::PreformattedText | LineType::ListItem | LineType::Quote => true,
65 fn get_partial_line_content(line_type: &LineType, line: &str) -> String {
66 let encoded_line = line.replace("<", "<").replace(">", ">");
67 return match line_type {
68 LineType::ListItem => format!("<li>{}</li>", encoded_line[2..].trim()),
69 LineType::Quote => encoded_line[1..].trim().to_string(),
70 LineType::PreformattedText => format!("{}\n", encoded_line),
75 fn get_full_line_content(line_type: &LineType, line: &str) -> String {
76 let encoded_line = line.replace("<", "<").replace(">", ">");
78 LineType::Text => format!("<p>{}</p>\n", encoded_line.trim()),
79 LineType::Blank => "<br/>\n".to_string(),
81 let url = get_link_address(line);
82 if url.starts_with("gemini:") {
83 format!("<div><a href=\"{}\">{}</a></div>\n", url, get_link_content(line))
85 format!("<div><a href=\"{}\">{}</a></div>\n", url.replace(".gmi", ".html"), get_link_content(line))
88 LineType::Heading1 => format!("<h1>{}</h1>\n", encoded_line[1..].trim()),
89 LineType::Heading2 => format!("<h2>{}</h2>\n", encoded_line[2..].trim()),
90 LineType::Heading3 => format!("<h3>{}</h3>\n", encoded_line[3..].trim()),
95 fn get_heading_wrapper(heading_stack: &mut Vec<u8>, line_type: &LineType) -> String {
96 let mut string = String::new();
97 let current_heading: u8 = match line_type {
98 LineType::Heading1 => 1,
99 LineType::Heading2 => 2,
100 LineType::Heading3 => 3,
104 if current_heading < 255 {
105 while let Some(open_heading) = heading_stack.pop() {
106 // You just encountered a more important heading.
107 // Put it back. Desist.
108 if open_heading < current_heading {
109 heading_stack.push(open_heading);
113 string.push_str("</div>");
115 if open_heading == current_heading {
119 heading_stack.push(current_heading);
120 string.push_str(&format!("<div class=\"h{}\">", current_heading));
126 fn close_heading_wrapper(heading_stack: &mut Vec<u8>) -> String {
127 let mut string = String::new();
128 while let Some(_open_heading) = heading_stack.pop() {
129 string.push_str("</div>");
134 fn get_line_opener(line_type: &LineType) -> &'static str {
136 LineType::ListItem => "<ul>",
137 LineType::Quote => "<blockquote>",
138 LineType::PreformattedText => "<pre>",
143 fn get_line_closer(line_type: &LineType) -> &'static str {
145 LineType::ListItem => "</ul>\n",
146 LineType::Quote => "</blockquote>\n",
147 LineType::PreformattedText => "</pre>\n",
152 fn get_link_content(line: &str) -> &str {
153 let components: Vec<&str> = line[2..].trim().splitn(2, " ").collect();
154 if components.len() > 1 {
155 return components[1].trim()
160 fn get_link_address(line: &str) -> &str {
161 let components: Vec<&str> = line[2..].trim().splitn(2, " ").collect();
165 fn identify_line(line: &str, is_preformatted: bool) -> LineType {
166 if line.starts_with("```") {
167 return LineType::PreformattedToggle;
170 return LineType::PreformattedText;
173 return LineType::Blank;
175 if line.starts_with("=>") {
176 return LineType::Link;
178 if line.starts_with("* ") {
179 return LineType::ListItem;
181 if line.starts_with(">") {
182 return LineType::Quote;
184 if line.starts_with("###") {
185 return LineType::Heading3;
187 if line.starts_with("##") {
188 return LineType::Heading2;
190 if line.starts_with("#") {
191 return LineType::Heading1;
197 #[derive(PartialEq, Eq)]