use regex::Regex;
use lazy_static::lazy_static;
lazy_static! {
static ref UNORDERED_LIST_PATTERN: Regex = Regex::new(r"^(\s*)([*+-])(\s+)").unwrap();
static ref ORDERED_LIST_PATTERN: Regex = Regex::new(r"^(\s*)(\d+\.)(\s+)").unwrap();
static ref UNORDERED_LIST_NO_SPACE_PATTERN: Regex = Regex::new(r"^(\s*)([*+-])([^\s])").unwrap();
static ref ORDERED_LIST_NO_SPACE_PATTERN: Regex = Regex::new(r"^(\s*)(\d+\.)([^\s])").unwrap();
static ref UNORDERED_LIST_MULTIPLE_SPACE_PATTERN: Regex = Regex::new(r"^(\s*)([*+-])(\s{2,})").unwrap();
static ref ORDERED_LIST_MULTIPLE_SPACE_PATTERN: Regex = Regex::new(r"^(\s*)(\d+\.)(\s{2,})").unwrap();
}
#[derive(Debug, Clone, PartialEq)]
pub enum ListMarkerType {
Asterisk,
Plus,
Minus,
Ordered,
}
#[derive(Debug, Clone)]
pub struct ListItem {
pub indentation: usize,
pub marker_type: ListMarkerType,
pub marker: String,
pub content: String,
pub spaces_after_marker: usize,
}
pub struct ListUtils;
impl ListUtils {
pub fn is_list_item(line: &str) -> bool {
UNORDERED_LIST_PATTERN.is_match(line) || ORDERED_LIST_PATTERN.is_match(line)
}
pub fn is_unordered_list_item(line: &str) -> bool {
UNORDERED_LIST_PATTERN.is_match(line)
}
pub fn is_ordered_list_item(line: &str) -> bool {
ORDERED_LIST_PATTERN.is_match(line)
}
pub fn is_list_item_without_space(line: &str) -> bool {
UNORDERED_LIST_NO_SPACE_PATTERN.is_match(line) || ORDERED_LIST_NO_SPACE_PATTERN.is_match(line)
}
pub fn is_list_item_with_multiple_spaces(line: &str) -> bool {
UNORDERED_LIST_MULTIPLE_SPACE_PATTERN.is_match(line) || ORDERED_LIST_MULTIPLE_SPACE_PATTERN.is_match(line)
}
pub fn parse_list_item(line: &str) -> Option<ListItem> {
if let Some(caps) = UNORDERED_LIST_PATTERN.captures(line) {
let indentation = caps[1].len();
let marker = caps[2].to_string();
let spaces = caps[3].len();
let content_start = indentation + marker.len() + spaces;
let content = if content_start < line.len() {
line[content_start..].to_string()
} else {
String::new()
};
let marker_type = match marker.as_str() {
"*" => ListMarkerType::Asterisk,
"+" => ListMarkerType::Plus,
"-" => ListMarkerType::Minus,
_ => unreachable!(),
};
return Some(ListItem {
indentation,
marker_type,
marker,
content,
spaces_after_marker: spaces,
});
}
if let Some(caps) = ORDERED_LIST_PATTERN.captures(line) {
let indentation = caps[1].len();
let marker = caps[2].to_string();
let spaces = caps[3].len();
let content_start = indentation + marker.len() + spaces;
let content = if content_start < line.len() {
line[content_start..].to_string()
} else {
String::new()
};
return Some(ListItem {
indentation,
marker_type: ListMarkerType::Ordered,
marker,
content,
spaces_after_marker: spaces,
});
}
None
}
pub fn is_list_continuation(line: &str, prev_list_item: &ListItem) -> bool {
if line.trim().is_empty() {
return true; }
let indentation = line.len() - line.trim_start().len();
let required_indent = prev_list_item.indentation + prev_list_item.marker.len() + prev_list_item.spaces_after_marker;
indentation >= required_indent && !Self::is_list_item(line)
}
pub fn fix_list_item_without_space(line: &str) -> String {
if let Some(caps) = UNORDERED_LIST_NO_SPACE_PATTERN.captures(line) {
let indentation = &caps[1];
let marker = &caps[2];
let first_char = &caps[3];
let content_start_pos = indentation.len() + marker.len() + 1;
let rest_of_content = if content_start_pos < line.len() {
&line[content_start_pos..]
} else {
""
};
format!("{}{} {}{}", indentation, marker, first_char, rest_of_content)
} else if let Some(caps) = ORDERED_LIST_NO_SPACE_PATTERN.captures(line) {
let indentation = &caps[1];
let marker = &caps[2];
let first_char = &caps[3];
let content_start_pos = indentation.len() + marker.len() + 1;
let rest_of_content = if content_start_pos < line.len() {
&line[content_start_pos..]
} else {
""
};
format!("{}{} {}{}", indentation, marker, first_char, rest_of_content)
} else {
line.to_string()
}
}
pub fn fix_list_item_with_multiple_spaces(line: &str) -> String {
if let Some(caps) = UNORDERED_LIST_MULTIPLE_SPACE_PATTERN.captures(line) {
let indentation = &caps[1];
let marker = &caps[2];
let content = line[indentation.len() + marker.len()..].trim_start();
format!("{}{} {}", indentation, marker, content)
} else if let Some(caps) = ORDERED_LIST_MULTIPLE_SPACE_PATTERN.captures(line) {
let indentation = &caps[1];
let marker = &caps[2];
let content = line[indentation.len() + marker.len()..].trim_start();
format!("{}{} {}", indentation, marker, content)
} else {
line.to_string()
}
}
}