use std::{collections::HashMap, path::Path}; use log::trace; use pandoc_ast::MutVisitor; // If a link points to `./a/b/c.ext`, and a file in the output directory `pdbook/./a/b/c.html` // exists, rewrite that link. pub struct RelativizeUrls<'a> { pub config: &'a crate::config::Config, pub extension: &'a str, pub build_dir: &'a Path, pub source_dir: &'a Path, } impl<'a> pandoc_ast::MutVisitor for RelativizeUrls<'a> { fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) { let link = match inline { pandoc_ast::Inline::Link(_, _, target) => &mut target.0, _ => return, }; if link.starts_with('#') || link.contains("://") { return; } let link_path = self.source_dir.join(&link); if link_path.is_absolute() { return; } let mut output_path = self.build_dir.join(&link_path); if !output_path.set_extension(self.extension) { return; } trace!("Checking output_path: {:?}", output_path); // TODO: warn when referencing a "markdown or other" file not specified in the summary if output_path.exists() { // TODO: relativize from URL root trace!("Relativizing link '{}'", link_path.display()); *link = Path::new(link) .with_extension(&self.extension) .to_str() .expect("Path constructed from UTF-8 valid strings in not UTF-8 valid") .to_string(); trace!("-> into '{}'", link); } } } // Applied just to the summary pub struct RelativizeSummary { level: usize, } impl pandoc_ast::MutVisitor for RelativizeSummary { fn walk_inline(&mut self, inline: &mut pandoc_ast::Inline) { if self.level == 0 { return; } let link = match inline { pandoc_ast::Inline::Link(_, _, target) => &mut target.0, _ => return, }; // Assume link is to a managed file for _ in 0..self.level { link.insert_str(0, "../"); } } } pub fn relativize_summary(summary: &pandoc_ast::Pandoc, level: usize) -> pandoc_ast::Pandoc { use std::sync::RwLock; lazy_static::lazy_static! { static ref CACHE: RwLock> = RwLock::new(HashMap::new()); } CACHE .write() .expect("Relativized summary cache poison") .entry(level) .or_insert_with(|| { let mut summary = summary.clone(); RelativizeSummary { level }.walk_pandoc(&mut summary); summary }) .clone() } pub struct InsertSummary<'a> { pub summary: &'a pandoc_ast::Pandoc, pub level: usize, } impl<'a> pandoc_ast::MutVisitor for InsertSummary<'a> { fn walk_pandoc(&mut self, pandoc: &mut pandoc_ast::Pandoc) { let summary = relativize_summary(self.summary, self.level); pandoc.blocks.insert( 0, pandoc_ast::Block::Div( (String::new(), vec!["summary".to_string()], vec![]), summary.blocks, ), ); } }