dynamic-html 1.0.0

Transpiler from dynamic-html to typescript
Documentation
pub mod generate;
pub mod parser;

use std::path;

use generate::escape;
pub use generate::GenerateOptions;
use parser::{handle_expression, normalize, search_delimiter};
use parser::{HtmlImportPart, HtmlPart, ParseError, CLOSE_DELIMITER, OPEN_DELIMITER};
use pathdiff::diff_paths;

#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;

#[derive(Debug, Clone)]
pub struct DynamicHtml {
    pub imports: Vec<HtmlImportPart>,
    pub parts: Vec<HtmlPart>,
}
impl DynamicHtml {
    pub fn parse(content: &String) -> Result<DynamicHtml, ParseError> {
        let mut last_index = 0;
        let mut parts: Vec<HtmlPart> = vec![];

        while last_index < content.len() {
            let open_index = search_delimiter(OPEN_DELIMITER, last_index, &content);
            let has_delimiter = open_index.is_some();
            let open_index = open_index.unwrap_or_else(|| content.len());

            let expression = &content[last_index..open_index].trim();
            if expression.len() != 0 {
                parts.push(HtmlPart::Literal(expression.to_string()));
            }

            if !has_delimiter {
                break;
            }

            last_index = open_index + 1;
            let close_index = search_delimiter(CLOSE_DELIMITER, last_index, &content);

            if let Some(close_index) = close_index {
                let expression = &content[last_index..close_index];
                let part = handle_expression(expression);

                if part.is_some() {
                    parts.push(part.unwrap());
                }

                last_index = close_index + 1;
            } else {
                return Err(ParseError::Unclosed);
            }
        }

        normalize(parts)
    }

    pub fn generate(&self, options: &GenerateOptions) -> String {
        let GenerateOptions {
            input_path,
            output_path,

            header,
            pre_imports,
            data_varname,
            escape_function,
        } = options;

        let input_path = path::Path::new(&input_path);
        let input_path = input_path.parent().unwrap();

        let output_path = path::Path::new(&output_path);
        let output_path = output_path.parent().unwrap();

        let imports = self
            .imports
            .iter()
            .map(|import| {
                let imports = match &import.imports {
                    Some(expr) => format!("{} {} {}", "{", expr, "}"),
                    None => "".to_string(),
                };

                let imports = match &import.default_name {
                    Some(expr) => format!("{}, {}", expr, imports),
                    None => imports,
                };

                let relative_path =
                    diff_paths(input_path.join(import.filename.clone()), output_path).unwrap();
                let relative_path = relative_path.to_str().unwrap();

                let relative_path = if &relative_path.chars().nth(0) == &Some('.') {
                    relative_path.to_string()
                } else {
                    format!("./{}", relative_path)
                };

                let resolved_path = if &import.filename.chars().nth(0) == &Some('.') {
                    relative_path.as_str()
                } else {
                    &import.filename
                };

                format!("import {} from \"{}\"", imports, resolved_path)
            })
            .reduce(|acc, e| acc + ";\n" + &e)
            .unwrap_or("".to_owned())
            + ";";

        let body = self
            .parts
            .iter()
            .map(|part| match part {
                HtmlPart::Literal(content) => format!("__output__ += \"{}\"", escape(content)),
                HtmlPart::Eval(content) => {
                    format!("__output__ += {}(({}))", escape_function, content)
                }
                HtmlPart::Unescaped(content) => format!("__output__ += String((\"{}\"))", content),
                HtmlPart::Block(content) => content.to_string(),
                _ => "// INVALID TYPE".to_string(),
            })
            .reduce(|acc, e| acc + ";\n  " + &e)
            .unwrap_or("".to_owned())
            + ";";

        format!(
            "{}\n{}\n{}

export default function({}: any, __output__: string = \"\"): string {}
  {}
  return __output__;
{}",
            header, pre_imports, imports, data_varname, "{", body, "}"
        )
    }
}

#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub fn parse(content: String, options: String) -> Result<String, JsValue> {
    println!("{:?}", &options);
    match DynamicHtml::parse(&content) {
        Ok(out) => Ok(out.generate(&GenerateOptions::from_json(options))),
        Err(_) => Err(JsValue::null()),
    }
}