Skip to content

Commit

Permalink
Add mdx serialization support
Browse files Browse the repository at this point in the history
  • Loading branch information
bnchi committed Oct 20, 2024
1 parent 9ed45b4 commit d3e5f3a
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 2 deletions.
45 changes: 45 additions & 0 deletions mdast_util_to_markdown/src/handle/mdx_expression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! JS equivalent: https://github.com/syntax-tree/mdast-util-mdx-expression/blob/main/lib/index.js#L42
use super::Handle;
use crate::state::{Info, State};
use alloc::{format, string::String};
use markdown::{
mdast::{MdxFlowExpression, MdxTextExpression, Node},
message::Message,
};

impl Handle for MdxFlowExpression {
fn handle(
&self,
state: &mut State,
_info: &Info,
_parent: Option<&Node>,
_node: &Node,
) -> Result<alloc::string::String, Message> {
Ok(handle_mdx_expression(&self.value, state))
}
}

impl Handle for MdxTextExpression {
fn handle(
&self,
state: &mut State,
_info: &Info,
_parent: Option<&Node>,
_node: &Node,
) -> Result<alloc::string::String, Message> {
Ok(handle_mdx_expression(&self.value, state))
}
}

fn handle_mdx_expression(value: &str, state: &State) -> String {
let result = state.indent_lines(value, |line, index, blank| {
let space = if index == 0 || blank { "" } else { " " };
let mut results = String::with_capacity(space.len() + line.len());
results.push_str(space);
results.push_str(line);
results
});

format!("{{{}}}", result)
}
20 changes: 20 additions & 0 deletions mdast_util_to_markdown/src/handle/mdx_js_esm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! JS equivalent: https://github.com/syntax-tree/mdast-util-mdxjs-esm/blob/main/lib/index.js#L79
use super::Handle;
use crate::state::{Info, State};
use markdown::{
mdast::{MdxjsEsm, Node},
message::Message,
};

impl Handle for MdxjsEsm {
fn handle(
&self,
_state: &mut State,
_info: &Info,
_parent: Option<&Node>,
_node: &Node,
) -> Result<alloc::string::String, Message> {
Ok(self.value.clone())
}
}
74 changes: 74 additions & 0 deletions mdast_util_to_markdown/src/handle/mdx_jsx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! JS equivalent: https://github.com/syntax-tree/mdast-util-to-markdown/blob/main/lib/handle/text.js
use super::Handle;
use crate::{
construct_name::ConstructName,
state::{Info, State},
};
use alloc::{string::String, vec::Vec};
use markdown::{
mdast::{AttributeContent, MdxJsxFlowElement, Node},
message::Message,
unist::Position,
};

#[allow(dead_code)]
trait MdxJsx {
fn children(&self) -> &Vec<Node>;
fn name(&self) -> &Option<String>;
fn attributes(&self) -> &Vec<AttributeContent>;
fn position(&self) -> &Option<Position>;
}

impl MdxJsx for MdxJsxFlowElement {
fn children(&self) -> &Vec<Node> {
&self.children
}

fn name(&self) -> &Option<String> {
&self.name
}

fn attributes(&self) -> &Vec<AttributeContent> {
&self.attributes
}

fn position(&self) -> &Option<Position> {
&self.position
}
}

impl Handle for MdxJsxFlowElement {
fn handle(
&self,
_state: &mut State,
_info: &Info,
_parent: Option<&Node>,
_node: &Node,
) -> Result<String, Message> {
Ok(String::new())
}
}

#[allow(dead_code)]
fn create_indent(depth: usize) -> String {
" ".repeat(depth)
}

