Skip to content

Instantly share code, notes, and snippets.

@yfaming
Created February 23, 2020 09:04
Show Gist options
  • Save yfaming/96fb3ea8ea639d0bee5b20083796159b to your computer and use it in GitHub Desktop.
Save yfaming/96fb3ea8ea639d0bee5b20083796159b to your computer and use it in GitHub Desktop.
mdtoc: A handy tool which prints table of contents of markdown file with proper indents
use markedit::{Heading, Matcher};
use pulldown_cmark::{Event, Parser, Tag};
use std::fmt::{self, Display};
use std::fs;
use std::io;
use std::mem;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name = "mdtoc", about = "print table of contents of markdown file")]
struct Opt {
markdown_file: String,
}
fn main() -> Result<(), io::Error> {
let opt = Opt::from_args();
let markdown = fs::read_to_string(opt.markdown_file)?;
let toc: Vec<Header> = extract_toc(&markdown);
for h in toc {
println!("{}", h);
}
Ok(())
}
fn extract_toc(markdown: &str) -> Vec<Header> {
let parser = Parser::new(markdown);
let mut heading = Heading::any_level();
let iter = parser.filter(move |e| heading.matches_event(e));
let mut toc = vec![];
let mut level: Option<u32> = None;
let mut text: Option<String> = None;
for event in iter {
match event {
Event::Start(Tag::Heading(lvl)) => {
level = Some(lvl);
}
Event::Text(txt) => match text {
Some(ref mut t) => {
t.push_str(txt.as_ref());
}
None => {
text = Some(txt.to_owned().to_string());
}
},
Event::End(Tag::Heading(_)) => {
let header = Header {
level: mem::replace(&mut level, None).expect("unreachable: no Event::Start(Tag::Heading(lvl)) before Event::End(Tag::Heading(lvl))"),
text: mem::replace(&mut text, None).expect("unreachable: no heading text"),
};
toc.push(header);
}
_ => {}
}
}
toc
}
#[derive(Debug)]
pub struct Header {
pub level: u32,
pub text: String,
}
impl Header {
pub fn indents(&self) -> usize {
if self.level <= 0 {
0
} else {
self.level as usize - 1
}
}
}
impl Display for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"{}{} {}",
" ".repeat(self.indents() * 4),
"#".repeat(self.level as usize),
self.text
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment