mupdf-sys 0.6.0

Rust FFI binding to MuPDF
Documentation
use std::{cell::RefCell, collections::HashSet};

use bindgen::callbacks::{EnumVariantValue, ItemInfo, ParseCallbacks};
use regex::{Captures, Regex, RegexBuilder};

#[derive(Debug)]
pub struct DocsCallbacks {
    types: Regex,
    full_names: RefCell<HashSet<String>>,
}

impl Default for DocsCallbacks {
    fn default() -> Self {
        Self {
            types: RegexBuilder::new("fz_[a-z_*]+")
                .case_insensitive(true)
                .build()
                .unwrap(),
            full_names: RefCell::default(),
        }
    }
}

impl ParseCallbacks for DocsCallbacks {
    fn item_name(&self, item_info: ItemInfo<'_>) -> Option<String> {
        self.full_names
            .borrow_mut()
            .insert(item_info.name.to_owned());
        None
    }

    fn enum_variant_name(
        &self,
        _enum_name: Option<&str>,
        original_variant_name: &str,
        _variant_value: EnumVariantValue,
    ) -> Option<String> {
        self.full_names
            .borrow_mut()
            .insert(original_variant_name.to_owned());
        None
    }

    fn process_comment(&self, comment: &str) -> Option<String> {
        let mut output = String::new();
        let mut newlines = 0;
        let mut arguments = false;

        for line in comment.split('\n') {
            let mut line = line.trim();
            if line.is_empty() {
                newlines += 1;
                continue;
            }

            let mut argument = false;
            if let Some(pline) = line.strip_prefix("@param") {
                line = pline;
                argument = true;
            }

            match newlines {
                _ if argument => output.push('\n'),
                0 => {}
                1 => output.push_str("<br>"),
                _ => output.push_str("\n\n"),
            };
            newlines = 0;

            if argument {
                if !arguments {
                    output.push_str("# Arguments\n");
                    arguments = true;
                }
                output.push_str("* ");
            }

            let line = line
                .replace('[', "\\[")
                .replace(']', "\\]")
                .replace('(', "\\(")
                .replace(')', "\\)")
                .replace('<', "\\<")
                .replace('>', "\\>")
                .replace("NULL", "`NULL`");
            let mut line = self.types.replace_all(&line, |c: &Captures| {
                let name = &c[0];
                if name.contains('*') {
                    return format!("`{name}`");
                }

                let full_names = self.full_names.borrow();
                if !full_names.contains(name) {
                    const SUFFIXES: [&str; 3] = ["s", "ed", "ped"];

                    for suffix in SUFFIXES {
                        if let Some(short_name) = name.strip_suffix(suffix) {
                            if !full_names.contains(short_name) {
                                return format!("[`{short_name}`]({short_name}){suffix}");
                            }
                        }
                    }

                    for suffix in SUFFIXES {
                        if let Some(short_name) = name.strip_suffix(suffix) {
                            return format!("[`{short_name}`]({short_name}){suffix}");
                        }
                    }
                }

                format!("[`{name}`]")
            });

            if let Some((first, rest)) = line.split_once(": ") {
                let mut new_line = String::new();

                for arg in first.split(", ") {
                    if arg.contains(|c: char| c.is_whitespace() || c == '`') {
                        new_line.clear();
                        break;
                    }

                    if !new_line.is_empty() {
                        new_line.push_str(", ");
                    }
                    new_line.push('`');
                    new_line.push_str(arg);
                    new_line.push('`');
                }

                if !new_line.is_empty() {
                    new_line.push_str(": ");
                    new_line.push_str(rest);
                    line = new_line.into();
                }
            }

            output.push_str(&line);

            newlines += 1;
        }
        Some(output)
    }
}