villain 0.0.1

A webframework that utilizes Vue templates to generate HTML. Aimed to replace the need for JavaScript in web development.
Documentation
use proc_macro::TokenStream;

use crate::parser::{
    parser::{Node, NodeType, Parser},
    scanner::Scanner,
    token::Token,
};

pub fn expand_template(path: String) -> TokenStream {
    let template_path_opt = if path.is_empty() {
        None
    } else {
        Some(path.replace('"', ""))
    };
    let template_path = template_path_opt.unwrap_or_else(|| "src/App.vue".to_string());
    let template = std::fs::read_to_string(template_path.clone())
        .unwrap_or_else(|_| panic!("Could not read template file: {template_path}"));

    let scanner = Scanner::new(template);
    let tokens: Vec<Token> = scanner.try_into().unwrap();
    let parser = Parser::new(tokens);
    let mut code: String = "".into();

    let root: Node = parser.try_into().unwrap();

    fn convert_children(code: &mut String, node: &Node) {
        match &node.node_type {
            NodeType::Tag(tag) => {
                if tag != "template" {
                    code.push_str(
                        format!(
                            "
                    let e = document.create_element(\"{tag}\").unwrap();
                    parents.last().unwrap().append_child(&e).unwrap();
                    parents.push(e);
                    "
                        )
                        .as_str(),
                    );
                }

                for child in &node.children {
                    convert_children(code, child);
                }

                code.push_str("parents.pop();");
            }
            NodeType::Attribute(name, value, _) => {
                if name == "v-model" {
                    let sig = value.as_ref().unwrap().value.as_ref().unwrap();

                    code.push_str(
                        format!(
                            r#"
    let cloned_{sig} = msg.clone();

    parents
        .last()
        .unwrap()
        .add_event_listener_with_callback(
            "keypress",
            &Closure::<dyn FnMut(web_sys::Event)>::new(move |event: web_sys::Event| {{
                let input = event
                    .current_target()
                    .unwrap()
                    .dyn_into::<web_sys::HtmlInputElement>()
                    .unwrap();

                cloned_{sig}.set(input.value().parse::<i32>().unwrap());
            }})
            .into_js_value()
            .as_ref()
            .unchecked_ref(),
        )
        .unwrap();
                        "#,
                        )
                        .as_str(),
                    );
                }

                code.push_str(
                    format!(
                        "
                     parents.last().unwrap().set_attribute(\"{}\", \"{}\").unwrap();",
                        name,
                        if let Some(token) = value {
                            token.value.as_ref().unwrap()
                        } else {
                            ""
                        }
                    )
                    .as_str(),
                );
            }
            NodeType::Text(text) => {
                code.push_str(
                    format!(
                        "
                    let e = document.create_text_node(\"{}\");
                    let p = parents.last().unwrap().clone();
                    p.append_child(&e).unwrap();

                    let document_clone = document.clone();
                    let future = msg.signal().for_each(move |value| {{
                        // This code is run for the current value of my_state,
                        // and also every time my_state changes


                        let n = document_clone.create_text_node(&format!(\"{{}}\", value));
                        p.append_child(&n).unwrap();
                        // p.remove_child(&e).unwrap();

                        async {{}}
                    }});
                    spawn_local(future);
                    ",
                        &text.escape_default()
                    )
                    .as_str(),
                );
            }
            _ => {}
        }
    }

    for child in &root.children {
        convert_children(&mut code, child);
    }

    format!(
        "fn template(document: web_sys::Document, root: web_sys::Element) {{
            // Stack of parents since nodes as nested and we basically emulate recursion
            use futures_signals::signal::Mutable;
            use futures_signals::signal::SignalExt;
            use wasm_bindgen_futures::spawn_local;

            let msg = Mutable::new(1);
            let mut parents = vec![root];
            {code}
        }}"
    )
    .parse()
    .unwrap()
}