#[allow(dead_code)]
fn infer_depth(state: &State) -> usize {
let mut depth: usize = 0;

for construct_name in state.stack.iter().rev() {
if matches!(
construct_name,
ConstructName::Blockquote | ConstructName::ListItem
) {
break;
} else {
depth += 1;
}
}

depth
}
3 changes: 3 additions & 0 deletions mdast_util_to_markdown/src/handle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub mod link_reference;
mod list;
mod list_item;
mod math;
mod mdx_expression;
mod mdx_js_esm;
mod mdx_jsx;
mod paragraph;
mod root;
pub mod strong;
Expand Down
1 change: 1 addition & 0 deletions mdast_util_to_markdown/src/handle/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn phrasing(child: &Node) -> bool {
| Node::InlineMath(_)
| Node::Link(_)
| Node::LinkReference(_)
| Node::MdxTextExpression(_)
| Node::Strong(_)
| Node::Text(_)
)
Expand Down
10 changes: 9 additions & 1 deletion mdast_util_to_markdown/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ impl<'a> State<'a> {
Node::ThematicBreak(thematic_break) => thematic_break.handle(self, info, parent, node),
Node::Math(math) => math.handle(self, info, parent, node),
Node::InlineMath(inline_math) => inline_math.handle(self, info, parent, node),
Node::MdxFlowExpression(mdx_flow_expression) => {
mdx_flow_expression.handle(self, info, parent, node)
}
Node::MdxTextExpression(mdx_text_expression) => {
mdx_text_expression.handle(self, info, parent, node)
}
Node::MdxjsEsm(mdx_js_esm) => mdx_js_esm.handle(self, info, parent, node),
_ => Err(Message {
place: None,
reason: format!("Unexpected node type `{:?}`", node),
Expand All @@ -350,7 +357,8 @@ impl<'a> State<'a> {
line += 1;
}

result.push_str(&map(&value[start..], line, value.is_empty()));
let value_slice = &value[start..];
result.push_str(&map(value_slice, line, value_slice.is_empty()));
result
}

Expand Down
12 changes: 11 additions & 1 deletion mdast_util_to_markdown/src/unsafe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,11 @@ impl<'a> Unsafe<'a> {
'<',
None,
None,
vec![ConstructName::DestinationLiteral],
vec![ConstructName::DestinationLiteral, ConstructName::Phrasing],
vec![],
false,
),
Self::new('<', None, None, vec![], vec![], true),
Self::new('=', None, None, vec![], vec![], true),
Self::new('>', None, None, vec![], vec![], true),
Self::new(
Expand Down Expand Up @@ -331,6 +332,15 @@ impl<'a> Unsafe<'a> {
false,
),
Self::new('$', None, "\\$".into(), vec![], vec![], true),
Self::new(
'{',
None,
None,
vec![ConstructName::Phrasing],
vec![],
false,
),
Self::new('{', None, None, vec![], vec![], true),
]
}

Expand Down
154 changes: 154 additions & 0 deletions mdast_util_to_markdown/tests/mdx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use markdown::{
mdast::{
Definition, MdxFlowExpression, MdxTextExpression, MdxjsEsm, Node, Paragraph, Root, Text,
},
to_mdast as from, ParseOptions,
};
use mdast_util_to_markdown::to_markdown as to;
use pretty_assertions::assert_eq;

#[test]
fn mdx() {
assert_eq!(
to(&Node::Root(Root {
children: vec![
Node::MdxFlowExpression(MdxFlowExpression {
value: "a + b".to_string(),
position: None,
stops: vec![]
}),
Node::MdxFlowExpression(MdxFlowExpression {
value: String::from("\nc +\n1\n"),
position: None,
stops: vec![]
}),
Node::MdxFlowExpression(MdxFlowExpression {
value: String::new(),
position: None,
stops: vec![]
}),
Node::Paragraph(Paragraph {
children: vec![Node::Text(Text {
value: "d".to_string(),
position: None
})],
position: None
})
],
position: None
}))
.unwrap(),
"{a + b}\n\n{\n c +\n 1\n}\n\n{}\n\nd\n",
"should serialize flow expressions"
);

assert_eq!(
to(&Node::Paragraph(Paragraph {
children: vec![
Node::Text(Text {
value: "a ".to_string(),
position: None
}),
Node::MdxTextExpression(MdxTextExpression {
value: "b + c".to_string(),
position: None,
stops: vec![]
}),
Node::Text(Text {
value: ", d ".to_string(),
position: None
}),
Node::MdxTextExpression(MdxTextExpression {
value: "e + 1".to_string(),
position: None,
stops: vec![]
}),
Node::Text(Text {
value: ", f ".to_string(),
position: None
}),
Node::MdxTextExpression(MdxTextExpression {
value: String::new(),
position: None,
stops: vec![]
}),
Node::Text(Text {
value: ".".to_string(),
position: None
}),
],
position: None
}))
.unwrap(),
"a {b + c}, d {e + 1}, f {}.\n",
"should serialize text expressions"
);

assert_eq!(
to(&Node::Paragraph(Paragraph {
children: vec![Node::Text(Text {
value: "a { b".to_string(),
position: None
})],
position: None
}))
.unwrap(),
"a \\{ b\n",
"should escape `{{` in text"
);

assert_eq!(
to(&Node::Definition(Definition {
position: None,
url: "x".to_string(),
title: "a\n{\nb".to_string().into(),
identifier: "a".to_string(),
label: None
}))
.unwrap(),
"[a]: x \"a\n\\{\nb\"\n",
"should escape `{{` at the start of a line"
);

assert_eq!(
to(&from(" {`\n a\n `}", &ParseOptions::mdx()).unwrap()).unwrap(),
"{`\n a\n `}\n",
"should strip superfluous whitespace depending on the opening prefix, when roundtripping expressions (flow)"
);

// This require changing to match the js tests when https://github.com/wooorm/markdown-rs/issues/150 is resolved
//assert_eq!(
// to(&from(" {`\n a\n `}", &ParseOptions::mdx()).unwrap()).unwrap(),
// "{`\n a\n `}\n",
// "should *not* strip superfluous whitespace (if there is more) when roundtripping expressions (flow)"
//);

//// This require changing to match the js tests when https://github.com/wooorm/markdown-rs/issues/150 is resolved
//assert_eq!(
// to(&from("a {`\n b\n `} c", &ParseOptions::mdx()).unwrap()).unwrap(),
// "a {`\n b\n `} c\n",
// "should not strip consecutive lines in expressions (text)"
//);

assert_eq!(
to(&Node::Root(Root {
children: vec![
Node::MdxjsEsm(MdxjsEsm {
value: String::from("import a from \"b\"\nexport var c = \"\""),
position: None,
stops: Vec::new()
}),
Node::Paragraph(Paragraph {
children: vec![Node::Text(Text {
value: "d".to_string(),
position: None
})],
position: None
})
],
position: None
}))
.unwrap(),
"import a from \"b\"\nexport var c = \"\"\n\nd\n"
)
}

0 comments on commit d3e5f3a

Please sign in to comment.