From 14d954a42d691a27ce84e39145b3e50685a9ee14 Mon Sep 17 00:00:00 2001 From: Max Value Date: Fri, 9 May 2025 01:04:09 +0100 Subject: [PATCH] Added proper TOC for HTML and gem Renamed schema to goodnight --- schema.gn => goodnight.gn | 4 ++ src/parser.rs | 59 ++++++++++++++++++- src/types/contents.rs | 113 +++++++++++++++++++++++++++++++++--- src/types/document.rs | 2 - src/types/heading.rs | 8 ++- src/types/reference_list.rs | 2 +- 6 files changed, 174 insertions(+), 14 deletions(-) rename schema.gn => goodnight.gn (98%) diff --git a/schema.gn b/goodnight.gn similarity index 98% rename from schema.gn rename to goodnight.gn index 8937f37..dec3edf 100644 --- a/schema.gn +++ b/goodnight.gn @@ -84,6 +84,10 @@ author: William Greenwood --- ``` +### This is a test of the heading levels + +#### Some further testing + ## Referencing All referencing in Goodnight is APA 7th [lindsey2015]. It can be done similar to referenced links with a syntax similar to YAML metadata: diff --git a/src/parser.rs b/src/parser.rs index f253e79..5091d05 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -91,7 +91,7 @@ pub fn parse_body (string: String, metadata: Metadata, reference_list: Option {document.push(Box::new(metadata.clone()))}, ('[', 'T', 'O', 'C', ']', ..) => { - document.push(Box::new(Contents{elements: None})) + document.push(Box::new(Contents{elements: parse_headings(string.clone(), reference_list.clone())})) }, // match all block quotes @@ -144,6 +144,63 @@ pub fn parse_body (string: String, metadata: Metadata, reference_list: Option) -> Option> { + let mut elements: Vec = Vec::new(); + + let mut lines = string.lines().peekable(); + while let Some(line) = lines.next() { + if !(line == "") { + let first = &(String::from(line) + " ")[0..7].chars().collect::>(); + match (first[0], first[1], first[2], first[3], first[4], first[5]) { + // match all code blocks so that we dont match headings in a preformatted area + ('`', '`', '`', ..) => { + while match lines.peek() { + Some(string) => { + if string.starts_with("```") {false} + else { + lines.next(); + true + } + }, + None => false + } {()} + lines.next(); // consume the final ``` + } + + // match all heading patterns + ('#', '#', '#', '#', '#', '#') => { + elements.push(Heading{text: parse_reference(&reference_list, line[6..].to_string()), level: 6}) + }, + ('#', '#', '#', '#', '#', ..) => { + elements.push(Heading{text: parse_reference(&reference_list, line[5..].to_string()), level: 5}) + }, + ('#', '#', '#', '#', ..) => { + elements.push(Heading{text: parse_reference(&reference_list, line[4..].to_string()), level: 4}) + }, + ('#', '#', '#', ..) => { + elements.push(Heading{text: parse_reference(&reference_list, line[3..].to_string()), level: 3}) + }, + ('#', '#', ..) => { + elements.push(Heading{text: parse_reference(&reference_list, line[2..].to_string()), level: 2}) + }, + ('#', ..) => { + elements.push(Heading{text: parse_reference(&reference_list, line[1..].to_string()), level: 1}) + }, + ('[', 'R', 'E', 'F', 'S', ']') => { + elements.push(Heading{text: String::from("Bibliography"), level: 1}) + }, + _ => {()} + } + } + } + + if elements.is_empty() { + None + } else { + Some(elements) + } +} + fn parse_reference(reference_list: &Option, text: String) -> String { match reference_list.clone() { Some(bibliography) => { diff --git a/src/types/contents.rs b/src/types/contents.rs index 44af23c..31c025a 100644 --- a/src/types/contents.rs +++ b/src/types/contents.rs @@ -10,17 +10,116 @@ impl Renderable for Contents { } fn render_html(&self) -> String { - String::from("\ -

Table of Contents

-

This is where the table of contents will go

- ") + let mut text = String::from("

Table of Contents

