Skip to content

Commit

Permalink
fenced codeblocks
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherBiscardi committed Apr 5, 2021
1 parent f6129c2 commit ab5d0f5
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 12 deletions.
5 changes: 5 additions & 0 deletions cli_test_mdx_file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ some content here
## that's why it's all headings and breaks

this can contain paragraphs now

```js title=codeblocks.js
const t = {};
console.log("something");
```
79 changes: 67 additions & 12 deletions src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,68 +1,108 @@
use nom::{character::complete::*, multi::many1_count, IResult};
use nom::{
character::complete::*, multi::many1_count, IResult,
};
use nom_supreme::{
error::ErrorTree,
final_parser::{final_parser, Location},
};
use std::fmt;

pub mod fenced_codeblocks;
pub mod headings;
pub mod paragraphs;
pub mod thematic_breaks;

pub use fenced_codeblocks::FencedCodeblock;
pub use headings::{atx_heading, ATXHeading};
pub use paragraphs::{paragraph, Paragraph};
pub use thematic_breaks::{thematic_break, ThematicBreak};

use self::fenced_codeblocks::fenced_codeblock;

// TODO: maybe get rid of Copy/Clone here?
// it's required by fold_many0
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum MdxAst<'a> {
ATXHeading(ATXHeading<'a>),
ThematicBreak(ThematicBreak),
Paragraph(Paragraph<'a>),
Codeblock(FencedCodeblock<'a>),
}
impl<'a> fmt::Display for MdxAst<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
MdxAst::ATXHeading(atx) => write!(f, "{}", atx),
MdxAst::ThematicBreak(brk) => write!(f, "{}", brk),
MdxAst::Paragraph(para) => write!(f, "{}", para),
MdxAst::ThematicBreak(brk) => {
write!(f, "{}", brk)
}
MdxAst::Paragraph(para) => {
write!(f, "{}", para)
}
MdxAst::Codeblock(codeblock) => {
write!(f, "{}", codeblock)
}
}
// Use `self.number` to refer to each positional data point.
}
}

