From a44eb7607670f8066e4c4aed08f6e2f2756e1ad7 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2024 13:17:02 -0400 Subject: [PATCH 1/7] Add support for underline extension --- src/cm.rs | 5 +++++ src/html.rs | 7 +++++++ src/main.rs | 2 ++ src/nodes.rs | 4 ++++ src/parser/inlines.rs | 5 +++++ src/parser/mod.rs | 16 ++++++++++++++++ src/tests.rs | 1 + src/tests/api.rs | 2 ++ src/tests/underline.rs | 10 ++++++++++ src/xml.rs | 1 + 10 files changed, 53 insertions(+) create mode 100644 src/tests/underline.rs diff --git a/src/cm.rs b/src/cm.rs index 9b8ba51b..83ea634e 100644 --- a/src/cm.rs +++ b/src/cm.rs @@ -386,6 +386,7 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { } NodeValue::Math(ref math) => self.format_math(math, allow_wrap, entering), NodeValue::WikiLink(ref nl) => return self.format_wikilink(nl, entering), + NodeValue::Underline => self.format_underline(), }; true } @@ -668,6 +669,10 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { write!(self, "^").unwrap(); } + fn format_underline(&mut self) { + write!(self, "__").unwrap(); + } + fn format_link(&mut self, node: &'a AstNode<'a>, nl: &NodeLink, entering: bool) -> bool { if is_autolink(node, nl) { if entering { diff --git a/src/html.rs b/src/html.rs index 025565d2..76ebf326 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1053,6 +1053,13 @@ impl<'o> HtmlFormatter<'o> { self.output.write_all(b"")?; } } + NodeValue::Underline => { + if entering { + self.output.write_all(b"")?; + } else { + self.output.write_all(b"")?; + } + } } Ok(false) } diff --git a/src/main.rs b/src/main.rs index 3caebc9b..f6458589 100644 --- a/src/main.rs +++ b/src/main.rs @@ -161,6 +161,7 @@ enum Extension { MathCode, WikilinksTitleAfterPipe, WikilinksTitleBeforePipe, + Underline, } #[derive(Clone, Copy, Debug, ValueEnum)] @@ -242,6 +243,7 @@ fn main() -> Result<(), Box> { .math_code(exts.contains(&Extension::MathCode)) .wikilinks_title_after_pipe(exts.contains(&Extension::WikilinksTitleAfterPipe)) .wikilinks_title_before_pipe(exts.contains(&Extension::WikilinksTitleBeforePipe)) + .underline(exts.contains(&Extension::Underline)) .front_matter_delimiter(cli.front_matter_delimiter); #[cfg(feature = "shortcodes")] diff --git a/src/nodes.rs b/src/nodes.rs index 79bbfd7e..8cf0ee3d 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -185,6 +185,9 @@ pub enum NodeValue { /// **Inline**. A wikilink to some URL. WikiLink(NodeWikiLink), + + /// **Inline**. Underline. Enabled with `underline` option. + Underline, } /// Alignment of a single table cell. @@ -500,6 +503,7 @@ impl NodeValue { NodeValue::Escaped => "escaped", NodeValue::Math(..) => "math", NodeValue::WikiLink(..) => "wikilink", + NodeValue::Underline => "underline", } } } diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs index 92c8a6ab..af17b894 100644 --- a/src/parser/inlines.rs +++ b/src/parser/inlines.rs @@ -161,6 +161,9 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { if options.extension.shortcodes { s.special_chars[b':' as usize] = true; } + if options.extension.underline { + s.special_chars[b'_' as usize] = true; + } for &c in &[b'"', b'\'', b'.', b'-'] { s.smart_chars[c as usize] = true; } @@ -1057,6 +1060,8 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { NodeValue::Strikethrough } else if self.options.extension.superscript && opener_char == b'^' { NodeValue::Superscript + } else if self.options.extension.underline && opener_char == b'_' && use_delims == 2 { + NodeValue::Underline } else if use_delims == 1 { NodeValue::Emph } else { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5bf26c20..17d61a96 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -453,6 +453,22 @@ pub struct ExtensionOptions { /// "

link label

\n"); /// ``` pub wikilinks_title_before_pipe: bool, + + /// Enables underlines using double underscores + /// + /// ```md + /// __underlined text__ + /// ``` + /// + /// ``` + /// # use comrak::{markdown_to_html, Options}; + /// let mut options = Options::default(); + /// options.extension.underline = true; + /// + /// assert_eq!(markdown_to_html("__underlined text__", &options), + /// "

underlined text

\n"); + /// ``` + pub underline: bool, } #[non_exhaustive] diff --git a/src/tests.rs b/src/tests.rs index 986cc83c..4aa48d91 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -25,6 +25,7 @@ mod superscript; mod table; mod tagfilter; mod tasklist; +mod underline; mod wikilinks; mod xml; diff --git a/src/tests/api.rs b/src/tests/api.rs index 16adef8d..e1e46847 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -52,6 +52,7 @@ fn exercise_full_api() { extension.shortcodes(true); extension.wikilinks_title_after_pipe(true); extension.wikilinks_title_before_pipe(true); + extension.underline(true); let mut parse = ParseOptionsBuilder::default(); parse.smart(false); @@ -231,5 +232,6 @@ fn exercise_full_api() { nodes::NodeValue::WikiLink(nl) => { let _: String = nl.url; } + nodes::NodeValue::Underline => {} } } diff --git a/src/tests/underline.rs b/src/tests/underline.rs new file mode 100644 index 00000000..8efa9a9f --- /dev/null +++ b/src/tests/underline.rs @@ -0,0 +1,10 @@ +use super::*; + +#[test] +fn underline() { + html_opts!( + [extension.underline], + concat!("__underlined text__\n"), + concat!("

underlined text

