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

Configure Feed

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

[docs] touch up crate docs

+133 -95
+3 -1
README.md
··· 1 1 # mdbookkit 2 2 3 - ![mdbookkit hero image](docs/src/media/social.webp) 3 + ![mdbookkit hero image](docs/src/media/banner.webp) 4 + 5 + ## [Read the book](https://tonywu6.github.io/mdbookkit)
+1
crates/mdbookkit/src/bin/rustdoc_link/client.rs
··· 40 40 sync::{EventSampler, EventSampling}, 41 41 }; 42 42 43 + /// LSP client to talk to rust-analyzer. 43 44 #[derive(Debug)] 44 45 pub struct Client { 45 46 env: Environment,
+3 -6
crates/mdbookkit/src/bin/rustdoc_link/page/diagnostic.rs
··· 2 2 3 3 use crate::diagnostics::{Diagnostics, ReportBuilder}; 4 4 5 - use super::{ 6 - super::link::diagnostic::{LinkDiagnostic, LinkStatus}, 7 - Pages, 8 - }; 5 + use super::{super::link::diagnostic::LinkDiagnostic, Pages}; 9 6 10 7 impl<'a, K: Eq + Hash> Pages<'a, K> { 11 8 pub fn diagnostics(&self) -> Vec<PageDiagnostics<'a, K>> ··· 29 26 } 30 27 } 31 28 32 - type PageDiagnostics<'a, K> = Diagnostics<'a, K, LinkDiagnostic, LinkStatus>; 29 + type PageDiagnostics<'a, K> = Diagnostics<'a, K, LinkDiagnostic>; 33 30 34 - type PageReporter<'a, K> = ReportBuilder<'a, K, LinkDiagnostic, LinkStatus, fn(&K) -> String>; 31 + type PageReporter<'a, K> = ReportBuilder<'a, K, LinkDiagnostic, fn(&K) -> String>;
+57 -64
crates/mdbookkit/src/diagnostics.rs
··· 1 + //! Error reporting for preprocessors. 2 + 1 3 use std::fmt::{self, Debug, Display, Write}; 2 4 3 5 use log::{Level, LevelFilter}; ··· 8 10 use owo_colors::Style; 9 11 use tap::{Pipe, Tap}; 10 12 13 + /// Trait for Markdown diagnostics. This will eventually be printed to stderr. 14 + /// 15 + /// Each [`Problem`] represents a specific message, such as a warning, associated with 16 + /// an [`Issue`] (the type and severity of the issue) and a location in the Markdown 17 + /// source, represented by [`LabeledSpan`]. 11 18 pub trait Problem: Send + Sync { 12 19 type Kind: Issue; 13 20 fn issue(&self) -> Self::Kind; 14 21 fn label(&self) -> LabeledSpan; 15 22 } 16 23 24 + /// Trait for diagnostics classes. This is like a specific error code. 17 25 pub trait Issue: Default + Debug + Display + Clone + Send + Sync { 18 26 fn level(&self) -> Level; 19 27 } 20 28 21 - pub struct Diagnostics<'a, K, P, S> { 29 + /// A collection of [`Problem`]s associated with a Markdown file. 30 + pub struct Diagnostics<'a, K, P> { 22 31 text: &'a str, 23 32 name: K, 24 33 issues: Vec<P>, 25 - status: S, 26 34 } 27 35 28 - impl<K, P> Diagnostics<'_, K, P, P::Kind> 36 + impl<K, P> Diagnostics<'_, K, P> 29 37 where 30 38 K: Title, 31 39 P: Problem, 32 40 { 41 + /// Render a report of the diagnostics using [miette]'s graphical reporting 33 42 pub fn to_report(&self, colored: bool) -> String { 34 43 let handler = if colored { 35 44 GraphicalTheme::unicode() ··· 43 52 .tap_mut(|t| t.styles.warning = Style::new().yellow().toggle(colored)) 44 53 .tap_mut(|t| t.styles.error = Style::new().red().toggle(colored)) 45 54 .tap_mut(|t| { 55 + // pre-emptively specify colors for all diagnostics, just for this collection 56 + // doing this because miette doesn't support associating colors with labels yet 46 57 t.styles.highlights = if colored { 47 58 self.issues 48 59 .iter() ··· 59 70 output 60 71 } 61 72 73 + /// Render the diagnostics as a list of log messages suitable for [`log`]. 62 74 pub fn to_logs(&self) -> String { 63 75 let mut output = String::new(); 64 76 LoggingReportHandler ··· 68 80 } 69 81 } 70 82 71 - impl<'a, K, P> Diagnostics<'a, K, P, P::Kind> 83 + impl<'a, K, P> Diagnostics<'a, K, P> 72 84 where 73 85 P: Problem, 74 86 { 75 87 pub fn new(text: &'a str, name: K, issues: Vec<P>) -> Self { 76 - let status = issues 77 - .iter() 78 - .map(|p| p.issue()) 79 - .min_by_key(|s| s.level()) 80 - .unwrap_or_default(); 81 - Self { 82 - text, 83 - name, 84 - issues, 85 - status, 86 - } 88 + Self { text, name, issues } 87 89 } 88 90 89 91 pub fn filtered(self, level: LevelFilter) -> Option<Self> { 90 - let Self { 91 - text, 92 - name, 93 - issues, 94 - status, 95 - } = self; 92 + let Self { text, name, issues } = self; 96 93 let issues = issues 97 94 .into_iter() 98 95 .filter(|p| p.issue().level() <= level) ··· 100 97 if issues.is_empty() { 101 98 None 102 99 } else { 103 - Some(Self { 104 - text, 105 - name, 106 - issues, 107 - status, 108 - }) 100 + Some(Self { text, name, issues }) 109 101 } 110 102 } 103 + 104 + fn status(&self) -> P::Kind { 105 + self.issues 106 + .iter() 107 + .map(|p| p.issue()) 108 + .min_by_key(|s| s.level()) 109 + .unwrap_or_default() 110 + } 111 111 } 112 112 113 - impl<K, P> Diagnostic for Diagnostics<'_, K, P, P::Kind> 113 + impl<K, P> Diagnostic for Diagnostics<'_, K, P> 114 114 where 115 115 K: Title, 116 116 P: Problem, 117 117 { 118 118 fn severity(&self) -> Option<Severity> { 119 - match self.status.level() { 119 + match self.status().level() { 120 120 Level::Error => Some(Severity::Error), 121 121 Level::Warn => Some(Severity::Warning), 122 122 _ => Some(Severity::Advice), ··· 132 132 } 133 133 134 134 fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> { 135 + // miette doesn't print the file name if there are no labels to report 136 + // so we print it here 135 137 if self.issues.is_empty() { 136 138 Some(Box::new(format!("in {}", self.name))) 137 139 } else { ··· 140 142 } 141 143 } 142 144 143 - impl<K, P, S> SourceCode for Diagnostics<'_, K, P, S> 145 + impl<K, P> SourceCode for Diagnostics<'_, K, P> 144 146 where 145 147 K: Title, 146 148 P: Send + Sync, 147 - S: Send + Sync, 148 149 { 149 150 fn read_span<'a>( 150 151 &'a self, ··· 168 169 } 169 170 } 170 171 171 - impl<K, P: Problem> Debug for Diagnostics<'_, K, P, P::Kind> { 172 + impl<K, P: Problem> Debug for Diagnostics<'_, K, P> { 172 173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 173 - fmt::Debug::fmt(&self.status, f) 174 + fmt::Debug::fmt(&self.status(), f) 174 175 } 175 176 } 176 177 177 - impl<K, P: Problem> Display for Diagnostics<'_, K, P, P::Kind> { 178 + impl<K, P: Problem> Display for Diagnostics<'_, K, P> { 178 179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 179 - fmt::Display::fmt(&self.status, f) 180 + fmt::Display::fmt(&self.status(), f) 180 181 } 181 182 } 182 183 183 - impl<K, P: Problem> std::error::Error for Diagnostics<'_, K, P, P::Kind> {} 184 + impl<K, P: Problem> std::error::Error for Diagnostics<'_, K, P> {} 184 185 185 - pub struct ReportBuilder<'a, K, P, S, F> { 186 - items: Vec<Diagnostics<'a, K, P, S>>, 186 + /// Builder for printing diagnostics over multiple files. 187 + pub struct ReportBuilder<'a, K, P, F> { 188 + items: Vec<Diagnostics<'a, K, P>>, 187 189 print_name: F, 188 190 log_filter: LevelFilter, 189 191 colored: bool, 190 192 logging: bool, 191 193 } 192 194 193 - impl<'a, K, P, S, F> ReportBuilder<'a, K, P, S, F> { 194 - pub fn new(items: Vec<Diagnostics<'a, K, P, S>>, print_name: F) -> Self { 195 + impl<'a, K, P, F> ReportBuilder<'a, K, P, F> { 196 + pub fn new(items: Vec<Diagnostics<'a, K, P>>, print_name: F) -> Self { 195 197 Self { 196 198 items, 197 199 print_name, ··· 201 203 } 202 204 } 203 205 204 - pub fn names<G>(self, print_name: G) -> ReportBuilder<'a, K, P, S, G> 206 + /// Specify how file names should be printed. 207 + pub fn names<G>(self, print_name: G) -> ReportBuilder<'a, K, P, G> 205 208 where 206 209 G: for<'b> Fn(&'b K) -> String, 207 210 { ··· 237 240 } 238 241 } 239 242 240 - impl<'a, K, P, F> ReportBuilder<'a, K, P, P::Kind, F> 243 + impl<'a, K, P, F> ReportBuilder<'a, K, P, F> 241 244 where 242 245 P: Problem, 243 246 { 244 - pub fn build(self) -> Reporter<'a, P, P::Kind> 247 + pub fn build(self) -> Reporter<'a, P> 245 248 where 246 249 F: for<'b> Fn(&'b K) -> String, 247 250 { ··· 256 259 let items = items 257 260 .into_iter() 258 261 .filter_map(|p| { 259 - if p.status.level() > log_filter { 262 + if p.status().level() > log_filter { 260 263 return None; 261 264 } 262 265 263 - let Diagnostics { 264 - text, 265 - name, 266 - issues, 267 - status, 268 - } = p.filtered(log_filter)?; 266 + let Diagnostics { text, name, issues } = p.filtered(log_filter)?; 269 267 270 268 Some(Diagnostics { 271 269 text, 272 270 name: print_name(&name), 273 271 issues, 274 - status, 275 272 }) 276 273 }) 277 274 .collect::<Vec<_>>(); 278 275 279 - let status = items 280 - .iter() 281 - .map(|p| p.status.clone()) 282 - .min_by_key(|s| s.level()) 283 - .unwrap_or_default(); 284 - 285 276 Reporter { 286 277 items, 287 - status, 288 278 colored, 289 279 logging, 290 280 } 291 281 } 292 282 } 293 283 294 - pub struct Reporter<'a, P, S> { 295 - items: Vec<Diagnostics<'a, String, P, S>>, 296 - status: S, 284 + pub struct Reporter<'a, P> { 285 + items: Vec<Diagnostics<'a, String, P>>, 297 286 colored: bool, 298 287 logging: bool, 299 288 } 300 289 301 - impl<P> Reporter<'_, P, P::Kind> 290 + impl<P> Reporter<'_, P> 302 291 where 303 292 P: Problem, 304 293 { 305 294 pub fn to_status(&self) -> P::Kind { 306 - self.status.clone() 295 + self.items 296 + .iter() 297 + .map(|p| p.status()) 298 + .min_by_key(|s| s.level()) 299 + .unwrap_or_default() 307 300 } 308 301 309 302 pub fn to_stderr(&self) -> &Self { ··· 312 305 } 313 306 314 307 if self.logging { 315 - let status = self.status.clone(); 308 + let status = self.to_status(); 316 309 let logs = self.to_logs(); 317 310 log::log!(status.level(), "{logs}"); 318 311 } else {
+5
crates/mdbookkit/src/env.rs
··· 2 2 use serde::Deserialize; 3 3 use tap::Pipe; 4 4 5 + /// Flag indicating how the program should proceed when there are warnings. 6 + /// 7 + /// Used in preprocessor options. 8 + /// 9 + /// Doc comments for variants in this enum will show up in autogenerated docs. 5 10 #[cfg_attr(feature = "common-cli", derive(clap::ValueEnum))] 6 11 #[derive(Deserialize, Debug, Default, Clone, Copy)] 7 12 #[serde(rename_all = "lowercase")]
+3
crates/mdbookkit/src/lib.rs
··· 3 3 //! This is the lib documentation. If you are looking for the mdBook [preprocessors] 4 4 //! that this crate provides, visit <https://tonywu6.github.io/mdbookkit/> instead. 5 5 //! 6 + //! At the moment, the sole purpose of this crate is to facilitate easier testing. Most of the APIs 7 + //! are not designed for library use and are explicitly NOT stable. 8 + //! 6 9 //! [preprocessors]: https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html 7 10 8 11 #[cfg(feature = "common-logger")]
+2
crates/mdbookkit/src/logging.rs
··· 1 + //! Progress reporting and logging for preprocessors. 2 + 1 3 use std::{fmt, sync::mpsc}; 2 4 3 5 pub fn spinner() -> SpinnerHandle {
+23 -12
crates/mdbookkit/src/logging/terminal.rs
··· 15 15 16 16 use super::{styled, Message}; 17 17 18 - /// Either a [console::Term] or an [env_logger::Logger]. 18 + /// Either a [`console::Term`] or an [`env_logger::Logger`]. 19 + /// 20 + /// This is automatically detected upon installing as the global logger. The logic is: 21 + /// 22 + /// - If the `RUST_LOG` env var is set, this will use [`env_logger`]. 23 + /// - If stderr is not "user-attended", as determined by [`console::user_attended_stderr()`], 24 + /// like if stderr is piped to a file, this will use [`env_logger`]. 25 + /// - Otherwise, this will use [`console`]. 26 + /// 27 + /// When this is a [`console::Term`], logs are handled by the global [`indicatif`] spinner. 28 + /// 29 + /// When this is an [`env_logger::Logger`], there will not be a spinner, and progress 30 + /// reports are printed as logs instead. 19 31 pub enum ConsoleLogger { 20 32 Console(Term), 21 33 Logger(Logger), 22 34 } 23 35 24 - impl Default for ConsoleLogger { 25 - fn default() -> Self { 36 + impl ConsoleLogger { 37 + /// Install a [`ConsoleLogger`] as the global [`log`] logger. 38 + pub fn install(name: &str) { 39 + maybe_spinner(name); 40 + let logger = Box::new(Self::new()); 41 + log::set_boxed_logger(logger).expect("logger should not have been set"); 42 + log::set_max_level(LevelFilter::max()); 43 + } 44 + 45 + fn new() -> Self { 26 46 if let Some(spinner) = SPINNER.get() { 27 47 Self::Console(spinner.term.clone()) 28 48 } else { ··· 32 52 .build() 33 53 .pipe(Self::Logger) 34 54 } 35 - } 36 - } 37 - 38 - impl ConsoleLogger { 39 - pub fn install(name: &str) { 40 - maybe_spinner(name); 41 - let logger = Box::new(Self::default()); 42 - log::set_boxed_logger(logger).expect("logger should not have been set"); 43 - log::set_max_level(LevelFilter::max()); 44 55 } 45 56 } 46 57
+35 -11
crates/mdbookkit/src/markdown.rs
··· 1 + //! Markdown-related utilities. 2 + 1 3 use std::{borrow::Cow, fmt::Write, ops::Range}; 2 4 3 5 use pulldown_cmark::{Event, Options}; 4 6 use pulldown_cmark_to_cmark::{cmark, Error}; 5 7 use tap::Pipe; 6 8 9 + /// _Patch_ a Markdown string, instead of regenerating it entirely. 10 + /// 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] 17 + /// which works on GitHub's ["alerts"][gh-alerts] syntax. 18 + /// 19 + /// [alerts]: https://crates.io/crates/mdbook-alerts 20 + /// [gh-alerts]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts 7 21 pub struct PatchStream<'a, S> { 8 22 source: &'a str, 9 23 stream: S, ··· 42 56 } 43 57 } 44 58 59 + impl<'a, S> PatchStream<'a, S> { 60 + /// Create a new patch stream. 61 + /// 62 + /// `stream` should be an [`Iterator`] yielding (`E`, [`Range<usize>`]), where `E` is an 63 + /// [`Iterator`] yielding [`Event`]s. 64 + /// 65 + /// [`Range<usize>`] is the byte span in `source` that should be patched. 66 + /// 67 + /// `E` is the replacement event stream to be rendered into `source` 68 + /// using [`pulldown_cmark_to_cmark`]. 69 + pub fn new(source: &'a str, stream: S) -> Self { 70 + Self { 71 + source, 72 + stream, 73 + start: Some(0), 74 + patch: None, 75 + } 76 + } 77 + } 78 + 45 79 impl<'a, S> PatchStream<'a, S> 46 80 where 47 81 Self: Iterator<Item = Result<Cow<'a, str>, Error>>, 48 82 { 83 + /// Render the patched Markdown source. 49 84 pub fn into_string(self) -> Result<String, Error> { 50 85 let mut out = String::new(); 51 86 for chunk in self { 52 87 write!(out, "{}", chunk?).unwrap(); 53 88 } 54 89 Ok(out) 55 - } 56 - } 57 - 58 - impl<'a, S> PatchStream<'a, S> { 59 - pub fn new(source: &'a str, stream: S) -> Self { 60 - Self { 61 - source, 62 - stream, 63 - start: Some(0), 64 - patch: None, 65 - } 66 90 } 67 91 } 68 92