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: print config docs from commands

Tony Wu 6069cdba 2c729f8c

+230 -314
+1 -14
Cargo.lock
··· 1656 1656 "log", 1657 1657 "mdbook", 1658 1658 "miette", 1659 + "minijinja", 1659 1660 "owo-colors", 1660 1661 "pulldown-cmark 0.13.0", 1661 1662 "pulldown-cmark-to-cmark", ··· 3228 3229 version = "0.2.2" 3229 3230 source = "registry+https://github.com/rust-lang/crates.io-index" 3230 3231 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3231 - 3232 - [[package]] 3233 - name = "util-clap-reflect" 3234 - version = "0.1.0" 3235 - dependencies = [ 3236 - "anyhow", 3237 - "clap", 3238 - "mdbook", 3239 - "mdbookkit", 3240 - "minijinja", 3241 - "serde", 3242 - "serde_json", 3243 - "tap", 3244 - ] 3245 3232 3246 3233 [[package]] 3247 3234 name = "util-mdbook-socials"
+3
crates/mdbook-permalinks/Cargo.toml
··· 40 40 predicates = { workspace = true } 41 41 rstest = { workspace = true } 42 42 tempfile = { workspace = true } 43 + 44 + [features] 45 + _testing = ["mdbookkit/_testing"]
+8
crates/mdbook-permalinks/src/main.rs
··· 41 41 42 42 match command { 43 43 Some(Command::Supports { .. }) => return Ok(()), 44 + #[cfg(feature = "_testing")] 45 + Some(Command::Describe) => { 46 + print!("{}", mdbookkit::docs::describe_preprocessor::<Config>()?); 47 + return Ok(()); 48 + } 44 49 None => {} 45 50 } 46 51 ··· 120 125 enum Command { 121 126 #[clap(hide = true)] 122 127 Supports { renderer: String }, 128 + #[cfg(feature = "_testing")] 129 + #[clap(hide = true)] 130 + Describe, 123 131 } 124 132 125 133 struct Environment {
+3
crates/mdbook-rustdoc-links/Cargo.toml
··· 55 55 rstest = { workspace = true } 56 56 similar = { workspace = true } 57 57 tempfile = { workspace = true } 58 + 59 + [features] 60 + _testing = ["mdbookkit/_testing"]
+12
crates/mdbook-rustdoc-links/src/main.rs
··· 181 181 Some(Command::Supports { .. }) => Ok(()), 182 182 Some(Command::Markdown(options)) => markdown(options).await, 183 183 Some(Command::RustAnalyzer) => which(), 184 + #[cfg(feature = "_testing")] 185 + Some(Command::Describe) => describe(), 184 186 None => mdbook().await, 185 187 } 186 188 } ··· 204 206 /// See <https://rust-lang.github.io/mdBook/for_developers/preprocessors.html#hooking-into-mdbook> 205 207 #[clap(hide = true)] 206 208 Supports { renderer: String }, 209 + 210 + #[cfg(feature = "_testing")] 211 + #[clap(hide = true)] 212 + Describe, 207 213 } 208 214 209 215 async fn mdbook() -> Result<()> { ··· 333 339 RustAnalyzer::Path => println!("using rust-analyzer on PATH (run `which rust-analyzer`)"), 334 340 } 335 341 342 + Ok(()) 343 + } 344 + 345 + #[cfg(feature = "_testing")] 346 + fn describe() -> Result<()> { 347 + print!("{}", mdbookkit::docs::describe_preprocessor::<Config>()?); 336 348 Ok(()) 337 349 } 338 350
+2 -1
crates/mdbookkit/Cargo.toml
··· 36 36 37 37 cargo-run-bin = { workspace = true, optional = true } 38 38 insta = { workspace = true, optional = true } 39 + minijinja = { workspace = true, optional = true } 39 40 url = { workspace = true, optional = true } 40 41 41 42 [features] 42 - _testing = ["dep:cargo-run-bin", "dep:insta", "dep:url"] 43 + _testing = ["dep:cargo-run-bin", "dep:insta", "dep:minijinja", "dep:url"] 43 44 44 45 [package.metadata.docs.rs] 45 46 all-features = true
+199
crates/mdbookkit/src/docs.rs
··· 1 + use anyhow::Result; 2 + use clap::{ArgAction, CommandFactory}; 3 + use minijinja::render; 4 + use serde::Serialize; 5 + use tap::Tap; 6 + 7 + pub fn describe_preprocessor<C: CommandFactory>() -> Result<String> { 8 + let template = r#" 9 + <div class="table-wrapper"> 10 + <table> 11 + 12 + <thead> 13 + <tr> 14 + <td>Option</td> 15 + <td>Summary</td> 16 + </tr> 17 + </thead> 18 + 19 + <tbody> 20 + 21 + {% for option in options %} 22 + 23 + <tr> 24 + <td style="text-align: left;"> 25 + 26 + [`{{ option.key }}`](#{{ option.key }}) 27 + 28 + </td> 29 + <td> 30 + 31 + {{ option.help }} 32 + 33 + </td> 34 + </tr> 35 + 36 + {% endfor %} 37 + 38 + </tbody> 39 + 40 + </table> 41 + </div> 42 + 43 + {% for option in options -%} 44 + 45 + ## `{{ option.key }}` 46 + 47 + {{ option.description }} 48 + 49 + {% if option.choices %} 50 + <div class="table-wrapper"> 51 + <table> 52 + 53 + <thead> 54 + <tr> 55 + <td>Choice</td> 56 + <td>Description</td> 57 + </tr> 58 + </thead> 59 + 60 + <tbody> 61 + 62 + {% for choice, description in option.choices %} 63 + <tr> 64 + 65 + <td style="text-align: left;"> 66 + <code>{{ choice }}</code> 67 + </td> 68 + 69 + <td> 70 + 71 + {{ description }} 72 + 73 + </td> 74 + 75 + </tr> 76 + {% endfor %} 77 + 78 + </tbody> 79 + 80 + </table> 81 + </div> 82 + {% endif %} 83 + 84 + <div class="table-wrapper"> 85 + <table> 86 + <tbody> 87 + 88 + {%- if option.default -%} 89 + 90 + <tr> 91 + <th style="text-align: left;">Default</th> 92 + <td> 93 + 94 + `{{ option.default }}` 95 + 96 + </td> 97 + </tr> 98 + 99 + {%- endif -%} 100 + 101 + {%- if option.type_id -%} 102 + 103 + <tr> 104 + <th style="text-align: left;">Type</th> 105 + <td> 106 + 107 + [`{{ option.type_id[0] }}`][{{ option.type_id[1] }}] 108 + 109 + </td> 110 + </tr> 111 + 112 + {%- endif -%} 113 + 114 + </tbody> 115 + </table> 116 + </div> 117 + 118 + {% endfor -%} 119 + "#; 120 + 121 + let options = C::command() 122 + .get_opts() 123 + .filter(|opt| !opt.is_hide_set()) 124 + .map(|opt| { 125 + let key = opt.get_long().unwrap().to_owned(); 126 + 127 + let help = opt.get_help().map(|h| h.to_string()).unwrap_or_default(); 128 + 129 + let description = opt 130 + .get_long_help() 131 + .map(|h| h.to_string()) 132 + .unwrap_or(help.clone()); 133 + 134 + let action = opt.get_action(); 135 + 136 + let type_id = if cfg!(debug_assertions) { 137 + let ty = format!("{:?}", opt.get_value_parser().type_id()) 138 + .replace("alloc::string::", ""); 139 + let name = ty.split("::").last().unwrap(); 140 + if matches!(action, ArgAction::Append) { 141 + Some((format!("Vec<{name}>"), format!("Vec<{ty}>"))) 142 + } else { 143 + Some((name.to_owned(), ty)) 144 + } 145 + } else { 146 + None 147 + }; 148 + 149 + let default = if let Some(d) = opt.get_default_values().iter().next() { 150 + Some(format!("{:?}", d.to_string_lossy().into_owned())) 151 + } else if matches!(action, ArgAction::SetTrue) { 152 + Some("false".into()) 153 + } else if matches!(action, ArgAction::SetFalse) { 154 + Some("true".into()) 155 + } else if matches!(action, ArgAction::Append) { 156 + Some("[]".into()) 157 + } else if !opt.is_required_set() { 158 + Some("None".into()) 159 + } else { 160 + None 161 + }; 162 + 163 + let choices = opt 164 + .get_possible_values() 165 + .iter() 166 + .filter_map(|v| { 167 + if v.is_hide_set() { 168 + None 169 + } else { 170 + let help = v.get_help()?; 171 + Some((format!("{:?}", v.get_name()), help.to_string())) 172 + } 173 + }) 174 + .collect(); 175 + 176 + #[derive(Serialize)] 177 + struct OptionItem { 178 + key: String, 179 + help: String, 180 + description: String, 181 + type_id: Option<(String, String)>, 182 + default: Option<String>, 183 + choices: Vec<(String, String)>, 184 + } 185 + 186 + OptionItem { 187 + key, 188 + help, 189 + description, 190 + type_id, 191 + default, 192 + choices, 193 + } 194 + }) 195 + .collect::<Vec<_>>() 196 + .tap_mut(|opts| opts.sort_by(|a, b| a.key.cmp(&b.key))); 197 + 198 + Ok(render!(template, options)) 199 + }
+2
crates/mdbookkit/src/lib.rs
··· 10 10 11 11 pub mod book; 12 12 pub mod diagnostics; 13 + #[cfg(feature = "_testing")] 14 + pub mod docs; 13 15 pub mod error; 14 16 pub mod logging; 15 17 pub mod markdown;
-20
utils/clap-reflect/Cargo.toml
··· 1 - [package] 2 - name = "util-clap-reflect" 3 - version = "0.1.0" 4 - 5 - authors.workspace = true 6 - edition.workspace = true 7 - license.workspace = true 8 - publish.workspace = true 9 - repository.workspace = true 10 - rust-version.workspace = true 11 - 12 - [dependencies] 13 - anyhow = { workspace = true } 14 - clap = { workspace = true } 15 - mdbook = { workspace = true } 16 - mdbookkit = { workspace = true } 17 - minijinja = { workspace = true } 18 - serde = { workspace = true } 19 - serde_json = { workspace = true } 20 - tap = { workspace = true }
-279
utils/clap-reflect/src/main.rs
··· 1 - //! Derive Markdown documentation from [`clap::Parser`]s. 2 - //! 3 - //! Functions as an mdBook preprocessor that replaces 4 - //! `<cmd-option>(autogenerated)</cmd-option>` with said documentation. 5 - //! 6 - //! - [mdbookkit::bin::rustdoc_link::env::Config] 7 - //! - [mdbookkit::bin::link_forever::Config] 8 - 9 - use std::io::{Read, Write}; 10 - 11 - use anyhow::Result; 12 - use clap::{ArgAction, CommandFactory, Parser, Subcommand, ValueEnum}; 13 - use mdbook::{book::Book, preprocess::PreprocessorContext, BookItem}; 14 - use minijinja::render; 15 - use serde::Serialize; 16 - 17 - use tap::{Pipe, Tap}; 18 - 19 - #[derive(Parser, Debug, Clone)] 20 - struct Program { 21 - #[arg(long, value_enum)] 22 - reflect: OptionType, 23 - #[command(subcommand)] 24 - command: Option<Command>, 25 - } 26 - 27 - #[derive(Subcommand, Debug, Clone)] 28 - enum Command { 29 - Supports { renderer: String }, 30 - Describe { ty: OptionType }, 31 - } 32 - 33 - #[derive(ValueEnum, Debug, Clone)] 34 - #[clap(rename_all = "kebab-case")] 35 - enum OptionType { 36 - RustdocLinkOptions, 37 - LinkForeverOptions, 38 - } 39 - 40 - impl OptionType { 41 - fn tag(&self) -> String { 42 - let tag = self.to_possible_value().unwrap().get_name().to_owned(); 43 - format!("<{tag}>(autogenerated)</{tag}>") 44 - } 45 - 46 - fn describe(&self) -> Result<String> { 47 - match self { 48 - Self::RustdocLinkOptions => { 49 - describe_options::<mdbookkit::bin::rustdoc_link::env::Config>() 50 - } 51 - Self::LinkForeverOptions => describe_options::<mdbookkit::bin::link_forever::Config>(), 52 - } 53 - } 54 - } 55 - 56 - fn main() -> Result<()> { 57 - let ty = match Program::parse() { 58 - Program { 59 - command: Some(Command::Supports { .. }), 60 - .. 61 - } => return Ok(()), 62 - 63 - Program { reflect, .. } => reflect, 64 - }; 65 - 66 - let (_, mut book): (PreprocessorContext, Book) = Vec::new() 67 - .pipe(|mut buf| std::io::stdin().read_to_end(&mut buf).and(Ok(buf)))? 68 - .pipe(String::from_utf8)? 69 - .pipe_as_ref(serde_json::from_str)?; 70 - 71 - let content = ty.describe()?; 72 - 73 - let tag = ty.tag(); 74 - 75 - book.for_each_mut(|page| { 76 - let BookItem::Chapter(page) = page else { 77 - return; 78 - }; 79 - page.content = page.content.replace(&tag, &content); 80 - }); 81 - 82 - let output = serde_json::to_string(&book)?; 83 - std::io::stdout().write_all(output.as_bytes())?; 84 - Ok(()) 85 - } 86 - 87 - #[derive(Serialize)] 88 - struct OptionItem { 89 - key: String, 90 - help: String, 91 - description: String, 92 - type_id: Option<(String, String)>, 93 - default: Option<String>, 94 - choices: Vec<(String, String)>, 95 - } 96 - 97 - fn describe_options<C: CommandFactory>() -> Result<String> { 98 - let template = r#" 99 - <div class="table-wrapper"> 100 - <table> 101 - 102 - <thead> 103 - <tr> 104 - <td>Option</td> 105 - <td>Summary</td> 106 - </tr> 107 - </thead> 108 - 109 - <tbody> 110 - 111 - {% for option in options %} 112 - 113 - <tr> 114 - <td style="text-align: left;"> 115 - 116 - [`{{ option.key }}`](#{{ option.key }}) 117 - 118 - </td> 119 - <td> 120 - 121 - {{ option.help }} 122 - 123 - </td> 124 - </tr> 125 - 126 - {% endfor %} 127 - 128 - </tbody> 129 - 130 - </table> 131 - </div> 132 - 133 - {% for option in options -%} 134 - 135 - ## `{{ option.key }}` 136 - 137 - {{ option.description }} 138 - 139 - {% if option.choices %} 140 - <div class="table-wrapper"> 141 - <table> 142 - 143 - <thead> 144 - <tr> 145 - <td>Choice</td> 146 - <td>Description</td> 147 - </tr> 148 - </thead> 149 - 150 - <tbody> 151 - 152 - {% for choice, description in option.choices %} 153 - <tr> 154 - 155 - <td style="text-align: left;"> 156 - <code>{{ choice }}</code> 157 - </td> 158 - 159 - <td> 160 - 161 - {{ description }} 162 - 163 - </td> 164 - 165 - </tr> 166 - {% endfor %} 167 - 168 - </tbody> 169 - 170 - </table> 171 - </div> 172 - {% endif %} 173 - 174 - <div class="table-wrapper"> 175 - <table> 176 - <tbody> 177 - 178 - {%- if option.default -%} 179 - 180 - <tr> 181 - <th style="text-align: left;">Default</th> 182 - <td> 183 - 184 - `{{ option.default }}` 185 - 186 - </td> 187 - </tr> 188 - 189 - {%- endif -%} 190 - 191 - {%- if option.type_id -%} 192 - 193 - <tr> 194 - <th style="text-align: left;">Type</th> 195 - <td> 196 - 197 - [`{{ option.type_id[0] }}`][{{ option.type_id[1] }}] 198 - 199 - </td> 200 - </tr> 201 - 202 - {%- endif -%} 203 - 204 - </tbody> 205 - </table> 206 - </div> 207 - 208 - {% endfor -%} 209 - "#; 210 - 211 - let options = C::command() 212 - .get_opts() 213 - .filter(|opt| !opt.is_hide_set()) 214 - .map(|opt| { 215 - let key = opt.get_long().unwrap().to_owned(); 216 - 217 - let help = opt.get_help().map(|h| h.to_string()).unwrap_or_default(); 218 - 219 - let description = opt 220 - .get_long_help() 221 - .map(|h| h.to_string()) 222 - .unwrap_or(help.clone()); 223 - 224 - let action = opt.get_action(); 225 - 226 - let type_id = if cfg!(debug_assertions) { 227 - let ty = format!("{:?}", opt.get_value_parser().type_id()) 228 - .replace("alloc::string::", ""); 229 - let name = ty.split("::").last().unwrap(); 230 - if matches!(action, ArgAction::Append) { 231 - Some((format!("Vec<{name}>"), format!("Vec<{ty}>"))) 232 - } else { 233 - Some((name.to_owned(), ty)) 234 - } 235 - } else { 236 - None 237 - }; 238 - 239 - let default = if let Some(d) = opt.get_default_values().iter().next() { 240 - Some(format!("{:?}", d.to_string_lossy().into_owned())) 241 - } else if matches!(action, ArgAction::SetTrue) { 242 - Some("false".into()) 243 - } else if matches!(action, ArgAction::SetFalse) { 244 - Some("true".into()) 245 - } else if matches!(action, ArgAction::Append) { 246 - Some("[]".into()) 247 - } else if !opt.is_required_set() { 248 - Some("None".into()) 249 - } else { 250 - None 251 - }; 252 - 253 - let choices = opt 254 - .get_possible_values() 255 - .iter() 256 - .filter_map(|v| { 257 - if v.is_hide_set() { 258 - None 259 - } else { 260 - let help = v.get_help()?; 261 - Some((format!("{:?}", v.get_name()), help.to_string())) 262 - } 263 - }) 264 - .collect(); 265 - 266 - OptionItem { 267 - key, 268 - help, 269 - description, 270 - type_id, 271 - default, 272 - choices, 273 - } 274 - }) 275 - .collect::<Vec<_>>() 276 - .tap_mut(|opts| opts.sort_by(|a, b| a.key.cmp(&b.key))); 277 - 278 - Ok(render!(template, options)) 279 - }