1use crate::ast::{Block, Doc, Example, Heading, TableOrFence};
5use crate::span::Span;
6
7pub fn structure(path: &str, source: &str, blocks: Vec<Block>) -> Doc {
15 let mut examples: Vec<Example> = Vec::new();
16 let mut orphan_attachments: Vec<TableOrFence> = Vec::new();
17 let mut headings: Vec<Heading> = Vec::new();
18 let mut scope_stack: Vec<(usize, String)> = Vec::new();
19 let mut last_example_idx: Option<usize> = None;
20 let mut attachment_open = false;
21 let mut delimiter_pending = true;
25
26 for block in blocks {
27 match &block {
28 Block::Heading(heading) => {
29 while scope_stack.last().is_some_and(|e| e.0 >= heading.level) {
31 scope_stack.pop();
32 }
33 scope_stack.push((heading.level, heading.text.clone()));
34 headings.push(heading.clone());
35 attachment_open = false;
36 delimiter_pending = true;
37 }
38 Block::Paragraph(_) | Block::ListItem(_) | Block::Blockquote(_) => {
39 let block_span = block.span();
40 examples.push(Example {
41 scope_stack: scope_texts(&scope_stack),
42 span: block_span,
43 body: vec![block],
44 preceded_by_delimiter: delimiter_pending,
45 });
46 last_example_idx = Some(examples.len() - 1);
47 attachment_open = true;
48 delimiter_pending = false;
49 }
50 Block::Table(_) | Block::Fence(_) => {
51 let target = if attachment_open {
52 last_example_idx
53 } else {
54 None
55 };
56 if let Some(idx) = target {
57 let block_span = block.span();
58 examples[idx].span = Span::from_offsets(
59 source,
60 examples[idx].span.start_offset,
61 block_span.end_offset,
62 );
63 examples[idx].body.push(block);
64 } else {
65 orphan_attachments.push(match block {
66 Block::Table(t) => TableOrFence::Table(t),
67 Block::Fence(f) => TableOrFence::Fence(f),
68 _ => unreachable!(),
69 });
70 }
71 }
72 Block::ThematicBreak(_) => {
73 attachment_open = false;
74 delimiter_pending = true;
75 }
76 }
77 }
78
79 Doc {
80 path: path.to_string(),
81 source: source.to_string(),
82 examples,
83 orphan_attachments,
84 headings,
85 }
86}
87
88fn scope_texts(scope_stack: &[(usize, String)]) -> Vec<String> {
89 scope_stack.iter().map(|e| e.1.clone()).collect()
90}