\n"), + ); +} diff --git a/src/xml.rs b/src/xml.rs index 3d2e3ebd..aef609c5 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -278,6 +278,7 @@ impl<'o> XmlFormatter<'o> { self.escape(nl.url.as_bytes())?; self.output.write_all(b"\"")?; } + NodeValue::Underline => {} } if node.first_child().is_some() { From f5b6e75e5b99734a6ab609e0b25500c1ca0601b2 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2024 13:36:30 -0400 Subject: [PATCH 2/7] Add support for spoiler extension --- src/cm.rs | 10 + src/html.rs | 10 + src/main.rs | 2 + src/nodes.rs | 9 + src/parser/inlines.rs | 26 +- src/parser/mod.rs | 19 +- src/parser/table.rs | 17 +- src/scanners.re | 14 +- src/scanners.rs | 1517 ++++++++++++++++++++++++++++------------- src/tests.rs | 1 + src/tests/api.rs | 5 + src/tests/spoiler.rs | 62 ++ src/xml.rs | 4 + 13 files changed, 1206 insertions(+), 490 deletions(-) create mode 100644 src/tests/spoiler.rs diff --git a/src/cm.rs b/src/cm.rs index 83ea634e..5ac12314 100644 --- a/src/cm.rs +++ b/src/cm.rs @@ -387,6 +387,8 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { NodeValue::Math(ref math) => self.format_math(math, allow_wrap, entering), NodeValue::WikiLink(ref nl) => return self.format_wikilink(nl, entering), NodeValue::Underline => self.format_underline(), + NodeValue::SpoileredText => self.format_spoiler(), + NodeValue::EscapedTag(ref net) => self.format_escaped_tag(net), }; true } @@ -673,6 +675,14 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { write!(self, "__").unwrap(); } + fn format_spoiler(&mut self) { + write!(self, "||").unwrap(); + } + + fn format_escaped_tag(&mut self, net: &String) { + self.output(net.as_bytes(), false, Escaping::Literal); + } + fn format_link(&mut self, node: &'a AstNode<'a>, nl: &NodeLink, entering: bool) -> bool { if is_autolink(node, nl) { if entering { diff --git a/src/html.rs b/src/html.rs index 76ebf326..473f643e 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1060,6 +1060,16 @@ impl<'o> HtmlFormatter<'o> { self.output.write_all(b"")?; } } + NodeValue::SpoileredText => { + if entering { + self.output.write_all(b"")?; + } else { + self.output.write_all(b"")?; + } + } + NodeValue::EscapedTag(ref net) => { + self.output.write_all(net.as_bytes())?; + } } Ok(false) } diff --git a/src/main.rs b/src/main.rs index f6458589..26543747 100644 --- a/src/main.rs +++ b/src/main.rs @@ -162,6 +162,7 @@ enum Extension { WikilinksTitleAfterPipe, WikilinksTitleBeforePipe, Underline, + Spoiler, } #[derive(Clone, Copy, Debug, ValueEnum)] @@ -244,6 +245,7 @@ fn main() -> Result<(), Box> { .wikilinks_title_after_pipe(exts.contains(&Extension::WikilinksTitleAfterPipe)) .wikilinks_title_before_pipe(exts.contains(&Extension::WikilinksTitleBeforePipe)) .underline(exts.contains(&Extension::Underline)) + .spoiler(exts.contains(&Extension::Spoiler)) .front_matter_delimiter(cli.front_matter_delimiter); #[cfg(feature = "shortcodes")] diff --git a/src/nodes.rs b/src/nodes.rs index 8cf0ee3d..8a2c6128 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -188,6 +188,13 @@ pub enum NodeValue { /// **Inline**. Underline. Enabled with `underline` option. Underline, + + /// **Inline**. Spoilered text. Enabled with `spoiler` option. + SpoileredText, + + /// **Inline**. Text surrounded by escaped markup. Enabled with `spoiler` option. + /// The `String` is the tag to be escaped. + EscapedTag(String), } /// Alignment of a single table cell. @@ -504,6 +511,8 @@ impl NodeValue { NodeValue::Math(..) => "math", NodeValue::WikiLink(..) => "wikilink", NodeValue::Underline => "underline", + NodeValue::SpoileredText => "spoiler", + NodeValue::EscapedTag(_) => "escaped_tag", } } } diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs index af17b894..eca80704 100644 --- a/src/parser/inlines.rs +++ b/src/parser/inlines.rs @@ -164,6 +164,9 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { if options.extension.underline { s.special_chars[b'_' as usize] = true; } + if options.extension.spoiler { + s.special_chars[b'|' as usize] = true; + } for &c in &[b'"', b'\'', b'.', b'-'] { s.smart_chars[c as usize] = true; } @@ -246,6 +249,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { Some(self.handle_delim(b'^')) } '$' => Some(self.handle_dollars()), + '|' if self.options.extension.spoiler => Some(self.handle_delim(b'|')), _ => { let endpos = self.find_special_char(); let mut contents = self.input[self.pos..endpos].to_vec(); @@ -343,7 +347,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { // This array is an important optimization that prevents searching down // the stack for openers we've previously searched for and know don't // exist, preventing exponential blowup on pathological cases. - let mut openers_bottom: [usize; 11] = [stack_bottom; 11]; + let mut openers_bottom: [usize; 12] = [stack_bottom; 12]; // This is traversing the stack from the top to the bottom, setting `closer` to // the delimiter directly above `stack_bottom`. In the case where we are processing @@ -367,12 +371,13 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { let mut mod_three_rule_invoked = false; let ix = match c.delim_char { - b'~' => 0, - b'^' => 1, - b'"' => 2, - b'\'' => 3, - b'_' => 4, - b'*' => 5 + (if c.can_open { 3 } else { 0 }) + (c.length % 3), + b'|' => 0, + b'~' => 1, + b'^' => 2, + b'"' => 3, + b'\'' => 4, + b'_' => 5, + b'*' => 6 + (if c.can_open { 3 } else { 0 }) + (c.length % 3), _ => unreachable!(), }; @@ -421,6 +426,7 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { || c.delim_char == b'_' || (self.options.extension.strikethrough && c.delim_char == b'~') || (self.options.extension.superscript && c.delim_char == b'^') + || (self.options.extension.spoiler && c.delim_char == b'|') { if opener_found { // Finally, here's the happy case where the delimiters @@ -1060,6 +1066,12 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { NodeValue::Strikethrough } else if self.options.extension.superscript && opener_char == b'^' { NodeValue::Superscript + } else if self.options.extension.spoiler && opener_char == b'|' { + if use_delims == 2 { + NodeValue::SpoileredText + } else { + NodeValue::EscapedTag("|".to_owned()) + } } else if self.options.extension.underline && opener_char == b'_' && use_delims == 2 { NodeValue::Underline } else if use_delims == 1 { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 17d61a96..fcb2cac6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -469,6 +469,22 @@ pub struct ExtensionOptions { /// "

