[Web] Generate sidebar from markdown headings
This commit is contained in:
parent
04b373283c
commit
2f26e4aad1
@ -1,6 +1,6 @@
|
||||
use markdown::mdast::Code;
|
||||
|
||||
use crate::highlighter::highlight;
|
||||
use super::highlighter::highlight;
|
||||
|
||||
use super::Printable;
|
||||
|
||||
|
@ -2,8 +2,7 @@ use misti::TokenType;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! replace {
|
||||
($classes:literal, $token:ident, $offset:ident, $output:ident) => {
|
||||
{
|
||||
($classes:literal, $token:ident, $offset:ident, $output:ident) => {{
|
||||
let start_pos = $token.position;
|
||||
let end_pos = $token.get_end_position();
|
||||
|
||||
@ -13,8 +12,7 @@ macro_rules! replace {
|
||||
$offset += 28 + $classes.len();
|
||||
|
||||
$output.replace_range(range, html.as_str());
|
||||
}
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn highlight(input: &String) -> String {
|
||||
@ -22,10 +20,7 @@ pub fn highlight(input: &String) -> String {
|
||||
let tokens = misti::tokenize(&input);
|
||||
|
||||
if tokens.is_err() {
|
||||
eprintln!(
|
||||
"Found a lexical error processing code.\n{:?}",
|
||||
tokens
|
||||
);
|
||||
eprintln!("Found a lexical error processing code.\n{:?}", tokens);
|
||||
return input.clone();
|
||||
}
|
||||
|
||||
@ -38,6 +33,9 @@ pub fn highlight(input: &String) -> String {
|
||||
match &token.token_type {
|
||||
TokenType::Datatype => replace!("class-name", token, offset, output),
|
||||
TokenType::Number => replace!("number", token, offset, output),
|
||||
TokenType::Identifier if token.value == "true" || token.value == "false" => {
|
||||
replace!("keyword", token, offset, output)
|
||||
}
|
||||
TokenType::String => {
|
||||
let start_pos = token.position;
|
||||
let end_pos = token.get_end_position();
|
||||
@ -58,7 +56,6 @@ pub fn highlight(input: &String) -> String {
|
||||
output
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
@ -1,7 +1,7 @@
|
||||
use markdown::mdast::InlineCode;
|
||||
|
||||
use super::highlighter::highlight;
|
||||
use super::Printable;
|
||||
use crate::highlighter::highlight;
|
||||
|
||||
impl Printable for InlineCode {
|
||||
fn to_html(&self) -> String {
|
||||
@ -14,7 +14,10 @@ impl Printable for InlineCode {
|
||||
.replace(">", ">");
|
||||
*/
|
||||
|
||||
format!("<code class=\"border border-border-color dark:border-transparent\">{}</code>", highlight(&self.value))
|
||||
format!(
|
||||
"<code class=\"border border-border-color dark:border-transparent\">{}</code>",
|
||||
highlight(&self.value)
|
||||
)
|
||||
}
|
||||
|
||||
fn get_text(&self) -> String {
|
||||
|
@ -10,6 +10,8 @@ mod root;
|
||||
mod strong;
|
||||
mod text;
|
||||
|
||||
mod highlighter;
|
||||
|
||||
pub trait Printable {
|
||||
fn to_html(&self) -> String;
|
||||
fn get_text(&self) -> String;
|
||||
|
@ -1,12 +1,13 @@
|
||||
use clap::Parser;
|
||||
use generator::Printable;
|
||||
use sidebar::SidebarGenerator;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::{fs, path::Path};
|
||||
|
||||
mod generator;
|
||||
mod sidebar;
|
||||
mod utils;
|
||||
mod highlighter;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
@ -116,6 +117,7 @@ fn process_markdown(file: &Path, input_folder: &Path, output_folder: &Path) -> R
|
||||
// let html_text = to_html(markdown_text.as_str());
|
||||
let md_ast = markdown::to_mdast(&markdown_text, &markdown::ParseOptions::gfm()).unwrap();
|
||||
let html_text = md_ast.to_html();
|
||||
let sidebar_html = md_ast.generate_sidebar();
|
||||
|
||||
// Read template.html
|
||||
let mut template_path = output_folder.clone();
|
||||
@ -124,9 +126,13 @@ fn process_markdown(file: &Path, input_folder: &Path, output_folder: &Path) -> R
|
||||
let template_contents = fs::read(template_path).unwrap();
|
||||
let template_contents = String::from_utf8(template_contents).unwrap();
|
||||
|
||||
let final_output = template_contents.replace("{{markdown}}", &html_text);
|
||||
let final_output = template_contents
|
||||
.replace("{{markdown}}", &html_text)
|
||||
.replace("{{sidebar}}", &sidebar_html);
|
||||
|
||||
//
|
||||
// Write to disk
|
||||
//
|
||||
let _ = File::create(&output_file)
|
||||
.unwrap()
|
||||
.write_all(final_output.as_bytes())
|
||||
|
92
doc-generator/src/sidebar/mod.rs
Normal file
92
doc-generator/src/sidebar/mod.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use markdown::mdast::{Heading, Node};
|
||||
|
||||
use crate::{generator::Printable, utils};
|
||||
|
||||
pub trait SidebarGenerator {
|
||||
fn generate_sidebar(&self) -> String;
|
||||
}
|
||||
|
||||
impl SidebarGenerator for Node {
|
||||
fn generate_sidebar(&self) -> String {
|
||||
match self {
|
||||
Node::Root(root) => {
|
||||
let children_nodes = root
|
||||
.children
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter_map(|x| match x {
|
||||
Node::Heading(h) if h.depth <= 3 => Some(h),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// A top level topic that contains other topics
|
||||
let topic = extract_topics(&children_nodes, 0, 1);
|
||||
|
||||
match topic {
|
||||
Some((t, _)) => {
|
||||
let html: String = t.children.iter().map(|x| x.get_html()).collect();
|
||||
format!("<ul>{}</ul>", html)
|
||||
}
|
||||
None => String::from("D:"),
|
||||
}
|
||||
}
|
||||
_ => panic!("??"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Topic {
|
||||
text: String,
|
||||
children: Box<Vec<Topic>>,
|
||||
}
|
||||
|
||||
impl Topic {
|
||||
pub fn get_html(&self) -> String {
|
||||
let extra = if self.children.len() > 0 {
|
||||
let children_html: String = self.children.iter().map(|x| x.get_html()).collect();
|
||||
|
||||
format!("<ol class=\"px-4\">{}</ol>", children_html)
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
|
||||
let html_fragment_link = utils::to_html_fragment(&self.text);
|
||||
format!(
|
||||
"<li class=\"my-2\"><a href=\"#{}\" class=\"inline-block w-full\">{}</a>{}</li>",
|
||||
html_fragment_link, self.text, extra
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the next heading and all its children
|
||||
// current_level: the depth of the heading to match
|
||||
fn extract_topics<'a>(
|
||||
headings: &'a Vec<Heading>,
|
||||
current_pos: usize,
|
||||
current_level: u8,
|
||||
) -> Option<(Topic, usize)> {
|
||||
match headings.get(current_pos) {
|
||||
Some(h) if h.depth == current_level => {
|
||||
let mut new_vec = Vec::new();
|
||||
let mut next_pos = current_pos + 1;
|
||||
|
||||
while let Some((topic, next)) = extract_topics(headings, next_pos, current_level + 1) {
|
||||
new_vec.push(topic);
|
||||
next_pos = next;
|
||||
}
|
||||
|
||||
let title = h.get_text();
|
||||
let topic = Topic {
|
||||
text: title,
|
||||
children: Box::new(new_vec),
|
||||
};
|
||||
|
||||
Some((topic, next_pos))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ use markdown::mdast::Node;
|
||||
use crate::generator::Printable;
|
||||
|
||||
pub fn to_html_fragment(text: &String) -> String {
|
||||
text.clone().to_lowercase().replace(" ", "-")
|
||||
text.clone().replace(" ", "-")
|
||||
}
|
||||
|
||||
pub fn collect_children_html(vec: &Vec<Node>) -> String {
|
||||
|
@ -17,12 +17,17 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="marked p-4 mx-auto max-w-screen-lg">
|
||||
<div class="mx-auto max-w-screen-lg grid" style="grid-template-columns: 15rem auto;">
|
||||
<div class="text-sm h-screen pt-12 sticky top-0">
|
||||
{{sidebar}}
|
||||
</div>
|
||||
<div class="marked">
|
||||
{{markdown}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Inter - Google fonts - load here as to not interrupt the page
|
||||
// Inter - Google fonts - load here to avoid interrupting the page
|
||||
const linkEl = document.createElement("link");
|
||||
linkEl.href = "https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;700&display=swap";
|
||||
linkEl.rel = "stylesheet";
|
||||
|
Loading…
Reference in New Issue
Block a user