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