Some text.
Some more text.
Some more text.
Some more text.
And fin.
// The Rust logic that's supposed to build `toc_result.rs` out of `example.md` but doesn't (yet) | |
use markdown_it::{plugins::cmark::block::heading::ATXHeading, Node}; | |
use serde::Serialize; | |
use slug::slugify; | |
// Recursive TOC struct | |
#[derive(Debug, PartialEq, Serialize)] | |
pub struct TableOfContents(Vec<(Heading, TableOfContents)>); | |
// Initializer. Pass in an AST and a heading level to start (level 2 is standard). | |
impl TableOfContents { | |
pub fn new(document: &Node) -> Self { | |
toc_for_level(&document.children, 2) | |
} | |
} | |
// Create a table of contents for a specific heading level | |
fn toc_for_level(nodes: &[Node], level: u8) -> TableOfContents { | |
let mut toc: Vec<(Heading, TableOfContents)> = Vec::new(); | |
// Iterate through each node in the provided portion of the AST | |
for (idx, node) in nodes.iter().enumerate() { | |
// Stop at each heading | |
if let Some(h) = node.cast::<ATXHeading>() { | |
// Stop at headings that match the desired level | |
if h.level == level { | |
// Convert the content of the heading to a string | |
let text = &node_to_string(node); | |
// Create a new heading to add to the TOC | |
let heading = Heading::new(level, text); | |
// Add the new heading to the mutable vector, and pass in the TOC | |
// for the next heading level down (h2 to h3, h3 to h4, etc.) by | |
// passing in the remaining nodes in the AST. This is the part that | |
// doesn't work, because passing &nodes[idx..] here means that the | |
// whole rest of the document is iterated over, not just the portion | |
// that's underneath this heading. But I can't figure out a good way | |
// to tell the function where to stop! Any help would be greatly | |
// appreciated! 💯❤️ | |
toc.push((heading, toc_for_level(&nodes[idx..], level + 1))); | |
} | |
} | |
} | |
TableOfContents(toc) | |
} | |
// Heading struct (for use in the TOC) | |
#[derive(Debug, PartialEq, Serialize)] | |
struct Heading { | |
level: u8, | |
text: String, | |
slug: String, | |
} | |
// Heading initializer | |
impl Heading { | |
fn new(level: u8, text: &str) -> Self { | |
let slug = slugify(text); | |
Self { | |
level, | |
text: String::from(text), | |
slug, | |
} | |
} | |
} |
// Desired result struct from parsing `example.md` | |
TableOfContents(vec![ | |
( | |
Heading { | |
level: 2, | |
text: String::from("Here's a level 2 header"), | |
slug: String::from("here-s-a-level-2-header"), | |
}, | |
TableOfContents(vec![ | |
( | |
Heading: { | |
level: 3, | |
text: String::from("Here's a level 3 header"), | |
slug: String::from("here-s-a-level-3-header"), | |
}, | |
TableOfContents(vec![]) | |
) | |
]), | |
), | |
( | |
Heading { | |
level: 2, | |
text: String::from("Back to level 2"), | |
slug: String::from("back-to-level-2"), | |
}, | |
TableOfContents(vec![ | |
( | |
Heading { | |
level: 3, | |
text: String::from("Going deeper"), | |
slug: String::from("going-deeper"), | |
}, | |
TableOfContents(vec![ | |
( | |
Heading { | |
level: 4, | |
text: String::from("Going yet deeper"), | |
slug: String::from("going-yet-deeper"), | |
}, | |
TableOfContents(vec![]), | |
) | |
]), | |
), | |
]), | |
), | |
]) |