underlined text

\n"); /// ``` pub underline: bool, + + /// Enables spoilers using double vertical bars + /// + /// ```md + /// Darth Vader is ||Luke's father|| + /// ``` + /// + /// ``` + /// # use comrak::{markdown_to_html, Options}; + /// let mut options = Options::default(); + /// options.extension.spoiler = true; + /// + /// assert_eq!(markdown_to_html("Darth Vader is ||Luke's father||", &options), + /// "

Darth Vader is Luke's father

\n"); + /// ``` + pub spoiler: bool, } #[non_exhaustive] @@ -1085,7 +1101,8 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { } } NodeValue::Table(..) => { - if !table::matches(&line[self.first_nonspace..]) { + if !table::matches(&line[self.first_nonspace..], self.options.extension.spoiler) + { return (false, container, should_continue); } continue; diff --git a/src/parser/table.rs b/src/parser/table.rs index fc7ed2db..651810bd 100644 --- a/src/parser/table.rs +++ b/src/parser/table.rs @@ -42,12 +42,14 @@ fn try_opening_header<'a>( return Some((container, false, false)); } - let delimiter_row = match row(&line[parser.first_nonspace..]) { + let spoiler = parser.options.extension.spoiler; + + let delimiter_row = match row(&line[parser.first_nonspace..], spoiler) { Some(delimiter_row) => delimiter_row, None => return Some((container, false, true)), }; - let header_row = match row(container.data.borrow().content.as_bytes()) { + let header_row = match row(container.data.borrow().content.as_bytes(), spoiler) { Some(header_row) => header_row, None => return Some((container, false, true)), }; @@ -141,7 +143,8 @@ fn try_opening_row<'a>( } let sourcepos = container.data.borrow().sourcepos; - let this_row = match row(&line[parser.first_nonspace..]) { + let spoiler = parser.options.extension.spoiler; + let this_row = match row(&line[parser.first_nonspace..], spoiler) { Some(this_row) => this_row, None => return None, }; @@ -200,7 +203,7 @@ struct Cell { content: String, } -fn row(string: &[u8]) -> Option { +fn row(string: &[u8], spoiler: bool) -> Option { let len = string.len(); let mut cells: Vec = vec![]; @@ -211,7 +214,7 @@ fn row(string: &[u8]) -> Option { let mut max_columns_abort = false; while offset < len && expect_more_cells { - let cell_matched = scanners::table_cell(&string[offset..]).unwrap_or(0); + let cell_matched = scanners::table_cell(&string[offset..], spoiler).unwrap_or(0); let pipe_matched = scanners::table_cell_end(&string[offset + cell_matched..]).unwrap_or(0); if cell_matched > 0 || pipe_matched > 0 { @@ -365,6 +368,6 @@ fn get_num_autocompleted_cells<'a>(container: &'a AstNode<'a>) -> usize { }; } -pub fn matches(line: &[u8]) -> bool { - row(line).is_some() +pub fn matches(line: &[u8], spoiler: bool) -> bool { + row(line, spoiler).is_some() } diff --git a/src/scanners.re b/src/scanners.re index 0b4fb1d9..1acde275 100644 --- a/src/scanners.re +++ b/src/scanners.re @@ -313,11 +313,13 @@ pub fn dangerous_url(s: &[u8]) -> Option { /*!re2c + table_spoiler = ['|']['|']; table_spacechar = [ \t\v\f]; table_newline = [\r]?[\n]; table_delimiter = (table_spacechar*[:]?[-]+[:]?table_spacechar*); table_cell = (escaped_char|[^\x00|\r\n])+; + table_cell_spoiler = (escaped_char|table_spoiler|[^\x00|\r\n])+; */ @@ -333,17 +335,25 @@ pub fn table_start(s: &[u8]) -> Option { */ } -pub fn table_cell(s: &[u8]) -> Option { +pub fn table_cell(s: &[u8], spoiler: bool) -> Option { let mut cursor = 0; let mut marker = 0; let len = s.len(); -/*!re2c + // In fact, `table_cell` matches non-empty table cells only. The empty // string is also a valid table cell, but is handled by the default rule. // This approach prevents re2c's match-empty-string warning. + if spoiler { +/*!re2c + table_cell_spoiler { return Some(cursor); } + * { return None; } +*/ + } else { +/*!re2c table_cell { return Some(cursor); } * { return None; } */ + } } pub fn table_cell_end(s: &[u8]) -> Option { diff --git a/src/scanners.rs b/src/scanners.rs index 4d48848e..59fb1e1e 100644 --- a/src/scanners.rs +++ b/src/scanners.rs @@ -21716,509 +21716,1080 @@ pub fn table_start(s: &[u8]) -> Option { } } -pub fn table_cell(s: &[u8]) -> Option { +pub fn table_cell(s: &[u8], spoiler: bool) -> Option { let mut cursor = 0; let mut marker = 0; let len = s.len(); - { - #[allow(unused_assignments)] - let mut yych: u8 = 0; - let mut yyaccept: usize = 0; - let mut yystate: usize = 0; - 'yyl: loop { - match yystate { - 0 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - cursor += 1; - match yych { - 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7B | 0x7D..=0x7F => { - yystate = 3; - continue 'yyl; - } - 0x5C => { - yystate = 5; - continue 'yyl; - } - 0xC2..=0xDF => { - yystate = 6; - continue 'yyl; - } - 0xE0 => { - yystate = 7; - continue 'yyl; - } - 0xE1..=0xEC | 0xEE..=0xEF => { - yystate = 8; - continue 'yyl; - } - 0xED => { - yystate = 9; - continue 'yyl; - } - 0xF0 => { - yystate = 10; - continue 'yyl; - } - 0xF1..=0xF3 => { - yystate = 11; - continue 'yyl; - } - 0xF4 => { - yystate = 12; - continue 'yyl; - } - _ => { - yystate = 1; - continue 'yyl; - } + // In fact, `table_cell` matches non-empty table cells only. The empty + // string is also a valid table cell, but is handled by the default rule. + // This approach prevents re2c's match-empty-string warning. + if spoiler { + { + #[allow(unused_assignments)] + let mut yych: u8 = 0; + let mut yyaccept: usize = 0; + let mut yystate: usize = 0; + 'yyl: loop { + match yystate { + 0 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + cursor += 1; + match yych { + 0x01..=0x09 + | 0x0B..=0x0C + | 0x0E..=0x26 + | 0x28..=0x5B + | 0x5D..=0x7B + | 0x7D..=0x7F => { + yystate = 3; + continue 'yyl; + } + 0x27 | 0x5C => { + yystate = 5; + continue 'yyl; + } + 0x7C => { + yystate = 6; + continue 'yyl; + } + 0xC2..=0xDF => { + yystate = 7; + continue 'yyl; + } + 0xE0 => { + yystate = 8; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + yystate = 9; + continue 'yyl; + } + 0xED => { + yystate = 10; + continue 'yyl; + } + 0xF0 => { + yystate = 11; + continue 'yyl; + } + 0xF1..=0xF3 => { + yystate = 12; + continue 'yyl; + } + 0xF4 => { + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 1; + continue 'yyl; + } + } + } + 1 => { + yystate = 2; + continue 'yyl; } - } - 1 => { - yystate = 2; - continue 'yyl; - } - 2 => { - return None; - } - 3 => { - yyaccept = 0; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7B | 0x7D..=0x7F => { - cursor += 1; - yystate = 3; - continue 'yyl; - } - 0x5C => { - cursor += 1; - yystate = 5; - continue 'yyl; - } - 0xC2..=0xDF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - 0xE0 => { - cursor += 1; - yystate = 15; - continue 'yyl; - } - 0xE1..=0xEC | 0xEE..=0xEF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - 0xED => { - cursor += 1; - yystate = 17; - continue 'yyl; - } - 0xF0 => { - cursor += 1; - yystate = 18; - continue 'yyl; - } - 0xF1..=0xF3 => { - cursor += 1; - yystate = 19; - continue 'yyl; - } - 0xF4 => { - cursor += 1; - yystate = 20; - continue 'yyl; - } - _ => { - yystate = 4; - continue 'yyl; - } + 2 => { + return None; + } + 3 => { + yyaccept = 0; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x01..=0x09 + | 0x0B..=0x0C + | 0x0E..=0x26 + | 0x28..=0x5B + | 0x5D..=0x7B + | 0x7D..=0x7F => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + 0x27 | 0x5C => { + cursor += 1; + yystate = 5; + continue 'yyl; + } + 0x7C => { + cursor += 1; + yystate = 14; + continue 'yyl; + } + 0xC2..=0xDF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + 0xE0 => { + cursor += 1; + yystate = 17; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + 0xED => { + cursor += 1; + yystate = 19; + continue 'yyl; + } + 0xF0 => { + cursor += 1; + yystate = 20; + continue 'yyl; + } + 0xF1..=0xF3 => { + cursor += 1; + yystate = 21; + continue 'yyl; + } + 0xF4 => { + cursor += 1; + yystate = 22; + continue 'yyl; + } + _ => { + yystate = 4; + continue 'yyl; + } + } + } + 4 => { + return Some(cursor); } - } - 4 => { - return Some(cursor); - } - 5 => { - yyaccept = 0; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7F => { - cursor += 1; - yystate = 3; - continue 'yyl; - } - 0x5C => { - cursor += 1; - yystate = 5; - continue 'yyl; - } - 0xC2..=0xDF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - 0xE0 => { - cursor += 1; - yystate = 15; - continue 'yyl; - } - 0xE1..=0xEC | 0xEE..=0xEF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - 0xED => { - cursor += 1; - yystate = 17; - continue 'yyl; - } - 0xF0 => { - cursor += 1; - yystate = 18; - continue 'yyl; - } - 0xF1..=0xF3 => { - cursor += 1; - yystate = 19; - continue 'yyl; - } - 0xF4 => { - cursor += 1; - yystate = 20; - continue 'yyl; - } - _ => { + 5 => { + yyaccept = 0; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x01..=0x09 + | 0x0B..=0x0C + | 0x0E..=0x26 + | 0x28..=0x5B + | 0x5D..=0x7B + | 0x7D..=0x7F => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + 0x27 | 0x5C | 0x7C => { + cursor += 1; + yystate = 5; + continue 'yyl; + } + 0xC2..=0xDF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + 0xE0 => { + cursor += 1; + yystate = 17; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + 0xED => { + cursor += 1; + yystate = 19; + continue 'yyl; + } + 0xF0 => { + cursor += 1; + yystate = 20; + continue 'yyl; + } + 0xF1..=0xF3 => { + cursor += 1; + yystate = 21; + continue 'yyl; + } + 0xF4 => { + cursor += 1; + yystate = 22; + continue 'yyl; + } + _ => { + yystate = 4; + continue 'yyl; + } + } + } + 6 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x27 | 0x7C => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 7 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 8 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0xA0..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 9 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 10 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x9F => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 11 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x90..=0xBF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 12 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 13 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x8F => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 14 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x27 | 0x7C => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 15 => { + cursor = marker; + if yyaccept == 0 { yystate = 4; continue 'yyl; - } - } - } - 6 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 3; - continue 'yyl; - } - _ => { yystate = 2; continue 'yyl; } } - } - 7 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0xA0..=0xBF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; + 16 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 17 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0xA0..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 18 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 19 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x9F => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 20 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x90..=0xBF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 21 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } + } + } + 22 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x8F => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + _ => { + yystate = 15; + continue 'yyl; + } } } - } - 8 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; - } - } - } - 9 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0x9F => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; - } - } - } - 10 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x90..=0xBF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; - } - } - } - 11 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; - } - } - } - 12 => { - yyaccept = 1; - marker = cursor; - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0x8F => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 2; - continue 'yyl; - } - } - } - 13 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 3; - continue 'yyl; - } - _ => { - yystate = 14; - continue 'yyl; - } + _ => { + panic!("internal lexer error") } } - 14 => { - cursor = marker; - if yyaccept == 0 { - yystate = 4; - continue 'yyl; - } else { + } + } + } else { + { + #[allow(unused_assignments)] + let mut yych: u8 = 0; + let mut yyaccept: usize = 0; + let mut yystate: usize = 0; + 'yyl: loop { + match yystate { + 0 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + cursor += 1; + match yych { + 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7B | 0x7D..=0x7F => { + yystate = 3; + continue 'yyl; + } + 0x5C => { + yystate = 5; + continue 'yyl; + } + 0xC2..=0xDF => { + yystate = 6; + continue 'yyl; + } + 0xE0 => { + yystate = 7; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + yystate = 8; + continue 'yyl; + } + 0xED => { + yystate = 9; + continue 'yyl; + } + 0xF0 => { + yystate = 10; + continue 'yyl; + } + 0xF1..=0xF3 => { + yystate = 11; + continue 'yyl; + } + 0xF4 => { + yystate = 12; + continue 'yyl; + } + _ => { + yystate = 1; + continue 'yyl; + } + } + } + 1 => { yystate = 2; continue 'yyl; } - } - 15 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0xA0..=0xBF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 14; - continue 'yyl; - } - } - } - 16 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 14; - continue 'yyl; - } + 2 => { + return None; + } + 3 => { + yyaccept = 0; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7B | 0x7D..=0x7F => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + 0x5C => { + cursor += 1; + yystate = 5; + continue 'yyl; + } + 0xC2..=0xDF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + 0xE0 => { + cursor += 1; + yystate = 15; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + 0xED => { + cursor += 1; + yystate = 17; + continue 'yyl; + } + 0xF0 => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + 0xF1..=0xF3 => { + cursor += 1; + yystate = 19; + continue 'yyl; + } + 0xF4 => { + cursor += 1; + yystate = 20; + continue 'yyl; + } + _ => { + yystate = 4; + continue 'yyl; + } + } + } + 4 => { + return Some(cursor); } - } - 17 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0x9F => { - cursor += 1; - yystate = 13; - continue 'yyl; - } - _ => { - yystate = 14; + 5 => { + yyaccept = 0; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x01..=0x09 | 0x0B..=0x0C | 0x0E..=0x5B | 0x5D..=0x7F => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + 0x5C => { + cursor += 1; + yystate = 5; + continue 'yyl; + } + 0xC2..=0xDF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + 0xE0 => { + cursor += 1; + yystate = 15; + continue 'yyl; + } + 0xE1..=0xEC | 0xEE..=0xEF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + 0xED => { + cursor += 1; + yystate = 17; + continue 'yyl; + } + 0xF0 => { + cursor += 1; + yystate = 18; + continue 'yyl; + } + 0xF1..=0xF3 => { + cursor += 1; + yystate = 19; + continue 'yyl; + } + 0xF4 => { + cursor += 1; + yystate = 20; + continue 'yyl; + } + _ => { + yystate = 4; + continue 'yyl; + } + } + } + 6 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 7 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0xA0..=0xBF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 8 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 9 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x9F => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 10 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x90..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 11 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 12 => { + yyaccept = 1; + marker = cursor; + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x8F => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 2; + continue 'yyl; + } + } + } + 13 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 3; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 14 => { + cursor = marker; + if yyaccept == 0 { + yystate = 4; continue 'yyl; - } - } - } - 18 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) } else { - 0 - } - }; - match yych { - 0x90..=0xBF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 14; + yystate = 2; continue 'yyl; } } - } - 19 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0xBF => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 14; - continue 'yyl; + 15 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0xA0..=0xBF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 16 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 17 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x9F => { + cursor += 1; + yystate = 13; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 18 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x90..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 19 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0xBF => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } + } + } + 20 => { + yych = unsafe { + if cursor < len { + *s.get_unchecked(cursor) + } else { + 0 + } + }; + match yych { + 0x80..=0x8F => { + cursor += 1; + yystate = 16; + continue 'yyl; + } + _ => { + yystate = 14; + continue 'yyl; + } } } - } - 20 => { - yych = unsafe { - if cursor < len { - *s.get_unchecked(cursor) - } else { - 0 - } - }; - match yych { - 0x80..=0x8F => { - cursor += 1; - yystate = 16; - continue 'yyl; - } - _ => { - yystate = 14; - continue 'yyl; - } + _ => { + panic!("internal lexer error") } } - _ => { - panic!("internal lexer error") - } } } } diff --git a/src/tests.rs b/src/tests.rs index 4aa48d91..3235b257 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -20,6 +20,7 @@ mod pathological; mod plugins; mod regressions; mod shortcodes; +mod spoiler; mod strikethrough; mod superscript; mod table; diff --git a/src/tests/api.rs b/src/tests/api.rs index e1e46847..3b6a2ed1 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -53,6 +53,7 @@ fn exercise_full_api() { extension.wikilinks_title_after_pipe(true); extension.wikilinks_title_before_pipe(true); extension.underline(true); + extension.spoiler(true); let mut parse = ParseOptionsBuilder::default(); parse.smart(false); @@ -233,5 +234,9 @@ fn exercise_full_api() { let _: String = nl.url; } nodes::NodeValue::Underline => {} + nodes::NodeValue::SpoileredText => {} + nodes::NodeValue::EscapedTag(data) => { + let _: &String = data; + } } } diff --git a/src/tests/spoiler.rs b/src/tests/spoiler.rs new file mode 100644 index 00000000..542debd0 --- /dev/null +++ b/src/tests/spoiler.rs @@ -0,0 +1,62 @@ +use super::*; + +#[test] +fn spoiler() { + html_opts!( + [extension.spoiler], + concat!("The ||dog dies at the end of Marley and Me||.\n"), + concat!( + "

