navigation maps for navigating across pages
This commit is contained in:
parent
28a39fc883
commit
09ff8a742e
15 changed files with 382 additions and 71 deletions
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::fmt::{self, Display, Write};
|
||||
|
||||
mod markdown;
|
||||
pub mod navmap;
|
||||
pub mod tree;
|
||||
|
||||
pub struct EscapeAttribute<'a>(&'a str);
|
||||
|
|
82
crates/treehouse/src/html/navmap.rs
Normal file
82
crates/treehouse/src/html/navmap.rs
Normal 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()
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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![],
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue