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.

refactor(permalinks): update tests

Tony Wu d4365fd8 30fab861

+388 -418
+1 -1
.vscode/settings.json
··· 7 7 "[markdown]": { 8 8 "editor.defaultFormatter": "esbenp.prettier-vscode" 9 9 }, 10 - "cSpell.words": ["cmark", "mdbookkit", "rstest", "unmark"], 10 + "cSpell.words": ["cmark", "mdbookkit", "Minato", "rstest", "unmark"], 11 11 "deno.enable": true, 12 12 "editor.formatOnSave": true, 13 13 "files.associations": {
+36 -14
crates/mdbook-permalinks/src/diagnostic.rs
··· 1 1 use std::{borrow::Cow, collections::BTreeMap}; 2 2 3 3 use miette::LabeledSpan; 4 - use tap::TapOptional; 4 + use tap::{Pipe, TapOptional}; 5 5 use url::Url; 6 6 7 7 use mdbookkit::diagnostics::{Diagnostics, Issue, IssueItem, ReportBuilder}; ··· 75 75 } 76 76 77 77 fn label(&self) -> LabeledSpan { 78 - let link = self.shorten(&self.link.link); 78 + let link = &self.link.link; 79 + let path = self.shorten(&self.link.link); 79 80 let status = &self.link.status; 80 81 let label = match status { 81 82 LinkStatus::Ignored => None, 82 - LinkStatus::Published => Some(format!("file: {link}")), 83 - LinkStatus::Permalink => Some(format!("link: {link}")), 84 - LinkStatus::Rewritten => Some(format!("file: {link}\nlink: {}", self.link.link)), 85 - LinkStatus::PathNotCheckedIn => Some(format!("{status}: {link}")), 86 - LinkStatus::NoSuchPath(candidates) => Some(format!("{status}: {link}")), 87 - LinkStatus::Error(..) => Some(format!("{status}")), 83 + LinkStatus::Published => Some(path.into()), 84 + LinkStatus::Permalink => Some(path.into()), 85 + LinkStatus::Rewritten => Some(format!("path: {path}\nlink: {:?}", &**link)), 86 + LinkStatus::PathNotCheckedIn => Some(format!("resolves to {path}")), 87 + LinkStatus::NoSuchPath(candidates) => candidates 88 + .iter() 89 + .filter_map(|url| self.shorten_url(url)) 90 + .fold(String::new(), |mut err, line| { 91 + err.push_str(&line); 92 + err.push('\n'); 93 + err 94 + }) 95 + .trim() 96 + .to_owned() 97 + .pipe(Some), 98 + LinkStatus::Error(..) => Some(status.to_string()), 88 99 }; 89 100 LabeledSpan::new_with_span(label, self.link.span.clone()) 90 101 } ··· 103 114 url.set_fragment(None); 104 115 }); 105 116 if let Some(url) = url { 106 - if let Some(rel) = self.root.make_relative(&url) { 107 - if rel.starts_with("../") { 108 - Cow::Owned(url.to_string()) 109 - } else { 110 - Cow::Owned(rel) 111 - } 117 + if let Some(shortened) = self.shorten_url(&url) { 118 + Cow::Owned(shortened) 112 119 } else { 113 120 Cow::Borrowed(path) 114 121 } 115 122 } else { 116 123 Cow::Borrowed(path) 124 + } 125 + } 126 + 127 + fn shorten_url(&self, url: &Url) -> Option<String> { 128 + if let Some(rel) = self.root.make_relative(url) 129 + && !rel.starts_with("../") 130 + { 131 + Some(rel) 132 + } else if url.scheme() == "file" { 133 + match url.to_file_path() { 134 + Ok(path) => Some(path.display().to_string()), 135 + Err(()) => Some(url.path().to_owned()), 136 + } 137 + } else { 138 + None 117 139 } 118 140 } 119 141 }
+7 -7
crates/mdbook-permalinks/src/link.rs
··· 6 6 #[derive(Debug, Default, Clone, thiserror::Error)] 7 7 pub enum LinkStatus { 8 8 #[default] 9 - #[error("link is ignored as it is not supported")] 9 + #[error("links ignored")] 10 10 Ignored, 11 11 12 12 // "published" as in published with the book 13 - #[error("link to book page or file")] 13 + #[error("linking to book page or file")] 14 14 Published, 15 15 16 - #[error("link to book page or file rewritten as path")] 16 + #[error("linking to book page or file, rewritten as paths")] 17 17 Rewritten, 18 - #[error("link converted to permalink")] 18 + #[error("links converted to permalinks")] 19 19 Permalink, 20 20 21 - #[error("path to a file outside source control")] 21 + #[error("paths outside of repository")] 22 22 PathNotCheckedIn, 23 - #[error("file does not exist at path")] 23 + #[error("paths inaccessible")] 24 24 NoSuchPath(Vec<Url>), 25 25 26 - #[error("error generating a link: {0}")] 26 + #[error("error encountered: {0}")] 27 27 Error(String), 28 28 } 29 29
+9 -11
crates/mdbook-permalinks/src/main.rs
··· 207 207 impl Resolver<'_, '_> { 208 208 fn resolve(self) { 209 209 if let Some(book) = &self.env.config.book_url { 210 - if let Some(path) = book.0.make_relative(&self.file_url) { 210 + if let Some(path) = book.0.make_relative(&self.file_url) 211 + && !path.starts_with("../") 212 + { 211 213 self.resolve_book(path) 212 214 } else { 213 215 self.resolve_file() ··· 253 255 }; 254 256 255 257 if relative_to_repo.starts_with("../") { 256 - // `path` could also be a symlink to a file outside source control somehow 257 - // in which case it would NOT be marked as LinkStatus::External; 258 258 link.status = LinkStatus::PathNotCheckedIn; 259 259 return; 260 260 } ··· 265 265 .tap_err(log_debug!()); 266 266 267 267 if !matches!(exists, Ok(true)) { 268 - link.status = LinkStatus::NoSuchPath(vec![]); 268 + link.status = LinkStatus::NoSuchPath(vec![file_url]); 269 269 return; 270 270 } 271 271 ··· 320 320 321 321 let path = { 322 322 let mut path = path; 323 - if let Some(idx) = path.rfind('#') { 323 + if let Some(idx) = path.find('#') { 324 324 path.truncate(idx) 325 325 }; 326 - if let Some(idx) = path.rfind('?') { 326 + if let Some(idx) = path.find('?') { 327 327 path.truncate(idx) 328 328 }; 329 329 path.strip_suffix(".html") ··· 358 358 ] 359 359 }; 360 360 361 - let mut not_found = None; 361 + let mut not_found = vec![]; 362 362 363 363 for file in try_files { 364 364 let Ok(file) = self.env.book_src.join(file).tap_err(log_debug!()) else { ··· 388 388 return; 389 389 } 390 390 391 - not_found.get_or_insert(file); 391 + not_found.push(file); 392 392 } 393 393 394 - if let Some(file) = not_found { 395 - link.status = LinkStatus::NoSuchPath(vec![]); 396 - } 394 + link.status = LinkStatus::NoSuchPath(not_found); 397 395 } 398 396 } 399 397
+8 -9
crates/mdbook-permalinks/src/tests.rs
··· 3 3 use anyhow::Result; 4 4 use log::LevelFilter; 5 5 use rstest::*; 6 - use tap::Pipe; 7 6 use url::Url; 8 7 9 8 use mdbookkit::{ ··· 12 11 testing::{CARGO_WORKSPACE_DIR, TestDocument, setup_logging}, 13 12 }; 14 13 15 - use crate::{ 16 - Config, Environment, VersionControl, 17 - link::LinkStatus, 18 - page::Pages, 19 - vcs::{GitHubPermalink, Permalink}, 20 - }; 14 + use crate::{Config, Environment, VersionControl, link::LinkStatus, page::Pages, vcs::Permalink}; 21 15 22 16 struct Fixture { 23 17 env: Environment, ··· 31 25 let env = Environment { 32 26 vcs: VersionControl { 33 27 root: CARGO_WORKSPACE_DIR.clone(), 34 - link: GitHubPermalink::new("lorem", "ipsum", "dolor").pipe(Permalink::GitHub), 28 + link: Permalink { 29 + template: "https://example.org/git/{tree}/{ref}/{path}" 30 + .parse() 31 + .unwrap(), 32 + reference: "v0.0".into(), 33 + }, 35 34 }, 36 35 book_src: CARGO_WORKSPACE_DIR 37 36 .join("crates/")? ··· 77 76 }; 78 77 } 79 78 80 - test_output!["tests/links.md",]; 79 + test_output!["tests/paths.md", "tests/urls.md", "tests/suffix.md",]; 81 80 82 81 macro_rules! matcher { 83 82 ( $pattern:pat ) => {
+1 -1
crates/mdbook-permalinks/src/tests/LICENSE
··· 1 - Minato City, Tokyo, Japan.jpg, David Kernan, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons, <https://commons.wikimedia.org/wiki/File:Minato_City,_Tokyo,_Japan.jpg> 1 + Minato City,_Tokyo,_Japan.jpg, David Kernan, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons, <https://commons.wikimedia.org/wiki/File:Minato_City,_Tokyo,_Japan.jpg>
crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg

This is a binary file and will not be displayed.

-66
crates/mdbook-permalinks/src/tests/links.md
··· 1 - # relative paths 2 - 3 - [Cargo.toml](../../../../Cargo.toml) 4 - 5 - ![error reporting](../../../../docs/src/permalinks/media/error-reporting.png) 6 - 7 - # absolute paths 8 - 9 - [LICENSE-APACHE.md](/LICENSE-APACHE.md) 10 - 11 - [links.md](/crates/mdbook-permalinks/src/tests/links.md#absolute-paths) 12 - 13 - # fragments 14 - 15 - [book.toml](/docs/book.toml#L40-44) 16 - 17 - # book files 18 - 19 - [Links](./links.md) 20 - 21 - [main.rs](../main.rs) 22 - 23 - ![selfie](Macaca_nigra_self-portrait_large.jpg) 24 - 25 - # file not found 26 - 27 - [Cargo.lock](../../Cargo.lock) 28 - 29 - [`//LICENSE-MIT.md`](//LICENSE-MIT.md) 30 - 31 - ![shinjuku.jpg](shinjuku.jpg) 32 - 33 - # canonical urls to book 34 - 35 - found: <https://example.org/book/tests/links> 36 - 37 - found: <https://example.org/book/tests/links.html> 38 - 39 - not found: <https://example.org/book/404> 40 - 41 - ignored: <https://example.com/book/links> 42 - 43 - trailing slash, found: <https://example.org/book/tests/trailing-slash/> 44 - 45 - trailing slash, found: <https://example.org/book/tests/trailing-slash> 46 - 47 - trailing slash, not found: <https://example.org/book/tests/links/> 48 - 49 - # canonical urls to HEAD 50 - 51 - [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 52 - 53 - [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 54 - 55 - [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 56 - 57 - # image-in-link 58 - 59 - [![crates.io](https://img.shields.io/crates/v/mdbookkit?style=flat-square)](https://crates.io/crates/mdbookkit) 60 - 61 - [![selfie](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg)](https://commons.wikimedia.org/wiki/File:Macaca_nigra_self-portrait_large.jpg) 62 - 63 - [![selfie](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg) <br> Self-portrait of a female Macaca nigra in North Sulawesi (2011)](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg) 64 - 65 - [Foo]: https://example.org 66 - [Bar]: https://example.org
+35
crates/mdbook-permalinks/src/tests/paths.md
··· 1 + # relative paths 2 + 3 + [](../../../../Cargo.toml) 4 + 5 + [](../../../../docs/src/permalinks/index.md) 6 + 7 + # absolute paths 8 + 9 + [](/LICENSE-APACHE.md) 10 + 11 + [](/crates/mdbook-permalinks/Cargo.toml) 12 + 13 + # book files 14 + 15 + [](paths.md) 16 + 17 + [](../main.rs) 18 + 19 + # images 20 + 21 + ![](Minato_City,_Tokyo,_Japan.jpg) 22 + 23 + ![](/docs/src/media/banner.webp) 24 + 25 + [![Banner image][banner-image]][banner-image] 26 + 27 + [banner-image]: /docs/src/media/banner.webp 28 + 29 + # not found 30 + 31 + [](../../Cargo.lock) 32 + 33 + [](//LICENSE-MIT.md) 34 + 35 + ![](shinjuku.jpg)
+7 -27
crates/mdbook-permalinks/src/tests/snaps/_stderr.ignored.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - info: link is ignored as it is not supported 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:41:10] 8 - 9 - │ ignored: <https://example.com/book/links> 10 - · ──────────────────────────────── 11 - 12 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 13 - 14 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 15 - 16 - │ trailing slash, not found: <https://example.org/book/tests/links/> 17 - 18 - │ # canonical urls to HEAD 19 - 20 - │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 6 + info: links ignored 7 + ╭─[crates/mdbook-permalinks/src/tests/urls.md:31:1] 21 8 22 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 9 + │ [](https://example.org/boo) 10 + · ─────────────────────────── 23 11 24 - │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 25 - 26 - │ # image-in-link 27 - 28 - │ [![crates.io](https://img.shields.io/crates/v/mdbookkit?style=flat-square)](https://crates.io/crates/mdbookkit) 29 - · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── 30 - 31 - │ [![selfie](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg)](https://commons.wikimedia.org/wiki/File:Macaca_nigra_self-portrait_large.jpg) 32 - · ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 33 - 12 + │ [](https://example.org/git) 13 + · ─────────────────────────── 34 14 ╰────
+27 -37
crates/mdbook-permalinks/src/tests/snaps/_stderr.no-such-path.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - warning: file does not exist at path 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:27:1] 8 - 9 - │ [Cargo.lock](../../Cargo.lock) 10 - · ───────────────┬────────────── 11 - · ╰── file does not exist at path: crates/mdbook-permalinks/Cargo.lock 12 - 13 - │ [`//LICENSE-MIT.md`](//LICENSE-MIT.md) 14 - 15 - │ ![shinjuku.jpg](shinjuku.jpg) 16 - · ──────────────┬────────────── 17 - · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/shinjuku.jpg 18 - 19 - │ # canonical urls to book 20 - 21 - │ found: <https://example.org/book/tests/links> 22 - 23 - │ found: <https://example.org/book/tests/links.html> 24 - 25 - │ not found: <https://example.org/book/404> 26 - · ───────────────┬────────────── 27 - · ╰── file does not exist at path: crates/mdbook-permalinks/src/404.md 28 - 29 - │ ignored: <https://example.com/book/links> 30 - 31 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 6 + warning: paths inaccessible 7 + ╭─[crates/mdbook-permalinks/src/tests/paths.md:31:1] 32 8 33 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 9 + │ [](../../Cargo.lock) 10 + · ──────────┬───────── 11 + · ╰── crates/mdbook-permalinks/Cargo.lock 34 12 35 - │ trailing slash, not found: <https://example.org/book/tests/links/> 36 - · ───────────────────┬─────────────────── 37 - · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/links/index.md 13 + │ [](//LICENSE-MIT.md) 38 14 39 - │ # canonical urls to HEAD 15 + │ ![](shinjuku.jpg) 16 + · ────────┬──────── 17 + · ╰── crates/mdbook-permalinks/src/tests/shinjuku.jpg 18 + ╰──── 19 + 20 + warning: paths inaccessible 21 + ╭─[crates/mdbook-permalinks/src/tests/urls.md:23:1] 40 22 41 - │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 23 + │ [](https://example.org/book/tests/urls/) 24 + · ────────────────────┬─────────────────── 25 + · ╰─┤ crates/mdbook-permalinks/src/tests/urls/index.md 26 + · │ crates/mdbook-permalinks/src/tests/urls/README.md 42 27 43 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 28 + │ [](https://example.org/book/tests/url) 29 + · ───────────────────┬────────────────── 30 + · ╰─┤ crates/mdbook-permalinks/src/tests/url.md 31 + · │ crates/mdbook-permalinks/src/tests/url/index.md 32 + · │ crates/mdbook-permalinks/src/tests/url/README.md 33 + · │ crates/mdbook-permalinks/src/tests/url 44 34 45 - │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 46 - · ────────────────────────────────────────────────────┬──────────────────────────────────────────────────── 47 - · ╰── file does not exist at path: crates/mdbook-permalinks/src/tests/shinjuku.jpg 35 + │ [](https://example.org/git/tree/HEAD/LICENSE-GPL.md) 36 + · ──────────────────────────┬───────────────────────── 37 + · ╰── LICENSE-GPL.md 48 38 49 39 ╰────
+6 -6
crates/mdbook-permalinks/src/tests/snaps/_stderr.not-checked-in.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - warning: path to a file outside source control 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:29:1] 6 + warning: paths outside of repository 7 + ╭─[crates/mdbook-permalinks/src/tests/paths.md:33:1] 8 8 9 - │ [`//LICENSE-MIT.md`](//LICENSE-MIT.md) 10 - · ───────────────────┬────────────────── 11 - · ╰── path to a file outside source control: file:///LICENSE-MIT.md 9 + │ [](//LICENSE-MIT.md) 10 + · ──────────┬───────── 11 + · ╰── resolves to /LICENSE-MIT.md 12 12 13 13 ╰────
+54 -20
crates/mdbook-permalinks/src/tests/snaps/_stderr.permalink.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - info: link converted to permalink 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:3:1] 6 + info: links converted to permalinks 7 + ╭─[crates/mdbook-permalinks/src/tests/paths.md:3:1] 8 8 9 - │ [Cargo.toml](../../../../Cargo.toml) 10 - · ──────────────────┬───────────────── 11 - · ╰── link: https://github.com/lorem/ipsum/tree/dolor/Cargo.toml 9 + │ [](../../../../Cargo.toml) 10 + · ─────────────┬──────────── 11 + · ╰── https://example.org/git/tree/v0.0/Cargo.toml 12 12 13 - │ ![error reporting](../../../../docs/src/permalinks/media/error-reporting.png) 14 - · ──────────────────────────────────────┬────────────────────────────────────── 15 - · ╰── link: https://github.com/lorem/ipsum/tree/dolor/docs/src/permalinks/media/error-reporting.png 13 + │ [](../../../../docs/src/permalinks/index.md) 14 + · ──────────────────────┬───────────────────── 15 + · ╰── https://example.org/git/tree/v0.0/docs/src/permalinks/index.md 16 16 17 17 │ # absolute paths 18 18 19 - │ [LICENSE-APACHE.md](/LICENSE-APACHE.md) 20 - · ───────────────────┬─────────────────── 21 - · ╰── link: https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md 19 + │ [](/LICENSE-APACHE.md) 20 + · ───────────┬────────── 21 + · ╰── https://example.org/git/tree/v0.0/LICENSE-APACHE.md 22 + 23 + │ [](/crates/mdbook-permalinks/Cargo.toml) 24 + · ────────────────────┬─────────────────── 25 + · ╰── https://example.org/git/tree/v0.0/crates/mdbook-permalinks/Cargo.toml 22 26 23 27 ╰──── 24 - ╭─[crates/mdbook-permalinks/src/tests/links.md:15:1] 28 + ╭─[crates/mdbook-permalinks/src/tests/paths.md:23:1] 25 29 26 - │ [book.toml](/docs/book.toml#L40-44) 27 - · ─────────────────┬───────────────── 28 - · ╰── link: https://github.com/lorem/ipsum/tree/dolor/docs/book.toml#L40-44 30 + │ ![](/docs/src/media/banner.webp) 31 + · ────────────────┬─────────────── 32 + · ╰── https://example.org/git/raw/v0.0/docs/src/media/banner.webp 33 + 34 + │ [![Banner image][banner-image]][banner-image] 35 + · ──────────────────────┬──────────────────────┬ 36 + · │ ╰── https://example.org/git/raw/v0.0/docs/src/media/banner.webp 37 + · ╰── https://example.org/git/tree/v0.0/docs/src/media/banner.webp 29 38 30 39 ╰──── 31 - ╭─[crates/mdbook-permalinks/src/tests/links.md:51:1] 40 + 41 + info: links converted to permalinks 42 + ╭─[crates/mdbook-permalinks/src/tests/suffix.md:1:1] 43 + │ [](/docs/book.toml?branch=default) 44 + · ─────────────────┬──────────────── 45 + · ╰── https://example.org/git/tree/v0.0/docs/book.toml?branch=default 46 + 47 + │ [](/docs/book.toml#L40-44) 48 + · ─────────────┬──────────── 49 + · ╰── https://example.org/git/tree/v0.0/docs/book.toml#L40-44 50 + 51 + │ [](/docs/book.toml?/#/L40-44) 52 + · ──────────────┬────────────── 53 + · ╰── https://example.org/git/tree/v0.0/docs/book.toml?/#/L40-44 54 + ╰──── 55 + 56 + info: links converted to permalinks 57 + ╭─[crates/mdbook-permalinks/src/tests/urls.md:15:1] 32 58 33 - │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 34 - · ───────────────────────────────────┬─────────────────────────────────── 35 - · ╰── link: https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md 59 + │ [](https://example.org/git/tree/HEAD/LICENSE-APACHE.md) 60 + · ───────────────────────────┬─────────────────────────── 61 + · ╰── https://example.org/git/tree/v0.0/LICENSE-APACHE.md 62 + 63 + │ [](https://example.org/git/tree/HEAD/Cargo.toml#L8-9) 64 + · ──────────────────────────┬────────────────────────── 65 + · ╰── https://example.org/git/tree/v0.0/Cargo.toml#L8-9 66 + 67 + │ ![](https://example.org/git/raw/HEAD/crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg) 68 + · ───────────────────────────────────────────────────┬────────────────────────────────────────────────── 69 + · ╰── https://example.org/git/raw/v0.0/crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg 36 70 37 71 ╰────
+14 -12
crates/mdbook-permalinks/src/tests/snaps/_stderr.published.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - info: link to book page or file 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:19:1] 6 + info: linking to book page or file 7 + ╭─[crates/mdbook-permalinks/src/tests/paths.md:15:1] 8 8 9 - │ [Links](./links.md) 10 - · ─────────┬───────── 11 - · ╰── file: crates/mdbook-permalinks/src/tests/links.md 9 + │ [](paths.md) 10 + · ──────┬───── 11 + · ╰── crates/mdbook-permalinks/src/tests/paths.md 12 12 13 - │ [main.rs](../main.rs) 14 - · ──────────┬────────── 15 - · ╰── file: crates/mdbook-permalinks/src/main.rs 13 + │ [](../main.rs) 14 + · ───────┬────── 15 + · ╰── crates/mdbook-permalinks/src/main.rs 16 + 17 + │ # images 16 18 17 - │ ![selfie](Macaca_nigra_self-portrait_large.jpg) 18 - · ───────────────────────┬─────────────────────── 19 - · ╰── file: crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg 19 + │ ![](Minato_City,_Tokyo,_Japan.jpg) 20 + · ─────────────────┬──────────────── 21 + · ╰── crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg 20 22 21 23 ╰────
+23 -59
crates/mdbook-permalinks/src/tests/snaps/_stderr.rewritten.snap
··· 1 1 --- 2 2 source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 106 3 + assertion_line: 105 4 4 expression: report 5 5 --- 6 - info: link to book page or file rewritten as path 7 - ╭─[crates/mdbook-permalinks/src/tests/links.md:11:1] 8 - 9 - │ [links.md](/crates/mdbook-permalinks/src/tests/links.md#absolute-paths) 10 - · ───────────────────────────────────┬─────────────────────────────────── 11 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 12 - · │ link: #absolute-paths 13 - 14 - ╰──── 15 - ╭─[crates/mdbook-permalinks/src/tests/links.md:35:8] 16 - 17 - │ found: <https://example.org/book/tests/links> 18 - · ───────────────────┬────────────────── 19 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 20 - · │ link: 21 - 22 - │ found: <https://example.org/book/tests/links.html> 23 - · ─────────────────────┬───────────────────── 24 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 25 - · │ link: 26 - 27 - │ not found: <https://example.org/book/404> 28 - 29 - │ ignored: <https://example.com/book/links> 30 - 31 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash/> 32 - · ────────────────────────┬─────────────────────── 33 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/trailing-slash/index.md 34 - · │ link: trailing-slash/index.md 35 - 36 - │ trailing slash, found: <https://example.org/book/tests/trailing-slash> 37 - · ───────────────────────┬─────────────────────── 38 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/trailing-slash/index.md 39 - · │ link: trailing-slash/index.md 40 - 41 - │ trailing slash, not found: <https://example.org/book/tests/links/> 42 - 43 - │ # canonical urls to HEAD 44 - 45 - │ [permalink](https://github.com/lorem/ipsum/tree/HEAD/LICENSE-APACHE.md) 6 + info: linking to book page or file, rewritten as paths 7 + ╭─[crates/mdbook-permalinks/src/tests/urls.md:3:1] 46 8 47 - │ [published](https://github.com/lorem/ipsum/tree/HEAD/crates/mdbook-permalinks/src/tests/links.md) 48 - · ────────────────────────────────────────────────┬──────────────────────────────────────────────── 49 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/links.md 50 - · │ link: 9 + │ [](https://example.org/book/tests/urls) 10 + · ───────────────────┬─────────────────── 11 + · ╰─┤ path: crates/mdbook-permalinks/src/tests/urls.md 12 + · │ link: "" 51 13 52 - │ [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 14 + │ [](https://example.org/book/tests/urls.html) 15 + · ──────────────────────┬───────────────────── 16 + · ╰─┤ path: crates/mdbook-permalinks/src/tests/urls.md 17 + · │ link: "" 53 18 54 - │ # image-in-link 19 + │ [](https://example.org/book/tests/trailing-slash) 20 + · ────────────────────────┬──────────────────────── 21 + · ╰─┤ path: crates/mdbook-permalinks/src/tests/trailing-slash/index.md 22 + · │ link: "trailing-slash/index.md" 55 23 56 - │ [![crates.io](https://img.shields.io/crates/v/mdbookkit?style=flat-square)](https://crates.io/crates/mdbookkit) 57 - 58 - │ [![selfie](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg)](https://commons.wikimedia.org/wiki/File:Macaca_nigra_self-portrait_large.jpg) 59 - · ─────────────────────────────────────────┬───────────────────────────────────────── 60 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg 61 - · │ link: Macaca_nigra_self-portrait_large.jpg 24 + │ [](https://example.org/book/tests/trailing-slash/) 25 + · ─────────────────────────┬──────────────────────── 26 + · ╰─┤ path: crates/mdbook-permalinks/src/tests/trailing-slash/index.md 27 + · │ link: "trailing-slash/index.md" 62 28 63 - │ [![selfie](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg) <br> Self-portrait of a female Macaca nigra in North Sulawesi (2011)](/crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg) 64 - · ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬ 65 - · │ ╰─┤ file: crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg 66 - · │ │ link: Macaca_nigra_self-portrait_large.jpg 67 - · ╰─┤ file: crates/mdbook-permalinks/src/tests/Macaca_nigra_self-portrait_large.jpg 68 - · │ link: Macaca_nigra_self-portrait_large.jpg 29 + │ ![](https://example.org/book/tests/Minato_City,_Tokyo,_Japan.jpg) 30 + · ────────────────────────────────┬──────────────────────────────── 31 + · ╰─┤ path: crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg 32 + · │ link: "Minato_City,_Tokyo,_Japan.jpg" 69 33 70 34 ╰────
-71
crates/mdbook-permalinks/src/tests/snaps/links.snap
··· 1 - --- 2 - source: crates/mdbook-permalinks/src/tests.rs 3 - assertion_line: 62 4 - expression: output 5 - --- 6 - # relative paths 7 - 8 - [Cargo.toml](https://github.com/lorem/ipsum/tree/dolor/Cargo.toml) 9 - 10 - ![error reporting](https://github.com/lorem/ipsum/tree/dolor/docs/src/permalinks/media/error-reporting.png) 11 - 12 - # absolute paths 13 - 14 - [LICENSE-APACHE.md](https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md) 15 - 16 - [links.md](#absolute-paths) 17 - 18 - # fragments 19 - 20 - [book.toml](https://github.com/lorem/ipsum/tree/dolor/docs/book.toml#L40-44) 21 - 22 - # book files 23 - 24 - [Links](./links.md) 25 - 26 - [main.rs](../main.rs) 27 - 28 - ![selfie](Macaca_nigra_self-portrait_large.jpg) 29 - 30 - # file not found 31 - 32 - [Cargo.lock](../../Cargo.lock) 33 - 34 - [`//LICENSE-MIT.md`](//LICENSE-MIT.md) 35 - 36 - ![shinjuku.jpg](shinjuku.jpg) 37 - 38 - # canonical urls to book 39 - 40 - found: [https://example.org/book/tests/links]() 41 - 42 - found: [https://example.org/book/tests/links.html]() 43 - 44 - not found: <https://example.org/book/404> 45 - 46 - ignored: <https://example.com/book/links> 47 - 48 - trailing slash, found: [https://example.org/book/tests/trailing-slash/](trailing-slash/index.md) 49 - 50 - trailing slash, found: [https://example.org/book/tests/trailing-slash](trailing-slash/index.md) 51 - 52 - trailing slash, not found: <https://example.org/book/tests/links/> 53 - 54 - # canonical urls to HEAD 55 - 56 - [permalink](https://github.com/lorem/ipsum/tree/dolor/LICENSE-APACHE.md) 57 - 58 - [published]() 59 - 60 - [file not found](https://github.com/lorem/ipsum/raw/HEAD/crates/mdbook-permalinks/src/tests/shinjuku.jpg) 61 - 62 - # image-in-link 63 - 64 - [![crates.io](https://img.shields.io/crates/v/mdbookkit?style=flat-square)](https://crates.io/crates/mdbookkit) 65 - 66 - [![selfie](Macaca_nigra_self-portrait_large.jpg)](https://commons.wikimedia.org/wiki/File:Macaca_nigra_self-portrait_large.jpg) 67 - 68 - [![selfie](Macaca_nigra_self-portrait_large.jpg) <br> Self-portrait of a female Macaca nigra in North Sulawesi (2011)](Macaca_nigra_self-portrait_large.jpg) 69 - 70 - [Foo]: https://example.org 71 - [Bar]: https://example.org
+40
crates/mdbook-permalinks/src/tests/snaps/paths.snap
··· 1 + --- 2 + source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 61 4 + expression: output 5 + --- 6 + # relative paths 7 + 8 + [](https://example.org/git/tree/v0.0/Cargo.toml) 9 + 10 + [](https://example.org/git/tree/v0.0/docs/src/permalinks/index.md) 11 + 12 + # absolute paths 13 + 14 + [](https://example.org/git/tree/v0.0/LICENSE-APACHE.md) 15 + 16 + [](https://example.org/git/tree/v0.0/crates/mdbook-permalinks/Cargo.toml) 17 + 18 + # book files 19 + 20 + [](paths.md) 21 + 22 + [](../main.rs) 23 + 24 + # images 25 + 26 + ![](Minato_City,_Tokyo,_Japan.jpg) 27 + 28 + ![](https://example.org/git/raw/v0.0/docs/src/media/banner.webp) 29 + 30 + [![Banner image](https://example.org/git/raw/v0.0/docs/src/media/banner.webp)](https://example.org/git/tree/v0.0/docs/src/media/banner.webp) 31 + 32 + [banner-image]: /docs/src/media/banner.webp 33 + 34 + # not found 35 + 36 + [](../../Cargo.lock) 37 + 38 + [](//LICENSE-MIT.md) 39 + 40 + ![](shinjuku.jpg)
+10
crates/mdbook-permalinks/src/tests/snaps/suffix.snap
··· 1 + --- 2 + source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 61 4 + expression: output 5 + --- 6 + [](https://example.org/git/tree/v0.0/docs/book.toml?branch=default) 7 + 8 + [](https://example.org/git/tree/v0.0/docs/book.toml#L40-44) 9 + 10 + [](https://example.org/git/tree/v0.0/docs/book.toml?/#/L40-44)
+38
crates/mdbook-permalinks/src/tests/snaps/urls.snap
··· 1 + --- 2 + source: crates/mdbook-permalinks/src/tests.rs 3 + assertion_line: 61 4 + expression: output 5 + --- 6 + # URLs to book 7 + 8 + []() 9 + 10 + []() 11 + 12 + [](trailing-slash/index.md) 13 + 14 + [](trailing-slash/index.md) 15 + 16 + ![](Minato_City,_Tokyo,_Japan.jpg) 17 + 18 + # URLs to HEAD 19 + 20 + [](https://example.org/git/tree/v0.0/LICENSE-APACHE.md) 21 + 22 + [](https://example.org/git/tree/v0.0/Cargo.toml#L8-9) 23 + 24 + ![](https://example.org/git/raw/v0.0/crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg) 25 + 26 + # not found 27 + 28 + [](https://example.org/book/tests/urls/) 29 + 30 + [](https://example.org/book/tests/url) 31 + 32 + [](https://example.org/git/tree/HEAD/LICENSE-GPL.md) 33 + 34 + # not applicable 35 + 36 + [](https://example.org/boo) 37 + 38 + [](https://example.org/git)
+5
crates/mdbook-permalinks/src/tests/suffix.md
··· 1 + [](/docs/book.toml?branch=default) 2 + 3 + [](/docs/book.toml#L40-44) 4 + 5 + [](/docs/book.toml?/#/L40-44)
+33
crates/mdbook-permalinks/src/tests/urls.md
··· 1 + # URLs to book 2 + 3 + [](https://example.org/book/tests/urls) 4 + 5 + [](https://example.org/book/tests/urls.html) 6 + 7 + [](https://example.org/book/tests/trailing-slash) 8 + 9 + [](https://example.org/book/tests/trailing-slash/) 10 + 11 + ![](https://example.org/book/tests/Minato_City,_Tokyo,_Japan.jpg) 12 + 13 + # URLs to HEAD 14 + 15 + [](https://example.org/git/tree/HEAD/LICENSE-APACHE.md) 16 + 17 + [](https://example.org/git/tree/HEAD/Cargo.toml#L8-9) 18 + 19 + ![](https://example.org/git/raw/HEAD/crates/mdbook-permalinks/src/tests/Minato_City,_Tokyo,_Japan.jpg) 20 + 21 + # not found 22 + 23 + [](https://example.org/book/tests/urls/) 24 + 25 + [](https://example.org/book/tests/url) 26 + 27 + [](https://example.org/git/tree/HEAD/LICENSE-GPL.md) 28 + 29 + # not applicable 30 + 31 + [](https://example.org/boo) 32 + 33 + [](https://example.org/git)
+32 -76
crates/mdbook-permalinks/src/vcs.rs
··· 38 38 39 39 let link = { 40 40 if let Some(pat) = &config.repo_url_template { 41 - CustomPermalink { 42 - pattern: pat 41 + Permalink { 42 + template: pat 43 43 .parse() 44 44 .context("failed to parse `repo-url-template` as a valid url")?, 45 45 reference, 46 46 } 47 - .pipe(Permalink::Custom) 48 47 } else { 49 48 let repo = match find_git_remote(&repo, book)? { 50 49 Ok(repo) => repo, ··· 65 64 }) 66 65 .context("help: use `repo-url-template` option for a custom remote") 67 66 .context("failed to parse git remote url")?; 68 - GitHubPermalink::new(&owner, &repo, &reference).pipe(Permalink::GitHub) 67 + Permalink::github(&owner, &repo, &reference) 69 68 } 70 69 }; 71 70 ··· 80 79 fn to_path(&self, link: &Url) -> Option<(String, ContentTypeHint)>; 81 80 } 82 81 83 - pub enum Permalink { 84 - GitHub(GitHubPermalink), 85 - Custom(CustomPermalink), 86 - } 87 - 88 - impl PermalinkFormat for Permalink { 89 - #[inline] 90 - fn to_link(&self, path: &str, hint: ContentTypeHint) -> Result<Url> { 91 - match self { 92 - Self::GitHub(this) => this.to_link(path, hint), 93 - Self::Custom(this) => this.to_link(path, hint), 94 - } 95 - } 96 - 97 - #[inline] 98 - fn to_path(&self, link: &Url) -> Option<(String, ContentTypeHint)> { 99 - match self { 100 - Self::GitHub(this) => this.to_path(link), 101 - Self::Custom(this) => this.to_path(link), 102 - } 103 - } 82 + pub struct Permalink { 83 + pub template: Url, 84 + pub reference: String, 104 85 } 105 86 106 - pub struct GitHubPermalink(CustomPermalink); 107 - 108 - impl PermalinkFormat for GitHubPermalink { 109 - #[inline] 110 - fn to_link(&self, path: &str, hint: ContentTypeHint) -> Result<Url> { 111 - self.0.to_link(path, hint) 112 - } 113 - 114 - #[inline] 115 - fn to_path(&self, link: &Url) -> Option<(String, ContentTypeHint)> { 116 - self.0.to_path(link) 117 - } 118 - } 119 - 120 - impl GitHubPermalink { 121 - /// c.f. <https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content--parameters> 122 - pub fn new(owner: &str, repo: &str, reference: &str) -> Self { 123 - let pattern = format!("https://github.com/{owner}/{repo}/{{tree}}/{{ref}}/{{path}}") 87 + impl Permalink { 88 + /// See <https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28#get-repository-content--parameters> 89 + pub fn github(owner: &str, repo: &str, reference: &str) -> Self { 90 + let template = format!("https://github.com/{owner}/{repo}/{{tree}}/{{ref}}/{{path}}") 124 91 .parse() 125 92 .expect("should be a valid url"); 126 93 let reference = reference.into(); 127 - Self(CustomPermalink { pattern, reference }) 94 + Self { 95 + template, 96 + reference, 97 + } 128 98 } 129 - } 130 - 131 - pub struct CustomPermalink { 132 - pub pattern: Url, 133 - pub reference: String, 134 99 } 135 100 136 101 /// `{` and `}` are always percent-encoded in path [^1]. ··· 145 110 }; 146 111 } 147 112 148 - impl PermalinkFormat for CustomPermalink { 113 + impl PermalinkFormat for Permalink { 149 114 fn to_link(&self, path: &str, hint: ContentTypeHint) -> Result<Url> { 150 115 let path = self 151 - .pattern 116 + .template 152 117 .path() 153 118 .split('/') 154 119 .map(|segment| match segment { ··· 164 129 .join("/"); 165 130 166 131 let query = self 167 - .pattern 132 + .template 168 133 .query_pairs() 169 134 .fold(SearchParams::new(String::new()), |mut search, (k, v)| { 170 135 match v.as_ref() { ··· 178 143 .finish() 179 144 .pipe(|query| if query.is_empty() { None } else { Some(query) }); 180 145 181 - let fragment = self.pattern.fragment(); 146 + let fragment = self.template.fragment(); 182 147 183 - self.pattern 148 + self.template 184 149 .clone() 185 150 .tap_mut(|u| u.set_path(&path)) 186 151 .tap_mut(|u| u.set_query(query.as_deref())) ··· 190 155 191 156 // this is kind of messy 192 157 fn to_path(&self, link: &Url) -> Option<(String, ContentTypeHint)> { 193 - if self.pattern.origin() != link.origin() { 158 + if self.template.origin() != link.origin() { 194 159 return None; 195 160 } 196 161 ··· 221 186 } 222 187 }; 223 188 224 - let mut lhs = self.pattern.path().split('/'); 189 + let mut lhs = self.template.path().split('/'); 225 190 let mut rhs = link.path().split('/'); 226 191 227 192 #[allow(clippy::while_let_on_iterator, reason = "symmetry")] ··· 253 218 254 219 let link_query = link.query_pairs().collect::<HashMap<_, _>>(); 255 220 256 - for (k, v) in self.pattern.query_pairs() { 221 + for (k, v) in self.template.query_pairs() { 257 222 match v.as_ref() { 258 223 "{path}" => match link_query.get(&k) { 259 224 Some(v) => { ··· 391 356 392 357 use crate::link::ContentTypeHint; 393 358 394 - use super::{CustomPermalink, PermalinkFormat, find_git_remote, remote_as_github}; 359 + use super::{Permalink, PermalinkFormat, find_git_remote, remote_as_github}; 395 360 396 361 #[test] 397 362 fn test_github_url_from_book() -> Result<()> { ··· 449 414 450 415 #[test] 451 416 fn test_path_to_link() -> Result<()> { 452 - let scheme = CustomPermalink { 453 - pattern: "https://github.com/lorem/ipsum/{tree}/{ref}/{path}".parse()?, 454 - reference: "main".into(), 455 - }; 417 + let scheme = Permalink::github("lorem", "ipsum", "main"); 456 418 457 419 let link = scheme.to_link(".editorconfig", ContentTypeHint::Tree)?; 458 420 ··· 466 428 467 429 #[test] 468 430 fn test_path_to_link_with_suffix() -> Result<()> { 469 - let scheme = CustomPermalink { 470 - pattern: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 431 + let scheme = Permalink { 432 + template: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 471 433 reference: "master".into(), 472 434 }; 473 435 ··· 483 445 484 446 #[test] 485 447 fn test_link_to_path() -> Result<()> { 486 - let scheme = CustomPermalink { 487 - pattern: "https://github.com/lorem/ipsum/{tree}/{ref}/{path}".parse()?, 488 - reference: "main".into(), 489 - }; 448 + let scheme = Permalink::github("lorem", "ipsum", "main"); 490 449 491 450 let (path, hint) = scheme 492 451 .to_path(&"https://github.com/lorem/ipsum/raw/HEAD/path/to/file".parse()?) ··· 500 459 501 460 #[test] 502 461 fn test_link_to_path_repo_root() -> Result<()> { 503 - let scheme = CustomPermalink { 504 - pattern: "https://github.com/lorem/ipsum/{tree}/{ref}/{path}".parse()?, 505 - reference: "main".into(), 506 - }; 462 + let scheme = Permalink::github("lorem", "ipsum", "main"); 507 463 508 464 let (path, _) = scheme 509 465 .to_path(&"https://github.com/lorem/ipsum/raw/HEAD".parse()?) ··· 522 478 523 479 #[test] 524 480 fn test_link_to_path_with_suffix() -> Result<()> { 525 - let scheme = CustomPermalink { 526 - pattern: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 481 + let scheme = Permalink { 482 + template: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 527 483 reference: "main".into(), 528 484 }; 529 485 ··· 538 494 539 495 #[test] 540 496 fn test_link_to_path_non_head() -> Result<()> { 541 - let scheme = CustomPermalink { 542 - pattern: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 497 + let scheme = Permalink { 498 + template: "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/{tree}/{path}?h={ref}".parse()?, 543 499 reference: "main".into(), 544 500 }; 545 501