The dog dies at the end of Marley and Me.

\n" + ), + ); +} + +#[test] +fn spoiler_in_table() { + html_opts!( + [extension.table, extension.spoiler], + concat!("Text | Result\n--- | ---\n`||some clever text||` | ||some clever text||\n"), + concat!( + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
TextResult
||some clever text||some clever text
\n" + ), + ); +} + +#[test] +fn spoiler_regressions() { + html_opts!( + [extension.spoiler], + concat!("|should not be spoiler|\n||should be spoiler||\n|||should be spoiler surrounded by pipes|||"), + concat!( + "

|should not be spoiler|\n", + "should be spoiler\n", + "|should be spoiler surrounded by pipes|

\n" + ), + ); +} + +#[test] +fn mismatched_spoilers() { + html_opts!( + [extension.spoiler], + concat!("|||this is a spoiler with pipe in front||\n||this is not a spoiler|\n||this is a spoiler with pipe after|||"), + concat!( + "

|this is a spoiler with pipe in front\n", + "||this is not a spoiler|\n", + "this is a spoiler with pipe after|

\n" + ), + ); +} diff --git a/src/xml.rs b/src/xml.rs index aef609c5..260382b1 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -279,6 +279,10 @@ impl<'o> XmlFormatter<'o> { self.output.write_all(b"\"")?; } NodeValue::Underline => {} + NodeValue::SpoileredText => {} + NodeValue::EscapedTag(ref data) => { + self.output.write_all(data.as_bytes())?; + } } if node.first_child().is_some() { From 5a3a04d989c9b10e76a7b4070c636bb2c431ed03 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2024 14:38:37 -0400 Subject: [PATCH 3/7] Add support for greentext extension --- src/cm.rs | 2 ++ src/main.rs | 2 ++ src/parser/mod.rs | 43 ++++++++++++++++++++++++++++++++++++++++-- src/tests.rs | 1 + src/tests/greentext.rs | 37 ++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/tests/greentext.rs diff --git a/src/cm.rs b/src/cm.rs index 5ac12314..c0be4699 100644 --- a/src/cm.rs +++ b/src/cm.rs @@ -598,6 +598,8 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { && !self.options.render.hardbreaks { self.cr(); + } else if self.options.render.hardbreaks { + self.output(&[b'\n'], allow_wrap, Escaping::Literal); } else { self.output(&[b' '], allow_wrap, Escaping::Literal); } diff --git a/src/main.rs b/src/main.rs index 26543747..537717fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,6 +163,7 @@ enum Extension { WikilinksTitleBeforePipe, Underline, Spoiler, + Greentext, } #[derive(Clone, Copy, Debug, ValueEnum)] @@ -246,6 +247,7 @@ fn main() -> Result<(), Box> { .wikilinks_title_before_pipe(exts.contains(&Extension::WikilinksTitleBeforePipe)) .underline(exts.contains(&Extension::Underline)) .spoiler(exts.contains(&Extension::Spoiler)) + .greentext(exts.contains(&Extension::Greentext)) .front_matter_delimiter(cli.front_matter_delimiter); #[cfg(feature = "shortcodes")] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index fcb2cac6..6c0eced8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -485,6 +485,35 @@ pub struct ExtensionOptions { /// "

Darth Vader is Luke's father

\n"); /// ``` pub spoiler: bool, + + /// Requires at least one space after a `>` character to generate a blockquote, + /// and restarts blockquote nesting across unique lines of input + /// + /// ```md + /// >implying implications + /// + /// > one + /// > > two + /// > three + /// ``` + /// + /// ``` + /// # use comrak::{markdown_to_html, Options}; + /// let mut options = Options::default(); + /// options.extension.greentext = true; + /// + /// assert_eq!(markdown_to_html(">implying implications", &options), + /// "

>implying implications

\n"); + /// + /// assert_eq!(markdown_to_html("> one\n> > two\n> three", &options), + /// concat!( + /// "
\n", + /// "

one

\n", + /// "
\n

two

\n
\n", + /// "

three

\n", + /// "
\n")); + /// ``` + pub greentext: bool, } #[non_exhaustive] @@ -1132,6 +1161,10 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { (true, container, should_continue) } + fn is_not_greentext(&mut self, line: &[u8]) -> bool { + !self.options.extension.greentext || strings::is_space_or_tab(line[self.first_nonspace + 1]) + } + fn open_new_blocks(&mut self, container: &mut &'a AstNode<'a>, line: &[u8], all_matched: bool) { let mut matched: usize = 0; let mut nl: NodeList = NodeList::default(); @@ -1166,7 +1199,8 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { self.first_nonspace + 1, ); self.advance_offset(line, first_nonspace + matched - offset, false); - } else if !indented && line[self.first_nonspace] == b'>' { + } else if !indented && line[self.first_nonspace] == b'>' && self.is_not_greentext(line) + { let blockquote_startpos = self.first_nonspace; let offset = self.first_nonspace + 1 - self.offset; @@ -1436,7 +1470,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { fn parse_block_quote_prefix(&mut self, line: &[u8]) -> bool { let indent = self.indent; - if indent <= 3 && line[self.first_nonspace] == b'>' { + if indent <= 3 && line[self.first_nonspace] == b'>' && self.is_not_greentext(line) { self.advance_offset(line, indent + 1, true); if strings::is_space_or_tab(line[self.offset]) { @@ -1723,6 +1757,11 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { if !self.current.same_node(last_matched_container) && container.same_node(last_matched_container) && !self.blank + && (!self.options.extension.greentext + || !matches!( + container.data.borrow().value, + NodeValue::BlockQuote | NodeValue::Document + )) && node_matches!(self.current, NodeValue::Paragraph) { self.add_line(self.current, line); diff --git a/src/tests.rs b/src/tests.rs index 3235b257..1c46644f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -12,6 +12,7 @@ mod description_lists; mod escaped_char_spans; mod footnotes; mod fuzz; +mod greentext; mod header_ids; mod math; mod multiline_block_quotes; diff --git a/src/tests/greentext.rs b/src/tests/greentext.rs new file mode 100644 index 00000000..209d140f --- /dev/null +++ b/src/tests/greentext.rs @@ -0,0 +1,37 @@ +use super::*; + +#[test] +fn greentext_preserved() { + html_opts!( + [extension.greentext, render.hardbreaks], + ">implying\n>>implying", + "

>implying
\n>>implying

\n" + ); +} + +#[test] +fn separate_quotes_on_line_end() { + html_opts!( + [extension.greentext], + "> 1\n>\n> 2", + "
\n

1

\n
\n

>

\n
\n

2

\n
\n" + ); +} + +#[test] +fn unnest_quotes_on_line_end() { + html_opts!( + [extension.greentext], + "> 1\n> > 2\n> 1", + "
\n

1

\n
\n

2

\n
\n

1

\n
\n" + ); +} + +#[test] +fn unnest_quotes_on_line_end_commonmark() { + html_opts!( + [extension.greentext], + "> 1\n> > 2\n> \n> 1", + "
\n

1

\n
\n

2

\n
\n

1

\n
\n" + ); +} From b829f22365b0c5a2bd74ca358cc9726f55855aec Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 3 Jun 2024 15:39:34 -0400 Subject: [PATCH 4/7] Add ignore setext render option --- src/main.rs | 5 +++++ src/parser/mod.rs | 27 +++++++++++++++++++++++++-- src/tests/api.rs | 1 + src/tests/core.rs | 9 +++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 537717fa..c11b9b7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -134,6 +134,10 @@ struct Cli { /// Include source position attribute in HTML and XML output #[arg(long)] sourcepos: bool, + + /// Ignore setext headers + #[arg(long)] + ignore_setext: bool, } #[derive(Clone, Copy, Debug, ValueEnum)] @@ -274,6 +278,7 @@ fn main() -> Result<(), Box> { .list_style(cli.list_style.into()) .sourcepos(cli.sourcepos) .escaped_char_spans(cli.escaped_char_spans) + .ignore_setext(cli.ignore_setext) .build()?; let options = Options { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6c0eced8..dfa02374 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -16,7 +16,7 @@ use crate::nodes::{ Ast, AstNode, ListDelimType, ListType, NodeCodeBlock, NodeDescriptionItem, NodeHeading, NodeHtmlBlock, NodeList, NodeValue, }; -use crate::scanners; +use crate::scanners::{self, SetextChar}; use crate::strings::{self, split_off_front_matter, Case}; use derive_builder::Builder; use std::cell::RefCell; @@ -735,6 +735,22 @@ pub struct RenderOptions { /// "

Notify user @example

\n"); /// ``` pub escaped_char_spans: bool, + + /// Ignore setext headings in input. + /// + /// ```rust + /// # use comrak::{markdown_to_html, Options}; + /// let mut options = Options::default(); + /// let input = "setext heading\n---"; + /// + /// assert_eq!(markdown_to_html(input, &options), + /// "

setext heading

\n"); + /// + /// options.render.ignore_setext = true; + /// assert_eq!(markdown_to_html(input, &options), + /// "

setext heading

\n
\n"); + /// ``` + pub ignore_setext: bool, } #[non_exhaustive] @@ -1165,6 +1181,13 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { !self.options.extension.greentext || strings::is_space_or_tab(line[self.first_nonspace + 1]) } + fn setext_heading_line(&mut self, s: &[u8]) -> Option { + match self.options.render.ignore_setext { + false => scanners::setext_heading_line(s), + true => None, + } + } + fn open_new_blocks(&mut self, container: &mut &'a AstNode<'a>, line: &[u8], all_matched: bool) { let mut matched: usize = 0; let mut nl: NodeList = NodeList::default(); @@ -1287,7 +1310,7 @@ impl<'a, 'o, 'c> Parser<'a, 'o, 'c> { } else if !indented && node_matches!(container, NodeValue::Paragraph) && unwrap_into( - scanners::setext_heading_line(&line[self.first_nonspace..]), + self.setext_heading_line(&line[self.first_nonspace..]), &mut sc, ) { diff --git a/src/tests/api.rs b/src/tests/api.rs index 3b6a2ed1..c7d7b526 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -71,6 +71,7 @@ fn exercise_full_api() { render.list_style(ListStyleType::Dash); render.sourcepos(false); render.escaped_char_spans(false); + render.ignore_setext(true); pub struct MockAdapter {} impl SyntaxHighlighterAdapter for MockAdapter { diff --git a/src/tests/core.rs b/src/tests/core.rs index 81bd231c..30061490 100644 --- a/src/tests/core.rs +++ b/src/tests/core.rs @@ -119,6 +119,15 @@ fn setext_heading_sourcepos() { ); } +#[test] +fn ignore_setext_heading() { + html_opts!( + [render.ignore_setext], + concat!("text text\n---"), + concat!("

text text

\n
\n"), + ); +} + #[test] fn html_block_1() { html_opts!( From 23ed7636d83bee4c2251046598738cc3a5001cfd Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 3 Jun 2024 15:47:26 -0400 Subject: [PATCH 5/7] Avoid generating links with empty inlines --- src/main.rs | 5 +++++ src/parser/inlines.rs | 29 +++++++++++++++++++++++++++-- src/parser/mod.rs | 15 +++++++++++++++ src/tests.rs | 1 + src/tests/api.rs | 1 + src/tests/empty.rs | 34 ++++++++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/tests/empty.rs diff --git a/src/main.rs b/src/main.rs index c11b9b7a..af2a550f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -138,6 +138,10 @@ struct Cli { /// Ignore setext headers #[arg(long)] ignore_setext: bool, + + /// Ignore empty links + #[arg(long)] + ignore_empty_links: bool, } #[derive(Clone, Copy, Debug, ValueEnum)] @@ -279,6 +283,7 @@ fn main() -> Result<(), Box> { .sourcepos(cli.sourcepos) .escaped_char_spans(cli.escaped_char_spans) .ignore_setext(cli.ignore_setext) + .ignore_empty_links(cli.ignore_empty_links) .build()?; let options = Options { diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs index eca80704..78dc4c28 100644 --- a/src/parser/inlines.rs +++ b/src/parser/inlines.rs @@ -9,8 +9,7 @@ use crate::nodes::{ use crate::parser::shortcodes::NodeShortCode; use crate::parser::{unwrap_into_2, unwrap_into_copy, AutolinkType, Callback, Options, Reference}; use crate::scanners; -use crate::strings; -use crate::strings::Case; +use crate::strings::{self, is_blank, Case}; use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::convert::TryFrom; @@ -1345,6 +1344,32 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { )); } + // Ensure there was text if this was a link and not an image link + if self.options.render.ignore_empty_links && !is_image { + let mut non_blank_found = false; + let mut tmpch = self.brackets[brackets_len - 1].inl_text.next_sibling(); + while let Some(tmp) = tmpch { + match tmp.data.borrow().value { + NodeValue::Text(ref s) if is_blank(s.as_bytes()) => (), + _ => { + non_blank_found = true; + break; + } + } + + tmpch = tmp.next_sibling(); + } + + if !non_blank_found { + self.brackets.pop(); + return Some(self.make_inline( + NodeValue::Text("]".to_string()), + self.pos - 1, + self.pos - 1, + )); + } + } + let after_link_text_pos = self.pos; // Try to find a link destination within parenthesis diff --git a/src/parser/mod.rs b/src/parser/mod.rs index dfa02374..09c55b0f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -751,6 +751,21 @@ pub struct RenderOptions { /// "

setext heading

\n
\n"); /// ``` pub ignore_setext: bool, + + /// Ignore empty links in input. + /// + /// ```rust + /// # use comrak::{markdown_to_html, Options}; + /// let mut options = Options::default(); + /// let input = "[]()"; + /// + /// assert_eq!(markdown_to_html(input, &options), + /// "

\n"); + /// + /// options.render.ignore_empty_links = true; + /// assert_eq!(markdown_to_html(input, &options), "

[]()

\n"); + /// ``` + pub ignore_empty_links: bool, } #[non_exhaustive] diff --git a/src/tests.rs b/src/tests.rs index 1c46644f..63296cca 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -9,6 +9,7 @@ mod autolink; mod commonmark; mod core; mod description_lists; +mod empty; mod escaped_char_spans; mod footnotes; mod fuzz; diff --git a/src/tests/api.rs b/src/tests/api.rs index c7d7b526..cbdb2906 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -72,6 +72,7 @@ fn exercise_full_api() { render.sourcepos(false); render.escaped_char_spans(false); render.ignore_setext(true); + render.ignore_empty_links(true); pub struct MockAdapter {} impl SyntaxHighlighterAdapter for MockAdapter { diff --git a/src/tests/empty.rs b/src/tests/empty.rs new file mode 100644 index 00000000..f77c3737 --- /dev/null +++ b/src/tests/empty.rs @@ -0,0 +1,34 @@ +use super::*; + +#[test] +fn no_empty_link() { + html_opts!( + [render.ignore_empty_links], + "[](https://example.com/evil-link-for-seo-spam)", + "

[](https://example.com/evil-link-for-seo-spam)

\n", + ); + + html_opts!( + [render.ignore_empty_links], + "[ ](https://example.com/evil-link-for-seo-spam)", + "

[ ](https://example.com/evil-link-for-seo-spam)

\n", + ); +} + +#[test] +fn empty_image_allowed() { + html_opts!( + [render.ignore_empty_links], + "![ ](https://example.com/evil-link-for-seo-spam)", + "

\"

\n", + ); +} + +#[test] +fn image_inside_link_allowed() { + html_opts!( + [render.ignore_empty_links], + "[![](https://example.com/image.png)](https://example.com/)", + "

\"\"

\n", + ); +} From f4f2e48e585b4bd338f0ac306ab2b29841016a67 Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 4 Jun 2024 13:11:37 +0300 Subject: [PATCH 6/7] fuzz: all_options: add new options/extensions. --- fuzz/fuzz_targets/all_options.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fuzz/fuzz_targets/all_options.rs b/fuzz/fuzz_targets/all_options.rs index af9ed045..22e8f40d 100644 --- a/fuzz/fuzz_targets/all_options.rs +++ b/fuzz/fuzz_targets/all_options.rs @@ -25,6 +25,9 @@ fuzz_target!(|s: &str| { extension.shortcodes = true; extension.wikilinks_title_after_pipe = true; extension.wikilinks_title_before_pipe = true; + extension.underline = true; + extension.spoiler = true; + extension.greentext = true; let mut parse = ParseOptions::default(); parse.smart = true; @@ -42,6 +45,8 @@ fuzz_target!(|s: &str| { render.list_style = ListStyleType::Star; render.sourcepos = true; render.escaped_char_spans = true; + render.ignore_setext = true; + render.ignore_empty_links = true; markdown_to_html( s, From 01e7e0bc7beb3c1ccecd7bb0d91f953f620b3e7d Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Tue, 4 Jun 2024 15:01:37 +0300 Subject: [PATCH 7/7] fuzz: Cargo.lock updates. --- fuzz/Cargo.lock | 99 ++++++++++++++----------------------------------- 1 file changed, 27 insertions(+), 72 deletions(-) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 763257b7..58aca423 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -158,7 +158,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] @@ -171,7 +171,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] @@ -188,14 +188,13 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "comrak" -version = "0.23.0" +version = "0.24.1" dependencies = [ "arbitrary", "clap", "derive_builder", "emojis", "entities", - "in-place", "memchr", "once_cell", "regex", @@ -227,9 +226,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -237,27 +236,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 1.0.109", + "strsim", + "syn", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -277,38 +276,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] name = "derive_builder" -version = "0.12.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.12.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "derive_builder_macro" -version = "0.12.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 1.0.109", + "syn", ] [[package]] @@ -319,9 +318,9 @@ checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e" [[package]] name = "emojis" -version = "0.5.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3407bc749191827d456a282321770847daf4b0a1128fde02597a8ed2e987b95d" +checksum = "9f619a926616ae7149a0d82610b051134a0d6c4ae2962d990c06c847a445c5d9" dependencies = [ "phf", ] @@ -358,12 +357,6 @@ dependencies = [ "regex", ] -[[package]] -name = "fastrand" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" - [[package]] name = "flate2" version = "1.0.30" @@ -398,15 +391,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "in-place" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb69f3adfd5f493210cece799f4620417bf9965bc1536c22ae0e29ba27a8c0" -dependencies = [ - "tempfile", -] - [[package]] name = "indexmap" version = "2.2.6" @@ -667,7 +651,7 @@ checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] @@ -703,29 +687,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.60" @@ -760,18 +727,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "terminal_size" version = "0.3.0" @@ -799,7 +754,7 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] @@ -888,7 +843,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn", "wasm-bindgen-shared", ] @@ -910,7 +865,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ]