1#[must_use]
10pub fn render_reference_markdown() -> String {
11 let mut out = String::with_capacity(8192);
12 out.push_str(REFERENCE_PREAMBLE);
13 out.push_str("<!-- BEGIN GENERATED: do not edit. Regenerate by running `cargo xtask doc-config`. -->\n\n");
14 render_reference_section(&mut out, "[lint]", "lint.");
15 render_reference_section(&mut out, "[fmt]", "fmt.");
16 render_reference_section(&mut out, "[parse]", "parse.");
17 render_reference_section(&mut out, "[render]", "render.");
18 out.push_str("<!-- END GENERATED -->\n");
19 out
20}
21
22#[must_use]
25pub fn render_default_toml() -> String {
26 let mut out = String::with_capacity(12_288);
27 out.push_str("# mdwright configuration\n");
28 out.push_str("#\n");
29 out.push_str("# Generated by `mdwright config init`. Every key below is set to\n");
30 out.push_str("# mdwright's current default. Edit or delete values to fit this project.\n");
31
32 let mut current_table = "";
33 for field in SCHEMA_FIELDS {
34 let Some((table, key)) = field.key.rsplit_once('.') else {
35 continue;
36 };
37 if table == current_table {
38 out.push('\n');
39 } else {
40 current_table = table;
41 out.push('\n');
42 out.push('[');
43 out.push_str(table);
44 out.push_str("]\n");
45 }
46 out.push_str("# Type: ");
47 out.push_str(field.ty);
48 out.push_str(".\n");
49 for line in field.description.lines() {
50 out.push_str("# ");
51 out.push_str(line);
52 out.push('\n');
53 }
54 out.push_str(key);
55 out.push_str(" = ");
56 out.push_str(field.default);
57 out.push('\n');
58 }
59
60 out
61}
62
63struct FieldDoc {
65 key: &'static str,
67 ty: &'static str,
69 default: &'static str,
71 description: &'static str,
73 cli_override: Option<&'static str>,
75}
76
77const SCHEMA_FIELDS: &[FieldDoc] = &[
78 FieldDoc {
80 key: "lint.preset",
81 ty: "\"default\" | \"all\" | \"none\"",
82 default: "\"default\"",
83 description: "Baseline lint rule set. Use `default` for curated defaults, `all` for every registered rule, or `none` with `lint.select` for an explicit set.",
84 cli_override: Some("--rules"),
85 },
86 FieldDoc {
87 key: "lint.select",
88 ty: "array of string",
89 default: "[]",
90 description: "Exact lint rule names to enable when `lint.preset = \"none\"`. Preset names are not valid rule names here.",
91 cli_override: Some("--rules"),
92 },
93 FieldDoc {
94 key: "lint.extend-select",
95 ty: "array of string",
96 default: "[]",
97 description: "Lint rule names to add on top of `lint.preset`.",
98 cli_override: Some("--rules"),
99 },
100 FieldDoc {
101 key: "lint.ignore",
102 ty: "array of string",
103 default: "[]",
104 description: "Lint rule names to remove after applying `lint.preset`, `lint.select`, and `lint.extend-select`.",
105 cli_override: Some("--rules"),
106 },
107 FieldDoc {
108 key: "lint.exclude",
109 ty: "array of string",
110 default: "[]",
111 description: "Gitignore-style patterns. Matching files are dropped from lint runs. Patterns are anchored to the directory containing the config file.",
112 cli_override: None,
113 },
114 FieldDoc {
115 key: "lint.info-strings.extra",
116 ty: "array of string",
117 default: "[]",
118 description: "Project-specific additions to the `info-string-typo` allowlist. The stdlib default allowlist still applies.",
119 cli_override: None,
120 },
121 FieldDoc {
122 key: "lint.render.renderer",
123 ty: "\"mathjax-v3\" | \"katex\"",
124 default: "\"mathjax-v3\"",
125 description: "Math renderer the `math/render-compat` rule should check against.",
126 cli_override: None,
127 },
128 FieldDoc {
129 key: "lint.render.packages",
130 ty: "array of string",
131 default: "[]",
132 description: "Renderer packages/extensions to load on top of the renderer's default autoload set (e.g. `[\"mhchem\", \"physics\"]`). Consumed by the `math/render-compat` rule.",
133 cli_override: None,
134 },
135 FieldDoc {
136 key: "lint.render.macros",
137 ty: "table",
138 default: "{}",
139 description: "User-declared macros known to be in scope, keyed by command name (no leading backslash). Each entry is either `name = arity` or `name = { arity = N }`.",
140 cli_override: None,
141 },
142 FieldDoc {
144 key: "fmt.profile",
145 ty: "\"preserve\" | \"mdformat\"",
146 default: "\"preserve\"",
147 description: "Formatter style profile. `preserve` keeps mdwright's identity-oriented defaults; `mdformat` applies mdformat-compatible defaults where verified rewrites can preserve semantics. Explicit `[fmt]` keys override profile defaults.",
148 cli_override: None,
149 },
150 FieldDoc {
151 key: "fmt.wrap",
152 ty: "\"keep\" | \"no\" | int",
153 default: "\"keep\"",
154 description: "Wrap mode for prose paragraphs. `keep` leaves existing breaks alone; `no` forbids new breaks; an integer enforces that display-column budget for breakable lines in every formatter profile.",
155 cli_override: None,
156 },
157 FieldDoc {
158 key: "fmt.wrap-strategy",
159 ty: "\"stable\" | \"balanced\"",
160 default: "\"stable\"",
161 description: "Reflow strategy used when `fmt.wrap` is an integer. `stable` greedily fills soft-break runs and is the default; `balanced` rebalances paragraphs for more even line lengths.",
162 cli_override: None,
163 },
164 FieldDoc {
165 key: "fmt.italic",
166 ty: "\"asterisk\" | \"underscore\" | \"preserve\"",
167 default: "\"preserve\"",
168 description: "Italic delimiter canonicalisation. `preserve` leaves source bytes; `asterisk` or `underscore` opts into the post-pass rewrite. See [Style knobs](format/style.md).",
169 cli_override: None,
170 },
171 FieldDoc {
172 key: "fmt.strong",
173 ty: "\"asterisk\" | \"underscore\" | \"preserve\"",
174 default: "\"preserve\"",
175 description: "Strong-emphasis delimiter canonicalisation. Independent of `fmt.italic`: `*italic*` with `__strong__` is expressible.",
176 cli_override: None,
177 },
178 FieldDoc {
179 key: "fmt.list-marker",
180 ty: "\"dash\" | \"asterisk\" | \"plus\" | \"preserve\"",
181 default: "\"preserve\"",
182 description: "Unordered-list bullet canonicalisation. Each marker is rewritten through a marker-local fact and the family commits only after verification.",
183 cli_override: None,
184 },
185 FieldDoc {
186 key: "fmt.ordered-list",
187 ty: "\"one\" | \"consistent\" | \"preserve\"",
188 default: "\"preserve\"",
189 description: "Ordered-list number canonicalisation. `one` rewrites markers to `1.` only when verification preserves the list start; `consistent` renumbers each list from the source's first item; `preserve` keeps source numbering verbatim.",
190 cli_override: None,
191 },
192 FieldDoc {
193 key: "fmt.thematic-break",
194 ty: "\"dash\" | \"asterisk\" | \"underscore\" | \"underscore-70\" | \"preserve\"",
195 default: "\"preserve\"",
196 description: "Thematic-break canonicalisation. Fixed character modes preserve the source repeat count and spacing; `underscore-70` rewrites the whole break line to mdformat's 70 underscores.",
197 cli_override: None,
198 },
199 FieldDoc {
200 key: "fmt.trailing-newline",
201 ty: "\"preserve\" | \"strip\" | \"ensure\" | bool",
202 default: "\"preserve\"",
203 description: "Trailing-newline policy at the document boundary. `true` is accepted as a synonym for `ensure` and `false` for `strip`.",
204 cli_override: None,
205 },
206 FieldDoc {
207 key: "fmt.end-of-line",
208 ty: "\"lf\" | \"crlf\" | \"keep\"",
209 default: "\"lf\"",
210 description: "Line-ending normalisation. `keep` adopts the first newline seen in the source.",
211 cli_override: None,
212 },
213 FieldDoc {
214 key: "fmt.exclude",
215 ty: "array of string",
216 default: "[]",
217 description: "Formatter-specific exclude globs, independent of `[lint] exclude`.",
218 cli_override: None,
219 },
220 FieldDoc {
221 key: "fmt.heading-attrs",
222 ty: "\"preserve\" | \"canonicalise\"",
223 default: "\"preserve\"",
224 description: "ATX heading `{#id .class key=val}` trailer emission. `preserve` emits the source trailer byte-verbatim. `canonicalise` emits id first, then classes, then key-value pairs.",
225 cli_override: None,
226 },
227 FieldDoc {
229 key: "fmt.refs.placement",
230 ty: "\"end\" | \"preserve\"",
231 default: "\"end\"",
232 description: "Where reference-link definitions are emitted: gathered and sorted at the end of the document, or kept in source order.",
233 cli_override: None,
234 },
235 FieldDoc {
236 key: "fmt.refs.style",
237 ty: "\"bare\" | \"angle\" | \"preserve\"",
238 default: "\"preserve\"",
239 description: "Destination style for reference-link and inline-link URLs. `preserve` keeps each destination's source form; `bare` strips wrapping `<...>` where the bare form still parses; `angle` wraps every destination in `<...>`.",
240 cli_override: None,
241 },
242 FieldDoc {
244 key: "fmt.footnotes.placement",
245 ty: "\"end\" | \"preserve\"",
246 default: "\"preserve\"",
247 description: "Where footnote definitions are emitted. Default is `preserve` because pulldown-cmark's HTML renderer ties footnote position to parse order; moving definitions would change the rendered HTML.",
248 cli_override: None,
249 },
250 FieldDoc {
252 key: "fmt.tables.style",
253 ty: "\"compact\" | \"align\" | \"preserve\"",
254 default: "\"compact\"",
255 description: "GFM table spacing policy. `compact` trims cell padding to one space on each side; `align` pads columns by display width; `preserve` keeps source cell spacing.",
256 cli_override: None,
257 },
258 FieldDoc {
260 key: "fmt.lists.continuation-indent",
261 ty: "\"marker-width\" | \"four-space\"",
262 default: "\"marker-width\"",
263 description: "Continuation indentation for wrapped list-item paragraphs. `marker-width` aligns to the source marker width; `four-space` matches mdformat's list continuation spelling.",
264 cli_override: None,
265 },
266 FieldDoc {
268 key: "fmt.frontmatter.preserve",
269 ty: "bool",
270 default: "true",
271 description: "Whether to emit document frontmatter byte-verbatim. `false` strips it.",
272 cli_override: None,
273 },
274 FieldDoc {
276 key: "fmt.math.normalise",
277 ty: "bool",
278 default: "false",
279 description: "Whether whole-block math regions are normalised. Off by default because math bytes are opaque to CommonMark.",
280 cli_override: None,
281 },
282 FieldDoc {
283 key: "fmt.math.render",
284 ty: "\"none\" | \"commonmark-katex\" | \"dollar\"",
285 default: "\"none\"",
286 description: "Math delimiter rendering policy for downstream renderers. `none` preserves source math regions; `commonmark-katex` records intent without rewriting; `dollar` rewrites bracket and paren math to dollar delimiters.",
287 cli_override: None,
288 },
289 FieldDoc {
291 key: "parse.math.delimiters",
292 ty: "\"tex\" | \"github\"",
293 default: "\"tex\"",
294 description: "Math delimiter recognition policy. `tex` recognises `\\(...\\)`, `\\[...\\]`, and LaTeX environments; `github` also recognises `$...$` and `$$...$$`.",
295 cli_override: None,
296 },
297 FieldDoc {
299 key: "parse.extensions.definition-lists",
300 ty: "bool",
301 default: "true",
302 description: "Recognise `Term\\n: definition\\n` definition lists. Turn off on non-mkdocs corpora to suppress recognition.",
303 cli_override: None,
304 },
305 FieldDoc {
306 key: "parse.extensions.abbreviation-lists",
307 ty: "bool",
308 default: "true",
309 description: "Recognise `*[ABBR]: definition` abbreviation declarations as a scan-and-preserve overlay. mdwright does not expand occurrences; the downstream renderer does.",
310 cli_override: None,
311 },
312 FieldDoc {
313 key: "parse.extensions.heading-attribute-lists",
314 ty: "bool",
315 default: "true",
316 description: "Recognise `# Heading {#id .class}` trailers via pulldown's heading-attribute extension. When off, the trailer reads as plain text in the heading body.",
317 cli_override: None,
318 },
319 FieldDoc {
320 key: "parse.extensions.block-attribute-lists",
321 ty: "bool",
322 default: "true",
323 description: "Recognise `{ .class }` on a line by itself after a non-empty block as a scan-and-preserve overlay. Inline attribute lists are out of scope.",
324 cli_override: None,
325 },
326 FieldDoc {
328 key: "parse.extensions.gfm.autolinks",
329 ty: "\"disabled\" | \"urls\" | \"urls-and-emails\"",
330 default: "\"urls-and-emails\"",
331 description: "Recognise GFM bare URL and email autolinks as document facts and render them as links. Use `urls` to leave bare emails as text or `disabled` for strict CommonMark-style text treatment.",
332 cli_override: None,
333 },
334 FieldDoc {
335 key: "parse.extensions.gfm.tagfilter",
336 ty: "bool",
337 default: "true",
338 description: "Apply GFM tagfiltering when rendering or building semantic signatures. This escapes the raw HTML tags that cmark-gfm filters, without rewriting source bytes.",
339 cli_override: None,
340 },
341 FieldDoc {
343 key: "parse.extensions.myst.directive-containers",
344 ty: "bool",
345 default: "true",
346 description: "Recognise MyST `:::{name}` directive containers with `:KEY: value` options as a scan-and-preserve overlay. mdwright does not expand directives; downstream renderers do.",
347 cli_override: None,
348 },
349 FieldDoc {
350 key: "parse.extensions.myst.inline-roles",
351 ty: "bool",
352 default: "true",
353 description: "Recognise MyST `` {role}`payload` `` inline roles as a scan-and-preserve overlay inside paragraph text.",
354 cli_override: None,
355 },
356 FieldDoc {
357 key: "parse.extensions.myst.substitution-references",
358 ty: "bool",
359 default: "true",
360 description: "Recognise MyST `{{name}}` inline substitution references as a scan-and-preserve overlay. Declarations live in YAML frontmatter and round-trip through the frontmatter path.",
361 cli_override: None,
362 },
363 FieldDoc {
364 key: "parse.extensions.myst.comments",
365 ty: "bool",
366 default: "true",
367 description: "Recognise MyST `%` line comments at line-start as a scan-and-preserve overlay.",
368 cli_override: None,
369 },
370 FieldDoc {
372 key: "parse.extensions.pandoc.fenced-divs",
373 ty: "bool",
374 default: "true",
375 description: "Recognise Pandoc `::: {.cls}` fenced div openers. The closer is a colon-only line of matching count.",
376 cli_override: None,
377 },
378 FieldDoc {
379 key: "parse.extensions.pandoc.short-form-divs",
380 ty: "bool",
381 default: "true",
382 description: "Recognise Pandoc `:::name` fenced div openers.",
383 cli_override: None,
384 },
385 FieldDoc {
386 key: "parse.extensions.pandoc.inline-attribute-spans",
387 ty: "bool",
388 default: "true",
389 description: "Recognise Pandoc `[content]{.cls}` inline attribute spans as a scan-and-preserve overlay.",
390 cli_override: None,
391 },
392 FieldDoc {
394 key: "render.profile",
395 ty: "\"pulldown\" | \"cmark-gfm\"",
396 default: "\"pulldown\"",
397 description: "HTML spelling profile for `mdwright render`. `pulldown` preserves the default renderer; `cmark-gfm` matches cmark-gfm spelling where parser semantics already agree.",
398 cli_override: Some("--render-profile"),
399 },
400];
401
402const REFERENCE_PREAMBLE: &str = r#"# Configuration
403
404mdwright reads configuration from (in precedence order):
405
4061. The file given via `--config PATH`.
4072. The nearest ancestor config discovered by walking upward from the
408 current directory. At each ancestor, candidates are tried in this
409 order: `.mdwright.toml`, `mdwright.toml`,
410 `pyproject.toml` containing a `[tool.mdwright]` table. The walk
411 stops at the filesystem root or at the first directory containing
412 `.git/` (the workspace boundary).
4133. Built-in defaults.
414
415A `pyproject.toml` *without* `[tool.mdwright]` does not stop the walk;
416discovery continues to the parent directory. A `.mdwright.toml` wins
417over a `pyproject.toml` in the same directory (matching ruff's
418"more-specific-name first" rule).
419
420Run `mdwright config init` to create a documented `.mdwright.toml`
421starter file with every option set to its default.
422
423## Single-file integration via `pyproject.toml`
424
425For projects that already use `pyproject.toml`, the entire mdwright
426configuration can live there under `[tool.mdwright]`:
427
428```toml
429# pyproject.toml
430[tool.mdwright]
431lint.preset = "default"
432lint.extend-select = ["latex-command"]
433
434[tool.mdwright.fmt]
435wrap = 100
436```
437
438## CLI overrides
439
440The following knobs accept CLI flags that take precedence over the
441config file:
442
443- `lint.preset`, `lint.select`, `lint.extend-select`, `lint.ignore`: `--rules`
444- `render.profile`: `mdwright render --render-profile`
445- `--no-suppress` toggles whether `<!-- mdwright: allow ... -->`
446 comments are honoured; there is no config-file equivalent.
447
448All `[fmt]` knobs are config-file-only.
449
450## Schema reference
451
452"#;
453
454fn render_reference_section(out: &mut String, heading: &str, prefix: &str) {
455 out.push_str("### `");
456 out.push_str(heading);
457 out.push_str("` and nested tables\n\n");
458 out.push_str("| Key | Type | Default | CLI override | Description |\n");
459 out.push_str("| --- | --- | --- | --- | --- |\n");
460 for field in SCHEMA_FIELDS {
461 if !field.key.starts_with(prefix) {
462 continue;
463 }
464 let cli = field.cli_override.unwrap_or("none");
465 let ty_escaped = field.ty.replace('|', "\\|");
466 out.push_str("| `");
467 out.push_str(field.key);
468 out.push_str("` | ");
469 out.push_str(&ty_escaped);
470 out.push_str(" | `");
471 out.push_str(field.default);
472 out.push_str("` | `");
473 out.push_str(cli);
474 out.push_str("` | ");
475 out.push_str(field.description);
476 out.push_str(" |\n");
477 }
478 out.push('\n');
479}