socketry-markdown 0.4.0

CommonMark compliant markdown parser in Rust with ASTs and extensions
Documentation
// Released under the MIT License.
// Copyright, 2026, by Samuel Williams.

use super::Handle;
use crate::markdown::{
    state::{Info, State},
    util::longest_char_streak::longest_char_streak,
};
use crate::{
    mdast::{Frontmatter, Node},
    message::Message,
};
use alloc::{
    boxed::Box,
    format,
    string::{String, ToString},
};

impl Handle for Frontmatter {
    fn handle(
        &self,
        _state: &mut State,
        _info: &Info,
        _parent: Option<&Node>,
        _node: &Node,
    ) -> Result<String, Message> {
        if !matches!(self.fence, '`' | '~' | '-' | '+')
            || self.info.trim_matches([' ', '\t']).is_empty()
            || self.info.contains(['\r', '\n'])
            || (self.fence == '`' && self.info.contains('`'))
        {
            return Err(Message {
                place: None,
                reason:
                    "Frontmatter requires a valid fence and a nonempty, single-line info string"
                        .into(),
                rule_id: Box::new("invalid-frontmatter".into()),
                source: Box::new("mdast-util-to-markdown".into()),
            });
        }
        let mut marker = self.fence;
        if matches!(marker, '-' | '+') {
            let delimiter = marker.to_string().repeat(3);
            if self
                .value
                .split(['\r', '\n'])
                .any(|line| line.trim_end_matches([' ', '\t']) == delimiter)
            {
                marker = '~';
            }
        }
        let (opening_length, closing_length) = if matches!(marker, '-' | '+') {
            (3, 3)
        } else {
            let minimum = (longest_char_streak(&self.value, marker) + 1).max(3);
            let opening = self.fence_length.max(minimum);
            (opening, self.closing_fence_length.max(opening))
        };
        let opening = marker.to_string().repeat(opening_length);
        let closing = marker.to_string().repeat(closing_length);
        let info = self.info.trim_matches([' ', '\t']);
        let separator = if matches!(marker, '-' | '+') || info.starts_with(marker) {
            " "
        } else {
            ""
        };
        let mut output = format!("{opening}{separator}{}\n", info);
        output.push_str(&self.value);
        if !self.value.is_empty() && !self.value.ends_with(['\r', '\n']) {
            output.push('\n');
        }
        output.push_str(&closing);
        Ok(output)
    }
}