#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TableAlignment {
Left, Center, Right, None, }
#[derive(Debug, Clone, PartialEq)]
pub enum LineType {
Heading(usize), ListItem,
OrderedListItem,
TaskListItem(bool), Blockquote,
CodeFence(Option<String>), InCode, HorizontalRule,
Image(String, String), TableHeader(Vec<String>), TableSeparator(Vec<TableAlignment>), TableRow(Vec<String>), FrontMatterDelimiter, FrontMatterContent, Text,
}
pub struct LineAnalyzer;
impl LineAnalyzer {
pub fn analyze_line(line: &str) -> LineType {
let trimmed = line.trim_start();
if (trimmed == "---" || trimmed == "+++") && line == trimmed {
return LineType::FrontMatterDelimiter;
}
if let Some(rest) = trimmed.strip_prefix('#') {
let mut level = 1;
let mut chars = rest.chars();
while let Some('#') = chars.next() {
level += 1;
if level > 6 {
break;
}
}
if level <= 6
&& rest
.chars()
.nth(level - 1)
.is_some_and(|c| c.is_whitespace())
{
return LineType::Heading(level);
}
}
if trimmed.starts_with("---")
|| trimmed.starts_with("***")
|| trimmed.starts_with("___")
&& trimmed
.chars()
.all(|c| c == '-' || c == '*' || c == '_' || c.is_whitespace())
{
return LineType::HorizontalRule;
}
if trimmed.starts_with("```") {
let lang = trimmed
.strip_prefix("```")
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
return LineType::CodeFence(lang);
}
if trimmed.starts_with("- [") {
if trimmed.contains("- [x]") || trimmed.contains("- [X]") {
return LineType::TaskListItem(true);
} else if trimmed.contains("- [ ]") {
return LineType::TaskListItem(false);
}
}
if trimmed.starts_with("- ") || trimmed.starts_with("* ") || trimmed.starts_with("+ ") {
return LineType::ListItem;
}
if let Some(ch) = trimmed.chars().next()
&& ch.is_ascii_digit()
{
let rest = &trimmed[1..];
if rest.starts_with(". ") || rest.starts_with(") ") {
return LineType::OrderedListItem;
}
}
if trimmed.starts_with("> ") {
return LineType::Blockquote;
}
if trimmed.starts_with("
{
let alt_text = &trimmed[2..alt_end];
let rest = &trimmed[alt_end + 2..];
if let Some(path_end) = rest.find(')') {
let path = &rest[..path_end];
return LineType::Image(alt_text.to_string(), path.to_string());
}
}
LineType::Text
}
pub fn contains_bold(line: &str) -> bool {
line.contains("**") || line.contains("__")
}
pub fn contains_italic(line: &str) -> bool {
line.contains('*') || line.contains('_')
}
pub fn contains_strikethrough(line: &str) -> bool {
line.contains("~~")
}
pub fn contains_inline_code(line: &str) -> bool {
line.contains('`') && !line.trim().starts_with("```")
}
pub fn contains_link(line: &str) -> bool {
line.contains('[') && line.contains("](")
}
pub fn is_table_row(line: &str) -> bool {
let trimmed = line.trim();
trimmed.contains('|') && !trimmed.starts_with("```")
}
pub fn is_table_separator(line: &str) -> bool {
let trimmed = line.trim();
if !trimmed.contains('|') {
return false;
}
let content: String = trimmed
.chars()
.filter(|c| *c != '|' && !c.is_whitespace())
.collect();
!content.is_empty() && content.chars().all(|c| c == '-' || c == ':')
}
pub fn parse_table_cells(line: &str) -> Vec<String> {
let trimmed = line.trim();
let stripped = trimmed.trim_matches('|');
stripped
.split('|')
.map(|cell| cell.trim().to_string())
.collect()
}
pub fn parse_table_alignment(line: &str) -> Vec<TableAlignment> {
Self::parse_table_cells(line)
.iter()
.map(|cell| {
let cell = cell.trim();
let starts_colon = cell.starts_with(':');
let ends_colon = cell.ends_with(':');
match (starts_colon, ends_colon) {
(true, true) => TableAlignment::Center,
(true, false) => TableAlignment::Left,
(false, true) => TableAlignment::Right,
(false, false) => TableAlignment::None,
}
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_heading_detection() {
assert_eq!(
LineAnalyzer::analyze_line("# Heading 1"),
LineType::Heading(1)
);
assert_eq!(
LineAnalyzer::analyze_line("## Heading 2"),
LineType::Heading(2)
);
assert_eq!(
LineAnalyzer::analyze_line("### Heading 3"),
LineType::Heading(3)
);
}
#[test]
fn test_list_detection() {
assert_eq!(LineAnalyzer::analyze_line("- Item"), LineType::ListItem);
assert_eq!(LineAnalyzer::analyze_line("* Item"), LineType::ListItem);
assert_eq!(
LineAnalyzer::analyze_line("1. Item"),
LineType::OrderedListItem
);
}
#[test]
fn test_task_list() {
assert_eq!(
LineAnalyzer::analyze_line("- [ ] Todo"),
LineType::TaskListItem(false)
);
assert_eq!(
LineAnalyzer::analyze_line("- [x] Done"),
LineType::TaskListItem(true)
);
}
#[test]
fn test_blockquote() {
assert_eq!(LineAnalyzer::analyze_line("> Quote"), LineType::Blockquote);
}
#[test]
fn test_code_fence() {
assert_eq!(
LineAnalyzer::analyze_line("```rust"),
LineType::CodeFence(Some("rust".to_string()))
);
assert_eq!(LineAnalyzer::analyze_line("```"), LineType::CodeFence(None));
}
#[test]
fn test_is_table_row() {
assert!(LineAnalyzer::is_table_row("| Name | Age |"));
assert!(LineAnalyzer::is_table_row("|Name|Age|"));
assert!(LineAnalyzer::is_table_row("| Name | Age | City |"));
assert!(!LineAnalyzer::is_table_row("Normal text"));
assert!(!LineAnalyzer::is_table_row("```|code|```"));
}
#[test]
fn test_is_table_separator() {
assert!(LineAnalyzer::is_table_separator("|---|---|"));
assert!(LineAnalyzer::is_table_separator("| --- | --- |"));
assert!(LineAnalyzer::is_table_separator("| :--- | ---: |"));
assert!(LineAnalyzer::is_table_separator("|:---:|:---:|"));
assert!(LineAnalyzer::is_table_separator("| :--- | :---: | ---: |"));
assert!(!LineAnalyzer::is_table_separator("| Name | Age |"));
assert!(!LineAnalyzer::is_table_separator("Normal text"));
}
#[test]
fn test_parse_table_cells() {
let cells = LineAnalyzer::parse_table_cells("| Name | Age |");
assert_eq!(cells, vec!["Name", "Age"]);
let cells = LineAnalyzer::parse_table_cells("|Name|Age|");
assert_eq!(cells, vec!["Name", "Age"]);
let cells = LineAnalyzer::parse_table_cells("| Name | Age | City |");
assert_eq!(cells, vec!["Name", "Age", "City"]);
let cells = LineAnalyzer::parse_table_cells("| Spaced | Content |");
assert_eq!(cells, vec!["Spaced", "Content"]);
}
#[test]
fn test_parse_table_alignment() {
let alignments = LineAnalyzer::parse_table_alignment("| :--- | ---: | :---: | --- |");
assert_eq!(
alignments,
vec![
TableAlignment::Left,
TableAlignment::Right,
TableAlignment::Center,
TableAlignment::None,
]
);
let alignments = LineAnalyzer::parse_table_alignment("|:---|---:|:---:|---|");
assert_eq!(
alignments,
vec![
TableAlignment::Left,
TableAlignment::Right,
TableAlignment::Center,
TableAlignment::None,
]
);
}
#[test]
fn test_front_matter_delimiter() {
assert_eq!(
LineAnalyzer::analyze_line("---"),
LineType::FrontMatterDelimiter
);
assert_eq!(
LineAnalyzer::analyze_line("+++"),
LineType::FrontMatterDelimiter
);
assert_eq!(LineAnalyzer::analyze_line("--- "), LineType::HorizontalRule);
assert_eq!(LineAnalyzer::analyze_line(" ---"), LineType::HorizontalRule);
}
}