Skip to main content

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}