navigation maps for navigating across pages

This commit is contained in:
りき萌 2023-08-27 14:50:46 +02:00
parent 28a39fc883
commit 09ff8a742e
15 changed files with 382 additions and 71 deletions

View file

@ -24,5 +24,6 @@ tower-livereload = "0.8.0"
walkdir = "2.3.3"
ulid = "1.0.0"
rand = "0.8.5"
serde_json = "1.0.105"

View file

@ -1,6 +1,7 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
time::Instant,
};
use anyhow::{bail, Context};
@ -18,7 +19,9 @@ use tower_livereload::LiveReloadLayer;
use walkdir::WalkDir;
use crate::{
cli::parse::parse_tree_with_diagnostics, html::tree::branches_to_html, tree::SemaRoots,
cli::parse::parse_tree_with_diagnostics,
html::{navmap::build_navigation_map, tree::branches_to_html},
tree::SemaRoots,
};
use crate::state::{FileId, Treehouse};
@ -103,6 +106,13 @@ impl Generator {
&dirs.template_dir.join("tree.hbs"),
)?;
struct ParsedTree {
tree_path: String,
file_id: FileId,
target_path: PathBuf,
}
let mut parsed_trees = vec![];
for path in &self.tree_files {
let utf8_filename = path.to_string_lossy();
@ -127,42 +137,60 @@ impl Generator {
continue;
}
};
let file_id = treehouse.add_file(
utf8_filename.into_owned(),
Some(tree_path.with_extension("").to_string_lossy().into_owned()),
source,
);
let tree_path = tree_path.with_extension("").to_string_lossy().into_owned();
let file_id =
treehouse.add_file(utf8_filename.into_owned(), Some(tree_path.clone()), source);
if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) {
let roots = SemaRoots::from_roots(&mut treehouse, file_id, roots);
let mut tree = String::new();
branches_to_html(&mut tree, &mut treehouse, file_id, &roots.branches);
let template_data = TemplateData { tree };
let templated_html = match handlebars.render("tree", &template_data) {
Ok(html) => html,
Err(error) => {
Self::wrangle_handlebars_error_into_diagnostic(
&mut treehouse,
tree_template,
error.line_no,
error.column_no,
error.desc,
)?;
continue;
}
};
std::fs::create_dir_all(
target_path
.parent()
.expect("there should be a parent directory to generate files into"),
)?;
std::fs::write(target_path, templated_html)?;
treehouse.roots.insert(tree_path.clone(), roots);
parsed_trees.push(ParsedTree {
tree_path,
file_id,
target_path,
});
}
}
for parsed_tree in parsed_trees {
let mut tree = String::new();
// Temporarily steal the tree out of the treehouse.
let roots = treehouse
.roots
.remove(&parsed_tree.tree_path)
.expect("tree should have been added to the treehouse");
branches_to_html(
&mut tree,
&mut treehouse,
parsed_tree.file_id,
&roots.branches,
);
treehouse.roots.insert(parsed_tree.tree_path, roots);
let template_data = TemplateData { tree };
let templated_html = match handlebars.render("tree", &template_data) {
Ok(html) => html,
Err(error) => {
Self::wrangle_handlebars_error_into_diagnostic(
&mut treehouse,
tree_template,
error.line_no,
error.column_no,
error.desc,
)?;
continue;
}
};
std::fs::create_dir_all(
parsed_tree
.target_path
.parent()
.expect("there should be a parent directory to generate files into"),
)?;
std::fs::write(parsed_tree.target_path, templated_html)?;
}
Ok(treehouse)
}
}
@ -181,6 +209,8 @@ pub struct TemplateData {
}
pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> {
let start = Instant::now();
info!("cleaning target directory");
let _ = std::fs::remove_dir_all(dirs.target_dir);
std::fs::create_dir_all(dirs.target_dir)?;
@ -193,8 +223,18 @@ pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> {
generator.add_directory_rec(dirs.content_dir)?;
let treehouse = generator.generate_all_files(dirs)?;
info!("generating navigation map");
let navigation_map = build_navigation_map(&treehouse, "index");
std::fs::write(
dirs.target_dir.join("navmap.js"),
navigation_map.to_javascript(),
)?;
treehouse.report_diagnostics()?;
let duration = start.elapsed();
info!("generation done in {duration:?}");
Ok(())
}

View file

@ -1,6 +1,7 @@
use std::fmt::{self, Display, Write};
mod markdown;
pub mod navmap;
pub mod tree;
pub struct EscapeAttribute<'a>(&'a str);

