toolkit for mdBook [mirror of my GitHub repo] docs.tonywu.dev/mdbookkit/
permalinks rust-analyzer mdbook
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: fix broken fragment links

Tony Wu cb52109c 6956ca15

+69 -16
+1 -1
TODO.md
··· 22 22 - [x] describe 23 23 24 24 - [x] postprocess 25 - - [ ] ~~anchors~~ 25 + - [x] anchors 26 26 - [x] external links 27 27 - [x] socials 28 28
+1 -1
crates/mdbook-permalinks/Cargo.toml
··· 10 10 repository.workspace = true 11 11 12 12 categories = ["text-processing", "development-tools", "command-line-utilities"] 13 - description = "Generate permalinks using paths in mdBook" 13 + description = "Generate permalinks in mdBook using paths" 14 14 documentation = "https://tonywu6.github.io/mdbookkit/permalinks" 15 15 keywords = ["mdbook", "markdown", "documentation", "git", "permalink"] 16 16 readme = "README.md"
+3 -1
docs/bin/main.rs
··· 4 4 5 5 use tracing::info_span; 6 6 7 - use mdbookkit::logging::Logging; 7 + use mdbookkit::{emit_error, error::ExitProcess, logging::Logging}; 8 8 9 9 mod postprocess; 10 10 mod preprocess; ··· 20 20 command: Some(Preprocess::Supports { .. }), 21 21 } => Ok(()), 22 22 } 23 + .exit(emit_error!()); 24 + Ok(()) 23 25 } 24 26 25 27 #[derive(clap::Parser, Debug, Clone)]
+55 -12
docs/bin/postprocess.rs
··· 1 - use std::{collections::HashMap, fmt::Write, path::PathBuf}; 1 + use std::{ 2 + collections::{HashMap, HashSet}, 3 + fmt::Write, 4 + path::PathBuf, 5 + }; 2 6 3 7 use anyhow::{Context, Result}; 4 8 use glob::glob; ··· 6 10 HtmlRewriter, RewriteStrSettings, Settings, element, html_content::ContentType, rewrite_str, 7 11 text, 8 12 }; 9 - use mdbookkit::url::UrlFromPath; 10 13 use minijinja::Environment; 11 14 use serde::Deserialize; 12 15 use serde_json::json; 13 16 use tap::{Pipe, Tap}; 14 - use tracing::{debug, info, info_span}; 17 + use tracing::{debug, error, info, info_span}; 15 18 use url::Url; 19 + 20 + use mdbookkit::{error::OnWarning, url::UrlFromPath}; 16 21 17 22 pub fn run(root_dir: PathBuf) -> Result<()> { 18 23 let jinja = ··· 71 76 72 77 debug!("{metadata:#?}"); 73 78 79 + let mut fragments: HashSet<Url> = HashSet::new(); 80 + let mut book_links: HashMap<Url, HashSet<Url>> = HashMap::new(); 81 + 74 82 for path in glob(out_dir.join("**/*.html")?.path())? { 75 - let url = path?.to_file_url(); 76 - let html = std::fs::read_to_string(url.path())?; 83 + let file_url = path?.to_file_url(); 84 + let html = std::fs::read_to_string(file_url.path())?; 77 85 78 86 let _span = info_span!("html").entered(); 79 87 80 - info!(%url); 88 + info!(%file_url); 81 89 82 90 let (og_title, og_description) = { 83 91 let mut title = String::new(); ··· 93 101 description.push_str(text.as_str()); 94 102 Ok(()) 95 103 }), 104 + element!("[id]", |elem| { 105 + if let Some(id) = elem.get_attribute("id") { 106 + let url = file_url.clone().tap_mut(|u| u.set_fragment(Some(&id))); 107 + fragments.insert(url); 108 + } 109 + Ok(()) 110 + }), 96 111 ], 97 112 ..Default::default() 98 113 } ··· 104 119 }; 105 120 106 121 let pathname = out_dir 107 - .make_relative(&url) 122 + .make_relative(&file_url) 108 123 .context("Failed to get page pathname")? 109 124 .replace("index.html", "") 110 125 .replace(".html", "") ··· 159 174 }), 160 175 element!(r#"img[src]"#, |elem| { 161 176 let src = elem.get_attribute("src").unwrap(); 162 - let src = url.join(&src)?; 177 + let src = file_url.join(&src)?; 163 178 let src = match src.scheme() { 164 179 "file" => src, 165 180 _ => return Ok(()), ··· 199 214 element!(r#"a"#, |elem| { 200 215 let Some(href) = elem 201 216 .get_attribute("href") 202 - .and_then(|u| u.parse::<Url>().ok()) 217 + .and_then(|href| file_url.join(&href).ok()) 203 218 else { 204 219 return Ok(()); 205 220 }; 206 - if href.origin() != book_url.origin() { 221 + if href.scheme() == "file" && href.fragment().is_some() { 222 + if let Some(set) = book_links.get_mut(&file_url) { 223 + set.insert(href); 224 + } else { 225 + let mut set = HashSet::default(); 226 + set.insert(href); 227 + book_links.insert(file_url.clone(), set); 228 + } 229 + } else if href.origin() != book_url.origin() { 207 230 elem.set_attribute("target", "_blank").unwrap(); 208 231 elem.set_attribute("rel", "noreferrer").unwrap(); 209 232 } ··· 215 238 } 216 239 .pipe(|settings| rewrite_str(&html, settings))?; 217 240 218 - std::fs::write(url.path(), html)?; 241 + std::fs::write(file_url.path(), html)?; 242 + } 243 + 244 + for (file_url, links) in book_links { 245 + for mut link in links { 246 + if !fragments.contains(&link) 247 + && let Some(id) = link.fragment() 248 + { 249 + let id = format!("#{id}"); 250 + link.set_fragment(None); 251 + let src = out_dir 252 + .make_relative(&file_url) 253 + .unwrap() 254 + .replace(".html", ".md"); 255 + let dst = out_dir 256 + .make_relative(&link) 257 + .unwrap() 258 + .replace(".html", ".md"); 259 + error!("{src} references non-existent {id:?} in {dst}"); 260 + } 261 + } 219 262 } 220 263 221 - Ok(()) 264 + OnWarning::FailInCi.check() 222 265 } 223 266 224 267 #[derive(Deserialize, Debug)]
+4
docs/src/permalinks/continuous-integration.md
··· 26 26 fetch-depth: 0 # https://github.com/actions/checkout/issues/1471#issuecomment-1771231294 27 27 ``` 28 28 29 + ## Logging 30 + 31 + See [Logging](logging.md). 32 + 29 33 ## Error handling 30 34 31 35 {{#include ../snippets/ci/error-handling.md}}
+4
docs/src/rustdoc-links/continuous-integration.md
··· 26 26 > rust-analyzer from rustup follows Rust's release schedule, which may lag behind the 27 27 > version bundled with the VS Code extension. 28 28 29 + ## Logging 30 + 31 + See [Logging](logging.md). 32 + 29 33 ## Error handling 30 34 31 35 {{#include ../snippets/ci/error-handling.md}}
+1 -1
docs/src/rustdoc-links/index.md
··· 48 48 - [Supported syntax](supported-syntax.md): Full list of link syntax with examples. Know 49 49 how to link to additional items such as 50 50 [functions, macros](supported-syntax.md#functions-and-macros), and 51 - [implementors](supported-syntax.md#implementors-and-fully-qualified-syntax). 51 + [implementors](supported-syntax.md#fully-qualified-paths). 52 52 53 53 - [Name resolution](name-resolution.md): Understand how the preprocessor finds Rust 54 54 items, including