From 5b63c0e51a5d37ee63d6c0a76cd1b5a95ed0de1b Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Sun, 25 Aug 2024 15:52:29 -0400 Subject: [PATCH 1/8] add tests of footnotes of footnotes --- tests/md_cases/footnotes_in_footnotes.toml | 48 ++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/md_cases/footnotes_in_footnotes.toml diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml new file mode 100644 index 0000000..711bf51 --- /dev/null +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -0,0 +1,48 @@ +[given] +md = ''' +- AAA: [footnote [^1] in a link](https://example.com) +- BBB: footnote contains footnote[^2] +- CCC: footnote contains link[^3] + +[^1]: the link's footnote text +[^2]: this footnote contains[^a] a footnote +[^3]: this footnote contains a [link][3a] +[^a]: this is the footnote's footnote + +[3a]: https://example.com/3a +''' + +[chained] +needed = false + + +[expect."just footnote in link"] +cli_args = ['- AAA'] +output = ''' +- AAA: [footnote [^1] in a link][1] + +[1]: https://example.com +[^1]: the link's footnote text +''' + + +[expect."just footnote contains footnote"] +cli_args = ['- BBB'] +output = ''' +- BBB: footnote contains footnote[^1] + +[^1]: this footnote contains[^2] a footnote + +[^2]: this is the footnote's footnote +''' + + +[expect."just footnote contains link"] +cli_args = ['- CCC'] +output = ''' +- CCC: footnote contains link[^1] + +[^1]: this footnote contains a [link][3a] + +[3a]: https://example.com/3a +''' From c6ece475f535a52ad40c9ec2664df59a1714bcfd Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Sun, 25 Aug 2024 16:03:21 -0400 Subject: [PATCH 2/8] fill in tests --- tests/md_cases/footnotes_in_footnotes.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index 711bf51..c207c09 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -25,6 +25,15 @@ output = ''' [^1]: the link's footnote text ''' +[expect."just footnote in link: json"] +cli_args = ['- AAA', '--output', 'json'] +output = ''' +- AAA: [footnote [^1] in a link][1] + +[1]: https://example.com +[^1]: the link's footnote text +''' + [expect."just footnote contains footnote"] cli_args = ['- BBB'] From 5111c5b5d76cec323461afdefe1384c45e2a73d1 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Sun, 25 Aug 2024 19:37:34 -0400 Subject: [PATCH 3/8] more tests of footnotes in links and footnotes --- tests/integ_test.rs | 17 ++-- tests/md_cases/footnotes_in_footnotes.toml | 91 ++++++++++++++++++++-- 2 files changed, 95 insertions(+), 13 deletions(-) diff --git a/tests/integ_test.rs b/tests/integ_test.rs index 145d797..055902d 100644 --- a/tests/integ_test.rs +++ b/tests/integ_test.rs @@ -14,14 +14,17 @@ impl Case { let all_cli_args = ["cmd"].iter().chain(&self.cli_args); let cli = mdq::cli::Cli::try_parse_from(all_cli_args).unwrap(); let (actual_success, actual_out) = mdq::run_in_memory(&cli, self.md); - if self.expect_output_json { - assert_eq!( - serde_json::from_str::(&actual_out).unwrap(), - serde_json::from_str::(self.expect_output).unwrap() - ); + let (actual_out, expect_out) = if self.expect_output_json { + let actual_obj = serde_json::from_str::(&actual_out).unwrap(); + let expect_obj = serde_json::from_str::(self.expect_output).unwrap(); + ( + serde_json::to_string_pretty(&actual_obj).unwrap(), + serde_json::to_string_pretty(&expect_obj).unwrap(), + ) } else { - assert_eq!(actual_out, self.expect_output); - } + (actual_out, self.expect_output.to_string()) + }; + assert_eq!(actual_out, expect_out); assert_eq!(actual_success, self.expect_success); } } diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index c207c09..741488c 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -16,7 +16,7 @@ md = ''' needed = false -[expect."just footnote in link"] +[expect."just link contains footnote"] cli_args = ['- AAA'] output = ''' - AAA: [footnote [^1] in a link][1] @@ -25,13 +25,34 @@ output = ''' [^1]: the link's footnote text ''' -[expect."just footnote in link: json"] +[expect."just link contains footnote: json"] cli_args = ['- AAA', '--output', 'json'] output = ''' -- AAA: [footnote [^1] in a link][1] - -[1]: https://example.com -[^1]: the link's footnote text +{ + "items": [ + { + "list_item": { + "item": [ + { + "paragraph": "AAA: [footnote [^1] in a link][1]" + } + ] + } + } + ], + "links": { + "1": { + "url": "https://example.com" + } + }, + "footnotes": { + "1": [ + { + "paragraph": "the link's footnote text" + } + ] + } +} ''' @@ -46,6 +67,32 @@ output = ''' ''' +[expect."just footnote contains footnote: json"] +cli_args = ['- BBB | P: *', '--output', 'json'] +output_json = true +output = ''' +{ + "items": [ + { + "paragraph": "BBB: footnote contains footnote[^1]" + } + ], + "footnotes": { + "1": [ + { + "paragraph": "this footnote contains[^2] a footnote" + } + ], + "2": [ + { + "paragraph": "this is the footnote's footnote" + } + ] + } +} +''' + + [expect."just footnote contains link"] cli_args = ['- CCC'] output = ''' @@ -55,3 +102,35 @@ output = ''' [3a]: https://example.com/3a ''' + + +[expect."just footnote contains link: json"] +cli_args = ['- CCC', '--output', 'json'] +output_json = true +output = ''' +{ + "items": [ + { + "list_item": { + "item": [ + { + "paragraph": "CCC: footnote contains link[^1]" + } + ] + } + } + ], + "footnotes": { + "1": [ + { + "paragraph": "this footnote contains a [link][3a]" + } + ] + }, + "links": { + "3a": { + "url": "https://example.com/3a" + } + } +} +''' From f2b0e39d182f1f0ca59b3fa441a9709f5f1b6b23 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Sun, 25 Aug 2024 20:17:26 -0400 Subject: [PATCH 4/8] rm test of footnote in link --- tests/md_cases/footnotes_in_footnotes.toml | 52 ++-------------------- 1 file changed, 3 insertions(+), 49 deletions(-) diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index 741488c..f00db55 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -1,6 +1,6 @@ [given] md = ''' -- AAA: [footnote [^1] in a link](https://example.com) +- AAA: (footnotes in links don't work: see https://gist.github.com/yshavit/6af0a784e338dc32e66717aa6f495ffe ) - BBB: footnote contains footnote[^2] - CCC: footnote contains link[^3] @@ -16,46 +16,6 @@ md = ''' needed = false -[expect."just link contains footnote"] -cli_args = ['- AAA'] -output = ''' -- AAA: [footnote [^1] in a link][1] - -[1]: https://example.com -[^1]: the link's footnote text -''' - -[expect."just link contains footnote: json"] -cli_args = ['- AAA', '--output', 'json'] -output = ''' -{ - "items": [ - { - "list_item": { - "item": [ - { - "paragraph": "AAA: [footnote [^1] in a link][1]" - } - ] - } - } - ], - "links": { - "1": { - "url": "https://example.com" - } - }, - "footnotes": { - "1": [ - { - "paragraph": "the link's footnote text" - } - ] - } -} -''' - - [expect."just footnote contains footnote"] cli_args = ['- BBB'] output = ''' @@ -105,19 +65,13 @@ output = ''' [expect."just footnote contains link: json"] -cli_args = ['- CCC', '--output', 'json'] +cli_args = ['- CCC | P:* ', '--output', 'json'] output_json = true output = ''' { "items": [ { - "list_item": { - "item": [ - { - "paragraph": "CCC: footnote contains link[^1]" - } - ] - } + "paragraph": "CCC: footnote contains link[^1]" } ], "footnotes": { From f5781d1d3290da1062bd4382fdf1adf8ae8858c8 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Mon, 26 Aug 2024 00:34:42 -0400 Subject: [PATCH 5/8] refactor --- src/fmt_md_inlines.rs | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/fmt_md_inlines.rs b/src/fmt_md_inlines.rs index 2cf5aeb..f4425f4 100644 --- a/src/fmt_md_inlines.rs +++ b/src/fmt_md_inlines.rs @@ -170,13 +170,17 @@ impl<'md> MdInlinesWriter<'md> { out.write_str("[^"); self.footnote_transformer.write(out, label); out.write_char(']'); - if self.seen_footnotes.insert(label) { - self.pending_references.footnotes.insert(label, text); - } + self.add_footnote(label, text); } } } + fn add_footnote(&mut self, label: &'md String, text: &'md Vec) { + if self.seen_footnotes.insert(label) { + self.pending_references.footnotes.insert(label, text); + } + } + /// Writes the inline portion of the link, which may be the full link if it was originally inlined. /// /// Examples: @@ -242,16 +246,20 @@ impl<'md> MdInlinesWriter<'md> { }; if let Some(reference_label) = reference_to_add { - if self.seen_links.insert(reference_label.clone()) { - self.pending_references.links.insert( - reference_label, - UrlAndTitle { - url: &link.url, - title: &link.title, - }, - ); - // else warn? - } + self.add_link_reference(reference_label, link); + } + } + + fn add_link_reference(&mut self, reference_label: LinkLabel<'md>, link: &'md LinkDefinition) { + if self.seen_links.insert(reference_label.clone()) { + self.pending_references.links.insert( + reference_label, + UrlAndTitle { + url: &link.url, + title: &link.title, + }, + ); + // else warn? } } From c00176cd98fa6f6857baa43bdd77f13c5dd7ec43 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Mon, 26 Aug 2024 01:06:02 -0400 Subject: [PATCH 6/8] recurse into footnote text Find other footnotes and link references. --- src/fmt_md_inlines.rs | 68 ++++++++++++++++++++++ tests/md_cases/footnotes_in_footnotes.toml | 6 +- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/fmt_md_inlines.rs b/src/fmt_md_inlines.rs index f4425f4..6bba74f 100644 --- a/src/fmt_md_inlines.rs +++ b/src/fmt_md_inlines.rs @@ -179,6 +179,74 @@ impl<'md> MdInlinesWriter<'md> { if self.seen_footnotes.insert(label) { self.pending_references.footnotes.insert(label, text); } + self.find_references_in_footnote_elems(text); + } + + /// Searches the footnote's text to find any link references and additional footnotes. + /// Otherwise, by the time we see them it'll be too late to add them to their respective + /// collections. + fn find_references_in_footnote_elems(&mut self, text: &'md Vec) { + for elem in text { + match elem { + MdElem::BlockQuote(block) => { + self.find_references_in_footnote_elems(&block.body); + } + MdElem::List(list) => { + for li in &list.items { + self.find_references_in_footnote_elems(&li.item); + } + } + MdElem::Section(section) => { + self.find_references_in_footnote_inlines(§ion.title); + self.find_references_in_footnote_elems(§ion.body); + } + MdElem::Paragraph(para) => { + self.find_references_in_footnote_inlines(¶.body); + } + MdElem::Table(table) => { + for row in &table.rows { + for cell in row { + self.find_references_in_footnote_inlines(cell); + } + } + } + MdElem::Inline(inline) => { + self.find_references_in_footnote_inlines([inline]); // TODO do I need the array? + } + MdElem::CodeBlock(_) | MdElem::Html(_) | MdElem::ThematicBreak => { + // nothing + } + } + } + } + + fn find_references_in_footnote_inlines(&mut self, text: I) + where + I: IntoIterator, + { + for inline in text.into_iter() { + match inline { + Inline::Footnote(footnote) => { + self.add_footnote(&footnote.label, &footnote.text); + } + Inline::Formatting(item) => { + self.find_references_in_footnote_inlines(&item.children); + } + Inline::Link(link) => { + let link_label = match &link.link_definition.reference { + LinkReference::Inline => None, + LinkReference::Full(reference) => Some(LinkLabel::Text(Cow::Borrowed(reference))), + LinkReference::Collapsed | LinkReference::Shortcut => Some(LinkLabel::Inline(&link.text)), + }; + if let Some(label) = link_label { + self.add_link_reference(label, &link.link_definition); + } + } + Inline::Image(_) | Inline::Text(_) => { + // nothing + } + } + } } /// Writes the inline portion of the link, which may be the full link if it was originally inlined. diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index f00db55..e4f6881 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -22,7 +22,6 @@ output = ''' - BBB: footnote contains footnote[^1] [^1]: this footnote contains[^2] a footnote - [^2]: this is the footnote's footnote ''' @@ -58,14 +57,13 @@ cli_args = ['- CCC'] output = ''' - CCC: footnote contains link[^1] -[^1]: this footnote contains a [link][3a] - [3a]: https://example.com/3a +[^1]: this footnote contains a [link][3a] ''' [expect."just footnote contains link: json"] -cli_args = ['- CCC | P:* ', '--output', 'json'] +cli_args = ['- CCC | P: *', '--output', 'json'] output_json = true output = ''' { From 62dae9ed466d4ab85195db42c5596ddcf43ac8d3 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Mon, 26 Aug 2024 01:21:59 -0400 Subject: [PATCH 7/8] add test for cycles, but ignored because of #188 --- src/fmt_md_inlines.rs | 2 +- tests/md_cases/footnotes_in_footnotes.toml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/fmt_md_inlines.rs b/src/fmt_md_inlines.rs index 6bba74f..99aa19b 100644 --- a/src/fmt_md_inlines.rs +++ b/src/fmt_md_inlines.rs @@ -178,8 +178,8 @@ impl<'md> MdInlinesWriter<'md> { fn add_footnote(&mut self, label: &'md String, text: &'md Vec) { if self.seen_footnotes.insert(label) { self.pending_references.footnotes.insert(label, text); + self.find_references_in_footnote_elems(text); } - self.find_references_in_footnote_elems(text); } /// Searches the footnote's text to find any link references and additional footnotes. diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index e4f6881..9452be3 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -86,3 +86,19 @@ output = ''' } } ''' + + +[expect."cyclic reference does't cause infinite loop"] +ignored = true # #188 +# When ready to add this back in, add the following to the input markdown: +# +# - DDD: footnote contains cycle[^cycle] +# +# [^cycle]: this footnote references itself[^cycle] +# +cli_args = ['- DDD | P: *'] +output = ''' +- DDD: footnote contains cycle[^cycle] + +[^cycle]: this footnote references itself[^cycle] +''' From 43c91035532a00c50b24249b68197c4887bab420 Mon Sep 17 00:00:00 2001 From: Yuval Shavit Date: Mon, 26 Aug 2024 01:27:27 -0400 Subject: [PATCH 8/8] fix ignore syntax --- tests/md_cases/footnotes_in_footnotes.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/md_cases/footnotes_in_footnotes.toml b/tests/md_cases/footnotes_in_footnotes.toml index 9452be3..69002d4 100644 --- a/tests/md_cases/footnotes_in_footnotes.toml +++ b/tests/md_cases/footnotes_in_footnotes.toml @@ -89,7 +89,7 @@ output = ''' [expect."cyclic reference does't cause infinite loop"] -ignored = true # #188 +ignore = '#188' # When ready to add this back in, add the following to the input markdown: # # - DDD: footnote contains cycle[^cycle]