From 2045b0bfc467efeb605aad7d8297a1fa1fc3e0e9 Mon Sep 17 00:00:00 2001 From: Kyle McCarthy Date: Tue, 3 Jan 2023 04:52:22 -0600 Subject: Add support for serializing trees as JSON This adds support for serializing the mdast syntax tree as JSON, with serde, through a feature. Closes GH-10. Related-to GH-30. Closes GH-37. --- .github/workflows/main.yml | 4 +- Cargo.toml | 6 ++ src/mdast.rs | 218 ++++++++++++++++++++++++++++++++++++++++++++- src/to_mdast.rs | 24 ++--- src/unist.rs | 2 + tests/mdx_jsx_text.rs | 14 ++- 6 files changed, 250 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a38c5c5..6157716 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,8 +11,8 @@ jobs: with: toolchain: stable components: rustfmt, clippy - - run: cargo fmt --check && cargo clippy --examples --tests --benches - - run: cargo test + - run: cargo fmt --check && cargo clippy --examples --tests --benches --all-features + - run: cargo test --all-features coverage: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index ca71e3f..0e1b7a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,15 @@ name = "bench" path = "benches/bench.rs" harness = false +[features] +default = ["json"] +json = ["dep:serde", "dep:serde_json"] + [dependencies] log = "0.4" unicode-id = { version = "0.3", features = ["no_std"] } +serde = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true } [dev-dependencies] env_logger = "0.10" diff --git a/src/mdast.rs b/src/mdast.rs index 7f3ac75..927c09c 100644 --- a/src/mdast.rs +++ b/src/mdast.rs @@ -15,6 +15,11 @@ pub type Stop = (usize, usize); /// Explicitness of a reference. #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "lowercase") +)] pub enum ReferenceKind { /// The reference is implicit, its identifier inferred from its content. Shortcut, @@ -28,6 +33,11 @@ pub enum ReferenceKind { /// /// Used to align the contents of table cells within a table. #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "lowercase") +)] pub enum AlignKind { /// Left alignment. /// @@ -73,6 +83,11 @@ pub enum AlignKind { /// Nodes. #[derive(Clone, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "type") +)] pub enum Node { // Document: /// Root. @@ -428,6 +443,11 @@ impl Node { /// MDX: attribute content. #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxJsxExpressionAttribute") +)] pub enum AttributeContent { /// JSX expression. /// @@ -435,7 +455,7 @@ pub enum AttributeContent { /// > | /// ^^^^^^ /// ``` - Expression(String, Vec), + Expression { value: String, stops: Vec }, /// JSX property. /// /// ```markdown @@ -444,9 +464,25 @@ pub enum AttributeContent { /// ``` Property(MdxJsxAttribute), } +// +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxJsxAttributeValueExpression") +)] +pub struct AttributeValueExpression { + pub value: String, + pub stops: Vec, +} /// MDX: attribute value. #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "type") +)] pub enum AttributeValue { /// Expression value. /// @@ -454,13 +490,14 @@ pub enum AttributeValue { /// > | /// ^^^ /// ``` - Expression(String, Vec), + Expression(AttributeValueExpression), /// Static value. /// /// ```markdown /// > | /// ^^^ /// ``` + #[cfg_attr(feature = "json", serde(rename = "literal"))] Literal(String), } @@ -471,6 +508,11 @@ pub enum AttributeValue { /// ^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "root") +)] pub struct Root { // Parent. /// Content model. @@ -486,6 +528,11 @@ pub struct Root { /// ^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "paragraph") +)] pub struct Paragraph { // Parent. /// Content model. @@ -501,6 +548,11 @@ pub struct Paragraph { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "heading") +)] pub struct Heading { // Parent. /// Content model. @@ -519,6 +571,11 @@ pub struct Heading { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "thematicBreak") +)] pub struct ThematicBreak { // Void. /// Positional info. @@ -532,6 +589,11 @@ pub struct ThematicBreak { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "blockquote") +)] pub struct BlockQuote { // Parent. /// Content model. @@ -547,6 +609,11 @@ pub struct BlockQuote { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "list") +)] pub struct List { // Parent. /// Content model. @@ -571,6 +638,11 @@ pub struct List { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "listItem") +)] pub struct ListItem { // Parent. /// Content model. @@ -593,6 +665,11 @@ pub struct ListItem { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "html") +)] pub struct Html { // Text. /// Content model. @@ -612,6 +689,11 @@ pub struct Html { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "code") +)] pub struct Code { // Text. /// Content model. @@ -636,6 +718,11 @@ pub struct Code { /// ^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "math") +)] pub struct Math { // Text. /// Content model. @@ -654,6 +741,11 @@ pub struct Math { /// ^^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "definition") +)] pub struct Definition { // Void. /// Positional info. @@ -686,6 +778,11 @@ pub struct Definition { /// ^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "text") +)] pub struct Text { // Text. /// Content model. @@ -701,6 +798,11 @@ pub struct Text { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "emphasis") +)] pub struct Emphasis { // Parent. /// Content model. @@ -716,6 +818,11 @@ pub struct Emphasis { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "strong") +)] pub struct Strong { // Parent. /// Content model. @@ -731,6 +838,11 @@ pub struct Strong { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "inlineCode") +)] pub struct InlineCode { // Text. /// Content model. @@ -746,6 +858,11 @@ pub struct InlineCode { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "inlineMath") +)] pub struct InlineMath { // Text. /// Content model. @@ -762,6 +879,11 @@ pub struct InlineMath { /// | b /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "break") +)] pub struct Break { // Void. /// Positional info. @@ -775,6 +897,11 @@ pub struct Break { /// ^^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "link") +)] pub struct Link { // Parent. /// Content model. @@ -796,6 +923,11 @@ pub struct Link { /// ^^^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "image") +)] pub struct Image { // Void. /// Positional info. @@ -819,6 +951,11 @@ pub struct Image { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "linkReference") +)] pub struct LinkReference { // Parent. /// Content model. @@ -827,6 +964,7 @@ pub struct LinkReference { pub position: Option, // Reference. /// Explicitness of a reference. + #[cfg_attr(feature = "json", serde(rename = "referenceType"))] pub reference_kind: ReferenceKind, // Association. /// Value that can match another node. @@ -850,6 +988,11 @@ pub struct LinkReference { /// ^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "imageReference") +)] pub struct ImageReference { // Void. /// Positional info. @@ -860,6 +1003,7 @@ pub struct ImageReference { pub alt: String, // Reference. /// Explicitness of a reference. + #[cfg_attr(feature = "json", serde(rename = "referenceType"))] pub reference_kind: ReferenceKind, // Association. /// Value that can match another node. @@ -883,6 +1027,11 @@ pub struct ImageReference { /// ^^^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "footnoteDefinition") +)] pub struct FootnoteDefinition { // Parent. /// Content model. @@ -911,6 +1060,11 @@ pub struct FootnoteDefinition { /// ^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "footnoteReference") +)] pub struct FootnoteReference { // Void. /// Positional info. @@ -939,6 +1093,11 @@ pub struct FootnoteReference { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "table") +)] pub struct Table { // Parent. /// Content model. @@ -957,6 +1116,11 @@ pub struct Table { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "tableRow") +)] pub struct TableRow { // Parent. /// Content model. @@ -972,6 +1136,11 @@ pub struct TableRow { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "tableCell") +)] pub struct TableCell { // Parent. /// Content model. @@ -987,6 +1156,11 @@ pub struct TableCell { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "delete") +)] pub struct Delete { // Parent. /// Content model. @@ -1006,6 +1180,11 @@ pub struct Delete { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "yaml") +)] pub struct Yaml { // Void. /// Content model. @@ -1025,6 +1204,11 @@ pub struct Yaml { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "toml") +)] pub struct Toml { // Void. /// Content model. @@ -1040,6 +1224,11 @@ pub struct Toml { /// ^^^^^^^^^^^^^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxjsEsm") +)] pub struct MdxjsEsm { // Literal. /// Content model. @@ -1058,6 +1247,11 @@ pub struct MdxjsEsm { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxFlowExpression") +)] pub struct MdxFlowExpression { // Literal. /// Content model. @@ -1076,6 +1270,11 @@ pub struct MdxFlowExpression { /// ^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxTextExpression") +)] pub struct MdxTextExpression { // Literal. /// Content model. @@ -1094,6 +1293,11 @@ pub struct MdxTextExpression { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxJsxFlowElement") +)] pub struct MdxJsxFlowElement { // Parent. /// Content model. @@ -1116,6 +1320,11 @@ pub struct MdxJsxFlowElement { /// ^^^^^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxJsxTextElement") +)] pub struct MdxJsxTextElement { // Parent. /// Content model. @@ -1138,6 +1347,11 @@ pub struct MdxJsxTextElement { /// ^ /// ``` #[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr( + feature = "json", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type", rename = "mdxJsxAttribute") +)] pub struct MdxJsxAttribute { // Void. /// Positional info. diff --git a/src/to_mdast.rs b/src/to_mdast.rs index e76bad5..4659d7b 100644 --- a/src/to_mdast.rs +++ b/src/to_mdast.rs @@ -2,11 +2,12 @@ use crate::event::{Event, Kind, Name, Point as EventPoint}; use crate::mdast::{ - AttributeContent, AttributeValue, BlockQuote, Break, Code, Definition, Delete, Emphasis, - FootnoteDefinition, FootnoteReference, Heading, Html, Image, ImageReference, InlineCode, - InlineMath, Link, LinkReference, List, ListItem, Math, MdxFlowExpression, MdxJsxAttribute, - MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Node, Paragraph, - ReferenceKind, Root, Strong, Table, TableCell, TableRow, Text, ThematicBreak, Toml, Yaml, + AttributeContent, AttributeValue, AttributeValueExpression, BlockQuote, Break, Code, + Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image, + ImageReference, InlineCode, InlineMath, Link, LinkReference, List, ListItem, Math, + MdxFlowExpression, MdxJsxAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, + MdxjsEsm, Node, Paragraph, ReferenceKind, Root, Strong, Table, TableCell, TableRow, Text, + ThematicBreak, Toml, Yaml, }; use crate::unist::{Point, Position}; use crate::util::{ @@ -14,7 +15,7 @@ use crate::util::{ decode as decode_character_reference, parse as parse_character_reference, }, infer::{gfm_table_align, list_item_loose, list_loose}, - mdx_collect::collect, + mdx_collect::{collect, Result as CollectResult}, normalize_identifier::normalize_identifier, slice::{Position as SlicePosition, Slice}, }; @@ -834,7 +835,7 @@ fn on_enter_mdx_jsx_tag_attribute(context: &mut CompileContext) -> Result<(), St fn on_enter_mdx_jsx_tag_attribute_expression(context: &mut CompileContext) -> Result<(), String> { on_enter_mdx_jsx_tag_any_attribute(context)?; - let result = collect( + let CollectResult { value, stops } = collect( context.events, context.bytes, context.index, @@ -846,7 +847,7 @@ fn on_enter_mdx_jsx_tag_attribute_expression(context: &mut CompileContext) -> Re .as_mut() .expect("expected tag") .attributes - .push(AttributeContent::Expression(result.value, result.stops)); + .push(AttributeContent::Expression { value, stops }); context.buffer(); @@ -855,7 +856,7 @@ fn on_enter_mdx_jsx_tag_attribute_expression(context: &mut CompileContext) -> Re /// Handle [`Enter`][Kind::Enter]:[`MdxJsxTagAttributeValueExpression`][Name::MdxJsxTagAttributeValueExpression]. fn on_enter_mdx_jsx_tag_attribute_value_expression(context: &mut CompileContext) { - let result = collect( + let CollectResult { value, stops } = collect( context.events, context.bytes, context.index, @@ -870,7 +871,10 @@ fn on_enter_mdx_jsx_tag_attribute_value_expression(context: &mut CompileContext) .attributes .last_mut() { - node.value = Some(AttributeValue::Expression(result.value, result.stops)); + node.value = Some(AttributeValue::Expression(AttributeValueExpression { + value, + stops, + })); } else { unreachable!("expected property") } diff --git a/src/unist.rs b/src/unist.rs index 514e661..56f1abf 100644 --- a/src/unist.rs +++ b/src/unist.rs @@ -6,6 +6,7 @@ use alloc::fmt; /// One place in a source file. #[derive(Clone, Eq, PartialEq)] +#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))] pub struct Point { /// 1-indexed integer representing a line in a source file. pub line: usize, @@ -34,6 +35,7 @@ impl fmt::Debug for Point { /// Location of a node in a source file. #[derive(Clone, Eq, PartialEq)] +#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))] pub struct Position { /// Represents the place of the first character of the parsed source region. pub start: Point, diff --git a/tests/mdx_jsx_text.rs b/tests/mdx_jsx_text.rs index f548672..d0d1972 100644 --- a/tests/mdx_jsx_text.rs +++ b/tests/mdx_jsx_text.rs @@ -1,8 +1,8 @@ mod test_utils; use markdown::{ mdast::{ - AttributeContent, AttributeValue, Emphasis, MdxJsxAttribute, MdxJsxTextElement, Node, - Paragraph, Root, Text, + AttributeContent, AttributeValue, AttributeValueExpression, Emphasis, MdxJsxAttribute, + MdxJsxTextElement, Node, Paragraph, Root, Text, }, to_html_with_options, to_mdast, unist::Position, @@ -165,7 +165,10 @@ fn mdx_jsx_text_core() -> Result<(), String> { children: vec![ Node::MdxJsxTextElement(MdxJsxTextElement { name: Some("a".into()), - attributes: vec![AttributeContent::Expression("...b".into(), vec![(0, 4)])], + attributes: vec![AttributeContent::Expression { + value: "...b".into(), + stops: vec![(0, 4)] + }], children: vec![], position: Some(Position::new(1, 1, 0, 1, 13, 12)) }), @@ -231,7 +234,10 @@ fn mdx_jsx_text_core() -> Result<(), String> { }), AttributeContent::Property(MdxJsxAttribute { name: "f".into(), - value: Some(AttributeValue::Expression("g".into(), vec![(0, 18)])), + value: Some(AttributeValue::Expression(AttributeValueExpression { + value: "g".into(), + stops: vec![(0, 18)] + })), }), ], children: vec![], -- cgit