use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
};
use crate::connector::OutputMapFile;
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq)]
pub struct ReadOutput {
pub addr: PathBuf,
pub key: String,
}
impl ReadOutput {
pub fn to_string(&self) -> String {
format!("out://{}[{}]", self.addr.to_string_lossy(), self.key)
}
}
pub fn get_read_outputs(config: &str) -> Vec<ReadOutput> {
let re = Regex::new(r#"out://([^\[]+)\[([^\]]+)\]"#).unwrap();
let mut outputs = Vec::new();
for cap in re.captures_iter(config) {
let filename = cap.get(1).map(|m| m.as_str()).unwrap_or("");
let key = cap.get(2).map(|m| m.as_str()).unwrap_or("");
outputs.push(ReadOutput {
addr: PathBuf::from(filename),
key: key.to_string(),
});
}
outputs
}
#[derive(Debug)]
pub struct TemplateResourceResult {
pub body: String,
pub found: HashMap<ReadOutput, String>,
pub missing: HashSet<ReadOutput>,
}
pub fn template_config(prefix: &Path, config: &str) -> anyhow::Result<TemplateResourceResult> {
let re = Regex::new(r#"out://(?<addr>[^\[]+)\[(?<key>[^\]]+)\]"#)?;
let mut found = HashMap::new();
let mut missing = HashSet::<ReadOutput>::new();
let output = re.replace_all(config, |caps: &Captures| {
let addr = &caps["addr"];
let key = &caps["key"];
let phy_addr = PathBuf::from(addr);
let addr = PathBuf::from(addr);
let prefix = prefix.to_path_buf();
match OutputMapFile::get(&prefix, &phy_addr, key) {
Ok(Some(val)) => {
found.insert(
ReadOutput {
addr,
key: key.to_string(),
},
val.clone(),
);
val
}
_ => {
let val = caps.get(0).unwrap().as_str().to_string();
missing.insert(ReadOutput {
addr,
key: key.to_string(),
});
val
}
}
});
Ok(TemplateResourceResult {
body: output.into_owned(),
found,
missing,
})
}
pub fn reverse_template_config(
prefix: &Path,
templated_config: &str,
raw_config: &str,
min_length: usize,
) -> anyhow::Result<String> {
let mut result = raw_config.to_string();
let template_result = template_config(prefix, templated_config)?;
for (key, value) in template_result.found {
if value.len() < min_length {
continue;
}
result = result.replace(&value, &key.to_string());
}
Ok(result)
}
const MIN_ANCHOR_LEN: usize = 3;
#[derive(Debug)]
pub struct Comment {
text: String,
after: Option<String>,
before: Option<String>,
}
fn code_lines(target: &str) -> Vec<(usize, String)> {
let mut out = Vec::new();
let mut idx = 0;
for line in target.lines() {
out.push((idx, line.to_string()));
idx += line.len() + 1; }
out
}
pub fn extract_comments(src: &str) -> Vec<Comment> {
let re_line = Regex::new(r"(?m)^\s*//.*").unwrap();
let mut comments = Vec::new();
let lines: Vec<&str> = src.lines().collect();
if let Some(first_line) = lines.first()
&& re_line.is_match(first_line) {
comments.push(Comment {
text: first_line.to_string(),
after: lines.get(1).map(|s| s.to_string()),
before: None,
});
}
for window in lines.windows(3) {
let prev_line = window.first()
.filter(|s| s.trim().len() >= MIN_ANCHOR_LEN)
.map(|s| s.to_string());
let line = window.get(1).unwrap();
let next_line = window
.get(2)
.filter(|s| s.trim().len() >= MIN_ANCHOR_LEN)
.map(|s| s.to_string());
if re_line.is_match(line) {
comments.push(Comment {
text: line.to_string(),
after: next_line,
before: prev_line,
});
}
}
if let Some(last_line) = lines.last()
&& re_line.is_match(last_line) {
comments.push(Comment {
text: last_line.to_string(),
after: None,
before: lines.get(lines.len() - 2).map(|s| s.to_string()),
});
}
comments
}
use strsim::levenshtein;
pub fn apply_comments(mut target: String, comments: Vec<Comment>) -> String {
let mut leftover_comments = Vec::new();
for c in comments {
if let Some(ref anchor) = c.before
&& let Some(pos) = target.find(anchor) {
insert_after_line(&mut target, pos, &c.text);
continue;
}
if let Some(ref anchor) = c.after
&& let Some(pos) = target.find(anchor) {
insert_before(&mut target, pos, &c.text);
continue;
}
if let Some(pos) = fuzzy_find(&code_lines(&target), &c.after, &c.before) {
insert_before(&mut target, pos, &c.text);
continue;
}
leftover_comments.push(c);
}
for c in leftover_comments {
if let Some(ref anchor) = c.before
&& let Some(pos) = target.find(anchor) {
insert_after_line(&mut target, pos, &c.text);
continue;
}
if let Some(ref anchor) = c.after
&& let Some(pos) = target.find(anchor) {
insert_before(&mut target, pos, &c.text);
continue;
}
if let Some(pos) = fuzzy_find(&code_lines(&target), &c.after, &c.before) {
insert_before(&mut target, pos, &c.text);
continue;
}
}
target
}
const MAX_FUZZY_DIST: usize = 6;
fn fuzzy_find(lines: &Vec<(usize, String)>, after: &Option<String>, before: &Option<String>) -> Option<usize> {
for dist in 0..=MAX_FUZZY_DIST {
for window in lines.windows(3) {
match (before, after) {
(None, None) => {
return None;
}
(None, Some(after)) => {
let (start, _) = window.get(1).unwrap();
let (_, next_line) = window.get(2).unwrap();
if levenshtein(after.trim(), next_line.trim()) <= dist {
return Some(*start);
}
}
(Some(before), None) => {
let (start, _) = window.get(1).unwrap();
let (_, prev_line) = window.first().unwrap();
if levenshtein(before.trim(), prev_line.trim()) <= dist {
return Some(*start);
}
}
(Some(before), Some(after)) => {
let (_, next_line) = window.get(2).unwrap();
let (start, _) = window.get(1).unwrap();
let (_, prev_line) = window.first().unwrap();
if levenshtein(after.trim(), next_line.trim()) <= dist && levenshtein(before.trim(), prev_line.trim()) <= dist {
return Some(*start);
}
}
}
}
}
None
}
fn insert_before(buf: &mut String, pos: usize, comment: &str) {
buf.insert_str(pos, &format!("{comment}\n"));
}
fn insert_after_line(buf: &mut String, pos: usize, comment: &str) {
if let Some(newline_off) = buf[pos..].find('\n') {
let insert_at = pos + newline_off + 1;
buf.insert_str(insert_at, &format!("{comment}\n"));
} else {
insert_before(buf, pos, comment);
}
}