1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
extern crate proc_macro;

use std::fmt::Write;

#[proc_macro]
pub fn templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
    templing_impl(input.value().as_str(), Vec::new())
        .parse()
        .unwrap()
}

#[proc_macro]
pub fn dbg_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
    panic!("{}", templing_impl(input.value().as_str(), Vec::new()));
}

#[proc_macro]
pub fn include_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
    let cargo_manifest_dir =
        std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
    let path = std::path::Path::new(&cargo_manifest_dir).join(input.value());
    let input = std::fs::read_to_string(&path).expect(&format!("Failed to read {:?}", path));
    templing_impl(input.as_str(), vec![path]).parse().unwrap()
}

#[proc_macro]
pub fn dbg_include_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
    let cargo_manifest_dir =
        std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
    let path = std::path::Path::new(&cargo_manifest_dir).join(input.value());
    let input = std::fs::read_to_string(&path).expect(&format!("Failed to read {:?}", path));
    panic!("{}", templing_impl(input.as_str(), vec![path]));
}

fn templing_impl(input: &str, file_dependencies: Vec<std::path::PathBuf>) -> String {
    let mut result = String::new();
    writeln!(&mut result, "{{").unwrap();
    for file in file_dependencies {
        writeln!(&mut result, "include_bytes!({:?});", file).unwrap();
    }
    writeln!(&mut result, "let mut templing_result = String::new();").unwrap();
    let mut current_line = 0;
    for line in input.lines() {
        current_line += 1;
        let non_ws = line.trim();
        if let Some(code) = non_ws.strip_prefix("- ") {
            writeln!(&mut result, "{}", code).unwrap();
        } else {
            let mut current_column = 1;
            let mut line = line.to_owned();
            if line.trim().starts_with('\\') {
                line = line.replacen('\\', "", 1);
                current_column += 1;
            }
            let mut line = line.as_str();
            let mut write_eol = true;
            if line.trim().starts_with('~') {
                let index = line.find('~').unwrap();
                current_column += line[index + 1..].chars().count();
                line = &line[index + 1..];
            }
            if line.trim().ends_with('~') {
                let index = line.rfind('~').unwrap();
                line = &line[..index];
                write_eol = false;
            }
            while !line.is_empty() {
                let index = match line.find("{{") {
                    Some(index) => index,
                    None => line.len(),
                };
                writeln!(
                    &mut result,
                    "templing_result.push_str({:?});",
                    &line[..index],
                )
                .unwrap();
                if index < line.len() {
                    current_column += line[..index + 2].chars().count();
                    writeln!(&mut result, "let templing_indentation = (templing_result.len() - templing_result.rfind('\\n').map(|pos| pos + 1).unwrap_or(0));").unwrap();
                    line = &line[index + 2..];
                    let index = line.find("}}").expect(&format!(
                        "Failed to find closing brackets for {}:{}",
                        current_line,
                        current_column - 2,
                    ));
                    let code = &line[..index];
                    if code.chars().next() == Some('#') {
                        writeln!(&mut result, "{}", &code[1..]).unwrap();
                    } else {
                        writeln!(
                            &mut result,
                            "let templing_value = {{ {} }}.to_string();",
                            code,
                        )
                        .unwrap();
                        writeln!(&mut result, "let templing_value = templing_value.trim();")
                            .unwrap();
                        writeln!(&mut result, "for (templing_part_index, templing_part) in templing_value.split('\\n').enumerate() {{").unwrap();
                        writeln!(&mut result, "if templing_part_index != 0 {{").unwrap();
                        writeln!(&mut result, "templing_result.push('\\n');").unwrap();
                        writeln!(&mut result, "for _ in 0..templing_indentation {{").unwrap();
                        writeln!(&mut result, "templing_result.push(' ');").unwrap();
                        writeln!(&mut result, "}}").unwrap();
                        writeln!(&mut result, "}}").unwrap();
                        writeln!(&mut result, "templing_result.push_str(templing_part);").unwrap();
                        writeln!(&mut result, "}}").unwrap();
                    }
                    current_column += line[..index + 2].chars().count();
                    line = &line[index + 2..];
                } else {
                    line = "";
                }
            }
            if write_eol {
                writeln!(&mut result, "templing_result.push('\\n');").unwrap();
            }
        }
    }
    writeln!(&mut result, "templing_result.trim().to_owned()").unwrap();
    writeln!(&mut result, "}}").unwrap();
    result
}