[Web] Generate sidebar from markdown headings
This commit is contained in:
parent
04b373283c
commit
2f26e4aad1
@ -1,6 +1,6 @@
|
|||||||
use markdown::mdast::Code;
|
use markdown::mdast::Code;
|
||||||
|
|
||||||
use crate::highlighter::highlight;
|
use super::highlighter::highlight;
|
||||||
|
|
||||||
use super::Printable;
|
use super::Printable;
|
||||||
|
|
||||||
|
@ -2,19 +2,17 @@ use misti::TokenType;
|
|||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! replace {
|
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 start_pos = $token.position;
|
let end_pos = $token.get_end_position();
|
||||||
let end_pos = $token.get_end_position();
|
|
||||||
|
|
||||||
let range = (start_pos + $offset)..(end_pos + $offset);
|
let range = (start_pos + $offset)..(end_pos + $offset);
|
||||||
let html = format!("<span class=\"token {}\">{}</span>", $classes, $token.value);
|
let html = format!("<span class=\"token {}\">{}</span>", $classes, $token.value);
|
||||||
|
|
||||||
$offset += 28 + $classes.len();
|
$offset += 28 + $classes.len();
|
||||||
|
|
||||||
$output.replace_range(range, html.as_str());
|
$output.replace_range(range, html.as_str());
|
||||||
}
|
}};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn highlight(input: &String) -> String {
|
pub fn highlight(input: &String) -> String {
|
||||||
@ -22,10 +20,7 @@ pub fn highlight(input: &String) -> String {
|
|||||||
let tokens = misti::tokenize(&input);
|
let tokens = misti::tokenize(&input);
|
||||||
|
|
||||||
if tokens.is_err() {
|
if tokens.is_err() {
|
||||||
eprintln!(
|
eprintln!("Found a lexical error processing code.\n{:?}", tokens);
|
||||||
"Found a lexical error processing code.\n{:?}",
|
|
||||||
tokens
|
|
||||||
);
|
|
||||||
return input.clone();
|
return input.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +33,9 @@ pub fn highlight(input: &String) -> String {
|
|||||||
match &token.token_type {
|
match &token.token_type {
|
||||||
TokenType::Datatype => replace!("class-name", token, offset, output),
|
TokenType::Datatype => replace!("class-name", token, offset, output),
|
||||||
TokenType::Number => replace!("number", 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 => {
|
TokenType::String => {
|
||||||
let start_pos = token.position;
|
let start_pos = token.position;
|
||||||
let end_pos = token.get_end_position();
|
let end_pos = token.get_end_position();
|
||||||
@ -58,7 +56,6 @@ pub fn highlight(input: &String) -> String {
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
@ -1,7 +1,7 @@
|
|||||||
use markdown::mdast::InlineCode;
|
use markdown::mdast::InlineCode;
|
||||||
|
|
||||||
|
use super::highlighter::highlight;
|
||||||
use super::Printable;
|
use super::Printable;
|
||||||
use crate::highlighter::highlight;
|
|
||||||
|
|
||||||
impl Printable for InlineCode {
|
impl Printable for InlineCode {
|
||||||
fn to_html(&self) -> String {
|
fn to_html(&self) -> String {
|
||||||
@ -14,7 +14,10 @@ impl Printable for InlineCode {
|
|||||||
.replace(">", ">");
|
.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 {
|
fn get_text(&self) -> String {
|
||||||
|
@ -10,6 +10,8 @@ mod root;
|
|||||||
mod strong;
|
mod strong;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
|
mod highlighter;
|
||||||
|
|
||||||
pub trait Printable {
|
pub trait Printable {
|
||||||
fn to_html(&self) -> String;
|
fn to_html(&self) -> String;
|
||||||
fn get_text(&self) -> String;
|
fn get_text(&self) -> String;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use generator::Printable;
|
use generator::Printable;
|
||||||
|
use sidebar::SidebarGenerator;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
mod generator;
|
mod generator;
|
||||||
|
mod sidebar;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod highlighter;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[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 html_text = to_html(markdown_text.as_str());
|
||||||
let md_ast = markdown::to_mdast(&markdown_text, &markdown::ParseOptions::gfm()).unwrap();
|
let md_ast = markdown::to_mdast(&markdown_text, &markdown::ParseOptions::gfm()).unwrap();
|
||||||
let html_text = md_ast.to_html();
|
let html_text = md_ast.to_html();
|
||||||
|
let sidebar_html = md_ast.generate_sidebar();
|
||||||
|
|
||||||
// Read template.html
|
// Read template.html
|
||||||
let mut template_path = output_folder.clone();
|
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 = fs::read(template_path).unwrap();
|
||||||
let template_contents = String::from_utf8(template_contents).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
|
// Write to disk
|
||||||
|
//
|
||||||
let _ = File::create(&output_file)
|
let _ = File::create(&output_file)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.write_all(final_output.as_bytes())
|
.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;
|
use crate::generator::Printable;
|
||||||
|
|
||||||
pub fn to_html_fragment(text: &String) -> String {
|
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 {
|
pub fn collect_children_html(vec: &Vec<Node>) -> String {
|
||||||
|
@ -17,12 +17,17 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<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;">
|
||||||
{{markdown}}
|
<div class="text-sm h-screen pt-12 sticky top-0">
|
||||||
|
{{sidebar}}
|
||||||
|
</div>
|
||||||
|
<div class="marked">
|
||||||
|
{{markdown}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<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");
|
const linkEl = document.createElement("link");
|
||||||
linkEl.href = "https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;700&display=swap";
|
linkEl.href = "https://fonts.googleapis.com/css2?family=Inter:wght@100;300;400;500;700&display=swap";
|
||||||
linkEl.rel = "stylesheet";
|
linkEl.rel = "stylesheet";
|
||||||
|
Loading…
Reference in New Issue
Block a user