Clone-less toc making
This commit is contained in:
parent
21d67235ae
commit
9398ab789c
|
@ -12,7 +12,7 @@ use context::RenderContext;
|
||||||
use errors::{Error, Result};
|
use errors::{Error, Result};
|
||||||
use front_matter::InsertAnchor;
|
use front_matter::InsertAnchor;
|
||||||
use link_checker::check_url;
|
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::site::resolve_internal_link;
|
||||||
use utils::vec::InsertMany;
|
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 highlighter: Option<(HighlightLines, bool)> = None;
|
||||||
|
|
||||||
let mut inserted_anchors: Vec<String> = vec![];
|
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 opts = Options::empty();
|
||||||
let mut has_summary = false;
|
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
|
// record header to make table of contents
|
||||||
let permalink = format!("{}#{}", context.current_page_permalink, id);
|
let permalink = format!("{}#{}", context.current_page_permalink, id);
|
||||||
let temp_header = TempHeader { level: header_ref.level, id, permalink, title };
|
let h = Header { level: header_ref.level, id, permalink, title, children: Vec::new() };
|
||||||
headers.push(temp_header);
|
headers.push(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.insert_anchor != InsertAnchor::None {
|
if context.insert_anchor != InsertAnchor::None {
|
||||||
|
@ -270,7 +270,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result<Render
|
||||||
Ok(Rendered {
|
Ok(Rendered {
|
||||||
summary_len: if has_summary { html.find(CONTINUE_READING) } else { None },
|
summary_len: if has_summary { html.find(CONTINUE_READING) } else { None },
|
||||||
body: html,
|
body: html,
|
||||||
toc: make_table_of_contents(&headers),
|
toc: make_table_of_contents(headers),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,112 +1,59 @@
|
||||||
|
/// Populated while receiving events from the markdown parser
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub level: i32,
|
pub level: i32,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub title: String,
|
|
||||||
pub permalink: String,
|
pub permalink: String,
|
||||||
|
pub title: String,
|
||||||
pub children: Vec<Header>,
|
pub children: Vec<Header>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
pub fn from_temp_header(tmp: &TempHeader, children: Vec<Header>) -> Header {
|
pub fn new(level: i32) -> Header {
|
||||||
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,
|
level,
|
||||||
id: String::new(),
|
id: String::new(),
|
||||||
permalink: String::new(),
|
permalink: String::new(),
|
||||||
title: String::new(),
|
title: String::new(),
|
||||||
|
children: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TempHeader {
|
impl Default for Header {
|
||||||
fn default() -> Self {
|
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
|
/// Converts the flat temp headers into a nested set of headers
|
||||||
/// representing the hierarchy
|
/// 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 toc = vec![];
|
||||||
let mut start_idx = 0;
|
'parent: for header in headers {
|
||||||
for (i, h) in temp_headers.iter().enumerate() {
|
if toc.is_empty() {
|
||||||
if i < start_idx {
|
toc.push(header);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let (end_idx, children) = find_children(h.level, start_idx + 1, temp_headers);
|
|
||||||
start_idx = end_idx;
|
// See if we have to insert as a child of a previous header
|
||||||
toc.push(Header::from_temp_header(h, children));
|
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
|
toc
|
||||||
|
@ -118,25 +65,25 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_basic_toc() {
|
fn can_make_basic_toc() {
|
||||||
let input = vec![TempHeader::new(1), TempHeader::new(1), TempHeader::new(1)];
|
let input = vec![Header::new(1), Header::new(1), Header::new(1)];
|
||||||
let toc = make_table_of_contents(&input);
|
let toc = make_table_of_contents(input);
|
||||||
assert_eq!(toc.len(), 3);
|
assert_eq!(toc.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_more_complex_toc() {
|
fn can_make_more_complex_toc() {
|
||||||
let input = vec![
|
let input = vec![
|
||||||
TempHeader::new(1),
|
Header::new(1),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(3),
|
Header::new(3),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(1),
|
Header::new(1),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(3),
|
Header::new(3),
|
||||||
TempHeader::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.len(), 2);
|
||||||
assert_eq!(toc[0].children.len(), 3);
|
assert_eq!(toc[0].children.len(), 3);
|
||||||
assert_eq!(toc[1].children.len(), 1);
|
assert_eq!(toc[1].children.len(), 1);
|
||||||
|
@ -147,15 +94,16 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn can_make_messy_toc() {
|
fn can_make_messy_toc() {
|
||||||
let input = vec![
|
let input = vec![
|
||||||
TempHeader::new(3),
|
Header::new(3),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(3),
|
Header::new(3),
|
||||||
TempHeader::new(2),
|
Header::new(2),
|
||||||
TempHeader::new(1),
|
Header::new(1),
|
||||||
TempHeader::new(4),
|
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.len(), 5);
|
||||||
assert_eq!(toc[2].children.len(), 1);
|
assert_eq!(toc[2].children.len(), 1);
|
||||||
assert_eq!(toc[4].children.len(), 1);
|
assert_eq!(toc[4].children.len(), 1);
|
||||||
|
|
Loading…
Reference in New Issue