use crate::backend::FileBackend;
pub async fn inject<B: FileBackend>(body: &str, backend: &B) -> String {
let re = regex::Regex::new(r"`!(\w+)\s*(.*?)`").unwrap();
let mut result = body.to_string();
let mut count = 0;
let matches: Vec<(String, String, String)> = re
.captures_iter(body)
.take(10)
.map(|cap| {
(
cap[0].to_string(),
cap[1].to_string(),
cap[2].trim().to_string(),
)
})
.collect();
for (full_match, cmd, args) in matches {
let output = match cmd.as_str() {
"tree" => {
let path = if args.is_empty() { "/" } else { &args };
backend.tree(path, 2).await
}
"read" => {
if args.is_empty() {
Ok("(error: !read requires path)".into())
} else {
backend.read(&args, false, 0, 0).await
}
}
"list" => {
let path = if args.is_empty() { "/" } else { &args };
backend.list(path).await
}
"search" => {
let parts: Vec<&str> = args.splitn(2, ' ').collect();
let pattern = parts.first().unwrap_or(&"");
let root = parts.get(1).unwrap_or(&"/");
backend.search(root, pattern, 10).await
}
"context" => backend.context().await,
_ => Ok(format!("(unknown command: !{cmd})")),
};
let replacement = match output {
Ok(text) => {
if text.len() > 2000 {
format!("{}...(truncated)", &text[..2000])
} else {
text
}
}
Err(e) => format!("(error: {e})"),
};
result = result.replacen(&full_match, &replacement, 1);
count += 1;
if count >= 10 {
break;
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_commands() {
let re = regex::Regex::new(r"`!(\w+)\s*(.*?)`").unwrap();
let body = "Tree: `!tree /` and file: `!read AGENTS.MD` and `!context`";
let matches: Vec<(String, String)> = re
.captures_iter(body)
.map(|c| (c[1].to_string(), c[2].trim().to_string()))
.collect();
assert_eq!(matches.len(), 3);
assert_eq!(matches[0], ("tree".into(), "/".into()));
assert_eq!(matches[1], ("read".into(), "AGENTS.MD".into()));
assert_eq!(matches[2], ("context".into(), "".into()));
}
}