rumdl_lib/rules/md060_table_format/
md060_config.rs1use crate::rule_config_serde::RuleConfig;
2use crate::types::LineLength;
3use serde::ser::Serializer;
4use serde::{Deserialize, Serialize};
5#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub enum ColumnAlign {
8 #[default]
10 Auto,
11 Left,
13 Center,
15 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 #[serde(default = "default_max_width", rename = "max-width")]
89 pub max_width: LineLength,
90
91 #[serde(default, rename = "column-align")]
108 pub column_align: ColumnAlign,
109
110 #[serde(default, rename = "column-align-header")]
124 pub column_align_header: Option<ColumnAlign>,
125
126 #[serde(default, rename = "column-align-body")]
140 pub column_align_body: Option<ColumnAlign>,
141
142 #[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) }
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 let normalized = s.trim().to_ascii_lowercase().replace('_', "-");
201
202 let valid_styles = ["aligned", "aligned-no-space", "compact", "tight", "any"];
203
204 if valid_styles.contains(&normalized.as_str()) {
205 Ok(normalized)
206 } else {
207 Err(serde::de::Error::custom(format!(
208 "Invalid table format style: {s}. Valid options: aligned, aligned-no-space, compact, tight, any"
209 )))
210 }
211}
212
213impl RuleConfig for MD060Config {
214 const RULE_NAME: &'static str = "MD060";
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_style_accepts_hyphen_and_underscore_variants() {
223 let kebab_case: MD060Config = toml::from_str("style = \"aligned-no-space\"").unwrap();
224 assert_eq!(kebab_case.style, "aligned-no-space");
225
226 let snake_case: MD060Config = toml::from_str("style = \"aligned_no_space\"").unwrap();
227 assert_eq!(snake_case.style, "aligned-no-space");
228 }
229
230 #[test]
231 fn test_style_normalizes_case_for_compatibility() {
232 let uppercase: MD060Config = toml::from_str("style = \"ALIGNED_NO_SPACE\"").unwrap();
233 assert_eq!(uppercase.style, "aligned-no-space");
234 }
235}