#[derive(Debug, Clone, PartialEq)]
pub struct Section {
pub id: String,
pub start_line: usize,
pub end_line: usize,
pub content: String,
pub indent: String,
}
pub fn find_section(text: &str, id: &str) -> Option<Section> {
let begin_marker = make_begin_marker(id);
let end_marker = make_end_marker(id);
let lines: Vec<&str> = text.lines().collect();
let mut begin_line = None;
let mut end_line = None;
let mut indent = String::new();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim_start();
let line_indent = &line[..line.len() - trimmed.len()];
if trimmed == begin_marker {
begin_line = Some(idx);
indent = line_indent.to_string();
} else if trimmed == end_marker && begin_line.is_some() {
end_line = Some(idx);
break;
}
}
match (begin_line, end_line) {
(Some(start), Some(end)) if start < end => {
let content_lines = &lines[start + 1..end];
let content = content_lines
.iter()
.map(|line| {
if line.starts_with(&indent) {
&line[indent.len()..]
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n");
Some(Section {
id: id.to_string(),
start_line: start,
end_line: end,
content,
indent,
})
}
_ => None,
}
}
pub fn make_begin_marker(id: &str) -> String {
format!("<!-- KUGIRI-BEGIN: {id} -->")
}
pub fn make_end_marker(id: &str) -> String {
format!("<!-- KUGIRI-END: {id} -->")
}
pub fn make_insert_marker(id: &str) -> String {
format!("<!-- KUGIRI-INSERT: {id} -->")
}
pub fn find_marker_for_anchor(text: &str, id: &str) -> Option<Section> {
if let Some(section) = find_section(text, id) {
return Some(section);
}
let insert_marker = make_insert_marker(id);
let lines: Vec<&str> = text.lines().collect();
for (idx, line) in lines.iter().enumerate() {
let trimmed = line.trim_start();
let line_indent = &line[..line.len() - trimmed.len()];
if trimmed == insert_marker {
return Some(Section {
id: id.to_string(),
start_line: idx,
end_line: idx, content: String::new(), indent: line_indent.to_string(),
});
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_section_exists() {
let text = r#"# README
<!-- KUGIRI-BEGIN: test-section -->
This is the content
of the test section
<!-- KUGIRI-END: test-section -->
More content after"#;
let section = find_section(text, "test-section").expect("Section should be found");
assert_eq!(section.id, "test-section");
assert_eq!(section.content, "This is the content\nof the test section");
assert_eq!(section.start_line, 2);
assert_eq!(section.end_line, 5);
assert_eq!(section.indent, "");
}
#[test]
fn test_find_section_with_indent() {
let text = r#"# README
<!-- KUGIRI-BEGIN: indented -->
This is indented
content here
<!-- KUGIRI-END: indented -->
More content"#;
let section = find_section(text, "indented").expect("Section should be found");
assert_eq!(section.id, "indented");
assert_eq!(section.content, "This is indented\ncontent here");
assert_eq!(section.indent, " ");
}
#[test]
fn test_find_section_not_exists() {
let text = "Some text without markers";
assert!(find_section(text, "non-existent").is_none());
}
#[test]
fn test_find_marker_for_anchor_with_insert() {
let text = r#"# README
<!-- KUGIRI-INSERT: insert-point -->
More content"#;
let marker = find_marker_for_anchor(text, "insert-point").expect("Marker should be found");
assert_eq!(marker.id, "insert-point");
assert_eq!(marker.indent, " ");
assert_eq!(marker.start_line, 2);
assert_eq!(marker.end_line, 2);
assert_eq!(marker.content, "");
}
#[test]
fn test_markers() {
assert_eq!(make_begin_marker("test"), "<!-- KUGIRI-BEGIN: test -->");
assert_eq!(make_end_marker("test"), "<!-- KUGIRI-END: test -->");
assert_eq!(make_insert_marker("test"), "<!-- KUGIRI-INSERT: test -->");
}
#[test]
fn test_nested_markers() {
let text = r#"# Document
<!-- KUGIRI-BEGIN: outer -->
Outer content start
<!-- KUGIRI-BEGIN: inner -->
Inner content
<!-- KUGIRI-END: inner -->
Outer content end
<!-- KUGIRI-END: outer -->
Footer"#;
let outer = find_section(text, "outer").expect("Outer section should be found");
assert_eq!(outer.id, "outer");
assert_eq!(outer.start_line, 2);
assert_eq!(outer.end_line, 10);
assert!(outer.content.contains("Outer content start"));
assert!(outer.content.contains("Inner content"));
assert!(outer.content.contains("Outer content end"));
let inner = find_section(text, "inner").expect("Inner section should be found");
assert_eq!(inner.id, "inner");
assert_eq!(inner.start_line, 5);
assert_eq!(inner.end_line, 7);
assert_eq!(inner.content, "Inner content");
}
#[test]
fn test_deeply_nested_markers() {
let text = r#"<!-- KUGIRI-BEGIN: level1 -->
Level 1 content
<!-- KUGIRI-BEGIN: level2 -->
Level 2 content
<!-- KUGIRI-BEGIN: level3 -->
Level 3 content
<!-- KUGIRI-END: level3 -->
Level 2 again
<!-- KUGIRI-END: level2 -->
Level 1 again
<!-- KUGIRI-END: level1 -->"#;
let level1 = find_section(text, "level1").expect("Level 1 should be found");
assert_eq!(level1.id, "level1");
assert!(level1.content.contains("Level 1 content"));
assert!(level1.content.contains("Level 2 content"));
assert!(level1.content.contains("Level 3 content"));
let level2 = find_section(text, "level2").expect("Level 2 should be found");
assert_eq!(level2.id, "level2");
assert_eq!(level2.indent, " ");
assert!(level2.content.contains("Level 2 content"));
assert!(level2.content.contains("Level 3 content"));
assert!(!level2.content.starts_with(" "));
let level3 = find_section(text, "level3").expect("Level 3 should be found");
assert_eq!(level3.id, "level3");
assert_eq!(level3.indent, " ");
assert_eq!(level3.content, "Level 3 content");
}
#[test]
fn test_nested_markers_wrong_order() {
let text = r#"<!-- KUGIRI-BEGIN: outer -->
Outer content
<!-- KUGIRI-BEGIN: inner -->
Inner content
<!-- KUGIRI-END: outer --> <!-- Wrong! This should not close outer -->
More content
<!-- KUGIRI-END: inner -->
<!-- KUGIRI-END: outer-real -->"#;
let outer = find_section(text, "outer");
assert!(outer.is_none());
let inner = find_section(text, "inner");
assert!(inner.is_some());
}
}