mdwright_format/options.rs
1/// Formatter knobs.
2///
3/// Style knobs (`italic`, `strong`, `list_marker`, `ordered_list`,
4/// `link_def_style`, `thematic_break_style`) default to `Preserve`;
5/// the structural-emit pipeline never consults them, so the defaults
6/// round-trip source bytes verbatim. Style rewrites are applied by the
7/// formatter's rewrite-family pipeline. Structural defaults:
8/// `wrap = keep`, `trailing-newline = preserve`, `end-of-line = lf`,
9/// empty exclude list.
10#[derive(Debug, Clone)]
11pub struct FmtOptions {
12 wrap: Wrap,
13 wrap_strategy: WrapStrategy,
14 italic: ItalicStyle,
15 strong: StrongStyle,
16 list_marker: ListMarkerStyle,
17 list_continuation_indent: ListContinuationIndent,
18 ordered_list: OrderedListStyle,
19 table: TableStyle,
20 trailing_newline: TrailingNewline,
21 end_of_line: EndOfLine,
22 exclude_globs: Vec<String>,
23 link_def_placement: Placement,
24 link_def_style: LinkDefStyle,
25 footnote_placement: Placement,
26 preserve_frontmatter: bool,
27 thematic_break_style: ThematicStyle,
28 math: MathOptions,
29 heading_attrs: HeadingAttrsStyle,
30}
31
32/// Heading-attribute trailer emission policy.
33///
34/// `# Heading {#id .class key=val}` parses (with
35/// `Options::ENABLE_HEADING_ATTRIBUTES`) into a `Tag::Heading` carrying
36/// the parsed `id`, `classes`, and `attrs`. This knob decides how the
37/// formatter re-emits the trailer.
38///
39/// - [`Self::Preserve`] (default): emit the source trailer byte-verbatim
40/// between the rendered inline body and the line terminator. Matches
41/// the preserve-by-default ethos every other style knob defaults to.
42/// - [`Self::Canonicalise`]: emit `{#id .class₁ .class₂ k=val}` in a
43/// fixed order: id first, then classes in source order, then
44/// key=value pairs in source order. Matches mdformat-mkdocs canonical.
45#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
46pub enum HeadingAttrsStyle {
47 /// Emit the source trailer byte-verbatim.
48 #[default]
49 Preserve,
50 /// Emit the trailer in fixed canonical order: `#id`, then classes
51 /// (source order), then `key=value` pairs (source order).
52 Canonicalise,
53}
54
55/// Math canonicalisation configuration.
56///
57/// All fields are off by default. Math regions are opaque to
58/// `CommonMark`: pulldown-cmark parses their bytes as prose, so any
59/// whitespace change inside shifts the byte-level event stream. Authors
60/// who render math downstream (`KaTeX`, `MathJax`) opt in.
61#[derive(Copy, Clone, Debug, Default)]
62pub struct MathOptions {
63 /// Whether whole-block math regions (display `\[…\]` / `$$…$$`
64 /// and environments standing alone) are normalised.
65 pub normalise: bool,
66 /// How math regions are emitted for downstream renderers. See
67 /// [`MathRender`] for the modes; default is [`MathRender::None`]
68 /// (verbatim emission, today's behaviour).
69 pub render: MathRender,
70}
71
72/// Delimiter rewrite policy for math regions at emit time.
73///
74/// mdwright never typesets math itself; downstream renderers
75/// (`KaTeX`, `MathJax`, `mkdocs-material`'s math plugin) do that. The
76/// modes here determine the *shape* of the math regions in the
77/// formatted output so the downstream renderer recognises them.
78///
79/// - [`Self::None`] (default): pass math regions through verbatim;
80/// today's behaviour.
81/// - [`Self::CommonmarkKatex`]: same emission as `None`, but signals
82/// intent in build logs. The bracket/paren forms (`\[…\]`, `\(…\)`)
83/// and dollar forms (`$$…$$`, `$…$`) are both recognised by `KaTeX`
84/// and `MathJax` v3 auto-renderers without rewriting.
85/// - [`Self::Dollar`]: rewrite `\[ … \]` to `$$ … $$` and `\( … \)`
86/// to `$ … $` at emit time. LaTeX environments
87/// (`\begin{align*}…\end{align*}`) are not rewritten; there is no
88/// dollar form of an environment.
89#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
90pub enum MathRender {
91 /// Pass math regions through verbatim.
92 #[default]
93 None,
94 /// Pass through verbatim; greppable signal that downstream is
95 /// `KaTeX` or `MathJax` over `CommonMark`-shaped math.
96 CommonmarkKatex,
97 /// Rewrite backslash-bracket / backslash-paren math to dollar
98 /// form. Environments are left unchanged.
99 Dollar,
100}
101
102impl FmtOptions {
103 /// Wrap mode for prose paragraphs.
104 #[must_use]
105 pub fn wrap(&self) -> Wrap {
106 self.wrap
107 }
108
109 /// Paragraph wrap strategy.
110 #[must_use]
111 pub fn wrap_strategy(&self) -> WrapStrategy {
112 self.wrap_strategy
113 }
114
115 /// Italic delimiter normalisation policy.
116 #[must_use]
117 pub fn italic(&self) -> ItalicStyle {
118 self.italic
119 }
120
121 /// Strong-emphasis delimiter normalisation policy.
122 #[must_use]
123 pub fn strong(&self) -> StrongStyle {
124 self.strong
125 }
126
127 /// Unordered-list bullet normalisation policy.
128 #[must_use]
129 pub fn list_marker(&self) -> ListMarkerStyle {
130 self.list_marker
131 }
132
133 /// List continuation indentation used when wrapping list-item paragraphs.
134 #[must_use]
135 pub fn list_continuation_indent(&self) -> ListContinuationIndent {
136 self.list_continuation_indent
137 }
138
139 /// Ordered-list number normalisation policy.
140 #[must_use]
141 pub fn ordered_list(&self) -> OrderedListStyle {
142 self.ordered_list
143 }
144
145 /// GFM table canonicalisation policy.
146 #[must_use]
147 pub fn table(&self) -> TableStyle {
148 self.table
149 }
150
151 /// Trailing-newline policy at the document boundary.
152 #[must_use]
153 pub fn trailing_newline(&self) -> TrailingNewline {
154 self.trailing_newline
155 }
156
157 /// Line-ending normalisation policy.
158 #[must_use]
159 pub fn end_of_line(&self) -> EndOfLine {
160 self.end_of_line
161 }
162
163 /// Formatter-specific exclude globs (independent of `[lint]
164 /// exclude`).
165 #[must_use]
166 pub fn exclude_globs(&self) -> &[String] {
167 &self.exclude_globs
168 }
169
170 /// Where reference-link definitions are emitted: gathered + sorted
171 /// at end of document, or kept in source positions.
172 #[must_use]
173 pub fn link_def_placement(&self) -> Placement {
174 self.link_def_placement
175 }
176
177 /// Whether reference-link and inline-link destinations are emitted
178 /// bare or angle-bracketed.
179 #[must_use]
180 pub fn link_def_style(&self) -> LinkDefStyle {
181 self.link_def_style
182 }
183
184 /// Where footnote definitions are emitted. Populated for parity
185 /// with [`Self::link_def_placement`]; session 11 reads this.
186 #[must_use]
187 pub fn footnote_placement(&self) -> Placement {
188 self.footnote_placement
189 }
190
191 /// Whether to emit the document's frontmatter byte-verbatim in
192 /// the formatted output. `true` (the default) preserves YAML or
193 /// TOML frontmatter exactly; `false` strips it.
194 #[must_use]
195 pub fn preserve_frontmatter(&self) -> bool {
196 self.preserve_frontmatter
197 }
198
199 /// Thematic-break canonicalisation policy. Defaults to
200 /// [`ThematicStyle::Preserve`]. The structural-emit path does not
201 /// consult this; a later canonicalisation post-pass will.
202 #[must_use]
203 pub fn thematic_break_style(&self) -> ThematicStyle {
204 self.thematic_break_style
205 }
206
207 /// Math canonicalisation configuration. See [`MathOptions`] for the
208 /// reason every field defaults off.
209 #[must_use]
210 pub fn math(&self) -> MathOptions {
211 self.math
212 }
213
214 /// Override the math options. Returns the receiver for chaining.
215 #[must_use]
216 pub fn with_math(mut self, math: MathOptions) -> Self {
217 self.math = math;
218 self
219 }
220
221 /// Override only the math render policy. Composes with the
222 /// existing [`MathOptions`]; preserves `normalise`.
223 #[must_use]
224 pub fn with_math_render(mut self, render: MathRender) -> Self {
225 self.math.render = render;
226 self
227 }
228
229 /// Heading-attribute trailer emission policy.
230 #[must_use]
231 pub fn heading_attrs(&self) -> HeadingAttrsStyle {
232 self.heading_attrs
233 }
234
235 /// Override the heading-attribute style.
236 #[must_use]
237 pub fn with_heading_attrs(mut self, style: HeadingAttrsStyle) -> Self {
238 self.heading_attrs = style;
239 self
240 }
241
242 /// Resolve the italic delimiter to emit, given the byte the
243 /// source originally used (`b'*'` or `b'_'`). `Preserve` returns
244 /// `source_delim`; fixed styles return their own byte. Keeps the
245 /// `match` out of every render site.
246 #[must_use]
247 pub fn resolve_italic(&self, source_delim: u8) -> u8 {
248 match self.italic {
249 ItalicStyle::Asterisk => b'*',
250 ItalicStyle::Underscore => b'_',
251 ItalicStyle::Preserve => source_delim,
252 }
253 }
254
255 /// Override the wrap mode. Returns the receiver for chaining.
256 /// Used by callers that build [`FmtOptions`] programmatically
257 /// (benches, golden tests, `--wrap` overrides).
258 #[must_use]
259 pub fn with_wrap(mut self, wrap: Wrap) -> Self {
260 self.wrap = wrap;
261 self
262 }
263
264 /// Override the paragraph wrap strategy.
265 #[must_use]
266 pub fn with_wrap_strategy(mut self, strategy: WrapStrategy) -> Self {
267 self.wrap_strategy = strategy;
268 self
269 }
270
271 /// Override the italic style. Used by callers that build options
272 /// programmatically (property tests, CLI overrides).
273 #[must_use]
274 pub fn with_italic(mut self, italic: ItalicStyle) -> Self {
275 self.italic = italic;
276 self
277 }
278
279 /// Override the strong style.
280 #[must_use]
281 pub fn with_strong(mut self, strong: StrongStyle) -> Self {
282 self.strong = strong;
283 self
284 }
285
286 /// Override the unordered-list bullet style.
287 #[must_use]
288 pub fn with_list_marker(mut self, list_marker: ListMarkerStyle) -> Self {
289 self.list_marker = list_marker;
290 self
291 }
292
293 /// Override list continuation indentation for wrapped list items.
294 #[must_use]
295 pub fn with_list_continuation_indent(mut self, indent: ListContinuationIndent) -> Self {
296 self.list_continuation_indent = indent;
297 self
298 }
299
300 /// Override the ordered-list numbering policy.
301 #[must_use]
302 pub fn with_ordered_list(mut self, ordered_list: OrderedListStyle) -> Self {
303 self.ordered_list = ordered_list;
304 self
305 }
306
307 /// Override the GFM table canonicalisation policy.
308 #[must_use]
309 pub fn with_table(mut self, table: TableStyle) -> Self {
310 self.table = table;
311 self
312 }
313
314 /// Override the thematic-break canonicalisation policy.
315 #[must_use]
316 pub fn with_thematic_break(mut self, thematic_break: ThematicStyle) -> Self {
317 self.thematic_break_style = thematic_break;
318 self
319 }
320
321 /// Override the link-destination style.
322 #[must_use]
323 pub fn with_link_def_style(mut self, link_def_style: LinkDefStyle) -> Self {
324 self.link_def_style = link_def_style;
325 self
326 }
327
328 /// Resolve the unordered-list bullet to emit, given the byte the
329 /// source used (`b'-'`, `b'*'`, or `b'+'`).
330 #[must_use]
331 pub fn resolve_list_marker(&self, source_marker: u8) -> u8 {
332 match self.list_marker {
333 ListMarkerStyle::Dash => b'-',
334 ListMarkerStyle::Asterisk => b'*',
335 ListMarkerStyle::Plus => b'+',
336 ListMarkerStyle::Preserve => source_marker,
337 }
338 }
339
340 // ----- Canonicalisation post-pass targets ---------------------
341 //
342 // Each accessor below maps a style knob to `Some(target)` when the
343 // user opted into canonicalisation and `None` when the knob is
344 // `Preserve`. Rewrite-family producers are the only
345 // consumers; identity emit never reads these.
346
347 /// Italic-delimiter target byte. `None` keeps source bytes.
348 #[must_use]
349 pub(crate) fn italic_target_byte(&self) -> Option<u8> {
350 match self.italic {
351 ItalicStyle::Asterisk => Some(b'*'),
352 ItalicStyle::Underscore => Some(b'_'),
353 ItalicStyle::Preserve => None,
354 }
355 }
356
357 /// Strong-delimiter target byte. `None` keeps source bytes.
358 #[must_use]
359 pub(crate) fn strong_target_byte(&self) -> Option<u8> {
360 match self.strong {
361 StrongStyle::Asterisk => Some(b'*'),
362 StrongStyle::Underscore => Some(b'_'),
363 StrongStyle::Preserve => None,
364 }
365 }
366
367 /// Unordered-list bullet target byte. `None` keeps source bytes.
368 #[must_use]
369 pub(crate) fn list_marker_target_byte(&self) -> Option<u8> {
370 match self.list_marker {
371 ListMarkerStyle::Dash => Some(b'-'),
372 ListMarkerStyle::Asterisk => Some(b'*'),
373 ListMarkerStyle::Plus => Some(b'+'),
374 ListMarkerStyle::Preserve => None,
375 }
376 }
377
378 /// Ordered-list renumbering policy for the canonicalisation pass.
379 #[must_use]
380 pub(crate) fn ordered_list_target(&self) -> Option<OrderedListStyle> {
381 match self.ordered_list {
382 OrderedListStyle::Consistent | OrderedListStyle::One => Some(self.ordered_list),
383 OrderedListStyle::Preserve => None,
384 }
385 }
386
387 /// Thematic-break shape for the canonicalisation pass.
388 #[must_use]
389 pub(crate) fn thematic_target(&self) -> Option<ThematicStyle> {
390 match self.thematic_break_style {
391 ThematicStyle::Dash | ThematicStyle::Asterisk | ThematicStyle::Underscore | ThematicStyle::Underscore70 => {
392 Some(self.thematic_break_style)
393 }
394 ThematicStyle::Preserve => None,
395 }
396 }
397
398 /// `true` iff GFM tables should be padded/aligned.
399 #[must_use]
400 pub(crate) fn should_pad_tables(&self) -> bool {
401 matches!(self.table, TableStyle::Pad)
402 }
403
404 /// Link-destination angle-bracket rewrite target. `None` keeps
405 /// source form per definition.
406 #[must_use]
407 pub(crate) fn link_def_target(&self) -> Option<LinkDefStyle> {
408 match self.link_def_style {
409 LinkDefStyle::Bare => Some(LinkDefStyle::Bare),
410 LinkDefStyle::Angle => Some(LinkDefStyle::Angle),
411 LinkDefStyle::Preserve => None,
412 }
413 }
414
415 /// True iff any style knob is set to a non-`Preserve` value. When
416 /// false, the canonicalisation pass is skipped entirely.
417 #[must_use]
418 pub(crate) fn has_any_canonicalisation(&self) -> bool {
419 self.italic_target_byte().is_some()
420 || self.strong_target_byte().is_some()
421 || self.list_marker_target_byte().is_some()
422 || self.thematic_target().is_some()
423 || self.ordered_list_target().is_some()
424 || self.should_pad_tables()
425 || self.link_def_target().is_some()
426 || matches!(self.heading_attrs, HeadingAttrsStyle::Canonicalise)
427 || matches!(self.math.render, MathRender::Dollar)
428 || self.math.normalise
429 || !self.preserve_frontmatter
430 }
431
432 /// Override the trailing-newline policy.
433 #[must_use]
434 pub fn with_trailing_newline(mut self, trailing_newline: TrailingNewline) -> Self {
435 self.trailing_newline = trailing_newline;
436 self
437 }
438
439 /// Override the end-of-line policy.
440 #[must_use]
441 pub fn with_end_of_line(mut self, end_of_line: EndOfLine) -> Self {
442 self.end_of_line = end_of_line;
443 self
444 }
445
446 /// Override formatter exclude globs.
447 #[must_use]
448 pub fn with_exclude_globs(mut self, exclude_globs: Vec<String>) -> Self {
449 self.exclude_globs = exclude_globs;
450 self
451 }
452
453 /// Override reference-definition placement.
454 #[must_use]
455 pub fn with_link_def_placement(mut self, placement: Placement) -> Self {
456 self.link_def_placement = placement;
457 self
458 }
459
460 /// Override footnote-definition placement.
461 #[must_use]
462 pub fn with_footnote_placement(mut self, placement: Placement) -> Self {
463 self.footnote_placement = placement;
464 self
465 }
466
467 /// Override frontmatter preservation.
468 #[must_use]
469 pub fn with_preserve_frontmatter(mut self, preserve_frontmatter: bool) -> Self {
470 self.preserve_frontmatter = preserve_frontmatter;
471 self
472 }
473}
474
475impl Default for FmtOptions {
476 fn default() -> Self {
477 Self {
478 wrap: Wrap::Keep,
479 wrap_strategy: WrapStrategy::Stable,
480 // Style knobs default to Preserve so structural emit
481 // round-trips source bytes. Canonicalisation reads these
482 // knobs to opt in to rewrites.
483 italic: ItalicStyle::Preserve,
484 strong: StrongStyle::Preserve,
485 list_marker: ListMarkerStyle::Preserve,
486 list_continuation_indent: ListContinuationIndent::MarkerWidth,
487 ordered_list: OrderedListStyle::Preserve,
488 table: TableStyle::Preserve,
489 trailing_newline: TrailingNewline::Preserve,
490 end_of_line: EndOfLine::Lf,
491 exclude_globs: Vec::new(),
492 link_def_placement: Placement::End,
493 link_def_style: LinkDefStyle::Preserve,
494 // Footnotes stay at their source position. Pulldown's HTML
495 // renderer emits the `<div class="footnote-definition">`
496 // block at the point of parsing, so moving definitions to
497 // the document tail under `Placement::End` would change the
498 // event stream.
499 footnote_placement: Placement::Preserve,
500 preserve_frontmatter: true,
501 thematic_break_style: ThematicStyle::Preserve,
502 math: MathOptions::default(),
503 heading_attrs: HeadingAttrsStyle::default(),
504 }
505 }
506}
507
508impl FmtOptions {
509 /// mdformat-compatible formatting profile where mdwright can
510 /// reproduce mdformat's spelling without weakening verification.
511 ///
512 /// mdformat 1.0 defaults to `wrap = keep`; callers that want a
513 /// column limit should still set [`Self::with_wrap`] explicitly.
514 #[must_use]
515 pub fn mdformat() -> Self {
516 Self::default()
517 .with_wrap_strategy(WrapStrategy::Stable)
518 .with_list_marker(ListMarkerStyle::Dash)
519 .with_list_continuation_indent(ListContinuationIndent::FourSpace)
520 .with_ordered_list(OrderedListStyle::One)
521 .with_thematic_break(ThematicStyle::Underscore70)
522 .with_table(TableStyle::Pad)
523 }
524}
525
526/// Trailing-newline policy applied at the document boundary.
527///
528/// `Preserve` (the default) matches the source's trailing-newline
529/// shape: if the source ends with one or more `\n` bytes, the
530/// formatted output ends with exactly one `\n`; if the source has no
531/// trailing `\n`, the output has none either. This is what
532/// formatter validation needs to hold: pulldown-cmark's HTML render of
533/// `\t\x10` is `<pre><code>\x10</code></pre>` while its
534/// render of `\t\x10\n` is `<pre><code>\x10\n</code></pre>`; the
535/// trailing LF lives inside the code body for any document ending in
536/// an indented code block. An unconditional "ensure trailing `\n`"
537/// post-pass cannot avoid that class of HTML-divergence; `Preserve`
538/// avoids it by construction.
539///
540/// `Strip` drops every trailing `\n`. `Ensure` forces exactly one
541/// trailing `\n` (the pre-Preserve `trailing_newline = true`
542/// behaviour); kept under an explicit name so callers opt in to the
543/// foot-gun rather than getting it by default.
544#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
545pub enum TrailingNewline {
546 /// Match the source: one trailing `\n` iff the source had any.
547 #[default]
548 Preserve,
549 /// Drop every trailing `\n`.
550 Strip,
551 /// Force exactly one trailing `\n`, appending if absent.
552 Ensure,
553}
554
555/// Emission position for collected items (link reference definitions,
556/// footnote definitions). `End` (the default) gathers items and sorts
557/// them at the end of the document; `Preserve` keeps source order.
558#[derive(Copy, Clone, Debug, PartialEq, Eq)]
559pub enum Placement {
560 End,
561 Preserve,
562}
563
564/// Destination style for link reference definitions and inline links.
565///
566/// `Bare` emits `[label]: url`; `Angle` emits `[label]: <url>`;
567/// `Preserve` (the default) emits whichever form the source used for
568/// each destination.
569#[derive(Copy, Clone, Debug, PartialEq, Eq)]
570pub enum LinkDefStyle {
571 Bare,
572 Angle,
573 Preserve,
574}
575
576/// Prose-wrap mode.
577///
578/// `Keep` (default) and `No` both leave existing breaks alone; the
579/// distinction is whether the formatter is *allowed* to introduce new
580/// breaks later (`Keep` means "do nothing if already within budget",
581/// `No` means "never break"). `At(n)` wraps at column `n`.
582#[derive(Copy, Clone, Debug, PartialEq, Eq)]
583pub enum Wrap {
584 Keep,
585 No,
586 At(u32),
587}
588
589impl Wrap {
590 /// Effective column target. `Keep` and `No` both return
591 /// `u32::MAX`, signalling the wrap pass "do not introduce breaks".
592 #[must_use]
593 pub fn columns(self) -> u32 {
594 match self {
595 Self::Keep | Self::No => u32::MAX,
596 Self::At(n) => n,
597 }
598 }
599
600 /// Reduce the target column count by `n` for nested contexts whose
601 /// physical lines will be prefixed (blockquote `> `, list-item
602 /// marker, footnote indent). `Keep` and `No` are unaffected.
603 #[must_use]
604 pub fn shrink(self, n: u32) -> Self {
605 match self {
606 Self::At(c) => Self::At(c.saturating_sub(n).max(1)),
607 Self::Keep | Self::No => self,
608 }
609 }
610}
611
612/// Paragraph-wrap planner.
613#[derive(Copy, Clone, Debug, PartialEq, Eq)]
614pub enum WrapStrategy {
615 /// Reflow each hard-break-bounded run with mdformat-compatible soft breaks.
616 Stable,
617 /// Rebalance the whole paragraph with a squared-slack planner.
618 Balanced,
619}
620
621impl WrapStrategy {
622 /// TOML spelling for this strategy.
623 #[must_use]
624 pub fn as_str(self) -> &'static str {
625 match self {
626 Self::Stable => "stable",
627 Self::Balanced => "balanced",
628 }
629 }
630}
631
632/// Italic delimiter normalisation policy. Defaults to `Preserve`:
633/// structural emit preserves each run's source delimiter. Fixed
634/// variants are consumed only by the canonicalisation post-pass.
635#[derive(Copy, Clone, Debug, PartialEq, Eq)]
636pub enum ItalicStyle {
637 Asterisk,
638 Underscore,
639 Preserve,
640}
641
642/// Strong-emphasis delimiter normalisation policy. Defaults to
643/// `Preserve`. Independent of [`ItalicStyle`] so an author can
644/// canonicalise one without forcing the other.
645#[derive(Copy, Clone, Debug, PartialEq, Eq)]
646pub enum StrongStyle {
647 Asterisk,
648 Underscore,
649 Preserve,
650}
651
652/// Unordered-list bullet normalisation policy. Defaults to
653/// `Preserve`: structural emit keeps each list's source bullet.
654#[derive(Copy, Clone, Debug, PartialEq, Eq)]
655pub enum ListMarkerStyle {
656 Dash,
657 Asterisk,
658 Plus,
659 Preserve,
660}
661
662/// Continuation indentation for wrapped list-item paragraphs.
663#[derive(Copy, Clone, Debug, PartialEq, Eq)]
664pub enum ListContinuationIndent {
665 /// Continue at the marker width: `- text` wraps to ` text`.
666 MarkerWidth,
667 /// Continue with four spaces after the containing block prefix.
668 FourSpace,
669}
670
671/// Ordered-list number normalisation policy.
672///
673/// `One` rewrites markers to `1.` where verification preserves the
674/// list start. `Consistent` renumbers from the source first marker.
675/// `Preserve` keeps source numbering verbatim.
676#[derive(Copy, Clone, Debug, PartialEq, Eq)]
677pub enum OrderedListStyle {
678 /// Rewrite every item marker in the list to `1.`.
679 One,
680 /// Renumber items from the source list's first marker.
681 Consistent,
682 Preserve,
683}
684
685/// Thematic-break canonicalisation policy. Defaults to `Preserve`:
686/// structural emit echoes the source `---` / `***` / `___` line
687/// verbatim. The fixed variants exist for the future canonicalisation
688/// pass.
689#[derive(Copy, Clone, Debug, PartialEq, Eq)]
690pub enum ThematicStyle {
691 Dash,
692 Asterisk,
693 Underscore,
694 /// Rewrite the whole break line to mdformat's 70 underscores.
695 Underscore70,
696 Preserve,
697}
698
699impl ThematicStyle {
700 /// The repeated byte the thematic-break line is built from, when
701 /// the style names a single byte. `Preserve` returns `None`
702 /// because the byte to emit comes from the source itself.
703 #[must_use]
704 pub fn as_byte(self) -> Option<u8> {
705 match self {
706 Self::Dash => Some(b'-'),
707 Self::Asterisk => Some(b'*'),
708 Self::Underscore | Self::Underscore70 => Some(b'_'),
709 Self::Preserve => None,
710 }
711 }
712}
713
714/// GFM table canonicalisation policy.
715#[derive(Copy, Clone, Debug, PartialEq, Eq)]
716pub enum TableStyle {
717 /// Keep source table spacing.
718 Preserve,
719 /// Pad cells and delimiter rows to mdformat-compatible widths.
720 Pad,
721}
722
723/// Line-ending policy. `Keep` adopts the first newline in the source.
724#[derive(Copy, Clone, Debug, PartialEq, Eq)]
725pub enum EndOfLine {
726 Lf,
727 Crlf,
728 Keep,
729}