Implement blockquote token
This commit is contained in:
parent
aa41e33ced
commit
260610c4a0
11 changed files with 263 additions and 120 deletions
|
|
@ -1,20 +1,21 @@
|
|||
use crate::syntax::content::parser::{
|
||||
State, Token,
|
||||
token::{Header, Paragraph, PreFormat, Quote, Verse},
|
||||
token::{Header, Paragraph, PreFormat, Verse},
|
||||
};
|
||||
|
||||
pub mod block;
|
||||
pub mod inline;
|
||||
pub mod anchor;
|
||||
pub mod list;
|
||||
pub mod quote;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Context {
|
||||
pub block: Block,
|
||||
pub inline: Inline,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub enum Block {
|
||||
Paragraph,
|
||||
Header(u8), // level
|
||||
|
|
@ -22,13 +23,15 @@ pub enum Block {
|
|||
PreFormat,
|
||||
Quote,
|
||||
Verse,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub enum Inline {
|
||||
Anchor,
|
||||
Code,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +52,7 @@ pub fn close(state: &State, tokens: &mut Vec<Token>) {
|
|||
tokens.push(Token::Header(Header::from_u8(level, false, None)));
|
||||
},
|
||||
Block::Quote => {
|
||||
tokens.push(Token::Quote(Quote::new(false)));
|
||||
panic!("End of input with open quote")
|
||||
},
|
||||
Block::Verse => {
|
||||
tokens.push(Token::Verse(Verse::new(false)));
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ pub fn parse(
|
|||
} else if Quote::probe(lexeme) {
|
||||
log!(VERBOSE, "Block Context: None -> Quote on {lexeme}");
|
||||
state.context.block = Block::Quote;
|
||||
tokens.push(Token::Quote(Quote::new(true)));
|
||||
iterator.next();
|
||||
return true;
|
||||
} else if Verse::probe(lexeme) {
|
||||
log!(VERBOSE, "Block Context: None -> Verse on {lexeme}");
|
||||
|
|
@ -93,15 +93,7 @@ pub fn parse(
|
|||
return super::list::parse(lexeme, state, tokens, iterator, graph);
|
||||
},
|
||||
Block::Quote => {
|
||||
if lexeme.match_char_sequence('\n', '>') {
|
||||
tokens.push(Token::LineBreak(LineBreak::default()));
|
||||
iterator.next();
|
||||
return true;
|
||||
} else if Quote::probe_end(lexeme) {
|
||||
tokens.push(Token::Quote(Quote::new(false)));
|
||||
log!(VERBOSE, "Block Context: Quote -> None on {lexeme}");
|
||||
state.context.block = Block::None;
|
||||
}
|
||||
return super::quote::parse(lexeme, state, tokens, iterator, graph);
|
||||
},
|
||||
Block::Verse => {
|
||||
if Verse::probe_end(lexeme) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ pub fn parse(
|
|||
let candidate = &mut buffer.candidate;
|
||||
let item_candidate = &mut buffer.item_candidate;
|
||||
|
||||
#[allow(clippy::wildcard_enum_match_arm)]
|
||||
match state.context.block {
|
||||
Block::List => {
|
||||
if lexeme.match_char(' ') && item_candidate.depth.is_none() {
|
||||
|
|
|
|||
97
src/syntax/content/parser/context/quote.rs
Normal file
97
src/syntax/content/parser/context/quote.rs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
use std::{iter::Peekable, slice::Iter};
|
||||
|
||||
use crate::{
|
||||
graph::Graph,
|
||||
prelude::*,
|
||||
syntax::content::parser::{
|
||||
Lexeme, State, Token,
|
||||
context::Block,
|
||||
format, state,
|
||||
token::{Anchor, Quote},
|
||||
},
|
||||
};
|
||||
|
||||
/// Handles open quote contexts until a quote is fully parsed.
|
||||
///
|
||||
/// A return of `true` will trigger a continue in the outer parser,
|
||||
/// skipping any further parsing of the current lexeme.
|
||||
///
|
||||
/// # Panics
|
||||
/// This parser can handle only the Quote context, and will panic if passed an
|
||||
/// unrelated context since it has no knowledge on how to handle them.
|
||||
pub fn parse(
|
||||
lexeme: &Lexeme,
|
||||
state: &mut State,
|
||||
tokens: &mut Vec<Token>,
|
||||
iterator: &mut Peekable<Iter<'_, Lexeme>>,
|
||||
graph: &Graph,
|
||||
) -> bool {
|
||||
let buffer = &mut state.buffers.quote;
|
||||
let candidate = &mut buffer.candidate;
|
||||
|
||||
#[allow(clippy::wildcard_enum_match_arm)]
|
||||
match state.context.block {
|
||||
Block::Quote => {
|
||||
if Quote::probe_end(lexeme) {
|
||||
log!("Probed end of quote on {lexeme}");
|
||||
let (text, text_tokens) = format(&candidate.text, graph);
|
||||
candidate.text = text;
|
||||
state.format_tokens.extend_from_slice(&text_tokens);
|
||||
|
||||
if let Some(citation) = &candidate.citation {
|
||||
let (formatted_citation, citation_tokens) =
|
||||
format(citation, graph);
|
||||
candidate.citation = Some(formatted_citation);
|
||||
state.format_tokens.extend_from_slice(&citation_tokens);
|
||||
|
||||
let mut first_anchor = Anchor::default();
|
||||
for token in citation_tokens {
|
||||
if let Token::Anchor(token_data) = token {
|
||||
first_anchor = *token_data.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if first_anchor.external() {
|
||||
candidate.url = first_anchor.destination();
|
||||
}
|
||||
}
|
||||
|
||||
tokens.push(Token::Quote(candidate.clone()));
|
||||
log!(VERBOSE, "Block Context: Quote -> None on {lexeme}");
|
||||
state.context.block = Block::None;
|
||||
*buffer = state::QuoteBuffer::default();
|
||||
} else if !buffer.in_citation
|
||||
&& lexeme.match_char('\n')
|
||||
&& lexeme.next() == "--"
|
||||
{
|
||||
log!("Matched citation start on {lexeme}");
|
||||
buffer.in_citation = true;
|
||||
iterator.next();
|
||||
iterator.next();
|
||||
} else if lexeme.match_char_sequence('\n', '>') {
|
||||
log!("Matched break-aware sequence on {lexeme}");
|
||||
candidate.text.push_str(" <\n");
|
||||
iterator.next();
|
||||
} else {
|
||||
log!("Entered quote else branch on {lexeme}");
|
||||
if buffer.in_citation {
|
||||
log!("Extending citation on {lexeme}");
|
||||
candidate.extend_citation(&lexeme.text());
|
||||
if lexeme.match_char('\n') && lexeme.next() == "--" {
|
||||
candidate.text.push('\n');
|
||||
iterator.next();
|
||||
} else if lexeme.match_char('\n') {
|
||||
buffer.in_citation = false;
|
||||
}
|
||||
} else {
|
||||
log!("Extending quote on {lexeme}");
|
||||
candidate.text.push_str(&lexeme.text());
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
panic!("Quote context parser called to handle non-quote context")
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ use std::collections::HashMap;
|
|||
|
||||
use crate::syntax::content::parser::{
|
||||
Token,
|
||||
context::{Block, Context, Inline},
|
||||
token::{Anchor, Item, List},
|
||||
context::Context,
|
||||
token::{Anchor, Item, List, Quote},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct State {
|
||||
pub context: Context,
|
||||
pub dom_ids: HashMap<String, Vec<String>>,
|
||||
|
|
@ -15,7 +15,7 @@ pub struct State {
|
|||
pub format_tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Switches {
|
||||
pub bold: bool,
|
||||
pub oblique: bool,
|
||||
|
|
@ -23,10 +23,11 @@ pub struct Switches {
|
|||
pub underline: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Buffers {
|
||||
pub anchor: AnchorBuffer,
|
||||
pub list: ListBuffer,
|
||||
pub quote: QuoteBuffer,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
|
|
@ -43,6 +44,12 @@ pub struct AnchorBuffer {
|
|||
pub destination: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct QuoteBuffer {
|
||||
pub candidate: Quote,
|
||||
pub in_citation: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AnchorBuffer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let display_text = if self.text.is_empty() {
|
||||
|
|
@ -67,37 +74,6 @@ impl std::fmt::Display for AnchorBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> State {
|
||||
State {
|
||||
context: Context {
|
||||
inline: Inline::None,
|
||||
block: Block::None,
|
||||
},
|
||||
dom_ids: HashMap::default(),
|
||||
switches: Switches {
|
||||
bold: false,
|
||||
crossout: false,
|
||||
oblique: false,
|
||||
underline: false,
|
||||
},
|
||||
buffers: Buffers {
|
||||
anchor: AnchorBuffer {
|
||||
candidate: Anchor::default(),
|
||||
text: String::default(),
|
||||
destination: String::default(),
|
||||
},
|
||||
list: ListBuffer {
|
||||
candidate: List::default(),
|
||||
item_candidate: Item::default(),
|
||||
depth: 0,
|
||||
},
|
||||
},
|
||||
format_tokens: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
use crate::syntax::content::{Parseable, parser::Lexeme};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct Quote {
|
||||
open: Option<bool>,
|
||||
pub text: String,
|
||||
pub citation: Option<String>,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
impl Quote {
|
||||
pub fn new(open: bool) -> Quote {
|
||||
Quote { open: Some(open) }
|
||||
}
|
||||
|
||||
pub fn probe_end(lexeme: &Lexeme) -> bool {
|
||||
lexeme.match_char_sequence('\n', '\n')
|
||||
}
|
||||
|
||||
pub fn extend_citation(&mut self, s: &str) {
|
||||
if let Some(current) = &self.citation {
|
||||
self.citation = Some(format!("{current}{s}"));
|
||||
} else {
|
||||
self.citation = Some(String::from(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parseable for Quote {
|
||||
|
|
@ -21,19 +27,26 @@ impl Parseable for Quote {
|
|||
}
|
||||
|
||||
fn lex(_lexeme: &Lexeme) -> Quote {
|
||||
Quote { open: None }
|
||||
Quote::default()
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
if let Some(open) = self.open {
|
||||
if open {
|
||||
"<blockquote>".to_owned()
|
||||
} else {
|
||||
"</blockquote>".to_owned()
|
||||
}
|
||||
let opening = if let Some(url) = &self.url {
|
||||
format!(r#"<blockquote cite="{url}">"#)
|
||||
} else {
|
||||
panic!("Attempt to render a quote tag while open state is unknown")
|
||||
}
|
||||
String::from("<blockquote>")
|
||||
};
|
||||
|
||||
let content = if let Some(citation) = &self.citation {
|
||||
format!(
|
||||
r#"{}<br/><p class="quote-citation">{citation}</p>"#,
|
||||
&self.text
|
||||
)
|
||||
} else {
|
||||
String::from(&self.text)
|
||||
};
|
||||
|
||||
format!("\n{opening}\n{content}\n</blockquote>\n")
|
||||
}
|
||||
|
||||
fn flatten(&self) -> String {
|
||||
|
|
@ -43,16 +56,14 @@ impl Parseable for Quote {
|
|||
|
||||
impl std::fmt::Display for Quote {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let display_open_state = match self.open {
|
||||
Some(open_state) => {
|
||||
if open_state {
|
||||
"open"
|
||||
} else {
|
||||
"closed"
|
||||
}
|
||||
},
|
||||
None => "unknown",
|
||||
};
|
||||
write!(f, "Quote [{display_open_state}]")
|
||||
let mut meta = String::default();
|
||||
if self.url.is_some() {
|
||||
meta.push_str("+url ");
|
||||
}
|
||||
if self.citation.is_some() {
|
||||
meta.push_str("+citation ");
|
||||
}
|
||||
|
||||
write!(f, "Quote [{}]", meta.trim())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue