diff --git a/examples/headers.rs b/examples/headers.rs index 2d30ab48..fa40fc7c 100644 --- a/examples/headers.rs +++ b/examples/headers.rs @@ -3,7 +3,7 @@ extern crate comrak; use comrak::{ - nodes::{AstNode, NodeValue}, + nodes::{AstNode, NodeCode, NodeValue}, parse_document, Arena, ComrakOptions, }; @@ -40,7 +40,7 @@ fn get_document_title(document: &str) -> String { fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut Vec) { match node.data.borrow().value { - NodeValue::Text(ref literal) | NodeValue::Code(ref literal) => { + NodeValue::Text(ref literal) | NodeValue::Code(NodeCode { ref literal, .. }) => { output.extend_from_slice(literal) } NodeValue::LineBreak | NodeValue::SoftBreak => output.push(b' '), diff --git a/examples/s-expr.rs b/examples/s-expr.rs index cd76e844..3cca0ee2 100644 --- a/examples/s-expr.rs +++ b/examples/s-expr.rs @@ -44,11 +44,19 @@ fn iter_nodes<'a, W: Write>( match &node.data.borrow().value { Text(t) => write!(writer, "{:?}", String::from_utf8_lossy(&t))?, value => { - try_node_inline!(value, Code); try_node_inline!(value, FootnoteDefinition); try_node_inline!(value, FootnoteReference); try_node_inline!(value, HtmlInline); + if let Code(code) = value { + return write!( + writer, + "Code({:?}, {})", + String::from_utf8_lossy(&code.literal), + code.num_backticks + ); + } + let has_blocks = node.children().any(|c| c.data.borrow().value.block()); write!(writer, "({:?}", value)?; diff --git a/src/cm.rs b/src/cm.rs index 4407ed9b..b7d1f24a 100644 --- a/src/cm.rs +++ b/src/cm.rs @@ -313,7 +313,7 @@ impl<'a, 'o> CommonMarkFormatter<'a, 'o> { NodeValue::Text(ref literal) => self.format_text(literal, allow_wrap, entering), NodeValue::LineBreak => self.format_line_break(entering), NodeValue::SoftBreak => self.format_soft_break(allow_wrap, entering), - NodeValue::Code(ref literal) => self.format_code(literal, allow_wrap, entering), + NodeValue::Code(ref code) => self.format_code(&code.literal, allow_wrap, entering), NodeValue::HtmlInline(ref literal) => self.format_html_inline(literal, entering), NodeValue::Strong => self.format_strong(), NodeValue::Emph => self.format_emph(node), diff --git a/src/html.rs b/src/html.rs index 94d52f1b..0e4017bd 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,5 +1,5 @@ use ctype::isspace; -use nodes::{AstNode, ListType, NodeValue, TableAlignment}; +use nodes::{AstNode, ListType, NodeCode, NodeValue, TableAlignment}; use parser::ComrakOptions; use regex::Regex; use scanners; @@ -338,7 +338,7 @@ impl<'o> HtmlFormatter<'o> { if plain { match node.data.borrow().value { NodeValue::Text(ref literal) - | NodeValue::Code(ref literal) + | NodeValue::Code(NodeCode { ref literal, .. }) | NodeValue::HtmlInline(ref literal) => { self.escape(literal)?; } @@ -369,7 +369,7 @@ impl<'o> HtmlFormatter<'o> { fn collect_text<'a>(&self, node: &'a AstNode<'a>, output: &mut Vec) { match node.data.borrow().value { - NodeValue::Text(ref literal) | NodeValue::Code(ref literal) => { + NodeValue::Text(ref literal) | NodeValue::Code(NodeCode { ref literal, .. }) => { output.extend_from_slice(literal) } NodeValue::LineBreak | NodeValue::SoftBreak => output.push(b' '), @@ -563,7 +563,7 @@ impl<'o> HtmlFormatter<'o> { } } } - NodeValue::Code(ref literal) => { + NodeValue::Code(NodeCode { ref literal, .. }) => { if entering { self.output.write_all(b"")?; self.escape(literal)?; diff --git a/src/nodes.rs b/src/nodes.rs index 5eb39e70..d4703492 100644 --- a/src/nodes.rs +++ b/src/nodes.rs @@ -114,7 +114,7 @@ pub enum NodeValue { LineBreak, /// **Inline**. A [code span](https://github.github.com/gfm/#code-spans). - Code(Vec), + Code(NodeCode), /// **Inline**. [Raw HTML](https://github.github.com/gfm/#raw-html) contained inline. HtmlInline(Vec), @@ -160,6 +160,19 @@ pub enum TableAlignment { Right, } +/// An inline [code span](https://github.github.com/gfm/#code-spans). +#[derive(Debug, Clone)] +pub struct NodeCode { + /// The URL for the link destination or image source. + pub num_backticks: usize, + + /// The content of the inline code span. + /// As the contents are not interpreted as Markdown at all, + /// they are contained within this structure, + /// rather than inserted into a child inline of any kind. + pub literal: Vec, +} + /// The details of a link's destination, or an image's source. #[derive(Debug, Clone)] pub struct NodeLink { diff --git a/src/parser/inlines.rs b/src/parser/inlines.rs index 20ad3ead..16c09731 100644 --- a/src/parser/inlines.rs +++ b/src/parser/inlines.rs @@ -1,7 +1,7 @@ use arena_tree::Node; use ctype::{ispunct, isspace}; use entity; -use nodes::{Ast, AstNode, NodeLink, NodeValue}; +use nodes::{Ast, AstNode, NodeCode, NodeLink, NodeValue}; use parser::{unwrap_into_2, unwrap_into_copy, AutolinkType, Callback, ComrakOptions, Reference}; use scanners; use std::cell::{Cell, RefCell}; @@ -518,7 +518,11 @@ impl<'a, 'r, 'o, 'd, 'i, 'c, 'subj> Subject<'a, 'r, 'o, 'd, 'i, 'c, 'subj> { Some(endpos) => { let buf = &self.input[startpos..endpos - openticks]; let buf = strings::normalize_code(buf); - make_inline(self.arena, NodeValue::Code(buf)) + let code = NodeCode { + num_backticks: openticks, + literal: buf, + }; + make_inline(self.arena, NodeValue::Code(code)) } } } diff --git a/src/tests.rs b/src/tests.rs index 6f742958..b560faec 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,4 @@ +use crate::nodes::{AstNode, NodeCode, NodeValue}; use cm; use html; use propfuzz::prelude::*; @@ -95,6 +96,18 @@ macro_rules! html_opts { }; } +fn asssert_node_eq<'a>(node: &'a AstNode<'a>, location: &[usize], expected: &NodeValue) { + let node = location + .iter() + .fold(node, |node, &n| node.children().nth(n).unwrap()); + + let data = node.data.borrow(); + let actual = format!("{:?}", data.value); + let expected = format!("{:?}", expected); + + compare_strs(&actual, &expected, "ast comparison"); +} + #[test] fn basic() { html( @@ -310,6 +323,27 @@ fn backticks() { ); } +#[test] +fn backticks_num() { + let input = "Some `code1`. More ``` code2 ```.\n"; + + let arena = Arena::new(); + let options = ComrakOptions::default(); + let root = parse_document(&arena, input, &options); + + let code1 = NodeValue::Code(NodeCode { + num_backticks: 1, + literal: b"code1".to_vec(), + }); + asssert_node_eq(root, &[0, 1], &code1); + + let code2 = NodeValue::Code(NodeCode { + num_backticks: 3, + literal: b"code2".to_vec(), + }); + asssert_node_eq(root, &[0, 3], &code2); +} + #[test] fn backslashes() { html( @@ -1065,9 +1099,9 @@ fn exercise_full_api() { let _: String = ::Anchorizer::new().anchorize("header".to_string()); - let _: &::nodes::AstNode = ::parse_document(&arena, "document", &default_options); + let _: &AstNode = ::parse_document(&arena, "document", &default_options); - let _: &::nodes::AstNode = ::parse_document_with_broken_link_callback( + let _: &AstNode = ::parse_document_with_broken_link_callback( &arena, "document", &default_options, @@ -1168,7 +1202,8 @@ fn exercise_full_api() { ::nodes::NodeValue::SoftBreak => {} ::nodes::NodeValue::LineBreak => {} ::nodes::NodeValue::Code(code) => { - let _: &Vec = code; + let _: usize = code.num_backticks; + let _: Vec = code.literal; } ::nodes::NodeValue::HtmlInline(html) => { let _: &Vec = html;