#[inline]
pub fn is_definition_line(line: &str) -> bool {
crate::utils::is_definition_list_item(line)
}
#[inline]
pub fn could_be_term_line(line: &str) -> bool {
let trimmed = line.trim();
!trimmed.is_empty() && !is_definition_line(line) && !line.starts_with(' ')
}
#[inline]
pub fn is_definition_continuation(line: &str) -> bool {
line.starts_with(" ") && !line.trim_start().starts_with(':')
}
#[derive(Debug, Clone, PartialEq)]
pub struct DefinitionEntry {
pub term: String,
pub term_line: usize,
pub definitions: Vec<Definition>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Definition {
pub text: String,
pub line: usize,
}
pub fn extract_definition_lists(content: &str) -> Vec<DefinitionEntry> {
let lines: Vec<&str> = content.lines().collect();
let mut entries = Vec::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let trimmed = line.trim();
if trimmed.is_empty() || is_definition_line(line) {
i += 1;
continue;
}
if i + 1 < lines.len() && is_definition_line(lines[i + 1]) {
let term = trimmed.to_string();
let term_line = i + 1;
let mut definitions = Vec::new();
i += 1;
while i < lines.len() && is_definition_line(lines[i]) {
let def_start_line = i + 1;
let def_line = lines[i].trim_start();
let def_text = if let Some(stripped) = def_line.strip_prefix(':') {
stripped.trim_start().to_string()
} else {
def_line.to_string()
};
let mut full_def = def_text;
while i + 1 < lines.len() && is_definition_continuation(lines[i + 1]) {
i += 1;
let continuation = lines[i].trim();
if !continuation.is_empty() {
full_def.push(' ');
full_def.push_str(continuation);
}
}
definitions.push(Definition {
text: full_def,
line: def_start_line,
});
i += 1;
}
entries.push(DefinitionEntry {
term,
term_line,
definitions,
});
} else {
i += 1;
}
}
entries
}
pub fn is_in_definition_marker(line: &str, position: usize) -> bool {
if !is_definition_line(line) {
return false;
}
let trimmed = line.trim_start();
let leading_ws = line.len() - trimmed.len();
position >= leading_ws && position < leading_ws + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_definition_line() {
assert!(is_definition_line(": Definition text"));
assert!(is_definition_line(": Definition text"));
assert!(is_definition_line(":\tDefinition text"));
assert!(is_definition_line(": Long definition"));
assert!(!is_definition_line("Term"));
assert!(!is_definition_line(" Term"));
assert!(!is_definition_line(""));
assert!(!is_definition_line(":NoSpace")); }
#[test]
fn test_could_be_term_line() {
assert!(could_be_term_line("Term"));
assert!(could_be_term_line("Multi Word Term"));
assert!(could_be_term_line("Term with special chars: like this"));
assert!(!could_be_term_line("")); assert!(!could_be_term_line(" ")); assert!(!could_be_term_line(": Definition")); assert!(!could_be_term_line(" Term")); }
#[test]
fn test_is_definition_continuation() {
assert!(is_definition_continuation(" Continuation text"));
assert!(is_definition_continuation(" More continuation"));
assert!(!is_definition_continuation(": New definition"));
assert!(!is_definition_continuation("No indent"));
assert!(!is_definition_continuation(" Only 2 spaces"));
}
#[test]
fn test_extract_definition_lists() {
let content = r#"First Term
: Definition of first term
Second Term
: Definition of second term
With continuation
Third Term
: First definition
: Second definition
"#;
let entries = extract_definition_lists(content);
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].term, "First Term");
assert_eq!(entries[0].definitions.len(), 1);
assert_eq!(entries[0].definitions[0].text, "Definition of first term");
assert_eq!(entries[1].term, "Second Term");
assert_eq!(entries[1].definitions.len(), 1);
assert_eq!(
entries[1].definitions[0].text,
"Definition of second term With continuation"
);
assert_eq!(entries[2].term, "Third Term");
assert_eq!(entries[2].definitions.len(), 2);
assert_eq!(entries[2].definitions[0].text, "First definition");
assert_eq!(entries[2].definitions[1].text, "Second definition");
}
#[test]
fn test_extract_definition_lists_complex() {
let content = r#"# Document
Regular paragraph.
Apple
: A fruit
: A technology company
Banana
: A yellow fruit
that grows in tropical climates
Not a definition list line.
"#;
let entries = extract_definition_lists(content);
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].term, "Apple");
assert_eq!(entries[0].definitions.len(), 2);
assert_eq!(entries[1].term, "Banana");
assert_eq!(entries[1].definitions.len(), 1);
assert!(entries[1].definitions[0].text.contains("tropical climates"));
}
#[test]
fn test_is_in_definition_marker() {
let line = ": Definition text";
assert!(is_in_definition_marker(line, 0)); assert!(is_in_definition_marker(line, 1)); assert!(!is_in_definition_marker(line, 4));
let line_with_ws = " : Definition";
assert!(!is_in_definition_marker(line_with_ws, 0)); assert!(is_in_definition_marker(line_with_ws, 2)); assert!(is_in_definition_marker(line_with_ws, 3));
let not_def = "Regular line";
assert!(!is_in_definition_marker(not_def, 0));
}
}