qi-rs 0.1.0

A tiny, fast, and powerful formatter for documentation, especially for content in CJK.
Documentation
pub mod range;
pub mod table;
pub mod util;

use util::{
    is_cjk, is_close_parentheses, is_colon, is_common_symbols, is_enclosed_cjk_letters_and_months,
    is_greek_and_coptic, is_latin1_supplement, is_open_parentheses,
    is_western_sentence_punctuation,
};
use wasm_bindgen::prelude::wasm_bindgen;

#[derive(PartialEq, Debug)]
#[wasm_bindgen]
pub enum CharType {
    Number,
    Alphabet,
    CJK,
    Colon,
    Other,
}

#[derive(Debug, Copy, Clone)]
#[wasm_bindgen]
pub struct SpacingOptions {
    pub punctuations: bool,
}

#[derive(Debug, Clone, Copy)]
#[wasm_bindgen]
pub struct Options {
    pub spacing: Option<SpacingOptions>,
}

#[wasm_bindgen]
pub fn get_char_type(c: char) -> CharType {
    if c.is_ascii_digit() {
        CharType::Number
    } else if c.is_ascii_alphabetic() {
        CharType::Alphabet
    } else if is_cjk(c) {
        CharType::CJK
    } else if is_colon(c) {
        CharType::Colon
    } else {
        CharType::Other
    }
}

fn spacing(
    pre_char: char,
    pre_type: &CharType,
    cur_char: char,
    cur_type: &CharType,
    options: Option<SpacingOptions>,
) -> bool {
    let spacing_opts = options.expect("spacing options should be set");
    match (pre_type, cur_type) {
        (CharType::Alphabet, CharType::Number) => false,
        (CharType::Alphabet, CharType::CJK) => true,
        (CharType::Alphabet, CharType::Other) => is_open_parentheses(cur_char),
        (CharType::CJK, CharType::Number) => true,
        (CharType::CJK, CharType::Alphabet) => true,
        (CharType::CJK, CharType::Other) => {
            is_common_symbols(cur_char)
                || is_latin1_supplement(cur_char)
                || is_greek_and_coptic(cur_char)
                || is_enclosed_cjk_letters_and_months(cur_char)
                || is_open_parentheses(cur_char)
        }
        (CharType::Number, CharType::Alphabet) => false,
        (CharType::Number, CharType::CJK) => true,
        (CharType::Number, CharType::Other) => false,
        (CharType::Other, CharType::CJK) => {
            is_common_symbols(pre_char)
                || is_latin1_supplement(pre_char)
                || is_greek_and_coptic(pre_char)
                || is_enclosed_cjk_letters_and_months(pre_char)
                || is_close_parentheses(pre_char)
                || (spacing_opts.punctuations && is_western_sentence_punctuation(pre_char))
        }
        (CharType::Other, CharType::Alphabet) => {
            is_close_parentheses(pre_char)
                || (spacing_opts.punctuations && is_western_sentence_punctuation(pre_char))
        }
        (CharType::Other, CharType::Number) => {
            spacing_opts.punctuations && is_western_sentence_punctuation(pre_char)
        }
        (CharType::Colon, CharType::Alphabet | CharType::CJK | CharType::Number) => true,
        (CharType::Colon, CharType::Other) => !cur_char.is_whitespace(),
        _ => false,
    }
}

#[wasm_bindgen]
pub fn format(text: &str, options: Option<Options>) -> String {
    let mut formatted = String::new();
    if text.is_empty() {
        return formatted;
    }
    let mut chars = text.chars();
    let first_char = chars.next().expect("should have at least one char");
    let mut pre_char = first_char;
    formatted.push(first_char);
    let mut pre_char_type = get_char_type(first_char);
    for cur_char in chars {
        let cur_char_type = get_char_type(cur_char);
        let default_spacing_opts = Some(SpacingOptions { punctuations: true });
        let spacing_opts = options.map_or(default_spacing_opts, |o| o.spacing);
        if cur_char_type != pre_char_type
            && spacing(
                pre_char,
                &pre_char_type,
                cur_char,
                &cur_char_type,
                spacing_opts,
            )
        {
            formatted.push('\u{0020}');
        }
        formatted.push(cur_char);
        pre_char_type = cur_char_type;
        pre_char = cur_char;
    }

    formatted
}