rumdl_lib/rules/md060_table_format/md060_config.rs
1use crate::rule_config_serde::RuleConfig;
2use crate::types::LineLength;
3use serde::ser::Serializer;
4use serde::{Deserialize, Serialize};
5/// Controls how cell text is aligned within padded columns.
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub enum ColumnAlign {
8 /// Use alignment indicators from delimiter row (`:---`, `:---:`, `---:`)
9 #[default]
10 Auto,
11 /// Force all columns to left-align text
12 Left,
13 /// Force all columns to center text
14 Center,
15 /// Force all columns to right-align text
16 Right,
17}
18
19impl Serialize for ColumnAlign {
20 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21 where
22 S: Serializer,
23 {
24 match self {
25 ColumnAlign::Auto => serializer.serialize_str("auto"),
26 ColumnAlign::Left => serializer.serialize_str("left"),
27 ColumnAlign::Center => serializer.serialize_str("center"),
28 ColumnAlign::Right => serializer.serialize_str("right"),
29 }
30 }
31}
32
33impl<'de> Deserialize<'de> for ColumnAlign {
34 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
35 where
36 D: serde::Deserializer<'de>,
37 {
38 let s = String::deserialize(deserializer)?;
39 match s.to_lowercase().as_str() {
40 "auto" => Ok(ColumnAlign::Auto),
41 "left" => Ok(ColumnAlign::Left),
42 "center" => Ok(ColumnAlign::Center),
43 "right" => Ok(ColumnAlign::Right),
44 _ => Err(serde::de::Error::custom(format!(
45 "Invalid column-align value: {s}. Valid options: auto, left, center, right"
46 ))),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52pub struct MD060Config {
53 #[serde(default = "default_enabled")]
54 pub enabled: bool,
55
56 #[serde(
57 default = "default_style",
58 serialize_with = "serialize_style",
59 deserialize_with = "deserialize_style"
60 )]
61 pub style: String,
62
63 /// Maximum table width before auto-switching to compact mode.
64 ///
65 /// - `0` (default): Inherit from MD013's `line-length` setting
66 /// - Non-zero: Explicit max width threshold
67 ///
68 /// When a table's aligned width would exceed this limit, MD060 automatically
69 /// uses compact formatting instead (minimal padding) to prevent excessively
70 /// long lines. This matches the behavior of Prettier's table formatting.
71 ///
72 /// # Examples
73 ///
74 /// ```toml
75 /// [MD013]
76 /// line-length = 100
77 ///
78 /// [MD060]
79 /// style = "aligned"
80 /// max-width = 0 # Uses MD013's line-length (100)
81 /// ```
82 ///
83 /// ```toml
84 /// [MD060]
85 /// style = "aligned"
86 /// max-width = 120 # Explicit threshold, independent of MD013
87 /// ```
88 #[serde(default = "default_max_width", rename = "max-width")]
89 pub max_width: LineLength,
90
91 /// Controls how cell text is aligned within the padded column width.
92 ///
93 /// - `auto` (default): Use alignment indicators from delimiter row (`:---`, `:---:`, `---:`)
94 /// - `left`: Force all columns to left-align text
95 /// - `center`: Force all columns to center text
96 /// - `right`: Force all columns to right-align text
97 ///
98 /// Only applies when `style = "aligned"` or `style = "aligned-no-space"`.
99 ///
100 /// # Examples
101 ///
102 /// ```toml
103 /// [MD060]
104 /// style = "aligned"
105 /// column-align = "center" # Center all cell text
106 /// ```
107 #[serde(default, rename = "column-align")]
108 pub column_align: ColumnAlign,
109
110 /// Controls alignment specifically for the header row.
111 ///
112 /// When set, overrides `column-align` for the header row only.
113 /// If not set, falls back to `column-align`.
114 ///
115 /// # Examples
116 ///
117 /// ```toml
118 /// [MD060]
119 /// style = "aligned"
120 /// column-align-header = "center" # Center header text
121 /// column-align-body = "left" # Left-align body text
122 /// ```
123 #[serde(default, rename = "column-align-header")]
124 pub column_align_header: Option<ColumnAlign>,
125
126 /// Controls alignment specifically for body rows (non-header, non-delimiter).
127 ///
128 /// When set, overrides `column-align` for body rows only.
129 /// If not set, falls back to `column-align`.
130 ///
131 /// # Examples
132 ///
133 /// ```toml
134 /// [MD060]
135 /// style = "aligned"
136 /// column-align-header = "center" # Center header text
137 /// column-align-body = "left" # Left-align body text
138 /// ```
139 #[serde(default, rename = "column-align-body")]
140 pub column_align_body: Option<ColumnAlign>,
141
142 /// Controls whether the last column in body rows is loosely formatted.
143 ///
144 /// - `false` (default): All columns padded to equal width across all rows.
145 /// - `true`: The last column width is capped at the header text width.
146 /// Body cells shorter than the header are padded to the header width.
147 /// Body cells longer than the header extend beyond without padding.
148 ///
149 /// Only applies when `style = "aligned"` or `style = "aligned-no-space"`.
150 ///
151 /// # Examples
152 ///
153 /// ```toml
154 /// [MD060]
155 /// style = "aligned"
156 /// loose-last-column = true
157 /// ```
158 #[serde(default, rename = "loose-last-column")]
159 pub loose_last_column: bool,
160}
161
162impl Default for MD060Config {
163 fn default() -> Self {
164 Self {
165 enabled: default_enabled(),
166 style: default_style(),
167 max_width: default_max_width(),
168 column_align: ColumnAlign::Auto,
169 column_align_header: None,
170 column_align_body: None,
171 loose_last_column: false,
172 }
173 }
174}
175
176fn default_enabled() -> bool {
177 false
178}
179
180fn default_style() -> String {
181 "any".to_string()
182}
183
184fn default_max_width() -> LineLength {
185 LineLength::from_const(0) // 0 = inherit from MD013
186}
187
188fn serialize_style<S>(style: &str, serializer: S) -> Result<S::Ok, S::Error>
189where
190 S: Serializer,
191{
192 serializer.serialize_str(style)
193}
194
195fn deserialize_style<'de, D>(deserializer: D) -> Result<String, D::Error>
196where
197 D: serde::Deserializer<'de>,
198{
199 let s = String::deserialize(deserializer)?;
200
201 let valid_styles = ["aligned", "aligned-no-space", "compact", "tight", "any"];
202
203 if valid_styles.contains(&s.as_str()) {
204 Ok(s)
205 } else {
206 Err(serde::de::Error::custom(format!(
207 "Invalid table format style: {s}. Valid options: aligned, aligned-no-space, compact, tight, any"
208 )))
209 }
210}
211
212impl RuleConfig for MD060Config {
213 const RULE_NAME: &'static str = "MD060";
214}