\n
    \n"); + let mut counters = [0; 6]; + + match &self.elements { + Some(elements) => { + let mut last_level = 1; + let mut depth = 1; + + for element in elements { + if element.level > last_level { + text.push_str(&"
      ".repeat(element.level - last_level)); + depth += element.level - last_level; + } + else if element.level < last_level { + text.push_str(&"
    ".repeat(last_level - element.level)); + depth -= last_level - element.level; + } + last_level = element.level; + + counters[element.level - 1] += 1; + + let plaintext = element.render_plaintext(); + let anchor: String = plaintext.to_lowercase().split_whitespace().map(|s| s.chars().chain(['-'])).flatten().collect(); + let anchor = &anchor[..anchor.len()-1]; + let counter_text = counters[..element.level] + .iter() + .map(|s| s .to_string() + .as_str() + .chars() + .chain(['.']) + .collect::>()) + .flatten() + .collect::(); + let counter_text = &counter_text[..counter_text.len()-1]; + + text.push_str(&match element.level { + 1 => { + counters[1..].fill(0); + if &element.text == "Bibliography" { + String::from("
  1. \n\tBibliography
  2. \n") + } else { + format!( + "
  3. \n\tSection {section} - {plaintext}
  4. \n", + section = counters[0] + ) + } + }, + 2..=5 => { + counters[element.level..].fill(0); + format!("
  5. \n\t({counter_text}) {plaintext}
  6. \n") + }, + _ => { + format!("
  7. \n\t({counter_text}) {plaintext}
  8. \n") + } + }) + }; + text + &"
".repeat(depth) + } + None => {String::new()} + } } fn render_gemtext(&self) -> String { - String::from("\ -## Table of Contents + let mut text = String::from("## Table of Contents\n\n"); + let mut counters = [0; 6]; + + match &self.elements { + Some(elements) => { + for element in elements { + text.push_str(&match element.level { + 1 => { + counters[0] += 1; + counters[1..].fill(0); + if &element.text == "Bibliography" { + format!("* {}", element.render_plaintext()) + } else { + format!("* Section {} - {}", counters[0], element.render_plaintext()) + } + }, + 2 => { + counters[1] += 1; + counters[2..].fill(0); + format!("* ({}.{}) {}", counters[0], counters[1], element.render_plaintext()) + }, + 3 => { + counters[2] += 1; + counters[3..].fill(0); + format!("* ({}.{}.{}) {}", counters[0], counters[1], counters[2], element.render_plaintext()) + }, + 4 => { + counters[3] += 1; + counters[4..].fill(0); + format!("* ({}.{}.{}.{}) {}", counters[0], counters[1], counters[2], counters[3], element.render_plaintext()) + }, + 5 => { + counters[4] += 1; + counters[5..].fill(0); + format!("* ({}.{}.{}.{}.{}) {}", counters[0], counters[1], counters[2], counters[3], counters[4], element.render_plaintext()) + }, + _ => { + counters[5] += 1; + format!("* ({}.{}.{}.{}.{}.{}) {}", counters[0], counters[1], counters[2], counters[3], counters[4], counters[5], element.render_plaintext()) + } + }) + }; + text + "\n" + } + None => {String::new()} + } -This is where the Table of Contents will go.\n\n") } fn render_plaintext(&self) -> String { diff --git a/src/types/document.rs b/src/types/document.rs index 98f5bad..3abb8c9 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use crate::types::metadata::Metadata; use crate::types::reference_list::ReferenceList; use crate::types::Renderable; diff --git a/src/types/heading.rs b/src/types/heading.rs index 39eabc4..6911ded 100644 --- a/src/types/heading.rs +++ b/src/types/heading.rs @@ -23,6 +23,8 @@ impl Renderable for Heading { fn render_html (&self) -> String { let text = parse_inline_html(self.text.trim()); + let anchor: String = text.to_lowercase().split_whitespace().map(|s| s.chars().chain(['-'])).flatten().collect(); + let anchor = &anchor[..anchor.len()-1]; let section = match self.level { 1 => "h2", 2 => "h3", @@ -30,7 +32,7 @@ impl Renderable for Heading { 4 => "h5", _ => "h6" }; - format!("<{section}>{text}\n") + format!("<{section} id={anchor}>{text}\n") } fn render_gemtext (&self) -> String { @@ -43,8 +45,8 @@ impl Renderable for Heading { } fn render_plaintext (&self) -> String { - let text = parse_inline_remove(self.text.trim()).to_uppercase(); - format!("{text}\n\n") + let text = parse_inline_remove(self.text.trim()); + format!("{text}\n") } } diff --git a/src/types/reference_list.rs b/src/types/reference_list.rs index a5c5fc0..0d6a3e8 100644 --- a/src/types/reference_list.rs +++ b/src/types/reference_list.rs @@ -108,7 +108,7 @@ impl Renderable for ReferenceList { } fn render_html(&self) -> String { - let mut full_text = String::from("

Bibliography

"); + let mut full_text = String::from("

Bibliography

"); for reference in &self.list { full_text.push_str(&(reference.render_html() + "\n\n")); } -- 2.39.2