use regex::Regex;
use lazy_static::lazy_static;
lazy_static! {
static ref BLOCKQUOTE_LINE: Regex = Regex::new(r"^(\s*)>\s?(.*)$").unwrap();
static ref EMPTY_BLOCKQUOTE_LINE: Regex = Regex::new(r"^(\s*)>\s*$").unwrap();
static ref NESTED_EMPTY_BLOCKQUOTE_LINE: Regex = Regex::new(r"^(\s*)>+\s*$").unwrap();
static ref BLOCKQUOTE_NO_SPACE: Regex = Regex::new(r"^(\s*)>([^\s].*)$").unwrap();
static ref BLOCKQUOTE_MULTIPLE_SPACES: Regex = Regex::new(r"^(\s*)>(\s{2,})(.*)$").unwrap();
static ref NESTED_BLOCKQUOTE: Regex = Regex::new(r"^(\s*)>((?:\s*>)+)(\s*.*)$").unwrap();
}
pub struct BlockquoteUtils;
impl BlockquoteUtils {
pub fn is_blockquote(line: &str) -> bool {
BLOCKQUOTE_LINE.is_match(line)
}
pub fn is_empty_blockquote(line: &str) -> bool {
if EMPTY_BLOCKQUOTE_LINE.is_match(line) {
return true;
}
if NESTED_EMPTY_BLOCKQUOTE_LINE.is_match(line) {
return true;
}
if BLOCKQUOTE_LINE.is_match(line) {
let content = Self::extract_content(line);
return content.trim().is_empty();
}
false
}
pub fn has_no_space_after_marker(line: &str) -> bool {
BLOCKQUOTE_NO_SPACE.is_match(line)
}
pub fn has_multiple_spaces_after_marker(line: &str) -> bool {
BLOCKQUOTE_MULTIPLE_SPACES.is_match(line)
}
pub fn is_nested_blockquote(line: &str) -> bool {
NESTED_BLOCKQUOTE.is_match(line)
}
pub fn get_nesting_level(line: &str) -> usize {
if !Self::is_blockquote(line) {
return 0;
}
let trimmed = line.trim_start();
let mut count = 0;
for c in trimmed.chars() {
if c == '>' {
count += 1;
} else {
break;
}
}
count
}
pub fn extract_content(line: &str) -> String {
if let Some(captures) = BLOCKQUOTE_LINE.captures(line) {
if let Some(content) = captures.get(2) {
return content.as_str().to_string();
}
}
String::new()
}
pub fn extract_indentation(line: &str) -> String {
if let Some(captures) = BLOCKQUOTE_LINE.captures(line) {
if let Some(indent) = captures.get(1) {
return indent.as_str().to_string();
}
}
String::new()
}
pub fn fix_blockquote_spacing(line: &str) -> String {
if !Self::is_blockquote(line) {
return line.to_string();
}
if Self::has_no_space_after_marker(line) {
if let Some(captures) = BLOCKQUOTE_NO_SPACE.captures(line) {
let indent = captures.get(1).map_or("", |m| m.as_str());
let content = captures.get(2).map_or("", |m| m.as_str());
return format!("{}> {}", indent, content);
}
} else if Self::has_multiple_spaces_after_marker(line) {
if let Some(captures) = BLOCKQUOTE_MULTIPLE_SPACES.captures(line) {
let indent = captures.get(1).map_or("", |m| m.as_str());
let content = captures.get(3).map_or("", |m| m.as_str());
return format!("{}> {}", indent, content);
}
}
line.to_string()
}
pub fn fix_nested_blockquote_spacing(line: &str) -> String {
if !Self::is_nested_blockquote(line) {
return line.to_string();
}
let level = Self::get_nesting_level(line);
let content = Self::extract_content(line);
let indent = Self::extract_indentation(line);
let mut result = indent;
for _ in 0..level {
result.push_str("> ");
}
result.push_str(&content);
result
}
pub fn has_blank_between_blockquotes(content: &str) -> Vec<usize> {
let lines: Vec<&str> = content.lines().collect();
let mut blank_line_numbers = Vec::new();
for i in 1..lines.len() {
let prev_line = lines[i - 1];
let current_line = lines[i];
if Self::is_blockquote(prev_line) && Self::is_blockquote(current_line) {
if current_line.trim().is_empty() {
blank_line_numbers.push(i + 1); }
}
}
blank_line_numbers
}
pub fn fix_blank_between_blockquotes(content: &str) -> String {
let lines: Vec<&str> = content.lines().collect();
let mut result = Vec::new();
let mut skip_next = false;
for i in 0..lines.len() {
if skip_next {
skip_next = false;
continue;
}
let current_line = lines[i];
if i > 0 && i < lines.len() - 1 {
let prev_line = lines[i - 1];
let next_line = lines[i + 1];
if Self::is_blockquote(prev_line) && Self::is_blockquote(next_line) && current_line.trim().is_empty() {
skip_next = false;
continue;
}
}
result.push(current_line);
}
result.join("\n")
}
}