Something went wrong. Try again.
Markdown parser fork with extended syntax for personal use.
Something went wrong. Try again.
7.8 kB · 242 lines
Rust
at hack
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243//! Block quotes occur in the [document][] content type.//!//! ## Grammar//!//! Block quotes form with the following BNF//! (<small>see [construct][crate::construct] for character groups</small>)://!//! ```bnf//! block_quote_start ::= '>' [ space_or_tab ]//! block_quote_cont ::= '>' [ space_or_tab ]//! ```//!//! Further lines that are not prefixed with `block_quote_cont` cause the block//! quote to be exited, except when those lines are lazy continuation.//! Like so many things in markdown, block quotes too are complex.//! See [*§ Phase 1: block structure* in `CommonMark`][commonmark-block] for//! more on parsing details.//!//! As block quote is a container, it takes several bytes from the start of the//! line, while the rest of the line includes more containers or flow.//!//! ## HTML//!//! Block quote relates to the `<blockquote>` element in HTML.//! See [*§ 4.4.4 The `blockquote` element*][html-blockquote] in the HTML spec//! for more info.//!//! ## Recommendation//!//! Always use a single space after a block quote marker (`>`).//! Never use lazy continuation.//!//! ## Tokens//!//! * [`BlockQuote`][Name::BlockQuote]//! * [`BlockQuoteMarker`][Name::BlockQuoteMarker]//! * [`BlockQuotePrefix`][Name::BlockQuotePrefix]//! * [`SpaceOrTab`][Name::SpaceOrTab]//!//! ## References//!//! * [`block-quote.js` in `micromark`](https://github.com/micromark/micromark/blob/main/packages/micromark-core-commonmark/dev/lib/block-quote.js)//! * [*§ 5.1 Block quotes* in `CommonMark`](https://spec.commonmark.org/0.31/#block-quotes)//!//! [document]: crate::construct::document//! [html-blockquote]: https://html.spec.whatwg.org/multipage/grouping-content.html#the-blockquote-element//! [commonmark-block]: https://spec.commonmark.org/0.31/#phase-1-block-structure
use crate::construct::partial_space_or_tab::space_or_tab_min_max;use crate::event::Name;use crate::state::{Name as StateName, State};use crate::tokenizer::Tokenizer;use crate::util::constant::TAB_SIZE;
/// Start of block quote.////// ```markdown/// > | > a/// ^/// ```pub fn start(tokenizer: &mut Tokenizer) -> State { if tokenizer.parse_state.options.constructs.block_quote { tokenizer.enter(Name::BlockQuote);
State::Retry(StateName::BlockQuoteContStart) } else { State::Nok }}
/// Start of block quote continuation.////// Also used to parse the first block quote opening.////// ```markdown/// | > a/// > | > b/// ^/// ```pub fn cont_start(tokenizer: &mut Tokenizer) -> State { if matches!(tokenizer.current, Some(b'\t' | b' ')) { tokenizer.attempt(State::Next(StateName::BlockQuoteContBefore), State::Nok); State::Retry(space_or_tab_min_max( tokenizer, 1, if tokenizer.parse_state.options.constructs.code_indented { TAB_SIZE - 1 } else { usize::MAX }, )) } else { State::Retry(StateName::BlockQuoteContBefore) }}
/// At `>`, after optional whitespace.////// Also used to parse the first block quote opening.////// ```markdown/// | > a/// > | > b/// ^/// ```pub fn cont_before(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { Some(b'>') => { tokenizer.enter(Name::BlockQuotePrefix); tokenizer.enter(Name::BlockQuoteMarker); tokenizer.consume(); tokenizer.exit(Name::BlockQuoteMarker);
// If we are looking for obsidian block quote metadata... if tokenizer.parse_state.options.constructs.obs_block_quote { // Clean up our flags tokenizer.tokenize_state.seen = false;
// Start looking for the metadata! tokenizer.attempt( State::Next(StateName::BlockQuoteContBeforeNoCallout), State::Next(StateName::BlockQuoteContAfter), ); return State::Next(StateName::ObsidianBlockQuoteCalloutStart); }
State::Next(StateName::BlockQuoteContAfter) } _ => State::Nok, }}
// Just the original version of the previous function, without looking for// Obsidian callout blocks...pub fn cont_before_no_callout(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { Some(b'>') => { tokenizer.enter(Name::BlockQuotePrefix); tokenizer.enter(Name::BlockQuoteMarker); tokenizer.consume(); tokenizer.exit(Name::BlockQuoteMarker);
State::Next(StateName::BlockQuoteContAfter) } _ => State::Nok, }}
/// After `>`, before optional whitespace.////// ```markdown/// > | > a/// ^/// > | >b/// ^/// ```pub fn cont_after(tokenizer: &mut Tokenizer) -> State { if let Some(b'\t' | b' ') = tokenizer.current { tokenizer.enter(Name::SpaceOrTab); tokenizer.consume(); tokenizer.exit(Name::SpaceOrTab); }
tokenizer.exit(Name::BlockQuotePrefix); State::Ok}
pub fn obs_callout_start(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { // Eat all whitespace before a callout Some(b' ' | b'\t') => { tokenizer.attempt( State::Next(StateName::ObsidianBlockQuoteCalloutStart), // State::Next(StateName::BlockQuoteContAfter), State::Nok, ); State::Retry(space_or_tab_min_max( tokenizer, 1, if tokenizer.parse_state.options.constructs.code_indented { TAB_SIZE - 1 } else { usize::MAX }, )) } Some(b'[') => { // We reuse state to mark that we've seen a possible callout start tokenizer.tokenize_state.seen = true; tokenizer.consume(); State::Next(StateName::ObsidianBlockQuoteCalloutStart) } Some(b'!') if tokenizer.tokenize_state.seen => { // We're inside a callout now tokenizer.enter(Name::ObsidianBlockQuoteCallout); tokenizer.enter(Name::ObsidianBlockQuoteCalloutType); tokenizer.consume();
// We reuse this state variable until we've found a closing ] tokenizer.tokenize_state.seen = true; State::Next(StateName::ObsidianBlockQuoteCalloutInner) } _ => State::Nok, }}
/// ```markdown/// > [!blahblah]/// ^/// ```pub fn obs_callout_inner(tokenizer: &mut Tokenizer) -> State { match tokenizer.current { Some(b']') if tokenizer.tokenize_state.seen => { // We've seen the end of the callout declaration tokenizer.tokenize_state.seen = false;
// TODO: Optionally check for - character tokenizer.consume(); tokenizer.exit(Name::ObsidianBlockQuoteCalloutType); tokenizer.enter(Name::ObsidianText); State::Next(StateName::ObsidianBlockQuoteCalloutInner) } Some(b'\n') => { // Resume normal flow! tokenizer.consume(); tokenizer.exit(Name::ObsidianText); tokenizer.exit(Name::ObsidianBlockQuoteCallout); tokenizer.tokenize_state.seen = false; // State::Next(StateName::BlockQuoteContBefore) State::Ok } _ if tokenizer.tokenize_state.seen => { tokenizer.consume(); State::Next(StateName::ObsidianBlockQuoteCalloutInner) } _ => { // We just save whatever this is as text tokenizer.consume(); State::Next(StateName::ObsidianBlockQuoteCalloutInner) } }}