use crate::utils::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct MergeMarkers {
pub generated_start: usize,
pub manual_start: usize,
pub manual_end: usize,
}
#[derive(Debug, Clone)]
pub struct MergedSections {
pub generated: String,
pub manual: String,
}
pub fn parse_merge_markers(content: &str) -> Option<MergeMarkers> {
let lines: Vec<&str> = content.lines().collect();
let mut generated_start = None;
let mut manual_start = None;
let mut manual_end = None;
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("<<<<<<< GENERATED") {
generated_start = Some(idx);
} else if trimmed == "=======" {
manual_start = Some(idx);
} else if trimmed.starts_with(">>>>>>> MANUAL") {
manual_end = Some(idx);
}
}
match (generated_start, manual_start, manual_end) {
(Some(gs), Some(ms), Some(me)) => Some(MergeMarkers {
generated_start: gs,
manual_start: ms,
manual_end: me,
}),
_ => None,
}
}
pub fn merge_sections(generated_code: &str, existing_content: &str) -> Result<String> {
let markers = match parse_merge_markers(existing_content) {
None => {
return Ok(format!(
"<<<<<<< GENERATED\n{}\n=======\n// Add your manual code here\n>>>>>>> MANUAL\n",
generated_code
));
}
Some(m) => m,
};
let lines: Vec<&str> = existing_content.lines().collect();
if markers.manual_start <= markers.generated_start {
return Err(Error::new(&format!(
"error[E0001]: Invalid merge marker order\n --> GENERATED marker at line {}, ======= marker at line {}\n |\n = help: Merge markers must appear in this order:\n = 1. <<<<<<< GENERATED\n = 2. =======\n = 3. >>>>>>> MANUAL\n = help: The ======= separator must come AFTER the <<<<<<< GENERATED marker",
markers.generated_start,
markers.manual_start
)));
}
if markers.manual_end <= markers.manual_start {
return Err(Error::new(&format!(
"error[E0001]: Invalid merge marker order\n --> ======= marker at line {}, >>>>>>> MANUAL marker at line {}\n |\n = help: Merge markers must appear in this order:\n = 1. <<<<<<< GENERATED\n = 2. =======\n = 3. >>>>>>> MANUAL\n = help: The >>>>>>> MANUAL marker must come AFTER the ======= separator",
markers.manual_start,
markers.manual_end
)));
}
let manual_section: String = lines[(markers.manual_start + 1)..markers.manual_end].join("\n");
let mut merged = String::new();
for line in &lines[..markers.generated_start] {
merged.push_str(line);
merged.push('\n');
}
merged.push_str("<<<<<<< GENERATED\n");
merged.push_str(generated_code);
merged.push_str("\n=======\n");
merged.push_str(&manual_section);
merged.push_str("\n>>>>>>> MANUAL\n");
for line in &lines[(markers.manual_end + 1)..] {
merged.push_str(line);
merged.push('\n');
}
Ok(merged)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_merge_markers_found() {
let content = r#"
<<<<<<< GENERATED
fn generated() {}
=======
fn manual() {}
>>>>>>> MANUAL
"#;
let markers = parse_merge_markers(content).unwrap();
assert_eq!(markers.generated_start, 1);
assert_eq!(markers.manual_start, 3);
assert_eq!(markers.manual_end, 5);
}
#[test]
fn test_parse_merge_markers_not_found() {
let content = "fn regular_code() {}";
assert!(parse_merge_markers(content).is_none());
}
#[test]
fn test_merge_sections_first_time() {
let generated = "fn new_fn() {}";
let result = merge_sections(generated, "").unwrap();
assert!(result.contains("<<<<<<< GENERATED"));
assert!(result.contains("fn new_fn() {}"));
assert!(result.contains("======="));
assert!(result.contains(">>>>>>> MANUAL"));
}
#[test]
fn test_merge_sections_preserves_manual() {
let existing = r#"
<<<<<<< GENERATED
fn old_generated() {}
=======
fn manual_code() {}
>>>>>>> MANUAL
"#;
let new_generated = "fn new_generated() {}";
let result = merge_sections(new_generated, existing).unwrap();
assert!(result.contains("fn new_generated() {}"));
assert!(result.contains("fn manual_code() {}"));
assert!(!result.contains("fn old_generated() {}"));
}
}