1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use comrak::{
Arena, Options,
arena_tree::NodeEdge,
format_html,
nodes::{Ast, NodeHeading, NodeValue, Sourcepos},
parse_document,
};
use std::cell::Ref;
#[derive(Debug)]
pub struct SectionBuilder<'a> {
pub heading: NodeHeading,
pub heading_sourcepos: Sourcepos,
pub heading_content: Vec<Ref<'a, Ast>>,
pub section_content: Vec<SectionContent<'a>>,
}
#[derive(Debug)]
pub enum SectionContent<'a> {
/// A fenced code block. For example zsh, text, python3, etc
CodeBlock(Ref<'a, Ast>),
/// A non-code piece of content.
Other(Ref<'a, Ast>),
}
#[derive(Debug)]
pub struct Section<'a> {
heading: NodeHeading,
heading_sourcepos: Sourcepos,
heading_content: Vec<Ref<'a, Ast>>,
section_content: Vec<SectionContent<'a>>,
}
impl<'a> SectionBuilder<'a> {
pub fn new(heading: NodeHeading, heading_sourcepos: Sourcepos) -> Self {
Self {
heading,
heading_sourcepos,
heading_content: Default::default(),
section_content: Default::default(),
}
}
pub fn build(self) -> Section<'a> {
Section {
heading: self.heading,
heading_sourcepos: self.heading_sourcepos,
heading_content: self.heading_content,
section_content: self.section_content,
}
}
}
fn main() {
let md_document = "# `cool` project ## yeah!\n\nExample readme of an awesome project\n\n```zsh\ntrue\n```\n\n```text\n```\n\n##\n\n## How are you?\n\nHello\n\n### Sub\n\nsub\n\n## Next\n\nnext\n\n### Sub\n\nsub 2\n";
// The returned nodes are created in the supplied Arena, and are bound by its lifetime.
let arena = Arena::new();
// Parse the document into a root `AstNode`
let root = parse_document(&arena, md_document, &Options::default());
let mut sections = vec![];
let mut current_section: Option<SectionBuilder> = None;
let mut current_heading_is_open = false;
// Traverse edges and gather sections of the document from AST.
// A section is the part of a document from the beginning of a heading,
// up until the beginning of the next heading, regardless of the level of the headings.
//
// For example, the following document consists of four sections:
//
// ```markdown
// # Eef freef
//
// ## Schmeep leep
//
// Zorp dorp.
//
// ### Burga borp
//
// Bazinga!
//
// ##
//
// Oops I forgor put title in above heading..
// ```
for edge in root.traverse() {
match edge {
NodeEdge::Start(node) => {
let data = node.data();
//println!("Entering node: {:?}", data);
if let NodeValue::Heading(heading) = data.value {
// Reached start of a heading, and thus the start of a new section.
// Headings do not "nest"; for example, `# hello ## world` on one line is *not* a h2 "inside" a h1.
if current_heading_is_open {
panic!(
"{}: Got new heading start while already inside a heading. Malformed document?",
data.sourcepos
);
}
// We are finished with previous section if there was any.
if let Some(previous_section) = current_section {
sections.push(previous_section.build());
}
// Starting current section.
current_section = Some(SectionBuilder::new(heading, data.sourcepos));
current_heading_is_open = true;
} else if current_heading_is_open {
// Currently inside a heading.
current_section
.as_mut()
.expect("Current section is already some at this point, because we are inside a heading.")
.heading_content
.push(data);
} else if let Some(current_section) = current_section.as_mut() {
// Inside a section, after the heading of the section has been closed.
let section_content = match data.value {
NodeValue::CodeBlock(_) => SectionContent::CodeBlock(data),
_ => SectionContent::Other(data),
};
current_section.section_content.push(section_content);
}
}
NodeEdge::End(node) => {
let data = node.data();
//println!("Leaving node: {:?}", data);
if let NodeValue::Heading(_) = data.value {
// Reached end of a heading. Keep in mind, this does not end the section itself.
// It is only the end of the beginning :^)
if !current_heading_is_open {
panic!(
"{}: Got end of heading while not yet inside a heading. Malformed document?",
data.sourcepos
);
}
current_heading_is_open = false;
}
}
}
}
if let Some(current_section) = current_section {
sections.push(current_section.build());
}
//println!();
let mut html = String::new();
format_html(root, &Options::default(), &mut html).unwrap();
println!("{}", html);
println!();
println!("{:#?}", sections);
println!();
//
//
//
for section in sections {
println!("Section heading: {:#?}", section.heading);
println!(
"Section heading source position: {:#?}",
section.heading_sourcepos
);
println!("Section heading content: {:?}", section.heading_content);
for content in section.section_content {
match content {
SectionContent::CodeBlock(code_block) => {
println!("{:?}", code_block);
}
other => {
println!("{:?}", other);
}
}
}
println!();
}
}