Compare commits
8 Commits
d761bd036d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| af63f069ab | |||
| 99b1205cec | |||
| 47cb8d2cc2 | |||
| e04beb51ee | |||
| b7ca4ac6e3 | |||
| ba598f7e34 | |||
| 34304546ad | |||
| 03b5360ec5 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "marginal"
|
name = "marginal"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "marginal"
|
name = "marginal"
|
||||||
version = "0.1.0"
|
version = "0.1.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = ["gitea"]
|
publish = ["gitea"]
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ command = [
|
|||||||
"cargo", "nextest", "run",
|
"cargo", "nextest", "run",
|
||||||
"--hide-progress-bar",
|
"--hide-progress-bar",
|
||||||
"--failure-output", "final",
|
"--failure-output", "final",
|
||||||
|
"--no-fail-fast",
|
||||||
]
|
]
|
||||||
need_stdout = true
|
need_stdout = true
|
||||||
analyzer = "nextest"
|
analyzer = "nextest"
|
||||||
|
|||||||
@@ -31,4 +31,6 @@ pub enum Block {
|
|||||||
Code { content: String, lang: String },
|
Code { content: String, lang: String },
|
||||||
Quote { inner: Box<Block> },
|
Quote { inner: Box<Block> },
|
||||||
Paragraph { inner: Vec<Inline> },
|
Paragraph { inner: Vec<Inline> },
|
||||||
|
List { ordered: bool, items: Vec<Block> },
|
||||||
|
Null,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,136 @@ impl ToHtml for Vec<Block> {
|
|||||||
impl ToHtml for Block {
|
impl ToHtml for Block {
|
||||||
fn to_html(&self) -> String {
|
fn to_html(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Block::Paragraph { inner } => format!("<p>{}</p><br>", inner.to_html()),
|
Block::Paragraph { inner } => format!("<p>{}</p>", inner.to_html()),
|
||||||
Block::Heading { inner, level } => {
|
Block::Heading { inner, level } => {
|
||||||
format!("<h{}>{}</h{}>", level, inner.to_html(), level)
|
format!("<h{}>{}</h{}>", level, inner.to_html(), level)
|
||||||
}
|
}
|
||||||
Block::Code { content, lang: _ } => format!("<pre><code>{content}</code></pre>"),
|
Block::Code { content, lang: _ } => format!("<pre><code>{content}</code></pre>"),
|
||||||
Block::Quote { inner } => format!("<div class=\"quote\">{}</div>", inner.to_html()),
|
Block::Quote { inner } => format!("<div class=\"quote\">{}</div>", inner.to_html()),
|
||||||
|
Block::List { ordered, items } => {
|
||||||
|
let tag = if *ordered { "ol" } else { "ul" };
|
||||||
|
let mut html = format!("<{}>", tag);
|
||||||
|
for item in items {
|
||||||
|
html.push_str(&format!("<li>{}</li>", &item.to_html()));
|
||||||
|
}
|
||||||
|
html.push_str(&format!("</{}>", tag));
|
||||||
|
html
|
||||||
|
}
|
||||||
|
Block::Null => "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{ast::*, generator::ToHtml};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn paragraph_to_html() {
|
||||||
|
let ast = Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hello".to_string(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<p>hello</p>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heading_to_html() {
|
||||||
|
let ast = Block::Heading {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hello".to_string(),
|
||||||
|
}],
|
||||||
|
level: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<h2>hello</h2>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code_to_html() {
|
||||||
|
let ast = Block::Code {
|
||||||
|
content: "echo 'hello world!'".to_string(),
|
||||||
|
lang: "bash".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<pre><code>echo 'hello world!'</code></pre>".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quote_to_html() {
|
||||||
|
let ast = Block::Quote {
|
||||||
|
inner: Box::new(Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "sun tzu".to_string(),
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<div class=\"quote\"><p>sun tzu</p></div>");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ordered_list_to_html() {
|
||||||
|
let ast = Block::List {
|
||||||
|
ordered: true,
|
||||||
|
items: vec![
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "item 1".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "item 2".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<ol><li><p>item 1</p></li><li><p>item 2</p></li></ol>".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unordered_list_to_html() {
|
||||||
|
let ast = Block::List {
|
||||||
|
ordered: false,
|
||||||
|
items: vec![
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "item 1".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "item 2".to_string(),
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<ul><li><p>item 1</p></li><li><p>item 2</p></li></ul>".to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,15 @@ use super::{GenerationError, ToHtml, TryToHtml};
|
|||||||
impl TryToHtml for PathBuf {
|
impl TryToHtml for PathBuf {
|
||||||
fn try_to_html(&self) -> Result<String, GenerationError> {
|
fn try_to_html(&self) -> Result<String, GenerationError> {
|
||||||
let content_md = fs::read_to_string(self)?;
|
let content_md = fs::read_to_string(self)?;
|
||||||
let (_rem, generated) = blocks(&content_md)?;
|
let (rem, generated) = blocks(&content_md)?;
|
||||||
|
|
||||||
|
if !rem.is_empty() {
|
||||||
|
Err(GenerationError::Termination {
|
||||||
|
file: self.to_owned(),
|
||||||
|
remainder: rem.to_string(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
Ok(generated.to_html())
|
Ok(generated.to_html())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,74 @@ impl ToHtml for Inline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_to_html() {
|
||||||
|
let ast = Inline::Text {
|
||||||
|
content: "hello".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "hello".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bold_to_html() {
|
||||||
|
let ast = Inline::Bold {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hello".to_string(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<b>hello</b>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn italic_to_html() {
|
||||||
|
let ast = Inline::Italic {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hello".to_string(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<i>hello</i>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn code_to_html() {
|
||||||
|
let ast = Inline::Code {
|
||||||
|
content: "echo 'hello'".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(html, "<code>echo 'hello'</code>".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn link_to_html() {
|
||||||
|
let ast = Inline::Link {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "my webpage".to_string(),
|
||||||
|
}],
|
||||||
|
href: crate::ast::Href("https://jlux.dev".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let html = ast.to_html();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html,
|
||||||
|
"<a href=\"https://jlux.dev\">my webpage</a>".to_string()
|
||||||
|
);
|
||||||
|
//format!("<a href=\"{}\">{}</a>", href, inner.to_html())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::fmt::Display;
|
use std::{fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
use crate::parser::MarkdownParseError;
|
use crate::parser::MarkdownParseError;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ pub mod inline;
|
|||||||
pub enum GenerationError {
|
pub enum GenerationError {
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
Parse(nom::Err<MarkdownParseError>),
|
Parse(nom::Err<MarkdownParseError>),
|
||||||
|
Termination { file: PathBuf, remainder: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for GenerationError {
|
impl Display for GenerationError {
|
||||||
@@ -18,8 +19,13 @@ impl Display for GenerationError {
|
|||||||
f,
|
f,
|
||||||
"Generation Error: {}",
|
"Generation Error: {}",
|
||||||
match self {
|
match self {
|
||||||
GenerationError::IO(e) => format!("IO: {e}"),
|
GenerationError::IO(e) => format!("IO error: {e}"),
|
||||||
GenerationError::Parse(e) => format!("Parse: {e}"),
|
GenerationError::Parse(e) => format!("Parse error: {e}"),
|
||||||
|
GenerationError::Termination { file, remainder } => format!(
|
||||||
|
"Termination error at `{}` before:\n{}",
|
||||||
|
file.display(),
|
||||||
|
remainder
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use nom::{
|
|||||||
IResult, Parser,
|
IResult, Parser,
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{tag, take_until},
|
bytes::complete::{tag, take_until},
|
||||||
|
combinator::peek,
|
||||||
multi::{many_m_n, many0, many1},
|
multi::{many_m_n, many0, many1},
|
||||||
sequence::{delimited, terminated},
|
sequence::{delimited, terminated},
|
||||||
};
|
};
|
||||||
@@ -15,12 +16,24 @@ pub fn blocks(input: &str) -> IResult<&str, Vec<Block>, MarkdownParseError> {
|
|||||||
|
|
||||||
pub fn block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
pub fn block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
||||||
terminated(
|
terminated(
|
||||||
alt((heading_block, code_block, quote_block, paragraph_block)),
|
alt((
|
||||||
|
heading_block,
|
||||||
|
ordered_list,
|
||||||
|
unordered_list,
|
||||||
|
code_block,
|
||||||
|
quote_block,
|
||||||
|
paragraph_block,
|
||||||
|
empty_line,
|
||||||
|
)),
|
||||||
tag("\n"),
|
tag("\n"),
|
||||||
)
|
)
|
||||||
.parse(input)
|
.parse(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty_line(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
||||||
|
tag("").parse(input).map(|(rem, _)| (rem, Block::Null))
|
||||||
|
}
|
||||||
|
|
||||||
fn paragraph_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
fn paragraph_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
||||||
(inline)
|
(inline)
|
||||||
.parse(input)
|
.parse(input)
|
||||||
@@ -45,7 +58,7 @@ fn code_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|||||||
delimited(
|
delimited(
|
||||||
tag("```"),
|
tag("```"),
|
||||||
(take_until("\n"), tag("\n"), take_until("```\n")),
|
(take_until("\n"), tag("\n"), take_until("```\n")),
|
||||||
tag("```\n"),
|
(tag("```"), peek(tag("\n"))),
|
||||||
)
|
)
|
||||||
.parse(input)
|
.parse(input)
|
||||||
.map(|(rem, (lang, _, code))| {
|
.map(|(rem, (lang, _, code))| {
|
||||||
@@ -72,6 +85,32 @@ fn quote_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unordered_list(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
||||||
|
many1((tag("- "), block)).parse(input).map(|(rem, v)| {
|
||||||
|
let items = v.into_iter().map(|(_, b)| b).collect();
|
||||||
|
(
|
||||||
|
rem,
|
||||||
|
Block::List {
|
||||||
|
ordered: false,
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ordered_list(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
||||||
|
many1((tag("1. "), block)).parse(input).map(|(rem, v)| {
|
||||||
|
let items = v.into_iter().map(|(_, b)| b).collect();
|
||||||
|
(
|
||||||
|
rem,
|
||||||
|
Block::List {
|
||||||
|
ordered: true,
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
//|-------------------------------------------------------------------------------|
|
//|-------------------------------------------------------------------------------|
|
||||||
//| TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS |
|
//| TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS |
|
||||||
//|-------------------------------------------------------------------------------|
|
//|-------------------------------------------------------------------------------|
|
||||||
@@ -83,10 +122,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_paragraph() {
|
fn single_paragraph() {
|
||||||
let md = "Hello markdown!!";
|
let md = "Hello markdown!!\n";
|
||||||
let (rem, block) = paragraph_block(md).unwrap();
|
let (rem, block) = paragraph_block(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
block,
|
block,
|
||||||
Block::Paragraph {
|
Block::Paragraph {
|
||||||
@@ -107,7 +146,7 @@ fn main() {
|
|||||||
";
|
";
|
||||||
let (rem, block) = code_block(md).unwrap();
|
let (rem, block) = code_block(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
block,
|
block,
|
||||||
Block::Code {
|
Block::Code {
|
||||||
@@ -125,7 +164,7 @@ echo \"hello world\"
|
|||||||
";
|
";
|
||||||
let (rem, block) = code_block(md).unwrap();
|
let (rem, block) = code_block(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
block,
|
block,
|
||||||
Block::Code {
|
Block::Code {
|
||||||
@@ -145,11 +184,11 @@ echo hello
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn level_1_heading() {
|
fn level_2_heading() {
|
||||||
let md = "## Heading2";
|
let md = "## Heading2\n";
|
||||||
let (rem, block) = heading_block(md).unwrap();
|
let (rem, block) = heading_block(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
block,
|
block,
|
||||||
Block::Heading {
|
Block::Heading {
|
||||||
@@ -163,16 +202,16 @@ echo hello
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heading_no_space() {
|
fn heading_no_space() {
|
||||||
let md = "#heading";
|
let md = "#heading\n";
|
||||||
assert!(heading_block(md).is_err());
|
assert!(heading_block(md).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn level_6_heading() {
|
fn level_6_heading() {
|
||||||
let md = "###### Heading6";
|
let md = "###### Heading6\n";
|
||||||
let (rem, block) = heading_block(md).unwrap();
|
let (rem, block) = heading_block(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
block,
|
block,
|
||||||
Block::Heading {
|
Block::Heading {
|
||||||
@@ -186,7 +225,7 @@ echo hello
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_level_7_heading() {
|
fn no_level_7_heading() {
|
||||||
let md = "####### Heading7";
|
let md = "####### Heading7\n";
|
||||||
assert!(heading_block(md).is_err());
|
assert!(heading_block(md).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,4 +272,155 @@ Hello MD
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_unordered_list() {
|
||||||
|
let md = "- a\n- b\n- c\n- b again with some `code`\n\n";
|
||||||
|
let (rem, block) = unordered_list(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rem, "\n");
|
||||||
|
assert_eq!(
|
||||||
|
block,
|
||||||
|
Block::List {
|
||||||
|
ordered: false,
|
||||||
|
items: vec![
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "a".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "b".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "c".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![
|
||||||
|
Inline::Text {
|
||||||
|
content: "b again with some ".to_string()
|
||||||
|
},
|
||||||
|
Inline::Code {
|
||||||
|
content: "code".to_string()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simple_ordered_list() {
|
||||||
|
let md = "1. a\n1. b\n1. c\n1. b again with some `code`\n\n";
|
||||||
|
let (rem, block) = ordered_list(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rem, "\n");
|
||||||
|
assert_eq!(
|
||||||
|
block,
|
||||||
|
Block::List {
|
||||||
|
ordered: true,
|
||||||
|
items: vec![
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "a".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "b".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "c".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![
|
||||||
|
Inline::Text {
|
||||||
|
content: "b again with some ".to_string()
|
||||||
|
},
|
||||||
|
Inline::Code {
|
||||||
|
content: "code".to_string()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_block() {
|
||||||
|
let md = "\n";
|
||||||
|
|
||||||
|
let (rem, ast) = block(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rem, "");
|
||||||
|
assert_eq!(ast, Block::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complex_1() {
|
||||||
|
let md = "
|
||||||
|
# hello
|
||||||
|
|
||||||
|
## second header
|
||||||
|
|
||||||
|
blablabla
|
||||||
|
|
||||||
|
1. hahaha
|
||||||
|
1. second
|
||||||
|
|
||||||
|
";
|
||||||
|
let (rem, ast) = blocks(md).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rem, "");
|
||||||
|
assert_eq!(
|
||||||
|
ast,
|
||||||
|
vec![
|
||||||
|
Block::Null,
|
||||||
|
Block::Heading {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hello".to_string()
|
||||||
|
}],
|
||||||
|
level: 1
|
||||||
|
},
|
||||||
|
Block::Null,
|
||||||
|
Block::Heading {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "second header".to_string()
|
||||||
|
}],
|
||||||
|
level: 2
|
||||||
|
},
|
||||||
|
Block::Null,
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "blablabla".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Null,
|
||||||
|
Block::List {
|
||||||
|
ordered: true,
|
||||||
|
items: vec![
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "hahaha".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
Block::Paragraph {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "second".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use nom::{
|
|||||||
Parser,
|
Parser,
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{is_not, tag},
|
bytes::complete::{is_not, tag},
|
||||||
multi::many0,
|
multi::many1,
|
||||||
sequence::delimited,
|
sequence::delimited,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ use crate::ast::{Href, Inline};
|
|||||||
use super::MarkdownParseError;
|
use super::MarkdownParseError;
|
||||||
|
|
||||||
pub fn inline(input: &str) -> IResult<&str, Vec<Inline>, MarkdownParseError> {
|
pub fn inline(input: &str) -> IResult<&str, Vec<Inline>, MarkdownParseError> {
|
||||||
many0(alt((
|
many1(alt((
|
||||||
text_inline,
|
text_inline,
|
||||||
bold_inline,
|
bold_inline,
|
||||||
italic_inline,
|
italic_inline,
|
||||||
@@ -85,10 +85,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_text() {
|
fn single_text() {
|
||||||
let md = "hello normal inline";
|
let md = "hello normal inline\n";
|
||||||
let (rem, parsed) = text_inline(md).unwrap();
|
let (rem, parsed) = text_inline(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed,
|
parsed,
|
||||||
Inline::Text {
|
Inline::Text {
|
||||||
@@ -184,7 +184,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normal_and_nested_bold() {
|
fn normal_and_nested_bold() {
|
||||||
let md = "some **extra* bold* stuff";
|
let md = "some *very *extra* bold* stuff";
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
let (rem, parsed) = inline(md).unwrap();
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
assert_eq!(rem, "");
|
||||||
@@ -194,7 +194,11 @@ mod test {
|
|||||||
Inline::Text {
|
Inline::Text {
|
||||||
content: "some ".to_string()
|
content: "some ".to_string()
|
||||||
},
|
},
|
||||||
Inline::Bold { inner: vec![] },
|
Inline::Bold {
|
||||||
|
inner: vec![Inline::Text {
|
||||||
|
content: "very ".to_string()
|
||||||
|
}]
|
||||||
|
},
|
||||||
Inline::Text {
|
Inline::Text {
|
||||||
content: "extra".to_string()
|
content: "extra".to_string()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//! A weird markdown parser. Please don't forget to add a newline in the end of a file or it won't
|
||||||
|
//! work :)
|
||||||
|
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
|||||||
Reference in New Issue
Block a user