1use lazy_static;
2use regex::{Captures, Regex};
3use serde::{Deserialize, Serialize};
4use std::{
5 collections::{HashMap, HashSet},
6 path::{Path, PathBuf},
7};
8
9use crate::{
10 connector::OutputMapFile,
11 ron::path_at::{Component, find_strings},
12};
13
14#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq)]
15pub struct ReadOutput {
16 pub addr: PathBuf,
17 pub key: String,
18}
19
20impl ReadOutput {
21 pub fn into_string(&self) -> String {
22 format!("out://{}[{}]", self.addr.to_string_lossy(), self.key)
23 }
24}
25
26lazy_static::lazy_static! {
27 pub static ref OUTREF_REGEX: Regex = Regex::new(r#"out://([^\[]+)\[([^\]]+)\]"#).unwrap();
28}
29
30pub fn get_read_outputs(config: &str) -> Vec<ReadOutput> {
33 let re = Regex::new(r#"out://([^\[]+)\[([^\]]+)\]"#).unwrap();
37 let mut outputs = Vec::new();
38 for cap in re.captures_iter(config) {
39 let filename = cap.get(1).map(|m| m.as_str()).unwrap_or("");
40 let key = cap.get(2).map(|m| m.as_str()).unwrap_or("");
41 outputs.push(ReadOutput {
42 addr: PathBuf::from(filename),
43 key: key.to_string(),
44 });
45 }
46 outputs
47}
48
49type RonPath = Vec<Component>;
50
51#[derive(Default)]
52pub struct RonOutputReport {
53 pub root: Option<String>,
54 pub reads_outputs: Vec<(RonPath, ReadOutput)>,
55}
56
57pub fn descend_get_read_outputs(config: &str) -> anyhow::Result<RonOutputReport> {
59 let mut res = RonOutputReport::default();
60
61 let reads_strings = find_strings(config)?;
62
63 res.root = reads_strings.root;
64
65 for (path, s) in reads_strings.strings {
66 if let Some(cap) = OUTREF_REGEX.captures(&s) {
67 let filename = cap.get(1).map(|m| m.as_str()).unwrap_or("");
68 let key = cap.get(2).map(|m| m.as_str()).unwrap_or("");
69 res.reads_outputs.push((
70 path,
71 ReadOutput {
72 addr: PathBuf::from(filename),
73 key: key.to_string(),
74 },
75 ));
76 }
77 }
78
79 Ok(res)
80}
81
82#[derive(Debug)]
83pub struct TemplateResourceResult {
84 pub body: String,
85 pub found: HashMap<ReadOutput, String>,
86 pub missing: HashSet<ReadOutput>,
87}
88
89pub fn template_config(prefix: &Path, config: &str) -> anyhow::Result<TemplateResourceResult> {
93 let re = Regex::new(r#"out://(?<addr>[^\[]+)\[(?<key>[^\]]+)\]"#)?;
95
96 let mut found = HashMap::new();
97 let mut missing = HashSet::<ReadOutput>::new();
98
99 let output = re.replace_all(config, |caps: &Captures| {
101 let addr = &caps["addr"];
102 let key = &caps["key"];
103 let phy_addr = PathBuf::from(addr);
104 let addr = PathBuf::from(addr);
105
106 let prefix = prefix.to_path_buf();
107
108 match OutputMapFile::get(&prefix, &phy_addr, key) {
111 Ok(Some(val)) => {
112 found.insert(
113 ReadOutput {
114 addr,
115 key: key.to_string(),
116 },
117 val.clone(),
118 );
119 val
120 }
121 _ => {
122 let val = caps.get(0).unwrap().as_str().to_string();
123 missing.insert(ReadOutput {
124 addr,
125 key: key.to_string(),
126 });
127 val
128 }
129 }
130 });
131
132 Ok(TemplateResourceResult {
133 body: output.into_owned(),
134 found,
135 missing,
136 })
137}
138
139pub fn reverse_template_config(
144 prefix: &Path,
145 templated_config: &str,
146 raw_config: &str,
147 min_length: usize,
148) -> anyhow::Result<String> {
149 let mut result = raw_config.to_string();
150 let template_result = template_config(prefix, templated_config)?;
151
152 for (key, value) in template_result.found {
153 if value.len() < min_length {
154 continue;
155 }
156
157 result = result.replace(&value, &key.into_string());
158 }
159
160 Ok(result)
161}
162
163const MIN_ANCHOR_LEN: usize = 3;
164
165#[derive(Debug)]
166pub struct Comment {
167 text: String,
168 after: Option<String>,
169 before: Option<String>,
170}
171
172fn code_lines(target: &str) -> Vec<(usize, String)> {
173 let mut out = Vec::new();
174 let mut idx = 0;
175 for line in target.lines() {
176 out.push((idx, line.to_string()));
177 idx += line.len() + 1; }
179 out
180}
181
182pub fn extract_comments(src: &str) -> Vec<Comment> {
183 let re_line = Regex::new(r"(?m)^\s*//.*").unwrap();
184
185 let mut comments = Vec::new();
186
187 let lines: Vec<&str> = src.lines().collect();
188
189 if let Some(first_line) = lines.first()
190 && re_line.is_match(first_line)
191 {
192 comments.push(Comment {
193 text: first_line.to_string(),
194 after: lines.get(1).map(|s| s.to_string()),
195 before: None,
196 });
197 }
198
199 for window in lines.windows(3) {
200 let prev_line = window
201 .first()
202 .filter(|s| s.trim().len() >= MIN_ANCHOR_LEN)
203 .map(|s| s.to_string());
204
205 let line = window.get(1).unwrap();
206
207 let next_line = window
208 .get(2)
209 .filter(|s| s.trim().len() >= MIN_ANCHOR_LEN)
210 .map(|s| s.to_string());
211
212 if re_line.is_match(line) {
213 comments.push(Comment {
214 text: line.to_string(),
215 after: next_line,
216 before: prev_line,
217 });
218 }
219 }
220
221 if let Some(last_line) = lines.last()
222 && re_line.is_match(last_line)
223 {
224 comments.push(Comment {
225 text: last_line.to_string(),
226 after: None,
227 before: lines.get(lines.len() - 2).map(|s| s.to_string()),
228 });
229 }
230 comments
231}
232
233use strsim::levenshtein;
234
235pub fn apply_comments(mut target: String, comments: Vec<Comment>) -> String {
236 let mut leftover_comments = Vec::new();
237
238 for c in comments {
239 if let Some(ref anchor) = c.before
241 && let Some(pos) = target.find(anchor)
242 {
243 insert_after_line(&mut target, pos, &c.text);
244 continue;
245 }
246
247 if let Some(ref anchor) = c.after
249 && let Some(pos) = target.find(anchor)
250 {
251 insert_before(&mut target, pos, &c.text);
252 continue;
253 }
254
255 if let Some(pos) = fuzzy_find(&code_lines(&target), &c.after, &c.before) {
257 insert_before(&mut target, pos, &c.text);
258 continue;
259 }
260
261 leftover_comments.push(c);
262
263 }
267
268 for c in leftover_comments {
269 if let Some(ref anchor) = c.before
271 && let Some(pos) = target.find(anchor)
272 {
273 insert_after_line(&mut target, pos, &c.text);
274 continue;
275 }
276
277 if let Some(ref anchor) = c.after
279 && let Some(pos) = target.find(anchor)
280 {
281 insert_before(&mut target, pos, &c.text);
282 continue;
283 }
284
285 if let Some(pos) = fuzzy_find(&code_lines(&target), &c.after, &c.before) {
287 insert_before(&mut target, pos, &c.text);
288 continue;
289 }
290
291 }
297 target
298}
299
300const MAX_FUZZY_DIST: usize = 6;
301fn fuzzy_find(lines: &[(usize, String)], after: &Option<String>, before: &Option<String>) -> Option<usize> {
302 for dist in 0..=MAX_FUZZY_DIST {
305 for window in lines.windows(3) {
307 match (before, after) {
308 (None, None) => {
309 return None;
310 }
311 (None, Some(after)) => {
312 let (start, _) = window.get(1).unwrap();
313 let (_, next_line) = window.get(2).unwrap();
314
315 if levenshtein(after.trim(), next_line.trim()) <= dist {
316 return Some(*start);
317 }
318 }
319 (Some(before), None) => {
320 let (start, _) = window.get(1).unwrap();
321 let (_, prev_line) = window.first().unwrap();
322
323 if levenshtein(before.trim(), prev_line.trim()) <= dist {
324 return Some(*start);
325 }
326 }
327 (Some(before), Some(after)) => {
328 let (_, next_line) = window.get(2).unwrap();
329 let (start, _) = window.get(1).unwrap();
330 let (_, prev_line) = window.first().unwrap();
331
332 if levenshtein(after.trim(), next_line.trim()) <= dist
333 && levenshtein(before.trim(), prev_line.trim()) <= dist
334 {
335 return Some(*start);
336 }
337 }
338 }
339 }
340 }
342 None
343}
344
345fn insert_before(buf: &mut String, pos: usize, comment: &str) {
347 buf.insert_str(pos, &format!("{comment}\n"));
348}
349
350fn insert_after_line(buf: &mut String, pos: usize, comment: &str) {
352 if let Some(newline_off) = buf[pos..].find('\n') {
353 let insert_at = pos + newline_off + 1;
354 buf.insert_str(insert_at, &format!("{comment}\n"));
355 } else {
356 insert_before(buf, pos, comment);
358 }
359}