markdownlint-rs 0.3.23

A fast, flexible, configuration-based command-line interface for linting Markdown/CommonMark files
Documentation
use crate::config::{Config, RuleConfig};
use std::fmt::Write as _;

const HEADER: &str = "\
# Generated by `mdlint migrate` from a markdownlint-cli2 configuration.
# See mdlint.default.toml in the mdlint repository for the full annotated reference
# of every option and rule parameter.
";

/// Render a `Config` as TOML, with `[rules.MDxxx]` sections in ascending numeric order
/// (`HashMap` iteration order is otherwise unstable, which would make repeated
/// migrations produce spurious diffs).
pub fn render(config: &Config) -> String {
    let mut out = String::from(HEADER);
    out.push('\n');

    writeln!(out, "default_enabled = {}", config.default_enabled).unwrap();
    writeln!(out, "gitignore = {}", config.gitignore).unwrap();
    writeln!(out, "no_inline_config = {}", config.no_inline_config).unwrap();
    writeln!(out, "fix = {}", config.fix).unwrap();

    if let Some(front_matter) = &config.front_matter {
        writeln!(out, "front_matter = {front_matter:?}").unwrap();
    }

    if !config.exclude.is_empty() {
        let quoted: Vec<String> = config.exclude.iter().map(|e| format!("{e:?}")).collect();
        writeln!(out, "exclude = [{}]", quoted.join(", ")).unwrap();
    }

    let mut rule_codes: Vec<&String> = config.rules.keys().collect();
    rule_codes.sort();

    for code in rule_codes {
        let rule_config = &config.rules[code];
        out.push('\n');
        writeln!(out, "[rules.{code}]").unwrap();

        match rule_config {
            RuleConfig::Enabled(enabled) => {
                writeln!(out, "enabled = {enabled}").unwrap();
            }
            RuleConfig::Config(params) => {
                let mut keys: Vec<&String> = params.keys().collect();
                keys.sort();
                for key in keys {
                    writeln!(out, "{} = {}", key, params[key]).unwrap();
                }
            }
        }
    }

    out
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn renders_sorted_rule_sections_and_reloads() {
        let mut rules = HashMap::new();
        rules.insert(
            "MD013".to_string(),
            RuleConfig::Config(HashMap::from([(
                "line_length".to_string(),
                toml::Value::Integer(100),
            )])),
        );
        rules.insert("MD003".to_string(), RuleConfig::Enabled(false));

        let config = Config {
            rules,
            ..Config::default()
        };

        let rendered = render(&config);
        let md013_pos = rendered.find("[rules.MD013]").unwrap();
        let md003_pos = rendered.find("[rules.MD003]").unwrap();
        assert!(md003_pos < md013_pos);

        let reloaded: Config = toml::from_str(&rendered).unwrap();
        assert_eq!(reloaded.rules.len(), 2);
    }
}