use anyhow::{Context, Result};
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use tower_lsp::lsp_types::Url;
use tracing::{debug, instrument};
use crate::domain::{LanguageInfo, LanguageRegistry, Snippet};
lazy_static! {
static ref LINE_COMMENT_START: Regex =
Regex::new(r"^(\s*)//\s*(.*)$").expect("compile line comment start regex");
static ref LINE_COMMENT_END: Regex =
Regex::new(r"^(.+?)(\s+)//\s*(.*)$").expect("compile line comment end regex");
static ref RUST_INDENT: Regex =
Regex::new(r"^( {4})+").expect("compile rust indentation regex");
}
pub struct LanguageTranslator;
impl LanguageTranslator {
#[instrument(skip(snippet))]
pub fn translate_snippet(snippet: &Snippet, language_id: &str, uri: &Url) -> Result<String> {
let content = if snippet.is_universal() {
debug!("Processing universal snippet: {}", snippet.title);
debug!("Original content: {:?}", snippet.get_content());
Self::translate_rust_patterns(snippet.get_content(), language_id, uri)
.context("translate Rust patterns to target language")?
} else {
snippet.get_content().to_string()
};
debug!("Final translated content: {:?}", content);
Ok(content)
}
#[instrument(skip(content))]
pub fn translate_rust_patterns(content: &str, language_id: &str, uri: &Url) -> Result<String> {
let target_lang = LanguageRegistry::get_language_info(language_id);
debug!("Translating Rust patterns for language: {}", language_id);
debug!("Input content: {:?}", content);
debug!("Content length: {} bytes", content.len());
let mut processed_content =
Self::translate_rust_patterns_line_by_line(content, &target_lang)
.context("process content line by line")?;
if let Some((target_start, target_end)) = &target_lang.block_comment {
let block_comment_regex = RegexBuilder::new(r"/\*(.*?)\*/")
.dot_matches_new_line(true)
.build()
.context("compile block comment regex")?;
processed_content = block_comment_regex
.replace_all(&processed_content, |caps: ®ex::Captures| {
format!("{}{}{}", target_start, &caps[1], target_end)
})
.to_string();
}
if processed_content.contains("{{ filename }}") {
let filename = uri.path().split('/').next_back().unwrap_or("untitled");
processed_content = processed_content.replace("{{ filename }}", filename);
}
debug!("Rust pattern translation complete");
debug!("Final content: {:?}", processed_content);
debug!("Final content length: {} bytes", processed_content.len());
Ok(processed_content)
}
fn translate_rust_patterns_line_by_line(
content: &str,
target_lang: &LanguageInfo,
) -> Result<String> {
let lines: Vec<&str> = content.split('\n').collect();
let mut processed_lines = Vec::new();
for line in lines {
let mut processed_line = line.to_string();
if let Some(target_comment) = &target_lang.line_comment {
if let Some(captures) = LINE_COMMENT_START.captures(line) {
processed_line = format!("{}{} {}", &captures[1], target_comment, &captures[2]);
}
else if let Some(captures) = LINE_COMMENT_END.captures(line) {
processed_line = format!(
"{}{}{} {}",
&captures[1], &captures[2], target_comment, &captures[3]
);
}
} else if let Some((block_start, block_end)) = &target_lang.block_comment {
if let Some(captures) = LINE_COMMENT_START.captures(line) {
processed_line = format!(
"{}{} {} {}",
&captures[1], block_start, &captures[2], block_end
);
} else if let Some(captures) = LINE_COMMENT_END.captures(line) {
processed_line = format!(
"{}{}{} {} {}",
&captures[1], &captures[2], block_start, &captures[3], block_end
);
}
}
if target_lang.indent_char != " " {
if let Some(captures) = RUST_INDENT.captures(&processed_line) {
let rust_indent_count = captures[0].len() / 4;
let new_indent = target_lang.indent_char.repeat(rust_indent_count);
processed_line = processed_line.replacen(&captures[0], &new_indent, 1);
}
}
processed_lines.push(processed_line);
}
Ok(processed_lines.join("\n"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn given_universal_snippet_when_translating_then_processes_content() {
let snippet = Snippet::new(
1,
"Test Universal Snippet".to_string(),
"// This is a test".to_string(),
"Test description".to_string(),
vec!["universal".to_string(), "_snip_".to_string()],
);
let uri = Url::parse("file:///test.py").expect("parse URI");
let result = LanguageTranslator::translate_snippet(&snippet, "python", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert!(translated.contains("# This is a test"));
}
#[test]
fn given_regular_snippet_when_translating_then_returns_as_is() {
let snippet = Snippet::new(
1,
"Test Regular Snippet".to_string(),
"// This is a test".to_string(),
"Test description".to_string(),
vec!["rust".to_string(), "_snip_".to_string()],
);
let uri = Url::parse("file:///test.py").expect("parse URI");
let result = LanguageTranslator::translate_snippet(&snippet, "python", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert_eq!(translated, "// This is a test"); }
#[test]
fn given_rust_line_comments_when_translating_to_python_then_converts_correctly() {
let uri = Url::parse("file:///test.py").expect("parse URI");
let rust_content = r#"// This is a line comment
// Indented comment
let x = 5; // End of line comment"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri);
assert!(result.is_ok());
let python_result = result.expect("Python translation result");
assert!(python_result.contains("# This is a line comment"));
assert!(python_result.contains(" # Indented comment"));
assert!(python_result.contains("let x = 5; # End of line comment"));
}
#[test]
fn given_rust_block_comments_when_translating_to_python_then_converts_correctly() {
let uri = Url::parse("file:///test.py").expect("parse URI");
let rust_content = r#"/* This is a block comment */
/*
Multi-line
block comment
*/"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "python", &uri);
assert!(result.is_ok());
let python_result = result.expect("Python translation result");
assert!(python_result.contains("\"\"\" This is a block comment \"\"\""));
assert!(python_result.contains("\"\"\"\nMulti-line\nblock comment\n\"\"\""));
}
#[test]
fn given_rust_indentation_when_translating_to_go_then_converts_to_tabs() {
let uri = Url::parse("file:///test.go").expect("parse URI");
let rust_content = r#"fn example() {
let x = 5;
let y = 10;
let z = 15;
}"#;
let result = LanguageTranslator::translate_rust_patterns(rust_content, "go", &uri);
assert!(result.is_ok());
let go_result = result.expect("Go translation result");
assert!(go_result.contains("fn example() {"));
assert!(go_result.contains("\tlet x = 5;"));
assert!(go_result.contains("\t\tlet y = 10;"));
assert!(go_result.contains("\t\t\tlet z = 15;"));
}
#[test]
fn given_filename_template_when_translating_then_replaces_correctly() {
let uri = Url::parse("file:///path/to/example.rs").expect("parse URI");
let content = "// File: {{ filename }}";
let result = LanguageTranslator::translate_rust_patterns(content, "rust", &uri);
assert!(result.is_ok());
let translated = result.expect("valid translation result");
assert!(translated.contains("// File: example.rs"));
}
}