1use std::fs;
2use std::path::Path;
3use syn::spanned::Spanned;
4use syn::visit::Visit;
5use syn::{parse_file, ItemStruct};
6
7#[derive(Debug)]
8pub struct InlinedTemplate {
10 pub struct_name: String,
11 pub start: (usize, usize),
12 pub end: (usize, usize),
13 pub template: String,
14}
15
16#[derive(Debug)]
17pub struct InlinedTemplateFinder {
19 pub file_contents: String,
20 pub templates: Vec<InlinedTemplate>,
21}
22
23impl InlinedTemplateFinder {
24 pub fn new(file_contents: String) -> Self {
26 InlinedTemplateFinder {
27 file_contents,
28 templates: Vec::new(),
29 }
30 }
31}
32
33impl<'ast> Visit<'ast> for InlinedTemplateFinder {
34 fn visit_item_struct(&mut self, i: &'ast ItemStruct) {
35 let mut has_pax = false;
36 let struct_name = i.ident.to_string();
37 for attr in &i.attrs {
38 if attr.path.is_ident("pax") {
40 has_pax = true;
41 }
42
43 if attr.path.is_ident("inlined") {
44 let start = attr.tokens.span().start();
45 let start_tuple = (start.line, start.column + 1);
46 let end = attr.tokens.span().end();
47 let end_tuple = (end.line, end.column + 1);
48 let content =
49 get_substring_by_line_column(&self.file_contents, start_tuple, end_tuple)
50 .unwrap()
51 .trim_start_matches("(")
52 .trim_end_matches(")")
53 .to_string();
54 if has_pax {
55 let found_template = InlinedTemplate {
56 struct_name: struct_name.clone(),
57 start: start_tuple,
58 end: end_tuple,
59 template: content,
60 };
61 self.templates.insert(0, found_template);
62 }
63 }
64 }
65 }
66}
67
68fn find_start_end_bytes(
69 input: &str,
70 start: (usize, usize),
71 end: (usize, usize),
72) -> (Option<usize>, Option<usize>) {
73 let mut current_line = 1;
74 let mut current_column = 1;
75 let mut start_byte = None;
76 let mut end_byte = None;
77
78 for (i, c) in input.char_indices() {
79 if current_line == start.0 && current_column == start.1 {
80 start_byte = Some(i);
81 }
82 if current_line == end.0 && current_column == end.1 {
83 end_byte = Some(i);
84 break;
85 }
86
87 if c == '\n' {
88 current_line += 1;
89 current_column = 1;
90 } else {
91 current_column += 1;
92 }
93 }
94
95 (start_byte, end_byte)
96}
97
98pub fn replace_by_line_column(
100 input: &str,
101 start: (usize, usize),
102 end: (usize, usize),
103 replacement: String,
104) -> Option<String> {
105 let (start_byte, end_byte) = find_start_end_bytes(input, start, end);
106
107 match (start_byte, end_byte) {
108 (Some(start_byte), Some(end_byte)) => {
109 let mut result = String::new();
110 result.push_str(&input[..start_byte]);
111 result.push_str(&replacement);
112 result.push_str(&input[end_byte..]);
113 Some(result)
114 }
115 _ => None,
116 }
117}
118
119pub fn get_substring_by_line_column(
121 input: &str,
122 start: (usize, usize),
123 end: (usize, usize),
124) -> Option<String> {
125 let (start_byte, end_byte) = find_start_end_bytes(input, start, end);
126
127 match (start_byte, end_byte) {
128 (Some(start_byte), Some(end_byte)) => Some(input[start_byte..end_byte].to_string()),
129 _ => None,
130 }
131}
132
133pub fn clear_inlined_template(file_path: &str, pascal_identifier: &str) {
135 let path = Path::new(file_path);
136 let content = fs::read_to_string(path).expect("Failed to read file");
137 let ast = parse_file(&content).expect("Failed to parse file");
138
139 let mut finder = InlinedTemplateFinder::new(content.clone());
140 finder.visit_file(&ast);
141
142 let mut modified_content = content;
143 for template in finder.templates {
144 if template.struct_name == pascal_identifier {
145 let blank_template = format!("()");
146 modified_content = replace_by_line_column(
147 &modified_content,
148 template.start,
149 template.end,
150 blank_template,
151 )
152 .unwrap();
153 }
154 }
155 fs::write(path, modified_content).expect("Failed to write to file");
156}