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.

chore: release v1.0.0 (#8)

authored by

tonywu6 and committed by
GitHub
a72594c5 236328d5

+1467 -325
+2
.github/workflows/ci.yml
··· 31 31 - stable 32 32 platform: 33 33 - ubuntu-latest 34 + # - macos-latest # is expensive 34 35 - windows-latest 36 + 35 37 fail-fast: false 36 38 37 39 runs-on: ${{ matrix.platform }}
+1
.github/workflows/docs.yml
··· 30 30 steps: 31 31 - uses: actions/checkout@v4 32 32 with: 33 + fetch-depth: 0 33 34 fetch-tags: true 34 35 35 36 - uses: dtolnay/rust-toolchain@stable
+17 -17
.github/workflows/release-plz.yml
··· 42 42 # token is required to not commit as github-actions[bot] 43 43 GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_GITHUB_TOKEN }} 44 44 45 - # release: 46 - # name: Release 47 - # runs-on: ubuntu-latest 45 + release: 46 + name: Release 47 + runs-on: ubuntu-latest 48 48 49 - # if: github.repository_owner == 'tonywu6' 49 + if: github.repository_owner == 'tonywu6' 50 50 51 - # permissions: 52 - # contents: write 51 + permissions: 52 + contents: write 53 53 54 - # steps: 55 - # - uses: actions/checkout@v4 56 - # with: 57 - # fetch-depth: 0 58 - # - uses: dtolnay/rust-toolchain@stable 59 - # - uses: release-plz/action@7419a2cb1535b9c0e852b4dec626967baf65c022 60 - # with: 61 - # command: release 62 - # env: 63 - # GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_GITHUB_TOKEN }} 64 - # CARGO_REGISTRY_TOKEN: ${{ secrets.RELEASE_PLZ_PUBLISH_TOKEN }} 54 + steps: 55 + - uses: actions/checkout@v4 56 + with: 57 + fetch-depth: 0 58 + - uses: dtolnay/rust-toolchain@stable 59 + - uses: release-plz/action@7419a2cb1535b9c0e852b4dec626967baf65c022 60 + with: 61 + command: release 62 + env: 63 + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_GITHUB_TOKEN }} 64 + CARGO_REGISTRY_TOKEN: ${{ secrets.RELEASE_PLZ_PUBLISH_TOKEN }}
+1 -3
.prettierrc.mjs
··· 1 1 // @ts-check 2 2 3 3 /** @type {import("prettier").Config} */ 4 - const config = { 4 + export default { 5 5 printWidth: 88, 6 6 proseWrap: "always", 7 7 tabWidth: 2, ··· 15 15 }, 16 16 ], 17 17 }; 18 - 19 - export default config;
+6
.stylelintignore
··· 1 + node_modules 2 + build 3 + dist 4 + target 5 + docs/app/dist.css 6 + docs/src/app
+9
.stylelintrc.mjs
··· 1 + // @ts-check 2 + 3 + /** @type {import('stylelint').Config} */ 4 + export default { 5 + extends: ["stylelint-config-standard", "stylelint-config-recess-order"], 6 + rules: { 7 + "no-descending-specificity": null, // unactionable with nested CSS 8 + }, 9 + };
+6
.vscode/settings.json
··· 1 1 { 2 + "[css]": { 3 + "editor.codeActionsOnSave": { 4 + "source.fixAll.stylelint": "explicit" 5 + } 6 + }, 2 7 "[markdown]": { 3 8 "editor.defaultFormatter": "esbenp.prettier-vscode" 4 9 }, 5 10 "cSpell.words": ["cmark", "mdbookkit", "unmark"], 6 11 "deno.enable": true, 12 + "editor.formatOnSave": true, 7 13 "files.associations": { 8 14 "**/*.snap": "markdown", 9 15 "**/*.snap.new": "markdown"
+2
Cargo.lock
··· 1640 1640 dependencies = [ 1641 1641 "anyhow", 1642 1642 "clap", 1643 + "env_logger", 1643 1644 "gix-url", 1645 + "log", 1644 1646 "mdbookkit", 1645 1647 "miette", 1646 1648 "serde",
+23 -2
README.md
··· 1 - [![mdbookkit hero image](docs/src/media/banner.webp)](https://tonywu6.github.io/mdbookkit) 1 + [![mdbookkit hero image](/docs/src/media/banner.webp)](https://tonywu6.github.io/mdbookkit/) 2 2 3 3 # mdbookkit 4 4 5 - ## [Read the book](https://tonywu6.github.io/mdbookkit) 5 + Quality-of-life plugins for your [mdBook] project. 6 6 7 7 [![crates.io](https://img.shields.io/crates/v/mdbookkit?style=flat-square)](https://crates.io/crates/mdbookkit) 8 8 [![documentation](https://img.shields.io/github/actions/workflow/status/tonywu6/mdbookkit/docs.yml?branch=main&event=release&style=flat-square&label=docs)](https://docs.rs/mdbookkit) 9 9 [![MIT/Apache-2.0 licensed](https://img.shields.io/crates/l/mdbookkit?style=flat-square)](/LICENSE-APACHE.md) 10 + 11 + ## [Read the book](https://tonywu6.github.io/mdbookkit/) 12 + 13 + You may be looking for: 14 + 15 + [**`mdbook-rustdoc-link`**](https://tonywu6.github.io/mdbookkit/rustdoc-link/) 16 + 17 + <!-- prettier-ignore-start --> 18 + 19 + - [Install & use](https://tonywu6.github.io/mdbookkit/rustdoc-link/getting-started#install) 20 + | [Using in CI](https://tonywu6.github.io/mdbookkit/rustdoc-link/continuous-integration) 21 + | [Caching](https://tonywu6.github.io/mdbookkit/rustdoc-link/caching) 22 + 23 + [**`mdbook-link-forever`**](https://tonywu6.github.io/mdbookkit/link-forever/) 24 + 25 + - [Install & use](https://tonywu6.github.io/mdbookkit/link-forever/#getting-started) 26 + | [Using in CI](https://tonywu6.github.io/mdbookkit/link-forever/continuous-integration) 27 + 28 + [mdBook]: https://rust-lang.github.io/mdBook/ 29 + 30 + <!-- prettier-ignore-end -->
+14
crates/mdbookkit/CHANGELOG.md
··· 1 + # CHANGELOG 2 + 3 + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and 4 + this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 + 6 + This file is autogenerated using [release-plz](https://release-plz.dev). 7 + 8 + ## Unreleased 9 + 10 + > Unreleased changes appear here, if any. 11 + 12 + ## [1.0.0](https://tonywu6.github.com/tonywu6/mdbookkit/releases/tag/mdbookkit-v1.0.0) - 2025-04-08 13 + 14 + Initial release.
+2 -2
crates/mdbookkit/README.md
··· 37 37 38 38 <!-- prettier-ignore-start --> 39 39 40 - [mdBook]: https://rust-lang.github.io/mdBook/ 41 40 [`mdbookkit`]: https://crates.io/crates/mdbookkit 42 - [preprocessors]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 43 41 [gh-releases]: https://github.com/tonywu6/mdbookkit/releases 42 + [mdBook]: https://rust-lang.github.io/mdBook/ 43 + [preprocessors]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 44 44 45 45 <!-- prettier-ignore-end -->
+39 -34
crates/mdbookkit/src/bin/rustdoc_link/cache.rs
··· 118 118 where 119 119 D: Iterator<Item = Cow<'a, Url>>, 120 120 { 121 - iter::once(Cow::Owned(env.source_dir.join("Cargo.toml").unwrap())) 122 - .chain(iter::once(Cow::Owned( 123 - env.crate_dir.join("Cargo.toml").unwrap(), 124 - ))) 125 - .chain(iter::once(Cow::Borrowed(&env.entrypoint))) 126 - .chain(deps) 127 - .collect::<BTreeSet<_>>() 128 - .into_iter() 129 - .filter_map(|dep| { 130 - if env.source_dir.make_relative(&dep)?.starts_with("../") { 131 - None 132 - } else { 133 - Some(dep.into_owned()) 134 - } 135 - }) 136 - .map(read_dep) 137 - .collect::<JoinSet<_>>() 138 - .join_all() 139 - .await 140 - .into_iter() 141 - .filter_map(|result| { 142 - result 143 - .context("failed to read cache dependency") 144 - .tap_err(log_debug!()) 145 - .ok() 146 - }) 147 - .collect::<Vec<_>>() 148 - .tap_mut(|deps| deps.sort_by(|(k1, _), (k2, _)| k1.cmp(k2))) 149 - .into_iter() 150 - .fold(Sha256::new(), |mut hash, (_, src)| { 151 - hash.update(src); 152 - hash 153 - }) 154 - .pipe(|hash| hash.finalize().digest()) 121 + [ 122 + Cow::Owned(env.source_dir.join("Cargo.toml").unwrap()), 123 + Cow::Owned(env.crate_dir.join("Cargo.toml").unwrap()), 124 + Cow::Borrowed(&env.entrypoint), 125 + ] 126 + .into_iter() 127 + .chain(iter::once(Cow::Borrowed(&env.entrypoint))) 128 + .chain(deps) 129 + .collect::<BTreeSet<_>>() 130 + .into_iter() 131 + .filter_map(|dep| { 132 + // exclude external sources from checksum 133 + if env.source_dir.make_relative(&dep)?.starts_with("../") { 134 + None 135 + } else { 136 + Some(dep.into_owned()) 137 + } 138 + }) 139 + .map(read_dep) 140 + .collect::<JoinSet<_>>() 141 + .join_all() 142 + .await 143 + .into_iter() 144 + .filter_map(|result| { 145 + // should be tolerable to skip files that we somehow can't read? 146 + result 147 + .context("failed to read cache dependency") 148 + .tap_err(log_debug!()) 149 + .ok() 150 + }) 151 + .collect::<Vec<_>>() 152 + .tap_mut(|deps| deps.sort_by(|(k1, _), (k2, _)| k1.cmp(k2))) // order affects hashing 153 + .into_iter() 154 + .fold(Sha256::new(), |mut hash, (name, src)| { 155 + log::debug!("hashing {name}"); 156 + hash.update(src); 157 + hash 158 + }) 159 + .pipe(|hash| hash.finalize().digest()) 155 160 } 156 161 } 157 162
+49
crates/mdbookkit/src/bin/rustdoc_link/client.rs
··· 42 42 }; 43 43 44 44 /// LSP client to talk to rust-analyzer. 45 + /// 46 + /// [`Client`] does not implement [`Clone`], because the server instance is lazily spawned 47 + /// and so must be unique for each client. To enable cloning, put this in an [`Arc`]. 45 48 #[derive(Debug)] 46 49 pub struct Client { 47 50 env: Environment, ··· 50 53 } 51 54 52 55 impl Client { 56 + /// Initialize a new LSP client. 57 + /// 58 + /// This does not actually spawn rust-analyzer. 53 59 pub fn new(env: Environment) -> Self { 54 60 let server = OnceCell::new(); 55 61 let docs = DocumentLock::default(); ··· 78 84 Ok(opened) 79 85 } 80 86 87 + /// Shutdown the server if it was spawned. 88 + /// 89 + /// Returns the [`Environment`] struct for further use. 90 + /// 91 + /// This moves `self`. To shutdown a [`Client`] that's in an [`Arc`], 92 + /// use [`client.drop`][Client::drop]. 81 93 pub async fn stop(self) -> Environment { 82 94 if let Some(server) = self.server.into_inner() { 83 95 server ··· 90 102 self.env 91 103 } 92 104 105 + /// See [`client.stop`][Client::stop]. 93 106 pub async fn drop(self: Arc<Self>) -> Result<Environment> { 94 107 let Some(this) = Arc::into_inner(self) else { 95 108 bail!("attempted to shutdown a client that is still referenced") ··· 126 139 percent_indexed: Option<u32>, 127 140 } 128 141 142 + /// Listen for [Work Done Progress][workDoneProgress] events from rust-analyzer 143 + /// to determine if indexing is done. 144 + /// 145 + /// The `cachePriming` events look like this: 146 + /// 147 + /// - [`WorkDoneProgress::Begin`] 148 + /// - [`WorkDoneProgress::Report`] for each crate indexed, with messages like `4/200 (core)` 149 + /// - ... 150 + /// - [`WorkDoneProgress::End`] 151 + /// 152 + /// (This is the ticker thing that shows up in the VS Code status bar). 153 + /// 154 + /// Notably, rust-analyzer seems to do several rounds of indexing, **not all of 155 + /// which is actually indexing the entire codebase:** 156 + /// 157 + /// - First, a `0/1 (<crate name>)` round that only indexes the current crate. 158 + /// 159 + /// - RA is **not ready** at this point: if we were to query for external docs 160 + /// immediately after this [`WorkDoneProgress::End`] event, almost all links 161 + /// will fail to resolve. 162 + /// 163 + /// - Then, a `0/x ...` round that seems to actually indexes everything, including 164 + /// [`std`] and all the dependencies. 165 + /// 166 + /// - RA is likely ready at this point. 167 + /// 168 + /// - Then, one or more additional rounds of indexing that finish very quickly. 169 + /// 170 + /// To be able to "reliably" determine that RA is ready for querying, the 171 + /// probing mechanism does essentially the following: 172 + /// 173 + /// 1. Ignore the first round of indexing. 174 + /// 2. Be extra pessimistic and wait for events to quiet down after receiving 175 + /// the first [`WorkDoneProgress::End`] event; see [`EventSampler`]. 176 + /// 177 + /// [workDoneProgress]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workDoneProgress 129 178 fn probe_progress(state: &mut State, progress: ProgressParams) { 130 179 match indexing_progress(&progress) { 131 180 Some(WorkDoneProgress::Begin(begin)) => {
+22 -16
crates/mdbookkit/src/bin/rustdoc_link/env.rs
··· 6 6 7 7 use anyhow::{anyhow, bail, Context, Result}; 8 8 use cargo_toml::{Manifest, Product}; 9 - #[cfg(feature = "common-cli")] 10 - use clap::ValueHint; 11 9 use lsp_types::Url; 12 10 use pulldown_cmark::Options; 13 11 use serde::{de::DeserializeOwned, Deserialize, Serialize}; ··· 19 17 20 18 use super::markdown; 21 19 20 + /// Configuration for the preprocessor. 21 + /// 22 + /// This is both deserialized from book.toml and parsed from the command line. 23 + /// 24 + /// Doc comments for attributes populate the `configuration.md` page in the docs. 22 25 #[derive(Deserialize, Debug, Default, Clone)] 23 26 #[cfg_attr(feature = "common-cli", derive(clap::Parser))] 24 27 #[serde(rename_all = "kebab-case", deny_unknown_fields)] ··· 33 36 #[serde(default)] 34 37 #[cfg_attr( 35 38 feature = "common-cli", 36 - arg(long, value_name("COMMAND"), value_hint(ValueHint::CommandString)) 39 + arg( 40 + long, 41 + value_name("COMMAND"), 42 + value_hint(clap::ValueHint::CommandString) 43 + ) 37 44 )] 38 45 pub rust_analyzer: Option<String>, 39 46 ··· 63 70 #[serde(default)] 64 71 #[cfg_attr( 65 72 feature = "common-cli", 66 - arg(long, value_name("PATH"), value_hint(ValueHint::DirPath)) 73 + arg(long, value_name("PATH"), value_hint(clap::ValueHint::DirPath)) 67 74 )] 68 75 pub manifest_dir: Option<PathBuf>, 69 76 ··· 73 80 #[serde(default)] 74 81 #[cfg_attr( 75 82 feature = "common-cli", 76 - arg(long, value_name("PATH"), value_hint(ValueHint::DirPath)) 83 + arg(long, value_name("PATH"), value_hint(clap::ValueHint::DirPath)) 77 84 )] 78 85 pub cache_dir: Option<PathBuf>, 79 86 ··· 193 200 .pipe(Url::from_directory_path) 194 201 .unwrap(); 195 202 196 - let temp_dir = config 197 - .cache_dir 198 - .clone() 199 - .map(TempDir::Persistent) 200 - .or_else(|| { 201 - tempfile::TempDir::new() 202 - .ok() 203 - .map(Arc::new) 204 - .map(TempDir::Transient) 205 - }) 206 - .context("failed to obtain a temporary directory")?; 203 + let temp_dir = match config.cache_dir.clone() { 204 + Some(path) => Some(TempDir::Persistent(path)), 205 + None => tempfile::TempDir::new() 206 + .ok() 207 + .map(Arc::new) 208 + .map(TempDir::Transient), 209 + } 210 + .context("failed to obtain a temporary directory")?; 207 211 208 212 Ok(Self { 209 213 temp_dir, ··· 327 331 } 328 332 } 329 333 334 + /// Look for rust-analyzer binary from the VS Code extension based on locations 335 + /// described in <https://rust-analyzer.github.io/book/vs_code.html>. 330 336 pub fn find_code_extension() -> Option<PathBuf> { 331 337 let home = dirs::home_dir()?; 332 338 [
+9 -1
crates/mdbookkit/src/bin/rustdoc_link/item.rs
··· 1 + use anyhow::Result; 1 2 use syn::{ 2 3 parenthesized, 3 4 parse::{End, Parse, ParseStream, Parser}, ··· 6 7 PathArguments, QSelf, Token, TypePath, 7 8 }; 8 9 10 + /// Texts that look like Rust items. 9 11 #[derive(Debug)] 10 12 pub struct Item { 13 + /// The parsed item name, which may be different from the source text (e.g. 14 + /// turbofish are expanded.) 11 15 pub name: String, 16 + /// The synthesized, syntactically valid statement that rust-analyzer can parse. 12 17 pub stmt: String, 18 + /// "Points of interest" in [`stmt`][Self::stmt] that can be used to construct 19 + /// [`TextDocumentPositionParams`][lsp_types::TextDocumentPositionParams]. 13 20 pub cursor: Cursor, 14 21 } 15 22 16 23 impl Item { 17 - pub fn parse(path: &str) -> anyhow::Result<Self> { 24 + /// Try to parse a link url as a Rust item. See [`ItemName`]. 25 + pub fn parse(path: &str) -> Result<Self> { 18 26 let path = match path.split_once('@') { 19 27 None => path, 20 28 Some((_, path)) => path,
+2 -2
crates/mdbookkit/src/bin/rustdoc_link/link.rs
··· 4 4 use lsp_types::Url; 5 5 use pulldown_cmark::{CowStr, Event, LinkType, Tag, TagEnd}; 6 6 use serde::{Deserialize, Serialize}; 7 - use tap::{Pipe, Tap}; 7 + use tap::{Pipe, Tap, TapFallible}; 8 8 9 9 use crate::log_trace; 10 10 ··· 37 37 }; 38 38 39 39 let state = Item::parse(path) 40 - .tap_mut(log_trace!()) 40 + .tap_err(log_trace!()) 41 41 .ok() 42 42 .map(LinkState::Parsed) 43 43 .unwrap_or(LinkState::Unparsed);
+1 -1
crates/mdbookkit/src/bin/rustdoc_link/link/diagnostic.rs
··· 62 62 }; 63 63 let label = match &self.state { 64 64 LinkState::Unparsed => Some(self.url.as_ref().into()), 65 - LinkState::Parsed(item) => Some(format!("failed to resolve links for {:?}", item.name)), 65 + LinkState::Parsed(item) => Some(format!("failed to resolve link for {:?}", item.name)), 66 66 LinkState::Resolved(links) => Some(format!("{}", links.url())), 67 67 }; 68 68 let label = LabeledSpan::new_with_span(label, self.span.clone());
+32 -5
crates/mdbookkit/src/bin/rustdoc_link/markdown.rs
··· 4 4 use crate::markdown::mdbook_markdown; 5 5 6 6 pub fn stream(text: &str, options: Options) -> MarkdownStream<'_> { 7 - Parser::new_with_broken_link_callback(text, options, Some(BrokenLinks)) 7 + Parser::new_with_broken_link_callback(text, options, Some(ItemLinks)) 8 8 } 9 9 10 - pub type MarkdownStream<'a> = Parser<'a, BrokenLinks>; 10 + pub type MarkdownStream<'a> = Parser<'a, ItemLinks>; 11 + 12 + /// [`BrokenLinkCallback`] implementation that unconditionally converts all "broken" 13 + /// links to links to be further processed. 14 + /// 15 + /// "Broken" links are links like `[text][link::item]` that don't have associated URLs, 16 + /// which are actually exactly what [rustdoc_link][super] wants. 17 + /// 18 + /// Links that are "broken" that aren't actually doc links won't show up in the output, 19 + /// because the preprocessor ignores links that cannot be parsed and is capable of 20 + /// emitting only changed links, see [`PatchStream`][crate::markdown::PatchStream]. 21 + pub struct ItemLinks; 11 22 12 - pub struct BrokenLinks; 23 + impl ItemLinks { 24 + // Explicitly disable smart punctuation to prevent quotes from being changed 25 + // or else things like lifetimes may become invalid 26 + const OPTIONS: pulldown_cmark::Options = 27 + mdbook_markdown().intersection(Options::ENABLE_SMART_PUNCTUATION.complement()); 28 + } 13 29 14 - impl<'input> BrokenLinkCallback<'input> for BrokenLinks { 30 + impl<'input> BrokenLinkCallback<'input> for ItemLinks { 15 31 fn handle_broken_link( 16 32 &mut self, 17 33 link: BrokenLink<'input>, 18 34 ) -> Option<(CowStr<'input>, CowStr<'input>)> { 35 + // try to strip away inline markups in order to support stylized shorthand links 36 + // for example, this extracts "std" from [`std`], removing the `inline code` markup 19 37 let inner = if let CowStr::Borrowed(inner) = link.reference { 20 - let parse = stream(inner, mdbook_markdown()); 38 + // this is currently done by manually parsing the inner text, filtering 39 + // the event stream, and then re-emitting it as text 40 + // 41 + // because of the 'input lifetime, this can only be done on CowStr::Borrowed, 42 + // otherwise the re-emitted text "may not live long enough." 43 + // 44 + // this should be okay in usage, because this is only called by the Parser, 45 + // which should only provide borrowed text. 46 + 47 + let parse = stream(inner, Self::OPTIONS); 21 48 22 49 let inner = parse 23 50 .filter_map(|event| match event {
+8
crates/mdbookkit/src/bin/rustdoc_link/sync.rs
··· 25 25 } 26 26 } 27 27 28 + /// Some kind of [debouncing]. 29 + /// 30 + /// Listens to events over an [`mpsc::Receiver<Poll<T>>`] and [notifies][Notify] 31 + /// subscribers of [`Poll::Ready`], but only if they are not "immediately" 32 + /// followed by more [`Poll::Pending`], the timing of which is determined by a 33 + /// configured [buffering time][EventSampling::buffer]. 34 + /// 35 + /// [debouncing]: https://developer.mozilla.org/en-US/docs/Glossary/Debounce 28 36 #[derive(Debug, Clone)] 29 37 pub struct EventSampler<T> { 30 38 state: Arc<RwLock<State<T>>>,
+4
crates/mdbookkit/src/diagnostics.rs
··· 22 22 } 23 23 24 24 /// Trait for diagnostics classes. This is like a specific error code. 25 + /// 26 + /// **For implementors:** The [`Display`] implementation, which is the title of each 27 + /// diagnostic message, should use plurals whenever possible, because error reporters 28 + /// may elect to group together multiple labels of the same [`Issue`] 25 29 pub trait Issue: Default + Debug + Display + Clone + Send + Sync { 26 30 fn level(&self) -> Level; 27 31 }
+23 -4
crates/mdbookkit/src/env.rs
··· 4 4 use serde::Deserialize; 5 5 use tap::Pipe; 6 6 7 + pub fn is_ci() -> Option<String> { 8 + let ci = std::env::var("CI").unwrap_or("".into()); 9 + if matches!(ci.as_str(), "" | "0" | "false") { 10 + None 11 + } else { 12 + Some(ci) 13 + } 14 + } 15 + 7 16 /// Flag indicating how the program should proceed when there are warnings. 8 17 /// 9 18 /// Used in preprocessor options. ··· 13 22 #[derive(Deserialize, Debug, Default, Clone, Copy)] 14 23 #[serde(rename_all = "lowercase")] 15 24 pub enum ErrorHandling { 16 - /// Fail if the environment variable `CI` is set to a value other than `0`. 25 + /// Fail if the environment variable `CI` is set to a value other than `0` or `false`. 17 26 /// Environments like GitHub Actions configure this automatically. 18 27 #[default] 19 28 #[serde(rename = "ci")] ··· 35 44 .pipe(Err) 36 45 } 37 46 Self::Env => { 38 - let ci = std::env::var("CI").unwrap_or("".into()); 39 - if matches!(ci.as_str(), "" | "0" | "false") { 47 + let Some(ci) = Self::warning_as_error() else { 40 48 return Ok(()); 41 - } 49 + }; 42 50 anyhow!("treating warnings as errors because fail-on-unresolved is \"ci\" and CI={ci}") 43 51 .context("preprocessor has errors") 44 52 .pipe(Err) ··· 46 54 }, 47 55 _ => Ok(()), 48 56 } 57 + } 58 + 59 + pub fn adjusted<T, E>(&self, result: Result<Result<T, E>, E>) -> Result<Result<T, E>, E> { 60 + match result { 61 + Ok(Err(error)) if Self::warning_as_error().is_some() => Err(error), 62 + result => result, 63 + } 64 + } 65 + 66 + fn warning_as_error() -> Option<String> { 67 + is_ci() 49 68 } 50 69 } 51 70
+3 -3
crates/mdbookkit/src/logging/terminal.rs
··· 17 17 18 18 /// Either a [`console::Term`] or an [`env_logger::Logger`]. 19 19 /// 20 - /// This is automatically detected upon installing as the global logger. The logic is: 20 + /// This is automatically detected upon installation as the global logger. The logic is: 21 21 /// 22 22 /// - If the `RUST_LOG` env var is set, this will use [`env_logger`]. 23 23 /// - If stderr is not "user-attended", as determined by [`console::user_attended_stderr()`], ··· 75 75 None 76 76 } else if !console::user_attended_stderr() { 77 77 // RUST_LOG not set but stderr isn't a terminal 78 - // log warnings and above 79 - Some(LevelFilter::Warn) 78 + // log info and above 79 + Some(LevelFilter::Info) 80 80 } else { 81 81 // use spinner instead 82 82 Some(LevelFilter::Off)
+15 -16
crates/mdbookkit/src/markdown.rs
··· 6 6 use pulldown_cmark_to_cmark::{cmark, Error}; 7 7 use tap::Pipe; 8 8 9 - /// _Patch_ a Markdown string, instead of regenerating it entirely. 9 + /// _Patch_ a Markdown string, instead of regenerating it entirely, in order to preserve 10 + /// as much of the original Markdown source as possible, especially with regard to whitespace. 10 11 /// 11 - /// Currently, whitespace is NOT preserved when using [`pulldown_cmark_to_cmark`] to 12 - /// generate Markdown from a [`pulldown_cmark::Event`] stream. 13 - /// 14 - /// This is problematic for mdBook preprocessors, because preprocessors downstream 15 - /// may need to work on syntax that is whitespace-sensitive. Normalizing all whitespace 16 - /// could cause such usage to no longer be recognized. An example is [`mdbook-alerts`][alerts] 12 + /// Currently, when using [`pulldown_cmark_to_cmark`] to generate Markdown from a 13 + /// [`pulldown_cmark::Event`] stream, whitespace is NOT preserved. This is problematic 14 + /// for mdBook preprocessors, because preprocessors downstream may need to work on 15 + /// syntax that is whitespace-sensitive. Normalizing all whitespace could cause such 16 + /// usage to no longer be recognized. An example is [`mdbook-alerts`][alerts] 17 17 /// which works on GitHub's ["alerts"][gh-alerts] syntax. 18 18 /// 19 19 /// [alerts]: https://crates.io/crates/mdbook-alerts ··· 74 74 /// **The yielded ranges must not overlap or decrease**, that is, for `span1` and 75 75 /// `span2`, where `span1` is yielded before `span2`, `span1.end <= span2.start`. 76 76 /// 77 - /// ## Panics 77 + /// # Panics 78 78 /// 79 79 /// Panic if ranges in `stream` are not monotonically increasing. 80 80 pub fn new(source: &'a str, stream: S) -> Self { ··· 102 102 } 103 103 104 104 /// <https://github.com/rust-lang/mdBook/blob/v0.4.47/src/utils/mod.rs#L197-L208> 105 - pub fn mdbook_markdown() -> Options { 106 - let mut opts = Options::empty(); 107 - opts.insert(Options::ENABLE_TABLES); 108 - opts.insert(Options::ENABLE_FOOTNOTES); 109 - opts.insert(Options::ENABLE_STRIKETHROUGH); 110 - opts.insert(Options::ENABLE_TASKLISTS); 111 - opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); 112 - opts 105 + pub const fn mdbook_markdown() -> Options { 106 + Options::empty() 107 + .union(Options::ENABLE_TABLES) 108 + .union(Options::ENABLE_FOOTNOTES) 109 + .union(Options::ENABLE_STRIKETHROUGH) 110 + .union(Options::ENABLE_TASKLISTS) 111 + .union(Options::ENABLE_HEADING_ATTRIBUTES) 113 112 } 114 113 115 114 pub type Spanned<T> = (T, Range<usize>);
+4 -3
crates/mdbookkit/tests/rustdoc_link.rs
··· 79 79 test_document!("../../../docs/src/rustdoc-link/supported-syntax.md"), 80 80 test_document!("../../../docs/src/rustdoc-link/known-issues.md"), 81 81 test_document!("../../../docs/src/rustdoc-link/getting-started.md"), 82 - test_document!("../../../docs/src/rustdoc-link.md"), 82 + test_document!("../../../docs/src/rustdoc-link/index.md"), 83 83 test_document!("tests/ra-known-quirks.md"), 84 84 ]; 85 85 ··· 120 120 } 121 121 122 122 #[test] 123 - #[ignore = "should run in CI"] 123 + #[ignore = "should run in a dedicated environment"] 124 124 fn test_minimum_env() -> Result<()> { 125 125 util::setup_logging(); 126 126 ··· 237 237 .stderr( 238 238 predicate::str::contains("failed to spawn rust-analyzer") 239 239 // https://github.com/rust-lang/rustup/issues/3846 240 - // rustup shims rust-analyzer even when it's not installed 240 + // rustup shims rust-analyzer when it's not installed 241 241 .or(predicate::str::contains("Unknown binary 'rust-analyzer")), 242 + // ^ doesn't have a closing `'` because on windows it says 'rust-analyzer.exe' 242 243 ); 243 244 244 245 log::info!("when: code extension is installed");
+5 -6
crates/mdbookkit/tests/snaps/rustdoc_link/getting-started.snap
··· 62 62 63 63 ![screen recording of mdbook-rustdoc-link during mdbook build](media/screencap.webp) 64 64 65 - To read more about this project, feel free to return to 66 - [Overview](../rustdoc-link.md#overview). 65 + To read more about this project, feel free to return to [Overview](index.md#overview). 67 66 68 67 > [!IMPORTANT] 69 68 > ··· 89 88 90 89 <!-- prettier-ignore-start --> 91 90 92 - [preprocessor]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 93 - [rust-analyzer]: https://rust-analyzer.github.io/ 94 - [ra-install]: https://rust-analyzer.github.io/book/rust_analyzer_binary.html 91 + [gh-releases]: https://github.com/tonywu6/mdbookkit/releases 95 92 [open-docs]: https://rust-analyzer.github.io/book/features.html#open-docs 93 + [preprocessor]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 96 94 [ra-extension]: https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer 97 - [gh-releases]: https://github.com/tonywu6/mdbookkit/releases 95 + [ra-install]: https://rust-analyzer.github.io/book/rust_analyzer_binary.html 96 + [rust-analyzer]: https://rust-analyzer.github.io/ 98 97 99 98 <!-- prettier-ignore-end -->
+14 -4
crates/mdbookkit/tests/snaps/rustdoc_link/known-issues.snap
··· 11 11 - [Incorrect links](#incorrect-links) 12 12 - [Macros](#macros) 13 13 - [Trait items](#trait-items) 14 + - [Private items](#private-items) 14 15 - [Unresolved items](#unresolved-items) 15 16 - [Associated items on primitive types](#associated-items-on-primitive-types) 16 17 - [Sites other than docs.rs](#sites-other-than-docsrs) ··· 31 32 32 33 ## Incorrect links 33 34 34 - In limited circumstances, rust-analyzer generates links that are incorrect or 35 - inaccessible. Unfortunately, for such items, you will still have to link by hand. 35 + In limited circumstances, the preprocessor generates links that are incorrect or 36 + inaccessible. 36 37 37 38 > [!NOTE] 38 39 > ··· 78 79 https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from<strong>-1</strong> 79 80 </a> 80 81 82 + ### Private items 83 + 84 + rustdoc has a [`private_intra_doc_links`][private_intra_doc_links] lint that warns you 85 + when your public documentation tries to link to private items. 86 + 87 + The preprocessor does not yet warn you about links to private items: rust-analyzer will 88 + generate links for items regardless of their crate-level visibility. 89 + 81 90 ## Unresolved items 82 91 83 92 ### Associated items on primitive types ··· 123 132 124 133 <!-- prettier-ignore-start --> 125 134 135 + [IpV6Addr]: https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from-1 126 136 [macro_export]: https://doc.rust-lang.org/stable/reference/macros-by-example.html#path-based-scope 127 137 [panic]: https://doc.rust-lang.org/stable/std/macro.panic.html 138 + [private_intra_doc_links]: https://doc.rust-lang.org/rustdoc/lints.html#private_intra_doc_links 128 139 [serde_json::json]: https://docs.rs/serde_json/1.0.140/serde_json/macro.json.html 140 + [sourcemap]: https://developer.mozilla.org/en-US/docs/Glossary/Source_map 129 141 [tokio::main]: https://docs.rs/tokio-macros/2.5.0/tokio_macros/attr.main.html 130 - [IpV6Addr]: https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from-1 131 - [sourcemap]: https://developer.mozilla.org/en-US/docs/Glossary/Source_map 132 142 133 143 <!-- prettier-ignore-end -->
+30 -30
crates/mdbookkit/tests/snaps/rustdoc_link/known-issues.stderr.snap
··· 3 3 expression: report 4 4 --- 5 5 info: successfully resolved all links 6 - ╭─[known-issues:44:3] 7 - 43 │ 8 - 44 │ - [~~`panic!`~~], and many other `std` macros 6 + ╭─[known-issues:45:3] 7 + 44 │ 8 + 45 │ - [~~`panic!`~~], and many other `std` macros 9 9 · ───────┬────── 10 10 · ╰── https://doc.rust-lang.org/stable/std/macros/macro.panic.html 11 - 45 │ - The correct link is 12 - 46 │ [https://doc.rust-lang.org/stable/std~~/macros~~/macro.panic.html][panic] 13 - 47 │ - [~~`serde_json::json!`~~] 11 + 46 │ - The correct link is 12 + 47 │ [https://doc.rust-lang.org/stable/std~~/macros~~/macro.panic.html][panic] 13 + 48 │ - [~~`serde_json::json!`~~] 14 14 · ────────────┬──────────── 15 15 · ╰── https://docs.rs/serde_json/1.0.140/serde_json/macros/macro.json.html 16 - 48 │ - The correct link is 17 - 49 │ [https://docs.rs/serde_json/1.0.140/serde_json~~/macros~~/macro.json.html][serde_json::json] 18 - 50 │ 19 - 51 │ Attribute macros generate links that use `macro.<macro_name>.html`, but rustdoc actually 20 - 52 │ generates `attr.<macro_name>.html`. For example: 21 - 53 │ 22 - 54 │ - [~~`tokio::main!`~~] 16 + 49 │ - The correct link is 17 + 50 │ [https://docs.rs/serde_json/1.0.140/serde_json~~/macros~~/macro.json.html][serde_json::json] 18 + 51 │ 19 + 52 │ Attribute macros generate links that use `macro.<macro_name>.html`, but rustdoc actually 20 + 53 │ generates `attr.<macro_name>.html`. For example: 21 + 54 │ 22 + 55 │ - [~~`tokio::main!`~~] 23 23 · ──────────┬───────── 24 24 · ╰── https://docs.rs/tokio-macros/2.5.0/tokio_macros/macro.main.html 25 - 55 │ - The correct link is 26 - 56 │ [https://docs.rs/tokio-macros/2.5.0/tokio_macros/~~macro~~attr.main.html][tokio::main] 27 - 57 │ 28 - 58 │ ### Trait items 29 - 59 │ 30 - 60 │ Rust allows methods to have the same name if they are from different traits, and types 31 - 61 │ can implement the same trait multiple times if the trait is generic. All such methods 32 - 62 │ will appear on the same page for the type. 33 - 63 │ 34 - 64 │ rustdoc will number the generated URL fragments so that they remain unique within the 35 - 65 │ HTML document. rust-analyzer does not yet have the ability to do so. 36 - 66 │ 37 - 67 │ For example, these are the same links: 38 - 68 │ 39 - 69 │ - [`<std::net::IpAddr as From<std::net::Ipv4Addr>>::from`] 25 + 56 │ - The correct link is 26 + 57 │ [https://docs.rs/tokio-macros/2.5.0/tokio_macros/~~macro~~attr.main.html][tokio::main] 27 + 58 │ 28 + 59 │ ### Trait items 29 + 60 │ 30 + 61 │ Rust allows methods to have the same name if they are from different traits, and types 31 + 62 │ can implement the same trait multiple times if the trait is generic. All such methods 32 + 63 │ will appear on the same page for the type. 33 + 64 │ 34 + 65 │ rustdoc will number the generated URL fragments so that they remain unique within the 35 + 66 │ HTML document. rust-analyzer does not yet have the ability to do so. 36 + 67 │ 37 + 68 │ For example, these are the same links: 38 + 69 │ 39 + 70 │ - [`<std::net::IpAddr as From<std::net::Ipv4Addr>>::from`] 40 40 · ────────────────────────────┬─────────────────────────── 41 41 · ╰── https://doc.rust-lang.org/stable/core/net/ip_addr/enum.IpAddr.html#method.from 42 - 70 │ - [`<std::net::IpAddr as From<std::net::Ipv6Addr>>::from`] 42 + 71 │ - [`<std::net::IpAddr as From<std::net::Ipv6Addr>>::from`] 43 43 · ────────────────────────────┬─────────────────────────── 44 44 · ╰── https://doc.rust-lang.org/stable/core/net/ip_addr/enum.IpAddr.html#method.from 45 - 71 │ 45 + 72 │ 46 46 ╰────
+2 -2
crates/mdbookkit/tests/snaps/rustdoc_link/ra-known-quirks.stderr.snap
··· 25 25 10 │ 26 26 11 │ - [str::parse] 27 27 · ──────┬───── 28 - · ╰── failed to resolve links for "str::parse" 28 + · ╰── failed to resolve link for "str::parse" 29 29 12 │ - [f64::MIN_POSITIVE] 30 30 · ─────────┬───────── 31 - · ╰── failed to resolve links for "f64::MIN_POSITIVE" 31 + · ╰── failed to resolve link for "f64::MIN_POSITIVE" 32 32 13 │ 33 33 14 │ # macro_export 34 34 15 │
+13 -13
crates/mdbookkit/tests/snaps/rustdoc_link/rustdoc-link.snap crates/mdbookkit/tests/snaps/rustdoc_link/index.snap
··· 39 39 40 40 <figure> 41 41 42 - ![screen recording of mdbook-rustdoc-link during mdbook build](rustdoc-link/media/screencap.webp) 42 + ![screen recording of mdbook-rustdoc-link during mdbook build](media/screencap.webp) 43 43 44 44 </figure> 45 45 46 46 ## Overview 47 47 48 - To get started, simply follow the [quickstart guide](rustdoc-link/getting-started.md)! 48 + To get started, simply follow the [quickstart guide](getting-started.md)! 49 49 50 50 If you would like to read more about this crate: 51 51 52 52 For **writing documentation** — 53 53 54 54 - To learn more about how it is resolving items into links, including 55 - [feature-gated items](rustdoc-link/name-resolution.md#feature-gated-items), see 56 - [Name resolution](rustdoc-link/name-resolution.md). 55 + [feature-gated items](name-resolution.md#feature-gated-items), see 56 + [Name resolution](name-resolution.md). 57 57 - To know how to link to other types of items like 58 - [functions, macros](rustdoc-link/supported-syntax.md#functions-and-macros), and 59 - [implementors](rustdoc-link/supported-syntax.md#implementors-and-fully-qualified-syntax), 60 - see [Supported syntax](rustdoc-link/supported-syntax.md). 58 + [functions, macros](supported-syntax.md#functions-and-macros), and 59 + [implementors](supported-syntax.md#implementors-and-fully-qualified-syntax), see 60 + [Supported syntax](supported-syntax.md). 61 61 62 62 For **adapting this crate to your project** — 63 63 64 64 - If you use [Cargo workspaces][workspaces], see specific instructions in 65 - [Workspace layout](rustdoc-link/workspace-layout.md). 65 + [Workspace layout](workspace-layout.md). 66 66 - If you are working on a large project, and processing is taking a long time, see the 67 - discussion in [Caching](rustdoc-link/caching.md). 67 + discussion in [Caching](caching.md). 68 68 69 69 For **additional usage information** — 70 70 71 71 - You can use this as a standalone command line tool: see 72 - [Standalone usage](rustdoc-link/standalone-usage.md). 73 - - For all available options and how to set them, see 74 - [Configuration](rustdoc-link/configuration.md). 75 - - Finally, review [Known issues](rustdoc-link/known-issues.md) and limitations. 72 + [Standalone usage](standalone-usage.md). 73 + - For tips on using this in CI, see [Continuous integration](continuous-integration.md). 74 + - For all available options and how to set them, see [Configuration](configuration.md). 75 + - Finally, review [Known issues](known-issues.md) and limitations. 76 76 77 77 Happy linking! 78 78
+1 -1
crates/mdbookkit/tests/snaps/rustdoc_link/rustdoc-link.stderr.snap crates/mdbookkit/tests/snaps/rustdoc_link/index.stderr.snap
··· 3 3 expression: report 4 4 --- 5 5 info: successfully resolved all links 6 - ╭─[rustdoc-link:25:5] 6 + ╭─[index:25:5] 7 7 24 │ 8 8 25 │ The [`option`][std::option] and [`result`][std::result] modules define optional and 9 9 · ───────────┬─────────── ───────────┬───────────
+4 -4
crates/mdbookkit/tests/snaps/rustdoc_link/supported-syntax.snap
··· 165 165 > 166 166 > [`std::vec`](https://doc.rust-lang.org/stable/alloc/vec/index.html "std::vec"), [`mod@std::vec`](https://doc.rust-lang.org/stable/alloc/vec/index.html "mod@std::vec"), and [`macro@std::vec`](https://doc.rust-lang.org/stable/alloc/vec/index.html "macro@std::vec") all link to the `vec` _module_. 167 167 168 - Currently, duplicate names in Rust are allowed only if they correspond to items in 169 - different [namespaces], for example, between macros and modules, and between struct 170 - fields and methods — this is mostly covered by the function and macro syntax, described 171 - [above](#functions-and-macros). 168 + This is largely okay because currently, duplicate names in Rust are allowed only if they 169 + correspond to items in different [namespaces], for example, between macros and modules, 170 + and between struct fields and methods — this is mostly covered by the function and macro 171 + syntax, described [above](#functions-and-macros). 172 172 173 173 If you encounter items that must be disambiguated using rustdoc's disambiguator syntax, 174 174 other than [the "special types" listed below](#special-types), please [file an
+4 -4
crates/mdbookkit/tests/snaps/rustdoc_link/supported-syntax.stderr.snap
··· 166 166 · │ ╰── https://doc.rust-lang.org/stable/alloc/vec/index.html 167 167 · ╰── https://doc.rust-lang.org/stable/alloc/vec/index.html 168 168 163 │ 169 - 164 │ Currently, duplicate names in Rust are allowed only if they correspond to items in 170 - 165 │ different [namespaces], for example, between macros and modules, and between struct 171 - 166 │ fields and methods — this is mostly covered by the function and macro syntax, described 172 - 167 │ [above](#functions-and-macros). 169 + 164 │ This is largely okay because currently, duplicate names in Rust are allowed only if they 170 + 165 │ correspond to items in different [namespaces], for example, between macros and modules, 171 + 166 │ and between struct fields and methods — this is mostly covered by the function and macro 172 + 167 │ syntax, described [above](#functions-and-macros). 173 173 168 │ 174 174 169 │ If you encounter items that must be disambiguated using rustdoc's disambiguator syntax, 175 175 170 │ other than [the "special types" listed below](#special-types), please [file an
+1
crates/mdbookkit/tests/tests/trailing-slash/index.md
··· 1 + <https://github.com/slorber/trailing-slash-guide>
+12 -2
deno.json
··· 3 3 "lib": ["esnext", "dom", "deno.ns"], 4 4 "strict": true 5 5 }, 6 - "exclude": ["node_modules", "build", "dist", "target", "docs/app/dist.js"], 6 + "exclude": [ 7 + "node_modules", 8 + "build", 9 + "dist", 10 + "target", 11 + "docs/app/dist.js", 12 + "docs/src/app" 13 + ], 7 14 "imports": { 8 15 "esbuild": "npm:esbuild@^0.25.1", 9 - "prettier": "npm:prettier@^3.5.3" 16 + "prettier": "npm:prettier@^3.5.3", 17 + "stylelint": "npm:stylelint@^16.18.0", 18 + "stylelint-config-recess-order": "npm:stylelint-config-recess-order@^6.0.0", 19 + "stylelint-config-standard": "npm:stylelint-config-standard@^38.0.0" 10 20 }, 11 21 "nodeModulesDir": "auto", 12 22 "tasks": {
+607 -2
deno.lock
··· 6 6 "npm:esbuild@*": "0.25.1", 7 7 "npm:esbuild@~0.25.1": "0.25.1", 8 8 "npm:prettier@*": "3.5.3", 9 - "npm:prettier@^3.5.3": "3.5.3" 9 + "npm:prettier@^3.5.3": "3.5.3", 10 + "npm:stylelint-config-recess-order@6": "6.0.0_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3", 11 + "npm:stylelint-config-standard@38": "38.0.0_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3", 12 + "npm:stylelint@^16.18.0": "16.18.0_@csstools+css-tokenizer@3.0.3_@csstools+css-parser-algorithms@3.0.4__@csstools+css-tokenizer@3.0.3_postcss-selector-parser@7.1.0_postcss@8.5.3" 10 13 }, 11 14 "jsr": { 12 15 "@std/encoding@1.0.8": { ··· 17 20 } 18 21 }, 19 22 "npm": { 23 + "@babel/code-frame@7.26.2": { 24 + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", 25 + "dependencies": [ 26 + "@babel/helper-validator-identifier", 27 + "js-tokens", 28 + "picocolors" 29 + ] 30 + }, 31 + "@babel/helper-validator-identifier@7.25.9": { 32 + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" 33 + }, 34 + "@csstools/css-parser-algorithms@3.0.4_@csstools+css-tokenizer@3.0.3": { 35 + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", 36 + "dependencies": [ 37 + "@csstools/css-tokenizer" 38 + ] 39 + }, 40 + "@csstools/css-tokenizer@3.0.3": { 41 + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==" 42 + }, 43 + "@csstools/media-query-list-parser@4.0.2_@csstools+css-parser-algorithms@3.0.4__@csstools+css-tokenizer@3.0.3_@csstools+css-tokenizer@3.0.3": { 44 + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", 45 + "dependencies": [ 46 + "@csstools/css-parser-algorithms", 47 + "@csstools/css-tokenizer" 48 + ] 49 + }, 50 + "@csstools/selector-specificity@5.0.0_postcss-selector-parser@7.1.0": { 51 + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", 52 + "dependencies": [ 53 + "postcss-selector-parser" 54 + ] 55 + }, 56 + "@dual-bundle/import-meta-resolve@4.1.0": { 57 + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==" 58 + }, 20 59 "@esbuild/aix-ppc64@0.25.1": { 21 60 "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==" 22 61 }, ··· 92 131 "@esbuild/win32-x64@0.25.1": { 93 132 "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==" 94 133 }, 134 + "@keyv/serialize@1.0.3": { 135 + "integrity": "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==", 136 + "dependencies": [ 137 + "buffer" 138 + ] 139 + }, 140 + "@nodelib/fs.scandir@2.1.5": { 141 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 142 + "dependencies": [ 143 + "@nodelib/fs.stat", 144 + "run-parallel" 145 + ] 146 + }, 147 + "@nodelib/fs.stat@2.0.5": { 148 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" 149 + }, 150 + "@nodelib/fs.walk@1.2.8": { 151 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 152 + "dependencies": [ 153 + "@nodelib/fs.scandir", 154 + "fastq" 155 + ] 156 + }, 157 + "ajv@8.17.1": { 158 + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", 159 + "dependencies": [ 160 + "fast-deep-equal", 161 + "fast-uri", 162 + "json-schema-traverse", 163 + "require-from-string" 164 + ] 165 + }, 166 + "ansi-regex@5.0.1": { 167 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 168 + }, 169 + "ansi-styles@4.3.0": { 170 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 171 + "dependencies": [ 172 + "color-convert" 173 + ] 174 + }, 175 + "argparse@2.0.1": { 176 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 177 + }, 178 + "array-union@2.1.0": { 179 + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" 180 + }, 181 + "astral-regex@2.0.0": { 182 + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" 183 + }, 184 + "balanced-match@2.0.0": { 185 + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==" 186 + }, 187 + "base64-js@1.5.1": { 188 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 189 + }, 190 + "braces@3.0.3": { 191 + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 192 + "dependencies": [ 193 + "fill-range" 194 + ] 195 + }, 196 + "buffer@6.0.3": { 197 + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 198 + "dependencies": [ 199 + "base64-js", 200 + "ieee754" 201 + ] 202 + }, 203 + "cacheable@1.8.10": { 204 + "integrity": "sha512-0ZnbicB/N2R6uziva8l6O6BieBklArWyiGx4GkwAhLKhSHyQtRfM9T1nx7HHuHDKkYB/efJQhz3QJ6x/YqoZzA==", 205 + "dependencies": [ 206 + "hookified", 207 + "keyv" 208 + ] 209 + }, 210 + "callsites@3.1.0": { 211 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" 212 + }, 213 + "color-convert@2.0.1": { 214 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 215 + "dependencies": [ 216 + "color-name" 217 + ] 218 + }, 219 + "color-name@1.1.4": { 220 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 221 + }, 222 + "colord@2.9.3": { 223 + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" 224 + }, 225 + "cosmiconfig@9.0.0": { 226 + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", 227 + "dependencies": [ 228 + "env-paths", 229 + "import-fresh", 230 + "js-yaml", 231 + "parse-json" 232 + ] 233 + }, 234 + "css-functions-list@3.2.3": { 235 + "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==" 236 + }, 237 + "css-tree@3.1.0": { 238 + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", 239 + "dependencies": [ 240 + "mdn-data", 241 + "source-map-js" 242 + ] 243 + }, 244 + "cssesc@3.0.0": { 245 + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" 246 + }, 247 + "debug@4.4.0": { 248 + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 249 + "dependencies": [ 250 + "ms" 251 + ] 252 + }, 253 + "dir-glob@3.0.1": { 254 + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 255 + "dependencies": [ 256 + "path-type" 257 + ] 258 + }, 259 + "emoji-regex@8.0.0": { 260 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 261 + }, 262 + "env-paths@2.2.1": { 263 + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" 264 + }, 265 + "error-ex@1.3.2": { 266 + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 267 + "dependencies": [ 268 + "is-arrayish" 269 + ] 270 + }, 95 271 "esbuild@0.25.1": { 96 272 "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", 97 273 "dependencies": [ ··· 122 298 "@esbuild/win32-x64" 123 299 ] 124 300 }, 301 + "fast-deep-equal@3.1.3": { 302 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 303 + }, 304 + "fast-glob@3.3.3": { 305 + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 306 + "dependencies": [ 307 + "@nodelib/fs.stat", 308 + "@nodelib/fs.walk", 309 + "glob-parent", 310 + "merge2", 311 + "micromatch" 312 + ] 313 + }, 314 + "fast-uri@3.0.6": { 315 + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==" 316 + }, 317 + "fastest-levenshtein@1.0.16": { 318 + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==" 319 + }, 320 + "fastq@1.19.1": { 321 + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 322 + "dependencies": [ 323 + "reusify" 324 + ] 325 + }, 326 + "file-entry-cache@10.0.7": { 327 + "integrity": "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw==", 328 + "dependencies": [ 329 + "flat-cache" 330 + ] 331 + }, 332 + "fill-range@7.1.1": { 333 + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 334 + "dependencies": [ 335 + "to-regex-range" 336 + ] 337 + }, 338 + "flat-cache@6.1.7": { 339 + "integrity": "sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew==", 340 + "dependencies": [ 341 + "cacheable", 342 + "flatted", 343 + "hookified" 344 + ] 345 + }, 346 + "flatted@3.3.3": { 347 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" 348 + }, 349 + "glob-parent@5.1.2": { 350 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 351 + "dependencies": [ 352 + "is-glob" 353 + ] 354 + }, 355 + "global-modules@2.0.0": { 356 + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", 357 + "dependencies": [ 358 + "global-prefix" 359 + ] 360 + }, 361 + "global-prefix@3.0.0": { 362 + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", 363 + "dependencies": [ 364 + "ini", 365 + "kind-of", 366 + "which" 367 + ] 368 + }, 369 + "globby@11.1.0": { 370 + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", 371 + "dependencies": [ 372 + "array-union", 373 + "dir-glob", 374 + "fast-glob", 375 + "ignore@5.3.2", 376 + "merge2", 377 + "slash" 378 + ] 379 + }, 380 + "globjoin@0.1.4": { 381 + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==" 382 + }, 383 + "has-flag@4.0.0": { 384 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 385 + }, 386 + "hookified@1.8.1": { 387 + "integrity": "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA==" 388 + }, 389 + "html-tags@3.3.1": { 390 + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==" 391 + }, 392 + "ieee754@1.2.1": { 393 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" 394 + }, 395 + "ignore@5.3.2": { 396 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" 397 + }, 398 + "ignore@7.0.3": { 399 + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==" 400 + }, 401 + "import-fresh@3.3.1": { 402 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 403 + "dependencies": [ 404 + "parent-module", 405 + "resolve-from@4.0.0" 406 + ] 407 + }, 408 + "imurmurhash@0.1.4": { 409 + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" 410 + }, 411 + "ini@1.3.8": { 412 + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" 413 + }, 414 + "is-arrayish@0.2.1": { 415 + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" 416 + }, 417 + "is-extglob@2.1.1": { 418 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" 419 + }, 420 + "is-fullwidth-code-point@3.0.0": { 421 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 422 + }, 423 + "is-glob@4.0.3": { 424 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 425 + "dependencies": [ 426 + "is-extglob" 427 + ] 428 + }, 429 + "is-number@7.0.0": { 430 + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 431 + }, 432 + "is-plain-object@5.0.0": { 433 + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" 434 + }, 435 + "isexe@2.0.0": { 436 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 437 + }, 438 + "js-tokens@4.0.0": { 439 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 440 + }, 441 + "js-yaml@4.1.0": { 442 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 443 + "dependencies": [ 444 + "argparse" 445 + ] 446 + }, 447 + "json-parse-even-better-errors@2.3.1": { 448 + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" 449 + }, 450 + "json-schema-traverse@1.0.0": { 451 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" 452 + }, 453 + "keyv@5.3.2": { 454 + "integrity": "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ==", 455 + "dependencies": [ 456 + "@keyv/serialize" 457 + ] 458 + }, 459 + "kind-of@6.0.3": { 460 + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" 461 + }, 462 + "known-css-properties@0.35.0": { 463 + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==" 464 + }, 465 + "lines-and-columns@1.2.4": { 466 + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" 467 + }, 468 + "lodash.truncate@4.4.2": { 469 + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" 470 + }, 471 + "mathml-tag-names@2.1.3": { 472 + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==" 473 + }, 474 + "mdn-data@2.12.2": { 475 + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==" 476 + }, 477 + "meow@13.2.0": { 478 + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==" 479 + }, 480 + "merge2@1.4.1": { 481 + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" 482 + }, 483 + "micromatch@4.0.8": { 484 + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 485 + "dependencies": [ 486 + "braces", 487 + "picomatch" 488 + ] 489 + }, 490 + "ms@2.1.3": { 491 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 492 + }, 493 + "nanoid@3.3.10": { 494 + "integrity": "sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg==" 495 + }, 496 + "normalize-path@3.0.0": { 497 + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 498 + }, 499 + "parent-module@1.0.1": { 500 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 501 + "dependencies": [ 502 + "callsites" 503 + ] 504 + }, 505 + "parse-json@5.2.0": { 506 + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 507 + "dependencies": [ 508 + "@babel/code-frame", 509 + "error-ex", 510 + "json-parse-even-better-errors", 511 + "lines-and-columns" 512 + ] 513 + }, 514 + "path-type@4.0.0": { 515 + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" 516 + }, 517 + "picocolors@1.1.1": { 518 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 519 + }, 520 + "picomatch@2.3.1": { 521 + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 522 + }, 523 + "postcss-resolve-nested-selector@0.1.6": { 524 + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==" 525 + }, 526 + "postcss-safe-parser@7.0.1_postcss@8.5.3": { 527 + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", 528 + "dependencies": [ 529 + "postcss" 530 + ] 531 + }, 532 + "postcss-selector-parser@7.1.0": { 533 + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", 534 + "dependencies": [ 535 + "cssesc", 536 + "util-deprecate" 537 + ] 538 + }, 539 + "postcss-sorting@8.0.2_postcss@8.5.3": { 540 + "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", 541 + "dependencies": [ 542 + "postcss" 543 + ] 544 + }, 545 + "postcss-value-parser@4.2.0": { 546 + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" 547 + }, 548 + "postcss@8.5.3": { 549 + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 550 + "dependencies": [ 551 + "nanoid", 552 + "picocolors", 553 + "source-map-js" 554 + ] 555 + }, 125 556 "prettier@3.5.3": { 126 557 "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==" 558 + }, 559 + "queue-microtask@1.2.3": { 560 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" 561 + }, 562 + "require-from-string@2.0.2": { 563 + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" 564 + }, 565 + "resolve-from@4.0.0": { 566 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" 567 + }, 568 + "resolve-from@5.0.0": { 569 + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" 570 + }, 571 + "reusify@1.1.0": { 572 + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" 573 + }, 574 + "run-parallel@1.2.0": { 575 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 576 + "dependencies": [ 577 + "queue-microtask" 578 + ] 579 + }, 580 + "signal-exit@4.1.0": { 581 + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" 582 + }, 583 + "slash@3.0.0": { 584 + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" 585 + }, 586 + "slice-ansi@4.0.0": { 587 + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 588 + "dependencies": [ 589 + "ansi-styles", 590 + "astral-regex", 591 + "is-fullwidth-code-point" 592 + ] 593 + }, 594 + "source-map-js@1.2.1": { 595 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" 596 + }, 597 + "string-width@4.2.3": { 598 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 599 + "dependencies": [ 600 + "emoji-regex", 601 + "is-fullwidth-code-point", 602 + "strip-ansi" 603 + ] 604 + }, 605 + "strip-ansi@6.0.1": { 606 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 607 + "dependencies": [ 608 + "ansi-regex" 609 + ] 610 + }, 611 + "stylelint-config-recess-order@6.0.0_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3": { 612 + "integrity": "sha512-1KqrttqpIrCYFAVQ1/bbgXo7EvvcjmkxxmnzVr+U66Xr2OlrNZqQ5+44Tmct6grCWY6wGTIBh2tSANqcmwIM2g==", 613 + "dependencies": [ 614 + "stylelint", 615 + "stylelint-order" 616 + ] 617 + }, 618 + "stylelint-config-recommended@16.0.0_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3": { 619 + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", 620 + "dependencies": [ 621 + "stylelint" 622 + ] 623 + }, 624 + "stylelint-config-standard@38.0.0_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3": { 625 + "integrity": "sha512-uj3JIX+dpFseqd/DJx8Gy3PcRAJhlEZ2IrlFOc4LUxBX/PNMEQ198x7LCOE2Q5oT9Vw8nyc4CIL78xSqPr6iag==", 626 + "dependencies": [ 627 + "stylelint", 628 + "stylelint-config-recommended" 629 + ] 630 + }, 631 + "stylelint-order@6.0.4_stylelint@16.18.0__@csstools+css-tokenizer@3.0.3__@csstools+css-parser-algorithms@3.0.4___@csstools+css-tokenizer@3.0.3__postcss-selector-parser@7.1.0__postcss@8.5.3_postcss@8.5.3": { 632 + "integrity": "sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==", 633 + "dependencies": [ 634 + "postcss", 635 + "postcss-sorting", 636 + "stylelint" 637 + ] 638 + }, 639 + "stylelint@16.18.0_@csstools+css-tokenizer@3.0.3_@csstools+css-parser-algorithms@3.0.4__@csstools+css-tokenizer@3.0.3_postcss-selector-parser@7.1.0_postcss@8.5.3": { 640 + "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", 641 + "dependencies": [ 642 + "@csstools/css-parser-algorithms", 643 + "@csstools/css-tokenizer", 644 + "@csstools/media-query-list-parser", 645 + "@csstools/selector-specificity", 646 + "@dual-bundle/import-meta-resolve", 647 + "balanced-match", 648 + "colord", 649 + "cosmiconfig", 650 + "css-functions-list", 651 + "css-tree", 652 + "debug", 653 + "fast-glob", 654 + "fastest-levenshtein", 655 + "file-entry-cache", 656 + "global-modules", 657 + "globby", 658 + "globjoin", 659 + "html-tags", 660 + "ignore@7.0.3", 661 + "imurmurhash", 662 + "is-plain-object", 663 + "known-css-properties", 664 + "mathml-tag-names", 665 + "meow", 666 + "micromatch", 667 + "normalize-path", 668 + "picocolors", 669 + "postcss", 670 + "postcss-resolve-nested-selector", 671 + "postcss-safe-parser", 672 + "postcss-selector-parser", 673 + "postcss-value-parser", 674 + "resolve-from@5.0.0", 675 + "string-width", 676 + "supports-hyperlinks", 677 + "svg-tags", 678 + "table", 679 + "write-file-atomic" 680 + ] 681 + }, 682 + "supports-color@7.2.0": { 683 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 684 + "dependencies": [ 685 + "has-flag" 686 + ] 687 + }, 688 + "supports-hyperlinks@3.2.0": { 689 + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", 690 + "dependencies": [ 691 + "has-flag", 692 + "supports-color" 693 + ] 694 + }, 695 + "svg-tags@1.0.0": { 696 + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==" 697 + }, 698 + "table@6.9.0": { 699 + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", 700 + "dependencies": [ 701 + "ajv", 702 + "lodash.truncate", 703 + "slice-ansi", 704 + "string-width", 705 + "strip-ansi" 706 + ] 707 + }, 708 + "to-regex-range@5.0.1": { 709 + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 710 + "dependencies": [ 711 + "is-number" 712 + ] 713 + }, 714 + "util-deprecate@1.0.2": { 715 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 716 + }, 717 + "which@1.3.1": { 718 + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 719 + "dependencies": [ 720 + "isexe" 721 + ] 722 + }, 723 + "write-file-atomic@5.0.1": { 724 + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", 725 + "dependencies": [ 726 + "imurmurhash", 727 + "signal-exit" 728 + ] 127 729 } 128 730 }, 129 731 "workspace": { 130 732 "dependencies": [ 131 733 "npm:esbuild@~0.25.1", 132 - "npm:prettier@^3.5.3" 734 + "npm:prettier@^3.5.3", 735 + "npm:stylelint-config-recess-order@6", 736 + "npm:stylelint-config-standard@38", 737 + "npm:stylelint@^16.18.0" 133 738 ] 134 739 } 135 740 }
+2
docs/Cargo.toml
··· 12 12 [dependencies] 13 13 anyhow = { workspace = true } 14 14 clap = { workspace = true } 15 + env_logger = { workspace = true } 15 16 gix-url = { version = "0.30.0" } 17 + log = { workspace = true } 16 18 miette = { workspace = true } 17 19 serde = { workspace = true } 18 20 serde_json = { workspace = true }
+1 -1
docs/app/build/build.ts
··· 23 23 logLevel: Deno.env.get("CI") ? "info" : undefined, 24 24 }); 25 25 26 - // generate `app/dist.css` and `app/dist.js` for mdBook that actually imports the bundle 26 + // generate `app/dist.css` and `app/dist.js` for mdBook that actually import the bundle 27 27 28 28 const css: string[] = []; 29 29 const esm: string[] = [];
+50 -45
docs/app/main.css
··· 8 8 --mono-font: 9 9 "Source Code Pro", "Consolas", "Ubuntu Mono", "Menlo", "DejaVu Sans Mono", 10 10 ui-monospace, monospace; 11 + 11 12 font-size: 16px; 12 13 } 13 14 ··· 18 19 -apple-system, 19 20 BlinkMacSystemFont, 20 21 "Segoe UI", 21 - "Noto Sans", 22 22 "Helvetica Neue", 23 23 ui-sans-serif, 24 24 sans-serif, ··· 47 47 } 48 48 49 49 p { 50 + margin-block: 0 1rem; 50 51 line-height: 1.6; 51 - margin-block-start: 0; 52 - margin-block-end: 1rem; 53 52 } 54 53 55 54 a { ··· 57 56 text-decoration-thickness: 0.1ex; 58 57 text-underline-offset: 0.6ex; 59 58 59 + strong, 60 + em, 61 + code { 62 + color: inherit; 63 + } 64 + 60 65 &:hover, 61 66 &:active { 62 67 text-decoration: none; ··· 69 74 &:has(del:only-child) { 70 75 text-decoration: none; 71 76 } 72 - 73 - strong, 74 - em, 75 - code { 76 - color: inherit; 77 - } 78 77 } 79 78 80 - sup a { 81 - text-decoration: none; 79 + sup { 80 + a { 81 + text-decoration: none; 82 + } 82 83 } 83 84 84 85 h2, ··· 102 103 103 104 ul, 104 105 ol { 105 - line-height: 1.6; 106 106 padding-inline-start: 1.8rem; 107 + line-height: 1.6; 107 108 } 108 109 109 110 li { ··· 121 122 margin: 0; 122 123 123 124 th { 124 - font-weight: 700; 125 125 padding: 3px 20px; 126 + font-weight: 700; 126 127 background-color: var(--table-header-bg); 127 128 } 128 129 ··· 136 137 tr > td:first-of-type { 137 138 white-space: nowrap; 138 139 139 - @media screen and (max-width: 420px) { 140 + @media screen and (width <= 420px) { 140 141 white-space: normal; 141 142 } 142 143 } ··· 161 162 162 163 blockquote { 163 164 padding: 0 0 0 1rem; 165 + background-color: unset; 164 166 border-block-start: none; 165 167 border-block-end: none; 166 - background-color: unset; 167 168 border-inline-start: 0.25rem solid var(--quote-border); 168 169 169 170 > :last-child { ··· 213 214 } 214 215 215 216 > li:target::before { 217 + inset: 0 0 0 -24px; 218 + outline: 0.5rem solid #ffb45420; 216 219 background-color: #ffb45420; 217 - outline: 0.5rem solid #ffb45420; 218 220 border: none; 219 221 border-radius: 0; 220 - top: 0; 221 - bottom: 0; 222 - left: -24px; 223 - right: 0; 224 222 } 225 223 } 226 224 227 225 :not(h1, h2, h3, h4, h5, h6, .footnote-definition > li):target { 228 - background-color: #ffb45420; 229 226 outline: 0.5rem solid #ffb45420; 227 + background-color: #ffb45420; 230 228 231 229 &.footnote-reference { 230 + outline: 0.25rem solid #ffb45440; 232 231 background-color: #ffb45440; 233 - outline: 0.25rem solid #ffb45440; 234 232 border-radius: 0; 235 233 } 236 234 } ··· 246 244 img, 247 245 video { 248 246 max-width: 100%; 249 - max-height: 100%; 250 247 height: auto; 248 + max-height: 100%; 251 249 object-fit: contain; 252 250 } 253 251 ··· 259 257 min-height: 0; 260 258 margin: 1.8rem; 261 259 262 - @media screen and (max-width: 768px) { 260 + @media screen and (width <= 768px) { 263 261 margin: 0; 264 262 } 265 263 ··· 276 274 } 277 275 278 276 > figcaption { 277 + margin-block-start: 0.8em; 279 278 font-size: 0.8em; 280 279 font-weight: 600; 281 - margin-block-start: 0.8em; 282 280 283 281 > p:last-child { 284 282 margin-block-end: 0; ··· 292 290 } 293 291 294 292 .code-header { 295 - margin: 0; 296 293 padding: 0; 297 - white-space: pre-wrap; 294 + margin: 0; 298 295 font-family: var(--mono-font); 296 + white-space: pre-wrap; 299 297 300 298 & h3 { 301 299 font-size: 1.125rem; ··· 312 310 313 311 figure.fig-text { 314 312 display: block; 315 - margin: 0 0 1rem 0; 316 313 padding: 0.6rem; 314 + margin: 0 0 1rem; 317 315 318 316 > :first-child { 319 317 margin-block-start: 0; ··· 323 321 margin-block-end: 0; 324 322 } 325 323 326 - @media screen and (max-width: 620px) { 324 + @media screen and (width <= 620px) { 327 325 overflow-x: auto; 328 326 329 327 > * { ··· 343 341 } 344 342 345 343 /* à la Google's developer docs */ 346 - 347 - @media screen and (max-width: 420px) { 344 + @media screen and (width <= 420px) { 348 345 :root { 349 346 /* 15px */ 350 347 --page-padding: 0; 348 + --blockquote-padding-block: 0.8rem; 349 + --blockquote-padding-inline: 20px; 351 350 } 352 351 353 352 .content { ··· 357 356 358 357 .content main { 359 358 > * { 360 - padding-inline: 20px; 359 + padding-inline: var(--blockquote-padding-inline); 361 360 } 362 361 363 362 > ul, ··· 374 373 margin-block-end: 1rem; 375 374 376 375 > figcaption { 377 - padding-inline: 20px; 378 - } 379 - 380 - &:not(.fig-text) { 381 - padding-inline: 0; 382 - } 383 - 384 - &.fig-text { 385 - padding: 0.8rem 20px; 376 + padding-inline: var(--blockquote-padding-inline); 386 377 } 387 378 388 379 > pre { ··· 390 381 margin-block-end: 1rem; 391 382 392 383 > code { 393 - padding: 0.8rem 20px; 384 + padding: var(--blockquote-padding-block) var(--blockquote-padding-inline); 394 385 } 395 386 } 396 387 } ··· 400 391 margin-block-end: 1rem; 401 392 402 393 > code { 403 - padding: 0.8rem 20px; 394 + padding: var(--blockquote-padding-block) var(--blockquote-padding-inline); 404 395 } 405 396 } 406 397 407 398 > blockquote { 408 - padding-inline-start: calc(20px - 0.25rem); 399 + padding-inline-start: calc(var(--blockquote-padding-inline) - 0.25rem); 409 400 } 410 401 411 402 .footnote-definition > li:target::before { ··· 421 412 } 422 413 423 414 .nav-wrapper { 424 - padding: 0 20px 32px 20px; 415 + padding: 0 var(--blockquote-padding-inline) 32px; 416 + } 417 + 418 + figure.fig-text { 419 + padding-block: var(--blockquote-padding-block); 420 + padding-inline: 0; 421 + 422 + > p { 423 + padding-inline: var(--blockquote-padding-inline); 424 + } 425 425 } 426 426 } 427 427 ··· 501 501 --mdbook-alerts-color: #d1242f; 502 502 } 503 503 } 504 + 505 + .sidebar li:has(a[href*="_internal/"]), 506 + a[href*="_internal/"] { 507 + display: none; 508 + }
+1
docs/app/main.ts
··· 1 + // https://github.com/rust-lang/mdBook/blob/master/CHANGELOG.md#mdbook-0448 1 2 document 2 3 .querySelectorAll<HTMLElement>(".footnote-definition a[href^='#fr-']") 3 4 .forEach((elem) => {
+1
docs/src/CHANGELOG.md
··· 1 + {{#include ../../crates/mdbookkit/CHANGELOG.md}}
+10 -2
docs/src/SUMMARY.md
··· 4 4 5 5 --- 6 6 7 - - [rustdoc-link](rustdoc-link.md) 7 + - [rustdoc-link](rustdoc-link/index.md) 8 8 - [Getting started](rustdoc-link/getting-started.md) 9 9 - [Motivation](rustdoc-link/motivation.md) 10 10 - [Name resolution](rustdoc-link/name-resolution.md) ··· 12 12 - [Workspace layout](rustdoc-link/workspace-layout.md) 13 13 - [Caching](rustdoc-link/caching.md) 14 14 - [Standalone usage](rustdoc-link/standalone-usage.md) 15 + - [Continuous integration](rustdoc-link/continuous-integration.md) 15 16 - [Configuration](rustdoc-link/configuration.md) 16 17 - [Known issues](rustdoc-link/known-issues.md) 17 18 18 19 --- 19 20 20 - - [link-forever](link-forever.md) 21 + - [link-forever](link-forever/index.md) 21 22 - [Feature sheet](link-forever/features.md) 22 23 - [`{{#include}}` workarounds](link-forever/working-with-include.md) 24 + - [Continuous integration](link-forever/continuous-integration.md) 23 25 - [Configuration](link-forever/configuration.md) 24 26 - [Known issues](link-forever/known-issues.md) 27 + 28 + --- 29 + 30 + [CHANGELOG](CHANGELOG.md) 31 + 32 + [INTERNAL: Repo README](./_internal/README.md)
+7
docs/src/_internal/README.md
··· 1 + # INTERNAL: Repo README 2 + 3 + > [!NOTE] 4 + > 5 + > This page is included only for link validation during build. 6 + 7 + {{#include ../../../README.md}}
+1 -1
docs/src/lib.rs
··· 7 7 use mdbookkit::bin::rustdoc_link::Resolver; 8 8 9 9 mod env { 10 - pub use mdbookkit::bin::rustdoc_link::env::Config; 10 + pub use mdbookkit::{bin::rustdoc_link::env::Config, env::is_ci}; 11 11 }
+13 -13
docs/src/rustdoc-link.md docs/src/rustdoc-link/index.md
··· 35 35 36 36 <figure> 37 37 38 - ![screen recording of mdbook-rustdoc-link during mdbook build](rustdoc-link/media/screencap.webp) 38 + ![screen recording of mdbook-rustdoc-link during mdbook build](media/screencap.webp) 39 39 40 40 </figure> 41 41 42 42 ## Overview 43 43 44 - To get started, simply follow the [quickstart guide](rustdoc-link/getting-started.md)! 44 + To get started, simply follow the [quickstart guide](getting-started.md)! 45 45 46 46 If you would like to read more about this crate: 47 47 48 48 For **writing documentation** — 49 49 50 50 - To learn more about how it is resolving items into links, including 51 - [feature-gated items](rustdoc-link/name-resolution.md#feature-gated-items), see 52 - [Name resolution](rustdoc-link/name-resolution.md). 51 + [feature-gated items](name-resolution.md#feature-gated-items), see 52 + [Name resolution](name-resolution.md). 53 53 - To know how to link to other types of items like 54 - [functions, macros](rustdoc-link/supported-syntax.md#functions-and-macros), and 55 - [implementors](rustdoc-link/supported-syntax.md#implementors-and-fully-qualified-syntax), 56 - see [Supported syntax](rustdoc-link/supported-syntax.md). 54 + [functions, macros](supported-syntax.md#functions-and-macros), and 55 + [implementors](supported-syntax.md#implementors-and-fully-qualified-syntax), see 56 + [Supported syntax](supported-syntax.md). 57 57 58 58 For **adapting this crate to your project** — 59 59 60 60 - If you use [Cargo workspaces][workspaces], see specific instructions in 61 - [Workspace layout](rustdoc-link/workspace-layout.md). 61 + [Workspace layout](workspace-layout.md). 62 62 - If you are working on a large project, and processing is taking a long time, see the 63 - discussion in [Caching](rustdoc-link/caching.md). 63 + discussion in [Caching](caching.md). 64 64 65 65 For **additional usage information** — 66 66 67 67 - You can use this as a standalone command line tool: see 68 - [Standalone usage](rustdoc-link/standalone-usage.md). 69 - - For all available options and how to set them, see 70 - [Configuration](rustdoc-link/configuration.md). 71 - - Finally, review [Known issues](rustdoc-link/known-issues.md) and limitations. 68 + [Standalone usage](standalone-usage.md). 69 + - For tips on using this in CI, see [Continuous integration](continuous-integration.md). 70 + - For all available options and how to set them, see [Configuration](configuration.md). 71 + - Finally, review [Known issues](known-issues.md) and limitations. 72 72 73 73 Happy linking! 74 74
+4 -4
docs/src/rustdoc-link/caching.md
··· 1 1 # Caching 2 2 3 3 By default, `mdbook-rustdoc-link` spawns a fresh `rust-analyzer` process every time it 4 - is run. `rust-analyzer` then reindexes your entire project before resolving links. 4 + is run. rust-analyzer then reindexes your entire project before resolving links. 5 5 6 6 This significantly impacts the responsiveness of `mdbook serve` — it is as if for every 7 7 live reload, you had to reopen your editor, and it gets even worse the more dependencies ··· 85 85 ## Help wanted 🙌 86 86 87 87 The cache feature, as it currently stands, is a workaround at best. If you have insights 88 - on how performance could be further improved, please [open an issue!][gh-issues]. 88 + on how performance could be further improved, please [open an issue!][gh-issues] 89 89 90 90 ### Cache priming and progress tracking 91 91 ··· 97 97 cache priming, before actually sending out external docs requests. This requires parsing 98 98 non-structured log messages that rust-analyzer sends out and some debouncing/throttling 99 99 logic, which is not ideal, see 100 - [client.rs](/crates/mdbookkit/src/bin/rustdoc_link/client.rs#L129-L132). 100 + [client.rs](/crates/mdbookkit/src/bin/rustdoc_link/client.rs#L142). 101 101 102 102 Not waiting for indexing to finish and sending out requests too early causes 103 103 rust-analyzer to respond with empty results. ··· 151 151 152 152 <!-- prettier-ignore-start --> 153 153 154 + [`ra-multiplex`]: https://github.com/pr2502/ra-multiplex 154 155 [gh-issues]: https://github.com/tonywu6/mdbookkit/issues 155 156 [lsp-work-done-progress]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workDoneProgress 156 157 [ra-architecture]: https://rust-analyzer.github.io/book/contributing/architecture.html#:~:text=The%20analyzer%20keeps%20all%20this%20input%20data%20in%20memory%20and%20never%20does%20any%20IO. 157 158 [ra-cache-priming]: https://rust-analyzer.github.io/book/configuration.html?highlight=cache%20priming#configuration 158 159 [ra-persistent-cache]: https://github.com/rust-lang/rust-analyzer/issues/4712 159 - [`ra-multiplex`]: https://github.com/pr2502/ra-multiplex 160 160 [salsa]: https://rust-analyzer.github.io/thisweek/2025/03/17/changelog-277.html 161 161 [specify-exclude-patterns]: https://rust-lang.github.io/mdBook/cli/serve.html#specify-exclude-patterns 162 162
+90
docs/src/rustdoc-link/continuous-integration.md
··· 1 + # Continuous integration 2 + 3 + This page gives information and tips for using `mdbook-rustdoc-link` in a continuous 4 + integration (CI) environment. 5 + 6 + The preprocessor behaves differently in terms of logging, error handling, etc., when it 7 + detects it is running in CI. 8 + 9 + <details class="toc" open> 10 + <summary>Sections</summary> 11 + 12 + - [Detecting CI](#detecting-ci) 13 + - [Installing rust-analyzer](#installing-rust-analyzer) 14 + - [Logging](#logging) 15 + - [Error handling](#error-handling) 16 + 17 + </details> 18 + 19 + ## Detecting CI 20 + 21 + To determine whether it is running in CI, the preprocessor honors the `CI` environment 22 + variable. Specifically: 23 + 24 + - If `CI` is set to `"true"`, then it is considered in CI[^ci-true]; 25 + - Otherwise, it is considered not in CI. 26 + 27 + Most major CI/CD services, such as [GitHub Actions][github-actions-ci] and [GitLab 28 + CI/CD][gitlab-ci], automatically configure this variable for you. 29 + 30 + ## Installing rust-analyzer 31 + 32 + rust-analyzer must be on `PATH` when running in CI[^ra-on-path]. 33 + 34 + One way is to install it via [rustup][rustup-ra]. For example, in [GitHub 35 + Actions][dtolnay/rust-toolchain], you can use: 36 + 37 + ```yaml 38 + steps: 39 + - uses: dtolnay/rust-toolchain@stable 40 + with: 41 + components: rust-analyzer 42 + ``` 43 + 44 + > [!NOTE] 45 + > 46 + > Be aware that rust-analyzer from rustup follows Rust's release schedule, which means 47 + > it may lag behind the version bundled with the VS Code extension. 48 + 49 + ## Logging 50 + 51 + By default, the preprocessor shows a progress spinner when it is running. 52 + 53 + When running in CI, progress is instead printed as logs (using [log] and 54 + [env_logger])[^stderr]. 55 + 56 + You can control logging levels using the [`RUST_LOG`] environment variable. 57 + 58 + ## Error handling 59 + 60 + By default, when the preprocessor encounters any non-fatal issues, such as when a link 61 + fails to resolve, it prints them as warnings but continues to run. This is so that your 62 + book continues to build via `mdbook serve` while you make edits. 63 + 64 + When running in CI, all such warnings are promoted to errors. The preprocessor will exit 65 + with a non-zero status code when there are warnings, which will fail your build. This 66 + prevents outdated or incorrect links from being accidentally deployed. 67 + 68 + You can explicitly control this behavior using the 69 + [`fail-on-warnings`](configuration.md#fail-on-warnings) option. 70 + 71 + [^ra-on-path]: 72 + Unless you use the [`rust-analyzer`](configuration.md#rust-analyzer) option. 73 + 74 + [^ci-true]: 75 + Specifically, when `CI` is anything other than `""`, `"0"`, or `"false"`. The logic 76 + is encapsulated in the [`is_ci`][crate::env::is_ci] function. 77 + 78 + [^stderr]: 79 + Specifically, when stderr is redirected to something that isn't a terminal, such as 80 + a file. 81 + 82 + <!-- prettier-ignore-start --> 83 + 84 + [`RUST_LOG`]: https://docs.rs/env_logger/latest/env_logger/#enabling-logging 85 + [dtolnay/rust-toolchain]: https://github.com/dtolnay/rust-toolchain 86 + [github-actions-ci]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables 87 + [gitlab-ci]: https://docs.gitlab.com/ci/variables/predefined_variables/ 88 + [rustup-ra]: https://rust-analyzer.github.io/book/rust_analyzer_binary.html#rustup 89 + 90 + <!-- prettier-ignore-end -->
+5 -6
docs/src/rustdoc-link/getting-started.md
··· 58 58 59 59 ![screen recording of mdbook-rustdoc-link during mdbook build](media/screencap.webp) 60 60 61 - To read more about this project, feel free to return to 62 - [Overview](../rustdoc-link.md#overview). 61 + To read more about this project, feel free to return to [Overview](index.md#overview). 63 62 64 63 > [!IMPORTANT] 65 64 > ··· 85 84 86 85 <!-- prettier-ignore-start --> 87 86 88 - [preprocessor]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 89 - [rust-analyzer]: https://rust-analyzer.github.io/ 90 - [ra-install]: https://rust-analyzer.github.io/book/rust_analyzer_binary.html 87 + [gh-releases]: https://github.com/tonywu6/mdbookkit/releases 91 88 [open-docs]: https://rust-analyzer.github.io/book/features.html#open-docs 89 + [preprocessor]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 92 90 [ra-extension]: https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer 93 - [gh-releases]: https://github.com/tonywu6/mdbookkit/releases 91 + [ra-install]: https://rust-analyzer.github.io/book/rust_analyzer_binary.html 92 + [rust-analyzer]: https://rust-analyzer.github.io/ 94 93 95 94 <!-- prettier-ignore-end -->
+14 -4
docs/src/rustdoc-link/known-issues.md
··· 7 7 - [Incorrect links](#incorrect-links) 8 8 - [Macros](#macros) 9 9 - [Trait items](#trait-items) 10 + - [Private items](#private-items) 10 11 - [Unresolved items](#unresolved-items) 11 12 - [Associated items on primitive types](#associated-items-on-primitive-types) 12 13 - [Sites other than docs.rs](#sites-other-than-docsrs) ··· 27 28 28 29 ## Incorrect links 29 30 30 - In limited circumstances, rust-analyzer generates links that are incorrect or 31 - inaccessible. Unfortunately, for such items, you will still have to link by hand. 31 + In limited circumstances, the preprocessor generates links that are incorrect or 32 + inaccessible. 32 33 33 34 > [!NOTE] 34 35 > ··· 74 75 https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from<strong>-1</strong> 75 76 </a> 76 77 78 + ### Private items 79 + 80 + rustdoc has a [`private_intra_doc_links`][private_intra_doc_links] lint that warns you 81 + when your public documentation tries to link to private items. 82 + 83 + The preprocessor does not yet warn you about links to private items: rust-analyzer will 84 + generate links for items regardless of their crate-level visibility. 85 + 77 86 ## Unresolved items 78 87 79 88 ### Associated items on primitive types ··· 119 128 120 129 <!-- prettier-ignore-start --> 121 130 131 + [IpV6Addr]: https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from-1 122 132 [macro_export]: https://doc.rust-lang.org/stable/reference/macros-by-example.html#path-based-scope 123 133 [panic]: https://doc.rust-lang.org/stable/std/macro.panic.html 134 + [private_intra_doc_links]: https://doc.rust-lang.org/rustdoc/lints.html#private_intra_doc_links 124 135 [serde_json::json]: https://docs.rs/serde_json/1.0.140/serde_json/macro.json.html 136 + [sourcemap]: https://developer.mozilla.org/en-US/docs/Glossary/Source_map 125 137 [tokio::main]: https://docs.rs/tokio-macros/2.5.0/tokio_macros/attr.main.html 126 - [IpV6Addr]: https://doc.rust-lang.org/stable/core/net/enum.IpAddr.html#method.from-1 127 - [sourcemap]: https://developer.mozilla.org/en-US/docs/Glossary/Source_map 128 138 129 139 <!-- prettier-ignore-end -->
+3 -3
docs/src/rustdoc-link/name-resolution.md
··· 49 49 > [`FromIterator`] is in the prelude starting from Rust 2021. 50 50 51 51 Though technically not required — to make items from your crate more distinguishable 52 - from others in the Markdown source code, you can write `crate::*`: 52 + from others in your Markdown source, you can write `crate::*`: 53 53 54 54 > ```md 55 55 > [Configurations](configuration.md) for the preprocessor is defined in the ··· 184 184 185 185 <!-- prettier-ignore-start --> 186 186 187 - [rustdoc-scoping]: https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html#valid-links 188 187 [didOpen]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen 189 - [why-lsp]: https://matklad.github.io/2022/04/25/why-lsp.html#Alternative-Theory:~:text=a%20language%20server%20must%20analyze%20any%20invalid%20program%20as%20best%20as%20it%20can.%20Working%20with%20incomplete%20and%20invalid%20programs%20is%20the%20first%20complication%20of%20a%20language%20server%20in%20comparison%20to%20a%20compiler. 190 188 [externalDocs]: https://rust-analyzer.github.io/book/contributing/lsp-extensions.html#open-external-documentation 189 + [rustdoc-scoping]: https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html#valid-links 190 + [why-lsp]: https://matklad.github.io/2022/04/25/why-lsp.html#Alternative-Theory:~:text=a%20language%20server%20must%20analyze%20any%20invalid%20program%20as%20best%20as%20it%20can.%20Working%20with%20incomplete%20and%20invalid%20programs%20is%20the%20first%20complication%20of%20a%20language%20server%20in%20comparison%20to%20a%20compiler. 191 191 192 192 <!-- prettier-ignore-end -->
+4 -4
docs/src/rustdoc-link/supported-syntax.md
··· 161 161 > 162 162 > [`std::vec`], [`mod@std::vec`], and [`macro@std::vec`] all link to the `vec` _module_. 163 163 164 - Currently, duplicate names in Rust are allowed only if they correspond to items in 165 - different [namespaces], for example, between macros and modules, and between struct 166 - fields and methods — this is mostly covered by the function and macro syntax, described 167 - [above](#functions-and-macros). 164 + This is largely okay because currently, duplicate names in Rust are allowed only if they 165 + correspond to items in different [namespaces], for example, between macros and modules, 166 + and between struct fields and methods — this is mostly covered by the function and macro 167 + syntax, described [above](#functions-and-macros). 168 168 169 169 If you encounter items that must be disambiguated using rustdoc's disambiguator syntax, 170 170 other than [the "special types" listed below](#special-types), please [file an
+2 -2
docs/src/rustdoc-link/workspace-layout.md
··· 16 16 Error: Cargo.toml does not have any lib or bin target 17 17 ``` 18 18 19 - This means it found your workspace `Cargo.toml` instead of a crate manifest. To use the 19 + This means it found your workspace `Cargo.toml` instead of a member crate's. To use the 20 20 preprocessor in this case, some extra setup is needed. 21 21 22 22 <details class="toc" open> ··· 90 90 91 91 A possible workaround would be to turn your book folder into a private crate that 92 92 depends on the crates you would like to document. Then you can link to them as if they 93 - are third-party crates. 93 + were third-party crates. 94 94 95 95 ``` 96 96 my-workspace/
+14
release-plz.toml
··· 3 3 4 4 [[package]] 5 5 name = "mdbookkit" 6 + 7 + [changelog] 8 + header = """# CHANGELOG 9 + 10 + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and 11 + this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 12 + 13 + This file is autogenerated using [release-plz](https://release-plz.dev). 14 + 15 + ## Unreleased 16 + 17 + > Unreleased changes appear here, if any. 18 + 19 + """
+1 -1
utils/mdbook-socials/src/main.rs
··· 163 163 elem.set_inner_content(&title, ContentType::Text); 164 164 Ok(()) 165 165 }), 166 - element!(r#"meta[property^="og"]"#, |elem| { 166 + element!(r#"meta[property^="og:"]"#, |elem| { 167 167 elem.remove(); 168 168 Ok(()) 169 169 }),
+1 -1
utils/testing/src/lib.rs
··· 97 97 log::info!("{because}"); 98 98 true 99 99 } else { 100 - panic!("{because} but CI=true") 100 + panic!("{because} but CI={ci}") 101 101 } 102 102 } 103 103