1use crate::ast::*;
2
3use crate::raw;
4use crate::raw::{Child, ComposedMarkdown, Special};
5use anyhow::anyhow;
6use cowstr::ToCowStr;
7use pulldown_cmark::{Event, HeadingLevel, Parser as MdParser, Tag};
8use regex::Regex;
9use std::str::FromStr;
10
11pub(crate) enum InnerContent {
12 Blocks(Vec<Block>),
13 Inlines(Vec<Inline>),
14}
15
16impl InnerContent {
17 pub(crate) fn into_blocks(self) -> Vec<Block> {
18 if let InnerContent::Blocks(b) = self {
19 b
20 } else {
21 panic!("Expected blocks")
22 }
23 }
24
25 pub(crate) fn into_inlines(self) -> Vec<Inline> {
26 if let InnerContent::Inlines(i) = self {
27 i
28 } else {
29 panic!("Expected inlines")
30 }
31 }
32
33 pub(crate) fn blocks_mut(&mut self) -> anyhow::Result<&mut Vec<Block>> {
34 if let InnerContent::Blocks(b) = self {
35 Ok(b)
36 } else {
37 Err(anyhow!("Expected block element"))
38 }
39 }
40
41 #[allow(unused)]
42 fn inlines_mut(&mut self) -> anyhow::Result<&mut Vec<Inline>> {
43 if let InnerContent::Inlines(i) = self {
44 Ok(i)
45 } else {
46 Err(anyhow!("Expected inline element"))
47 }
48 }
49
50 pub(crate) fn push_inline(&mut self, item: Inline) {
51 match self {
52 InnerContent::Blocks(b) => b.push(Block::Plain(vec![item])),
53 InnerContent::Inlines(i) => i.push(item),
54 }
55 }
56}
57
58impl From<raw::Value> for Value {
59 fn from(value: raw::Value) -> Self {
60 match value {
61 raw::Value::Flag(f) => Value::Flag(f),
62 raw::Value::Content(c) => Value::Content(ComposedMarkdown::from(c).into()),
63 raw::Value::String(s) => Value::String(s),
64 }
65 }
66}
67
68impl From<raw::Parameter> for Parameter {
69 fn from(value: raw::Parameter) -> Self {
70 Parameter {
71 key: value.key,
72 value: value.value.into(),
73 span: value.span,
74 }
75 }
76}
77
78impl From<Child> for Inline {
92 fn from(value: Child) -> Self {
93 match value.elem {
94 Special::Math { inner, is_block } => Inline::Math(Math {
95 label: value.label,
96 source: inner,
97 display_block: is_block,
98 span: value.span,
99 }),
100 Special::CodeBlock {
101 inner, attributes, ..
102 } => Inline::CodeBlock(CodeBlock {
103 label: value.label,
104 source: inner,
105 attributes,
106 display_cell: false,
107 global_idx: value.identifier,
108 span: value.span,
109 }),
110 Special::CodeInline { inner } => Inline::Code(inner),
111 Special::Command {
112 function,
113 parameters,
114 body,
115 } => {
116 let parameters = parameters.into_iter().map(|p| p.into()).collect();
117 let body = body.map(|b| ComposedMarkdown::from(b).into());
118
119 Inline::Command(Command {
120 function,
121 label: value.label,
122 parameters,
123 body,
124 span: value.span,
125 global_idx: value.identifier,
126 })
127 }
128 Special::Verbatim { inner } => Inline::Text(inner),
129 }
130 }
131}
132
133impl From<ComposedMarkdown> for Vec<Block> {
134 fn from(composed: ComposedMarkdown) -> Self {
135 let parser: MdParser = MdParser::new(&composed.src);
136 let r = Regex::new(r"elem-([0-9]+)").expect("invalid regex expression");
137 let mut inners = vec![InnerContent::Blocks(Vec::new())];
138
139 for event in parser {
140 match event {
141 Event::Start(t) => match t {
142 Tag::Paragraph
143 | Tag::Heading(_, _, _)
144 | Tag::BlockQuote
145 | Tag::CodeBlock(_)
146 | Tag::TableHead
147 | Tag::TableRow
148 | Tag::TableCell
149 | Tag::Emphasis
150 | Tag::Strong
151 | Tag::Strikethrough
152 | Tag::Image(_, _, _) => inners.push(InnerContent::Inlines(Vec::new())),
153 Tag::Link(_, _, _) => inners.push(InnerContent::Inlines(Vec::new())),
154 Tag::List(_) | Tag::Item | Tag::Table(_) | Tag::FootnoteDefinition(_) => {
155 inners.push(InnerContent::Blocks(Vec::new()))
156 }
157 },
158 Event::End(t) => {
159 let inner = inners.pop().expect("No inner content");
160 match t {
161 Tag::Paragraph => inners
162 .last_mut()
163 .unwrap()
164 .blocks_mut()
165 .expect("for paragraph")
166 .push(Block::Paragraph(inner.into_inlines())),
167 Tag::Heading(lvl, id, classes) => inners
168 .last_mut()
169 .unwrap()
170 .blocks_mut()
171 .expect("for heading")
172 .push(Block::Heading {
173 lvl: heading_to_lvl(lvl),
174 id: id.map(|s| s.into()),
175 classes: classes.into_iter().map(|s| s.into()).collect(),
176 inner: inner.into_inlines(),
177 }),
178 Tag::BlockQuote => inners
179 .last_mut()
180 .unwrap()
181 .blocks_mut()
182 .expect("for blockquote")
183 .push(Block::BlockQuote(inner.into_inlines())),
184 Tag::List(idx) => inners
185 .last_mut()
186 .unwrap()
187 .blocks_mut()
188 .expect("for list")
189 .push(Block::List(idx, inner.into_blocks())),
190 Tag::Item => inners
191 .last_mut()
192 .unwrap()
193 .blocks_mut()
194 .expect("for item")
195 .push(Block::ListItem(inner.into_blocks())),
196 Tag::Emphasis => {
197 let src = inner.into_inlines();
198
199 inners
200 .last_mut()
201 .unwrap()
202 .push_inline(Inline::Styled(src, Style::Emphasis))
203 }
204 Tag::Strong => inners
205 .last_mut()
206 .unwrap()
207 .push_inline(Inline::Styled(inner.into_inlines(), Style::Strong)),
208 Tag::Strikethrough => inners.last_mut().unwrap().push_inline(
209 Inline::Styled(inner.into_inlines(), Style::Strikethrough),
210 ),
211 Tag::Link(tp, url, alt) => {
212 inners.last_mut().unwrap().push_inline(Inline::Link(
213 tp,
214 url.to_cowstr(),
215 alt.to_cowstr(),
216 inner.into_inlines(),
217 ))
218 }
219 Tag::Image(tp, url, alt) => {
220 inners.last_mut().unwrap().push_inline(Inline::Image(
221 tp,
222 url.to_cowstr(),
223 alt.to_cowstr(),
224 inner.into_inlines(),
225 ))
226 }
227 _ => {} }
229 }
230 Event::Html(src) => {
231 let is_insert = r.captures(src.as_ref()).and_then(|c| c.get(1));
232
233 if let Some(match_) = is_insert {
234 let idx = usize::from_str(match_.as_str()).unwrap();
235 let elem = composed.children[idx].clone();
236 inners.last_mut().unwrap().push_inline(elem.into());
237 } else {
238 inners
239 .last_mut()
240 .unwrap()
241 .push_inline(Inline::Html(src.to_cowstr()));
242 }
243 }
244 other => {
245 let inner = match other {
246 Event::Text(s) => Inline::Text(s.to_cowstr()),
247 Event::Code(s) => Inline::Code(s.to_cowstr()),
248 Event::SoftBreak => Inline::SoftBreak,
249 Event::HardBreak => Inline::HardBreak,
250 Event::Rule => Inline::Rule,
251 _ => unreachable!(),
252 };
253
254 let c = inners.last_mut().unwrap();
255 c.push_inline(inner);
256 }
257 }
258 }
259 let b = inners.remove(0).into_blocks();
260 b.clone()
261 }
262}
263
264fn heading_to_lvl(value: HeadingLevel) -> u8 {
265 match value {
266 HeadingLevel::H1 => 1,
267 HeadingLevel::H2 => 2,
268 HeadingLevel::H3 => 3,
269 HeadingLevel::H4 => 4,
270 HeadingLevel::H5 => 5,
271 HeadingLevel::H6 => 6,
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use crate::ast;
278 use crate::ast::Block::ListItem;
279 use crate::ast::{Block, Command, Inline, Math, Parameter, Style, Value};
280 use crate::code_ast::types::{CodeContent, CodeElem};
281 use crate::common::Span;
282 use crate::raw::{parse_to_doc, ComposedMarkdown, Element, ElementInfo, Special};
283
284 use pulldown_cmark::LinkType;
285
286 #[test]
287 fn simple_command() {
288 let stuff = vec![
289 ElementInfo {
290 element: Element::Markdown("regular stuff ".into()),
291 span: Span::new(0, 0),
292 },
293 ElementInfo {
294 element: Element::Special(
295 None,
296 Special::Command {
297 function: "func".into(),
298 parameters: vec![],
299 body: Some(vec![ElementInfo {
300 element: Element::Markdown("x".into()),
301 span: Span::new(0, 0),
302 }]),
303 },
304 ),
305 span: Span::new(0, 0),
306 },
307 ];
308
309 let composed = ComposedMarkdown::from(stuff);
310 let doc = Vec::from(composed);
311
312 let expected = vec![Block::Paragraph(vec![
313 Inline::Text("regular stuff ".into()),
314 Inline::Command(Command {
315 function: "func".into(),
316 label: None,
317 parameters: vec![],
318 body: Some(vec![Block::Paragraph(vec![Inline::Text("x".into())])]),
319 span: Span::new(0, 0),
320 global_idx: 0,
321 }),
322 ])];
323
324 assert_eq!(expected, doc);
325 }
326
327 #[test]
328 fn markdown_elements() {
329 let input = include_str!("../../resources/tests/markdown_elems.md");
330 let input_doc = parse_to_doc(input).expect("rawdoc parse error");
331 let composed = ComposedMarkdown::from(input_doc.src);
332 let output_doc = Vec::from(composed);
333
334 let expected = vec![
335 Block::Heading {
336 lvl: 1,
337 id: None,
338 classes: vec![],
339 inner: vec![Inline::Text("Heading".into())],
340 },
341 Block::Heading {
342 lvl: 2,
343 id: None,
344 classes: vec![],
345 inner: vec![Inline::Text("Subheading".into())],
346 },
347 Block::List(
348 None,
349 vec![
350 ListItem(vec![Block::Plain(vec![Inline::Text(
351 "unordered list".into(),
352 )])]),
353 ListItem(vec![Block::Plain(vec![Inline::Text("item 2".into())])]),
354 ],
355 ),
356 Block::List(
357 Some(1),
358 vec![
359 ListItem(vec![Block::Plain(vec![Inline::Text(
360 "ordered list".into(),
361 )])]),
362 ListItem(vec![Block::Plain(vec![Inline::Text("item 2".into())])]),
363 ],
364 ),
365 Block::Paragraph(vec![
366 Inline::Link(
367 LinkType::Inline,
368 "path/is/here".into(),
369 "".into(),
370 vec![Inline::Text("link".into())],
371 ),
372 Inline::SoftBreak,
373 Inline::Image(
374 LinkType::Inline,
375 "path/is/here".into(),
376 "".into(),
377 vec![Inline::Text("image".into())],
378 ),
379 ]),
380 Block::Paragraph(vec![
381 Inline::Styled(vec![Inline::Text("emph".into())], Style::Emphasis),
382 Inline::SoftBreak,
383 Inline::Styled(vec![Inline::Text("strong".into())], Style::Strong),
384 ]),
385 Block::Plain(vec![Inline::Code("code inline".into())]),
386 Block::Plain(vec![Inline::CodeBlock(ast::CodeBlock {
387 label: None,
388 source: CodeContent {
389 blocks: vec![CodeElem::Src("\ncode block\n\n".to_string())],
390 meta: Default::default(),
391 hash: 8014072465408005981,
392 },
393
394 display_cell: false,
395 global_idx: 0,
396 span: Span::new(180, 198),
397 attributes: vec![],
398 })]),
399 Block::Plain(vec![Inline::Math(Math {
400 label: None,
401 source: "math inline".into(),
402 display_block: false,
403 span: Span::new(200, 213),
404 })]),
405 Block::Plain(vec![Inline::Math(Math {
406 label: None,
407 source: "\nmath block\n".into(),
408 display_block: true,
409 span: Span::new(215, 231),
410 })]),
411 ];
412
413 assert_eq!(expected, output_doc);
414 }
415
416 #[test]
417 fn commands() {
418 let input = include_str!("../../resources/tests/commands.md");
419 let input_doc = parse_to_doc(input).expect("rawdoc parse error");
420 let composed = ComposedMarkdown::from(input_doc.src);
421 let output_doc = Vec::from(composed);
422
423 let expected = vec![
424 Block::Plain(vec![Inline::Command(Command {
425 function: "func".into(),
426 label: None,
427 parameters: vec![],
428 body: None,
429 span: Span::new(0, 5),
430 global_idx: 0,
431 })]),
432 Block::Plain(vec![Inline::Command(Command {
433 function: "func_param".into(),
434 label: None,
435 parameters: vec![
436 Parameter {
437 key: None,
438 value: Value::String("p1".into()),
439 span: Span::new(19, 21),
440 },
441 Parameter {
442 key: Some("x".into()),
443 value: Value::String("p2".into()),
444 span: Span::new(23, 27),
445 },
446 ],
447 body: None,
448 span: Span::new(7, 28),
449
450 global_idx: 1,
451 })]),
452 Block::Plain(vec![Inline::Command(Command {
453 function: "func_body".into(),
454 label: None,
455 parameters: vec![],
456 body: Some(vec![Block::Paragraph(vec![Inline::Text(
457 "hello there".into(),
458 )])]),
459 span: Span::new(30, 55),
460 global_idx: 2,
461 })]),
462 Block::Plain(vec![Inline::Command(Command {
463 function: "func_all".into(),
464 label: None,
465 parameters: vec![
466 Parameter {
467 key: None,
468 value: Value::String("p1".into()),
469 span: Span::new(67, 69),
470 },
471 Parameter {
472 key: Some("x".into()),
473 value: Value::String("p2".into()),
474 span: Span::new(71, 75),
475 },
476 ],
477 body: Some(vec![Block::Paragraph(vec![Inline::Text(
478 "hello there".into(),
479 )])]),
480 span: Span::new(57, 91),
481 global_idx: 3,
482 })]),
483 Block::Plain(vec![Inline::Command(Command {
484 function: "func_inner".into(),
485 label: None,
486 parameters: vec![],
487 body: Some(vec![
488 Block::Plain(vec![Inline::Code("#func".into())]),
489 Block::Plain(vec![Inline::Command(Command {
490 function: "inner".into(),
491 label: None,
492 parameters: vec![],
493 body: Some(vec![Block::Plain(vec![Inline::Math(Math {
494 label: None,
495 source: "math".into(),
496 display_block: false,
497 span: Span::new(122, 128),
498 })])]),
499 span: Span::new(114, 130),
500 global_idx: 0,
501 })]),
502 ]),
503 span: Span::new(93, 132),
504 global_idx: 4,
505 })]),
506 ];
507
508 assert_eq!(expected, output_doc);
509 }
510}