Clone-less toc making

This commit is contained in:
Vincent Prouillet 2019-01-28 00:34:18 +01:00
parent 21d67235ae
commit 9398ab789c
2 changed files with 54 additions and 106 deletions

View File

@ -12,7 +12,7 @@ use context::RenderContext;
use errors::{Error, Result};
use front_matter::InsertAnchor;
use link_checker::check_url;
use table_of_contents::{Header, make_table_of_contents, TempHeader};
use table_of_contents::{Header, make_table_of_contents};
use utils::site::resolve_internal_link;
use utils::vec::InsertMany;
@ -140,7 +140,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
let mut highlighter: Option<(HighlightLines, bool)> = None;
let mut inserted_anchors: Vec<String> = vec![];
let mut headers: Vec<TempHeader> = vec![];
let mut headers: Vec<Header> = vec![];
let mut opts = Options::empty();
let mut has_summary = false;
@ -253,8 +253,8 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
// record header to make table of contents
let permalink = format!("{}#{}", context.current_page_permalink, id);
let temp_header = TempHeader { level: header_ref.level, id, permalink, title };
headers.push(temp_header);
let h = Header { level: header_ref.level, id, permalink, title, children: Vec::new() };
headers.push(h);
}
if context.insert_anchor != InsertAnchor::None {
@ -270,7 +270,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
Ok(Rendered {
summary_len: if has_summary { html.find(CONTINUE_READING) } else { None },
body: html,
toc: make_table_of_contents(&headers),
toc: make_table_of_contents(headers),
})
}
}

View File

@ -1,112 +1,59 @@
/// Populated while receiving events from the markdown parser
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct Header {
#[serde(skip_serializing)]
pub level: i32,
pub id: String,
pub title: String,
pub permalink: String,
pub title: String,
pub children: Vec<Header>,
}
impl Header {
pub fn from_temp_header(tmp: &TempHeader, children: Vec<Header>) -> Header {
pub fn new(level: i32) -> Header {
Header {
level: tmp.level,
id: tmp.id.clone(),
title: tmp.title.clone(),
permalink: tmp.permalink.clone(),
children,
}
}
}
/// Populated while receiving events from the markdown parser
#[derive(Debug, PartialEq, Clone)]
pub struct TempHeader {
pub level: i32,
pub id: String,
pub permalink: String,
pub title: String,
}
impl TempHeader {
pub fn new(level: i32) -> TempHeader {
TempHeader {
level,
id: String::new(),
permalink: String::new(),
title: String::new(),
children: Vec::new(),
}
}
}
impl Default for TempHeader {
impl Default for Header {
fn default() -> Self {
TempHeader::new(0)
Header::new(0)
}
}
/// Recursively finds children of a header
fn find_children(
parent_level: i32,
start_at: usize,
temp_headers: &[TempHeader],
) -> (usize, Vec<Header>) {
let mut headers = vec![];
let mut start_at = start_at;
// If we have children, we will need to skip some headers since they are already inserted
let mut to_skip = 0;
for h in &temp_headers[start_at..] {
// stop when we encounter a title at the same level or higher
// than the parent one. Here a lower integer is considered higher as we are talking about
// HTML headers: h1, h2, h3, h4, h5 and h6
if h.level <= parent_level {
return (start_at, headers);
}
// Do we need to skip some headers?
if to_skip > 0 {
to_skip -= 1;
continue;
}
let (end, children) = find_children(h.level, start_at + 1, temp_headers);
headers.push(Header::from_temp_header(h, children));
// we didn't find any children
if end == start_at {
start_at += 1;
to_skip = 0;
} else {
// calculates how many we need to skip. Since the find_children start_at starts at 1,
// we need to remove 1 to ensure correctness
to_skip = end - start_at - 1;
start_at = end;
}
// we don't want to index out of bounds
if start_at + 1 > temp_headers.len() {
return (start_at, headers);
}
}
(start_at, headers)
}
/// Converts the flat temp headers into a nested set of headers
/// representing the hierarchy
pub fn make_table_of_contents(temp_headers: &[TempHeader]) -> Vec<Header> {
pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> {
let mut toc = vec![];
let mut start_idx = 0;
for (i, h) in temp_headers.iter().enumerate() {
if i < start_idx {
'parent: for header in headers {
if toc.is_empty() {
toc.push(header);
continue;
}
let (end_idx, children) = find_children(h.level, start_idx + 1, temp_headers);
start_idx = end_idx;
toc.push(Header::from_temp_header(h, children));
// See if we have to insert as a child of a previous header
for h in toc.iter_mut().rev() {
// Look in its children first
for child in h.children.iter_mut().rev() {
if header.level > child.level {
child.children.push(header);
continue 'parent;
}
}
if header.level > h.level {
h.children.push(header);
continue 'parent;
}
}
// Nop, just insert it
toc.push(header)
}
toc
@ -118,25 +65,25 @@ mod tests {
#[test]
fn can_make_basic_toc() {
let input = vec![TempHeader::new(1), TempHeader::new(1), TempHeader::new(1)];
let toc = make_table_of_contents(&input);
let input = vec![Header::new(1), Header::new(1), Header::new(1)];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 3);
}
#[test]
fn can_make_more_complex_toc() {
let input = vec![
TempHeader::new(1),
TempHeader::new(2),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(1),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(3),
Header::new(1),
Header::new(2),
Header::new(2),
Header::new(3),
Header::new(2),
Header::new(1),
Header::new(2),
Header::new(3),
Header::new(3),
];
let toc = make_table_of_contents(&input);
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 2);
assert_eq!(toc[0].children.len(), 3);
assert_eq!(toc[1].children.len(), 1);
@ -147,15 +94,16 @@ mod tests {
#[test]
fn can_make_messy_toc() {
let input = vec![
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(2),
TempHeader::new(3),
TempHeader::new(2),
TempHeader::new(1),
TempHeader::new(4),
Header::new(3),
Header::new(2),
Header::new(2),
Header::new(3),
Header::new(2),
Header::new(1),
Header::new(4),
];
let toc = make_table_of_contents(&input);
let toc = make_table_of_contents(input);
println!("{:#?}", toc);
assert_eq!(toc.len(), 5);
assert_eq!(toc[2].children.len(), 1);
assert_eq!(toc[4].children.len(), 1);