View file

@ -0,0 +1,82 @@
use std::collections::HashMap;
use serde::Serialize;
use crate::{
state::Treehouse,
tree::{attributes::Content, SemaBranchId},
};
#[derive(Debug, Clone, Default, Serialize)]
pub struct NavigationMap {
/// Tells you which pages need to be opened to get to the key.
pub paths: HashMap<String, Vec<String>>,
}
impl NavigationMap {
pub fn to_javascript(&self) -> String {
format!(
"export const navigationMap = {};",
serde_json::to_string(&self.paths)
.expect("serialization of the navigation map should not fail")
)
}
}
#[derive(Debug, Clone, Default)]
pub struct NavigationMapBuilder {
stack: Vec<String>,
navigation_map: NavigationMap,
}
impl NavigationMapBuilder {
pub fn enter_tree(&mut self, tree: String) {
self.stack.push(tree.clone());
self.navigation_map.paths.insert(tree, self.stack.clone());
}
pub fn exit_tree(&mut self) {
self.stack.pop();
}
pub fn finish(self) -> NavigationMap {
self.navigation_map
}
}
pub fn build_navigation_map(treehouse: &Treehouse, root_tree_path: &str) -> NavigationMap {
let mut builder = NavigationMapBuilder::default();
fn rec_branch(
treehouse: &Treehouse,
builder: &mut NavigationMapBuilder,
branch_id: SemaBranchId,
) {
let branch = treehouse.tree.branch(branch_id);
if let Content::Link(linked) = &branch.attributes.content {
rec_tree(treehouse, builder, linked);
} else {
for &child_id in &branch.children {
rec_branch(treehouse, builder, child_id);
}
}
}
fn rec_tree(treehouse: &Treehouse, builder: &mut NavigationMapBuilder, tree_path: &str) {
if let Some(roots) = treehouse.roots.get(tree_path) {
// Pages can link to each other causing infinite recursion, so we need to handle that
// case by skipping pages that already have been analyzed.
if !builder.navigation_map.paths.contains_key(tree_path) {
builder.enter_tree(tree_path.to_owned());
for &branch_id in &roots.branches {
rec_branch(treehouse, builder, branch_id);
}
builder.exit_tree();
}
}
}
rec_tree(treehouse, &mut builder, root_tree_path);
builder.finish()
}

View file

@ -17,7 +17,6 @@ pub fn branch_to_html(
file_id: FileId,
branch_id: SemaBranchId,
) {
// Reborrow because the closure requires unique access (it adds a new diagnostic.)
let source = treehouse.source(file_id);
let branch = treehouse.tree.branch(branch_id);

View file

@ -8,7 +8,7 @@ use codespan_reporting::{
};
use ulid::Ulid;
use crate::tree::{SemaBranchId, SemaTree};
use crate::tree::{SemaBranchId, SemaRoots, SemaTree};
pub type Files = SimpleFiles<String, String>;
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
@ -20,6 +20,7 @@ pub struct Treehouse {
pub tree: SemaTree,
pub branches_by_named_id: HashMap<String, SemaBranchId>,
pub roots: HashMap<String, SemaRoots>,
// Bit of a hack because I don't wanna write my own `Files`.
tree_paths: Vec<Option<String>>,
@ -42,6 +43,7 @@ impl Treehouse {
tree: SemaTree::default(),
branches_by_named_id: HashMap::new(),
roots: HashMap::new(),
tree_paths: vec![],