hmd 0.4.13

Custom Markdown Engine for my personal blog.
Documentation
use crate::consts::*;
use crate::markdown::icon::get_icon;
use crate::markdown::line::{Line, LineType};
use crate::markdown::colors::COLOR_NAMES;
use crate::utils::*;
use std::str::FromStr;


pub fn render_independent_tag(lines: &Vec<Line>) -> Vec<Line> {

    let mut result = Vec::with_capacity(lines.len());

    for ln in lines.iter() {

        if ln.line_type == LineType::Tag {
            let mut new_line = ln.clone();

            match parse_tag(&ln.content, 0) {
                Err(_) => {new_line.line_type = LineType::Paragraph;}
                Ok(c) => {new_line.content = c;}
            }

            result.push(new_line);
        }

        else {
            result.push(ln.clone());
        }

    }

    result
}


pub fn render_tag(content: &Vec<u16>) -> Vec<u16> {

    let mut result = vec![];
    let mut curr_index = 0;
    let mut last_index = 0;

    while curr_index < content.len() {

        if is_tag(content, curr_index) {
            let tag = parse_tag(content, curr_index);

            if tag.is_ok() {
                result.push(content[last_index..curr_index].to_vec());
                result.push(tag.unwrap());
                last_index = get_bracket_end_index(content, curr_index).unwrap() + 1;
                curr_index = last_index;
                continue;
            }

        }

        curr_index += 1;
    }

    if last_index < curr_index {
        result.push(content[last_index..].to_vec());
    }

    result.concat()
}


pub fn is_tag(content: &[u16], index: usize) -> bool {

    content[index] == U16_LEFT_SQUARE_BRACKET && index + 1 < content.len() && content[index + 1] == U16_LEFT_SQUARE_BRACKET && {

        let end1 = match get_bracket_end_index(content, index) {
            None => {return false;}
            Some(i) => i
        };

        let end2 = match get_bracket_end_index(content, index + 1) {
            None => {return false;}
            Some(i) => i
        };

        end2 + 1 == end1
    }
}


fn parse_tag(content: &Vec<u16>, index: usize) -> Result<Vec<u16>, ()> {

    let end_index = get_bracket_end_index(content, index + 1).unwrap();
    let content = lowercase_and_remove_spaces(&content[index + 2..end_index]);

    if content.len() == 0 {
        return Err(());
    }

    // <span class="color_red">
    if is_color_name(&content) {
        return Ok(vec![
            into_v16("<span class=\"color_"),
            content,
            into_v16("\">")
        ].concat());
    }

    // <span class="size_red">
    if is_size_name(&content) {
        return Ok(vec![
            into_v16("<span class=\"size_"),
            content,
            into_v16("\">")
        ].concat());
    }

    // <div class="align_center">
    if is_alignment_name(&content) {
        return Ok(vec![
            into_v16("<div class=\"align_"),
            content,
            into_v16("\">")
        ].concat());
    }

    // <div class="box">
    if is_box_name(&content) {
        return Ok(into_v16("<div class=\"box\">"));
    }

    if is_blank_name(&content) {
        return Ok(into_v16("&nbsp;"));
    }

    if is_icon(&content) {

        match parse_icon(&content) {
            Some(s) => {return Ok(s);}
            _ => {}
        }

    }

    if is_char(&content) {

        match parse_char(&content) {
            Some(s) => {return Ok(s);}
            _ => {}
        }

    }

    if content[0] == U16_SLASH {

        if is_color_name(&content[1..]) || is_size_name(&content[1..]) {
            return Ok(into_v16("</span>"));
        }

        if is_alignment_name(&content[1..]) || is_box_name(&content[1..]) {
            return Ok(into_v16("</div>"));
        }

    }

    Err(())
}

fn is_color_name(string: &[u16]) -> bool {
    COLOR_NAMES.contains(&string.to_vec())
}

fn is_size_name(string: &[u16]) -> bool {
    string == &into_v16("giant") ||
    string == &into_v16("big") ||
    string == &into_v16("small") ||
    string == &into_v16("medium")
}

fn is_alignment_name(string: &[u16]) -> bool {
    string == &into_v16("left") ||
    string == &into_v16("right") ||
    string == &into_v16("center")
}

fn is_box_name(string: &[u16]) -> bool {
    string.len() == 3 && string[0] == U16_SMALL_B && string[1] == U16_SMALL_O && string[2] == U16_SMALL_X
}

fn is_blank_name(string: &[u16]) -> bool {
    string.len() == 5 && string[0] == U16_SMALL_B && string[1] == U16_SMALL_L && string[2] == U16_SMALL_A && string[3] == U16_SMALL_N && string[4] == U16_SMALL_K
}

// if true, it's possibly a valid icon
// if not, it can never be an icon
fn is_icon(string: &[u16]) -> bool {
    string.len() > 5 && string[0] == U16_SMALL_I && string[1] == U16_SMALL_C && string[2] == U16_SMALL_O && string[3] == U16_SMALL_N && string[4] == U16_EQUAL
}

fn parse_icon(content: &[u16]) -> Option<Vec<u16>> {

    let mut curr_icon = None;
    let mut curr_size = None;
    let args = parse_arguments(content);

    // if the same arg is given twice, the later one is applied
    // I'm not raising an error for that
    for (key, value) in args.iter() {

        if key == &into_v16("icon") {
            curr_icon = Some(value.clone());
        }

        else if key == &into_v16("size") {
            curr_size = Some(value.clone());
        }

        else {
            return None;
        }

    }

    let curr_icon = if curr_icon.is_none() {
        return None
    } else {
        curr_icon.unwrap()
    };

    let curr_size = if curr_size.is_none() {
        24
    } else {
        match usize::from_str(&String::from_utf16_lossy(&curr_size.unwrap())) {
            Err(_) => {return None}
            Ok(n) => n
        }
    };

    get_icon(&curr_icon, curr_size, None, false)
}

// if true, it's possibly a valid char
// if not, it can never be an char
fn is_char(string: &[u16]) -> bool {
    string.len() > 5 && string[0] == U16_SMALL_C && string[1] == U16_SMALL_H && string[2] == U16_SMALL_A && string[3] == U16_SMALL_R && string[4] == U16_EQUAL
}

fn parse_char(content: &[u16]) -> Option<Vec<u16>> {
    let args = parse_arguments(content);

    if args.len() != 1 {
        return None;
    }

    let num = &args[0].1;

    match u16::from_str(&String::from_utf16_lossy(num)) {
        Err(_) => None,
        Ok(n) => Some(into_v16(&format!("&#{};", n) ))
    }

}

// `[[a = b, c = d, e = f]]` -> vec![(`a`, `b`), (`c`, `d`), (`e`, `f`)]
fn parse_arguments(content: &[u16]) -> Vec<(Vec<u16>, Vec<u16>)> {  // Vec<(key, value)>
    content.split(
        |c|
        *c == U16_COMMA
    ).filter_map(
        |arg| {
            let arg_split = arg.split(
                |c|
                *c == U16_EQUAL
            ).map(
                |word|
                word.to_vec()
            ).collect::<Vec<Vec<u16>>>();

            if arg_split.len() == 2 {
                Some((arg_split[0].clone(), arg_split[1].clone()))
            } else {
                None
            }
        }
    ).collect()
}