discord-message-format/src/parse_basic.rs

88 lines
2.7 KiB
Rust

use super::{
ast::DiscordComponent, parse_code::*, parse_inline_style::*, parse_link::*, parse_quotes::*,
};
pub fn parse(text: &'_ str) -> Vec<DiscordComponent<'_>> {
let mut tokens = Vec::new();
let mut working_plain_start: isize = -1;
let mut i = 0;
while i < text.len() {
let is_line_start = matches!(tokens.last(), None | Some(DiscordComponent::LineBreak));
match parse_token(&text[i..], is_line_start) {
Some((token, consumed)) => {
if working_plain_start >= 0 {
let plain_start = working_plain_start as usize;
tokens.push(DiscordComponent::Plain(&text[plain_start..i]));
working_plain_start = -1;
}
tokens.push(token);
i += consumed;
}
None => {
if working_plain_start < 0 {
working_plain_start = i as isize;
}
let mut next_char = i + 1;
while !text.is_char_boundary(next_char) {
next_char += 1;
}
i = next_char;
}
}
}
if working_plain_start >= 0 {
let plain_start = working_plain_start as usize;
tokens.push(DiscordComponent::Plain(&text[plain_start..]));
}
tokens
}
pub fn parse_token(text: &'_ str, is_line_start: bool) -> Option<(DiscordComponent<'_>, usize)> {
parse_escaped_literal(text)
.or_else(|| {
if is_line_start {
parse_quotes(text)
} else {
None
}
})
.or_else(|| parse_code_block(text))
.or_else(|| parse_code(text))
.or_else(|| parse_link(text))
.or_else(|| parse_bold(text))
.or_else(|| parse_italic(text))
.or_else(|| parse_strikethrough(text))
.or_else(|| parse_underline(text))
.or_else(|| parse_spoiler(text))
.or_else(|| parse_line_break(text))
}
pub fn parse_escaped_literal(text: &'_ str) -> Option<(DiscordComponent<'_>, usize)> {
let mut chars = text.chars();
if let Some('\\') = chars.next() {
return match chars.next() {
Some(x @ '\\') | Some(x @ '`') | Some(x @ '*') | Some(x @ '_') | Some(x @ '{')
| Some(x @ '}') | Some(x @ '[') | Some(x @ ']') | Some(x @ '(') | Some(x @ ')')
| Some(x @ '#') | Some(x @ '+') | Some(x @ '-') | Some(x @ '.') | Some(x @ '!') => {
Some((DiscordComponent::Literal(x), 2))
}
_ => None,
};
}
None
}
pub fn parse_line_break(text: &'_ str) -> Option<(DiscordComponent<'_>, usize)> {
if text.starts_with('\n') {
return Some((DiscordComponent::LineBreak, 1));
}
None
}