pub fn mdx_elements(input: &str) -> Result<Vec<MdxAst>, ErrorTree<Location>> {
pub fn mdx_elements(
input: &str,
) -> Result<Vec<MdxAst>, ErrorTree<Location>> {
final_parser(mdx_elements_internal)(input)
}
fn mdx_elements_internal(input: &str) -> IResult<&str, Vec<MdxAst>, ErrorTree<&str>> {
fn mdx_elements_internal(
input: &str,
) -> IResult<&str, Vec<MdxAst>, ErrorTree<&str>> {
let (input, _) = multispace0(input)?;
let (input, result) = nom::multi::separated_list1(many1_count(newline), mdx_ast)(input)?;
let (input, result) = nom::multi::separated_list1(
many1_count(newline),
mdx_ast,
)(input)?;
let (input, _) = multispace0(input)?;
let (input, _) = nom::combinator::eof(input)?;
Ok((input, result))
}

fn mdx_ast(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
nom::branch::alt((ast_atx_heading, ast_thematic_break, ast_paragraph))(input)
fn mdx_ast(
input: &str,
) -> IResult<&str, MdxAst, ErrorTree<&str>> {
nom::branch::alt((
ast_atx_heading,
ast_thematic_break,
ast_codeblock,
ast_paragraph,
))(input)
}

/// We have to wrap the structs to fit in the MdxAst
fn ast_atx_heading(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
fn ast_atx_heading(
input: &str,
) -> IResult<&str, MdxAst, ErrorTree<&str>> {
let (input, atx) = atx_heading(input)?;
Ok((input, MdxAst::ATXHeading(atx)))
}

fn ast_thematic_break(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
fn ast_thematic_break(
input: &str,
) -> IResult<&str, MdxAst, ErrorTree<&str>> {
let (input, thematic_break) = thematic_break(input)?;
Ok((input, MdxAst::ThematicBreak(thematic_break)))
}

fn ast_paragraph(input: &str) -> IResult<&str, MdxAst, ErrorTree<&str>> {
fn ast_paragraph(
input: &str,
) -> IResult<&str, MdxAst, ErrorTree<&str>> {
let (input, paragraph) = paragraph(input)?;
Ok((input, MdxAst::Paragraph(paragraph)))
}

fn ast_codeblock(
input: &str,
) -> IResult<&str, MdxAst, ErrorTree<&str>> {
let (input, codeblock) = fenced_codeblock(input)?;
Ok((input, MdxAst::Codeblock(codeblock)))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -93,6 +133,21 @@ mod tests {
)
);
}

#[test]
fn parse_codeblock() {
assert_eq!(
mdx_ast("```\n\nconst t = {}\n```").unwrap(),
(
"",
MdxAst::Codeblock(FencedCodeblock {
language: "",
infostring: "",
code: "\nconst t = {}\n"
}),
)
);
}
}

#[cfg(test)]
Expand Down
135 changes: 135 additions & 0 deletions src/ast/fenced_codeblocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use nom::{
branch::alt,
bytes::complete::take_until,
character::complete::{char, space0},
multi::fold_many_m_n,
IResult,
};
use nom_supreme::{error::ErrorTree, tag::complete::tag};
use std::fmt;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct FencedCodeblock<'a> {
pub language: &'a str,
pub infostring: &'a str,
pub code: &'a str,
}
impl fmt::Display for FencedCodeblock<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"```
{}
```",
self.code.to_string()
)
}
}

fn parse_infostring(
input: &str,
) -> IResult<&str, &str, ErrorTree<&str>> {
let (input, _) = space0(input)?;
let (input, infostring) = take_until("\n")(input)?;
let (input, _) =
nom::character::complete::line_ending(input)?;
Ok((input, infostring))
}

pub fn fenced_codeblock(
input: &str,
) -> IResult<&str, FencedCodeblock, ErrorTree<&str>> {
// basically anything can start with 0-3 spaces. We don't really care.
let (input, _) =
fold_many_m_n(0, 3, tag(" "), 0, |acc: u8, _| {
acc + 1
})(input)?;

// codeblocks can be defined by ``` or ~~~, which
// also have slightly different rules as to what's
// allowable in the infostring
let (input, c) = alt((char('`'), char('~')))(input)?;

// try to parse at least two more chars of the same type.
let (input, num_break_chars) = fold_many_m_n(
2,
1000,
char(c),
0,
|acc: u8, _| acc + 1,
)(input)?;
let (input, infostring) = parse_infostring(input)?;
// infostring parsing
// end at newline
//code vv
// : IResult<&str, &str, ErrorTree<&str>>
let (input, code) = take_until("```")(input)?;
let (input, _) = tag("```")(input)?;
Ok((
input,
FencedCodeblock {
language: "",
infostring,
code: code,
},
))
}

#[cfg(test)]
mod tests {
use super::*;
use nom::{error::ErrorKind, Err::Error};

#[test]
fn parse_fenced_codeblock() {
assert_eq!(
fenced_codeblock("```\n\n```").unwrap(),
(
"",
FencedCodeblock {
language: "",
infostring: "",
code: "\n",
}
)
);
}
#[test]
fn parse_fenced_codeblock_infostring() {
assert_eq!(
fenced_codeblock(
"```js title=something.txt
const t = {};
```"
)
.unwrap(),
(
"",
FencedCodeblock {
language: "",
infostring: "js title=something.txt",
code: "const t = {};\n",
}
)
);
}

#[test]
fn parse_fenced_codeblock_infostring_with_spaces() {
assert_eq!(
fenced_codeblock(
"``` js title=something.txt
const t = {};
```"
)
.unwrap(),
(
"",
FencedCodeblock {
language: "",
infostring: "js title=something.txt",
code: "const t = {};\n",
}
)
);
}
}

0 comments on commit ab5d0f5

Please sign in to comment.