alef 0.103.6

Opinionated polyglot binding generator for Rust libraries
Documentation
#[derive(Clone, Copy)]
pub(super) enum FenceEvent {
    Open(usize),
    Close,
}

pub(super) fn fence_event(line: &str, opening_ticks: Option<usize>) -> Option<FenceEvent> {
    let trimmed = line.trim_start();
    let tick_count = trimmed.chars().take_while(|character| *character == '`').count();
    match opening_ticks {
        Some(opening_ticks) if tick_count >= opening_ticks && trimmed[tick_count..].trim().is_empty() => {
            Some(FenceEvent::Close)
        }
        None if tick_count >= 3 => Some(FenceEvent::Open(tick_count)),
        _ => None,
    }
}

pub(super) fn apply_fence_event(opening_ticks: &mut Option<usize>, event: FenceEvent) {
    *opening_ticks = match event {
        FenceEvent::Open(ticks) => Some(ticks),
        FenceEvent::Close => None,
    };
}

pub(super) fn canonicalize_rustdoc_markdown(doc: &str) -> String {
    let mut output = Vec::new();
    let mut fence_ticks = None;
    let mut previous_was_heading = false;

    for raw_line in doc.lines() {
        let trimmed = raw_line.trim_start();
        let tick_count = trimmed.chars().take_while(|character| *character == '`').count();
        let is_fence = tick_count >= 3;
        let closes_fence = fence_ticks
            .is_some_and(|opening_ticks| tick_count >= opening_ticks && trimmed[tick_count..].trim().is_empty());
        let is_heading = fence_ticks.is_none() && is_markdown_heading(trimmed);

        if is_heading && output.last().is_some_and(|line: &String| !line.is_empty()) {
            output.push(String::new());
        } else if previous_was_heading && !raw_line.trim().is_empty() {
            output.push(String::new());
        }

        let line = if fence_ticks.is_none() && is_fence && trimmed[tick_count..].trim().is_empty() {
            let indentation = &raw_line[..raw_line.len() - trimmed.len()];
            format!("{indentation}{}rust", "`".repeat(tick_count))
        } else if fence_ticks.is_some() || is_fence {
            raw_line.to_string()
        } else {
            normalize_sentence_spacing(raw_line)
        };
        output.push(line);

        if closes_fence {
            fence_ticks = None;
        } else if fence_ticks.is_none() && is_fence {
            fence_ticks = Some(tick_count);
        }
        previous_was_heading = is_heading;
    }

    output.join("\n")
}

pub(super) fn markdown_to_table_cell_inline(doc: &str) -> String {
    let mut parts = Vec::new();
    let mut code_lines = Vec::new();
    let mut fence_ticks = None;

    for line in doc.lines() {
        let trimmed = line.trim();
        if let Some(event) = fence_event(line, fence_ticks) {
            match event {
                FenceEvent::Open(ticks) => fence_ticks = Some(ticks),
                FenceEvent::Close => {
                    fence_ticks = None;
                    parts.push(render_inline_code(&code_lines.join(" ")));
                    code_lines.clear();
                }
            }
            continue;
        }
        if trimmed.is_empty() {
            continue;
        }
        if fence_ticks.is_some() {
            code_lines.push(trimmed);
        } else {
            parts.push(trimmed.to_string());
        }
    }

    if fence_ticks.is_some() && !code_lines.is_empty() {
        parts.push(render_inline_code(&code_lines.join(" ")));
    }

    parts.join(" ")
}

fn is_markdown_heading(line: &str) -> bool {
    let hashes = line.chars().take_while(|character| *character == '#').count();
    (1..=6).contains(&hashes) && line.as_bytes().get(hashes) == Some(&b' ')
}

fn normalize_sentence_spacing(line: &str) -> String {
    let chars: Vec<char> = line.chars().collect();
    let mut output = String::with_capacity(line.len());
    let mut code_delimiter = 0usize;
    let mut index = 0usize;

    while index < chars.len() {
        if chars[index] == '`' {
            let start = index;
            while index < chars.len() && chars[index] == '`' {
                index += 1;
            }
            let run = index - start;
            if code_delimiter == 0 {
                code_delimiter = run;
            } else if code_delimiter == run {
                code_delimiter = 0;
            }
            output.extend(std::iter::repeat_n('`', run));
            continue;
        }

        if chars[index] == ' ' && code_delimiter == 0 {
            let start = index;
            while index < chars.len() && chars[index] == ' ' {
                index += 1;
            }
            let previous = output.chars().next_back();
            if index < chars.len() && index - start > 1 && previous.is_some_and(|ch| matches!(ch, '.' | '?' | '!')) {
                output.push(' ');
            } else {
                output.extend(std::iter::repeat_n(' ', index - start));
            }
            continue;
        }

        output.push(chars[index]);
        index += 1;
    }

    output
}

fn render_inline_code(code: &str) -> String {
    let max_run = code
        .split(|character| character != '`')
        .map(str::len)
        .max()
        .unwrap_or(0);
    let delimiter = "`".repeat(max_run + 1);
    if code.starts_with('`') || code.ends_with('`') {
        format!("{delimiter} {code} {delimiter}")
    } else {
        format!("{delimiter}{code}{delimiter}")
    }
}