context-notation 0.1.4

Featherweight semantic notation for text
Documentation
use crate::safety::html::unsafe_string;
use synoptic::TokOpt;

pub const TAB_WIDTH: usize = 2;

pub fn convert_code_to_html(kind: &str, code: &str) -> Option<String> {
    // Add some aliases to file extensions
    let kind = match kind {
        "rust" => "rs",
        "python" => "py",
        "javascript" => "js",
        "typescript" => "ts",
        _ => kind,
    };

    let mut clip = 0;

    for character in code.chars().rev() {
        match character {
            '\n' | ' ' => clip += 1,
            _ => {
                break;
            }
        }
    }

    let (code, trailing_whitespace) = code.split_at(code.len() - clip);

    let trailing_whitespace = trailing_whitespace.replace('\n', "&#10;");

    let Some(mut highlighter) = synoptic::from_extension(kind, TAB_WIDTH) else {
        return None;
    };

    let lines = code
        .lines()
        .map(|line| line.to_owned())
        .collect::<Vec<String>>();

    highlighter.run(&lines);

    let highlighted = Some(
        lines
            .iter()
            .enumerate()
            .map(|(line_number, line)| {
                highlighter
                    .line(line_number, line)
                    .into_iter()
                    .map(|token| match token {
                        TokOpt::Some(text, kind) => {
                            format!("<span class=\"{kind}\">{}</span>", unsafe_string(&text))
                        }
                        TokOpt::None(text) => unsafe_string(&text).to_string(),
                    })
                    .collect::<Vec<String>>()
                    .concat()
            })
            .collect::<Vec<String>>()
            .join("&#10;")
            + &format!("{trailing_whitespace}"),
    );

    highlighted
}