use crate::links::{detect_links, is_inside_link_url};
use std::fmt::Write;
#[allow(dead_code)] pub fn is_table_row(line: &str) -> bool {
let trimmed = line.trim();
trimmed.starts_with('|') && trimmed.ends_with('|')
}
#[allow(dead_code)] pub fn is_table_separator(line: &str) -> bool {
let trimmed = line.trim();
if !trimmed.starts_with('|') || !trimmed.ends_with('|') {
return false;
}
trimmed
.split('|')
.skip(1)
.take_while(|cell| !cell.is_empty())
.all(|cell| cell.trim().chars().all(|c| c == '-' || c == ':'))
}
#[allow(dead_code)] pub fn parse_table_row(row: &str) -> Option<Vec<String>> {
let trimmed = row.trim();
if !trimmed.starts_with('|') || !trimmed.ends_with('|') {
return None;
}
let links = detect_links(trimmed);
let mut cells: Vec<String> = Vec::new();
let mut current_cell = String::new();
let mut in_cell = false;
for (pos, ch) in trimmed.chars().enumerate() {
if ch == '|' {
if is_inside_link_url(trimmed, pos, &links) {
current_cell.push(ch);
} else {
if in_cell {
cells.push(current_cell.trim().to_string());
current_cell = String::new();
}
in_cell = true;
}
} else if in_cell {
current_cell.push(ch);
}
}
if cells.is_empty() {
None
} else {
Some(cells)
}
}
#[allow(dead_code)] pub fn normalize_table(header: &str, _separator: &str, rows: &[&str]) -> Option<String> {
let headers = parse_table_row(header)?;
let mut data_rows = Vec::new();
for row in rows {
data_rows.push(parse_table_row(row)?);
}
let mut col_widths = vec![0; headers.len()];
for (i, header) in headers.iter().enumerate() {
col_widths[i] = header.len().max(col_widths[i]);
}
for row in &data_rows {
for (i, cell) in row.iter().enumerate() {
if i < col_widths.len() {
col_widths[i] = cell.len().max(col_widths[i]);
}
}
}
let mut result = String::new();
let _ = write!(result, "|");
for (i, header) in headers.iter().enumerate() {
let _ = write!(result, " {:<width$} |", header, width = col_widths[i]);
}
let _ = writeln!(result);
let _ = write!(result, "|");
for (i, _) in headers.iter().enumerate() {
let _ = write!(result, "-{}-|", "-".repeat(col_widths[i]));
}
for row in data_rows {
let _ = writeln!(result);
let _ = write!(result, "|");
for (i, cell) in row.iter().enumerate() {
if i < col_widths.len() {
let _ = write!(result, " {:<width$} |", cell, width = col_widths[i]);
}
}
}
Some(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_module_exports() {
assert!(is_table_row("| A | B |"));
assert!(is_table_separator("|---|---|"));
let cells = parse_table_row("| Name | Age |");
assert_eq!(cells, Some(vec!["Name".to_string(), "Age".to_string()]));
}
#[test]
fn test_table_separator_detection() {
assert!(is_table_separator("|---|---|"));
assert!(is_table_separator("| --- | --- |"));
assert!(is_table_separator("| :--- | ---: |"));
assert!(!is_table_separator("| abc | def |"));
assert!(!is_table_separator("no pipes here"));
}
#[test]
fn test_table_row_detection() {
assert!(is_table_row("| A | B |"));
assert!(is_table_row("|A|B|"));
assert!(is_table_row(" | A | B | "));
assert!(!is_table_row("A | B"));
assert!(!is_table_row("| A | B"));
}
#[test]
fn test_parse_table_row() {
let cells = parse_table_row("| Name | Age |");
assert_eq!(cells, Some(vec!["Name".to_string(), "Age".to_string()]));
let cells2 = parse_table_row("|A|B|C|");
assert_eq!(
cells2,
Some(vec!["A".to_string(), "B".to_string(), "C".to_string()])
);
let invalid = parse_table_row("no pipes");
assert_eq!(invalid, None);
}
#[test]
fn test_normalize_table() {
let header = "| Name | Age |";
let separator = "|------|-----|";
let rows = vec!["| Alice | 30 |", "| Bob | 25 |"];
let result = normalize_table(header, separator, &rows);
assert!(result.is_some());
let normalized = result.unwrap();
assert!(normalized.contains("Alice"));
assert!(normalized.contains("Bob"));
}
}