mirror of
https://github.com/getzola/zola
synced 2024-12-13 22:02:29 +00:00
Clone-less toc making
This commit is contained in:
parent
21d67235ae
commit
9398ab789c
2 changed files with 54 additions and 106 deletions
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue