templing/
lib.rs

1extern crate proc_macro;
2
3use std::fmt::Write;
4
5#[proc_macro]
6pub fn templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
8    templing_impl(input.value().as_str(), Vec::new())
9        .parse()
10        .unwrap()
11}
12
13#[proc_macro]
14pub fn dbg_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
15    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
16    panic!("{}", templing_impl(input.value().as_str(), Vec::new()));
17}
18
19#[proc_macro]
20pub fn include_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
21    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
22    let cargo_manifest_dir =
23        std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
24    let path = std::path::Path::new(&cargo_manifest_dir).join(input.value());
25    let input = std::fs::read_to_string(&path).expect(&format!("Failed to read {:?}", path));
26    templing_impl(input.as_str(), vec![path]).parse().unwrap()
27}
28
29#[proc_macro]
30pub fn dbg_include_templing(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
31    let input: syn::LitStr = syn::parse(input).expect("Expected a string literal as input");
32    let cargo_manifest_dir =
33        std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
34    let path = std::path::Path::new(&cargo_manifest_dir).join(input.value());
35    let input = std::fs::read_to_string(&path).expect(&format!("Failed to read {:?}", path));
36    panic!("{}", templing_impl(input.as_str(), vec![path]));
37}
38
39fn templing_impl(input: &str, file_dependencies: Vec<std::path::PathBuf>) -> String {
40    let mut result = String::new();
41    writeln!(&mut result, "{{").unwrap();
42    for file in file_dependencies {
43        writeln!(&mut result, "include_bytes!({:?});", file).unwrap();
44    }
45    writeln!(&mut result, "let mut templing_result = String::new();").unwrap();
46    let mut current_line = 0;
47    for line in input.lines() {
48        current_line += 1;
49        let non_ws = line.trim();
50        if let Some(code) = non_ws.strip_prefix("- ") {
51            writeln!(&mut result, "{}", code).unwrap();
52        } else {
53            let mut current_column = 1;
54            let mut line = line.to_owned();
55            if line.trim().starts_with('\\') {
56                line = line.replacen('\\', "", 1);
57                current_column += 1;
58            }
59            let mut line = line.as_str();
60            let mut write_eol = true;
61            if line.trim().starts_with('~') {
62                let index = line.find('~').unwrap();
63                current_column += line[index + 1..].chars().count();
64                line = &line[index + 1..];
65            }
66            if line.trim().ends_with('~') {
67                let index = line.rfind('~').unwrap();
68                line = &line[..index];
69                write_eol = false;
70            }
71            while !line.is_empty() {
72                let index = match line.find("{{") {
73                    Some(index) => index,
74                    None => line.len(),
75                };
76                writeln!(
77                    &mut result,
78                    "templing_result.push_str({:?});",
79                    &line[..index],
80                )
81                .unwrap();
82                if index < line.len() {
83                    current_column += line[..index + 2].chars().count();
84                    writeln!(&mut result, "let templing_indentation = (templing_result.len() - templing_result.rfind('\\n').map(|pos| pos + 1).unwrap_or(0));").unwrap();
85                    line = &line[index + 2..];
86                    let index = line.find("}}").expect(&format!(
87                        "Failed to find closing brackets for {}:{}",
88                        current_line,
89                        current_column - 2,
90                    ));
91                    let code = &line[..index];
92                    if code.chars().next() == Some('#') {
93                        writeln!(&mut result, "{}", &code[1..]).unwrap();
94                    } else {
95                        writeln!(
96                            &mut result,
97                            "let templing_value = {{ {} }}.to_string();",
98                            code,
99                        )
100                        .unwrap();
101                        writeln!(&mut result, "let templing_value = templing_value.trim();")
102                            .unwrap();
103                        writeln!(&mut result, "for (templing_part_index, templing_part) in templing_value.split('\\n').enumerate() {{").unwrap();
104                        writeln!(&mut result, "if templing_part_index != 0 {{").unwrap();
105                        writeln!(&mut result, "templing_result.push('\\n');").unwrap();
106                        writeln!(&mut result, "for _ in 0..templing_indentation {{").unwrap();
107                        writeln!(&mut result, "templing_result.push(' ');").unwrap();
108                        writeln!(&mut result, "}}").unwrap();
109                        writeln!(&mut result, "}}").unwrap();
110                        writeln!(&mut result, "templing_result.push_str(templing_part);").unwrap();
111                        writeln!(&mut result, "}}").unwrap();
112                    }
113                    current_column += line[..index + 2].chars().count();
114                    line = &line[index + 2..];
115                } else {
116                    line = "";
117                }
118            }
119            if write_eol {
120                writeln!(&mut result, "templing_result.push('\\n');").unwrap();
121            }
122        }
123    }
124    writeln!(&mut result, "templing_result.trim().to_owned()").unwrap();
125    writeln!(&mut result, "}}").unwrap();
126    result
127}