diff --git a/src/fmt_json.rs b/src/fmt_json.rs index 771abf1..54e5348 100644 --- a/src/fmt_json.rs +++ b/src/fmt_json.rs @@ -121,10 +121,17 @@ impl TextOnly { I: Borrow, { let mut str = String::new(); + Self::build_strings(&mut str, line); + str + } + + fn build_strings(out: &mut String, line: &[I]) + where + I: Borrow, + { for i in line { - Self::build_string(&mut str, i.borrow()) + Self::build_string(out, i.borrow()) } - str } fn build_string(out: &mut String, elem: &Inline) { @@ -151,6 +158,9 @@ impl TextOnly { } out.push('>'); } + Inline::Footnote { .. } => { + // ignore + } } } } diff --git a/src/fmt_md.rs b/src/fmt_md.rs index 486e856..b5b7869 100644 --- a/src/fmt_md.rs +++ b/src/fmt_md.rs @@ -249,6 +249,11 @@ where out.write_char('!'); write_link_inline(out, link, |out| out.write_str(alt)); } + Inline::Footnote { label, .. } => { + out.write_str("[^"); + out.write_str(label); + out.write_char(']'); + } } } diff --git a/src/select.rs b/src/select.rs index 327d943..64343b5 100644 --- a/src/select.rs +++ b/src/select.rs @@ -125,6 +125,7 @@ impl Selector { Inline::Image { .. } => { false // TODO } + Inline::Footnote { .. } => false, }; if matched { SelectResult::Found(vec![node]) diff --git a/src/tree.rs b/src/tree.rs index ad53ccd..69ee97a 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -45,7 +45,7 @@ pub enum MdqNode { } /// See https://github.github.com/gfm/#link-reference-definitions -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum LinkReference { Inline, Full(String), @@ -115,6 +115,10 @@ pub enum Inline { alt: String, link: Link, }, + Footnote { + label: String, + text: Vec, + }, } #[derive(Debug, PartialEq)] @@ -185,6 +189,18 @@ impl TryFrom for MdqNode { } } +/// Defines all the mdx nodes as match arms. This let us easily mark them as TODOs, and in particular makes it so that +/// the prod and test code both marks them as TODOs using the same source list (namely, this macro). +macro_rules! mdx_nodes { + {} => { + Node::MdxJsxFlowElement(_) + | Node::MdxjsEsm(_) + | Node::MdxTextExpression(_) + | Node::MdxJsxTextElement(_) + | Node::MdxFlowExpression(_) + }; +} + impl MdqNode { fn read(node: Node, opts: &ReadOptions) -> Result { let lookups = Lookups::new(&node, opts)?; @@ -265,8 +281,12 @@ impl MdqNode { text: MdqNode::inlines(node.children, lookups)?, link: lookups.resolve_link(node.identifier, node.label, node.reference_kind)?, }), - Node::FootnoteReference(_) => { - todo!() + Node::FootnoteReference(node) => { + let definition = lookups.resolve_footnote(&node.identifier, &node.label)?; + MdqNode::Inline(Inline::Footnote { + label: node.label.unwrap_or(node.identifier), + text: MdqNode::all(definition.children.clone(), lookups)?, + }) } Node::Strong(node) => MdqNode::Inline(Inline::Span { variant: SpanVariant::Strong, @@ -347,11 +367,11 @@ impl MdqNode { value: node.value, }), - Node::MdxJsxFlowElement(_) - | Node::MdxjsEsm(_) - | Node::MdxTextExpression(_) - | Node::MdxJsxTextElement(_) - | Node::MdxFlowExpression(_) => return Err(NoNode::Invalid(InvalidMd::Unsupported(node))), + mdx_nodes! {} => { + // If you implement this, make sure to remove the mdx_nodes macro. That means you'll also need to + // adjust the test `nodes_matcher` macro. + return Err(NoNode::Invalid(InvalidMd::Unsupported(node))); + } }; Ok(result) } @@ -564,6 +584,19 @@ impl Lookups { }) } + fn resolve_footnote(&self, identifier: &String, label: &Option) -> Result<&FootnoteDefinition, NoNode> { + if label.is_none() { + todo!("What is this case???"); + } + let Some(definition) = self.footnote_definitions.get(identifier) else { + let human_visible_identifier = label.to_owned().unwrap_or_else(|| identifier.to_string()); + return Err(NoNode::Invalid(InvalidMd::MissingReferenceDefinition( + human_visible_identifier, + ))); + }; + Ok(definition) + } + fn build_lookups(&mut self, node: &Node, read_opts: &ReadOptions) -> Result<(), InvalidMd> { let x = format!("{:?}", node); let _ = x; @@ -677,6 +710,11 @@ mod tests { $( Node::$variant(_) => {} )* + mdx_nodes!{} => { + // If you implement mdx nodes, you should also remove the mdx_nodes macro. That will + // (correctly) break this macro. You should add those MDX arms to the get_mdast_node_names + // function, to ensure that we have tests for them. + } }); vec![$(stringify!($variant).to_string(),)*].into_iter().collect() } @@ -698,21 +736,56 @@ mod tests { }); } - #[ignore] // TODO un-ignore #[test] fn footnote() { - let (root, lookups) = parse_with( - &ParseOptions::gfm(), - indoc! {r#" - Cool story [^a] + { + let (root, lookups) = parse_with( + &ParseOptions::gfm(), + indoc! {r#" + Cool story [^a]! - [^a]: My _footnote_ - with two lines."#}, - ); - check!(&root.children[0], Node::Paragraph(_), lookups => MdqNode::Paragraph{ body } = { - assert_eq!(format!("{:?}", body), "TOOD") - }); - check!(no_node: &root.children[1], Node::FootnoteDefinition(_), lookups => NoNode::Skipped); + [^a]: My footnote + with two lines."#}, + ); + unwrap!(&root.children[0], Node::Paragraph(p)); + check!(&p.children[1], Node::FootnoteReference(_), lookups => MdqNode::Inline(footnote) = { + assert_eq!(footnote, Inline::Footnote{ + label: "a".to_string(), + text: vec![ + text_paragraph("My footnote\nwith two lines.") + ], + }) + }); + check!(no_node: &root.children[1], Node::FootnoteDefinition(_), lookups => NoNode::Skipped); + } + { + let (root, lookups) = parse_with( + &ParseOptions::gfm(), + indoc! {r#" + Cool story [^a]! + + [^a]: - footnote is a list"#}, + ); + unwrap!(&root.children[0], Node::Paragraph(p)); + + check!(&p.children[1], Node::FootnoteReference(_), lookups => MdqNode::Inline(footnote) = { + assert_eq!(footnote, Inline::Footnote{ + label: "a".to_string(), + text: vec![ + MdqNode::List { + starting_index: None, + items: vec![ + ListItem{ + checked: None, + item: vec![text_paragraph("footnote is a list")], + } + ], + }, + ], + }) + }); + check!(no_node: &root.children[1], Node::FootnoteDefinition(_), lookups => NoNode::Skipped); + } } #[test] @@ -1421,7 +1494,6 @@ mod tests { }); } - #[ignore] // TODO un-ignore #[test] fn all_variants_tested() { let timeout = time::Duration::from_millis(500); @@ -1465,7 +1537,7 @@ mod tests { impl NodesChecker { fn new() -> Self { Self { - require: Self::count_node_enums(), + require: Self::get_mdast_node_names(), } } @@ -1499,14 +1571,12 @@ mod tests { /// /// This isn't 100% fool-proof (it requires manually ensuring that each variant is on its own line, though /// `cargo fmt` helps with that), but it should be good enough in practice. - fn count_node_enums() -> Arc>> { + fn get_mdast_node_names() -> Arc>> { let all_node_names = nodes_matcher![ Root, BlockQuote, FootnoteDefinition, - MdxJsxFlowElement, List, - MdxjsEsm, Toml, Yaml, Break, @@ -1514,19 +1584,16 @@ mod tests { InlineMath, Delete, Emphasis, - MdxTextExpression, FootnoteReference, Html, Image, ImageReference, - MdxJsxTextElement, Link, LinkReference, Strong, Text, Code, Math, - MdxFlowExpression, Heading, Table, ThematicBreak,