diff --git a/.gitea/workflows/cargo-release.yml b/.gitea/workflows/cargo-release.yml.notready similarity index 100% rename from .gitea/workflows/cargo-release.yml rename to .gitea/workflows/cargo-release.yml.notready diff --git a/Cargo.lock b/Cargo.lock index 33f2ea2..4caf412 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "marginal" -version = "0.1.0" +version = "0.1.2" dependencies = [ "nom", ] diff --git a/Cargo.toml b/Cargo.toml index 5ebb963..d4b710b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marginal" -version = "0.1.0" +version = "0.1.2" edition = "2024" publish = ["gitea"] diff --git a/bacon.toml b/bacon.toml index ec3d796..57b320e 100644 --- a/bacon.toml +++ b/bacon.toml @@ -66,6 +66,7 @@ command = [ "cargo", "nextest", "run", "--hide-progress-bar", "--failure-output", "final", + "--no-fail-fast", ] need_stdout = true analyzer = "nextest" diff --git a/src/ast.rs b/src/ast.rs index 3474045..4c241ad 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -31,4 +31,6 @@ pub enum Block { Code { content: String, lang: String }, Quote { inner: Box }, Paragraph { inner: Vec }, + UnorderedList { items: Vec }, + OrderedList { items: Vec }, } diff --git a/src/generator/block.rs b/src/generator/block.rs index 1b520be..0d33b4d 100644 --- a/src/generator/block.rs +++ b/src/generator/block.rs @@ -15,12 +15,28 @@ impl ToHtml for Vec { impl ToHtml for Block { fn to_html(&self) -> String { match self { - Block::Paragraph { inner } => format!("

{}


", inner.to_html()), + Block::Paragraph { inner } => format!("

{}

", inner.to_html()), Block::Heading { inner, level } => { format!("{}", level, inner.to_html(), level) } Block::Code { content, lang: _ } => format!("
{content}
"), Block::Quote { inner } => format!("
{}
", inner.to_html()), + Block::UnorderedList { items } => { + let mut html = "
    ".to_string(); + for item in items { + html.push_str(&format!("
  • {}
  • ", &item.to_html())); + } + html.push_str("
"); + html + } + Block::OrderedList { items } => { + let mut html = "
    ".to_string(); + for item in items { + html.push_str(&format!("
  1. {}
  2. ", &item.to_html())); + } + html.push_str("
"); + html + } } } } diff --git a/src/parser/block.rs b/src/parser/block.rs index f1f96ad..433841a 100644 --- a/src/parser/block.rs +++ b/src/parser/block.rs @@ -3,6 +3,7 @@ use nom::{ IResult, Parser, branch::alt, bytes::complete::{tag, take_until}, + combinator::peek, multi::{many_m_n, many0, many1}, sequence::{delimited, terminated}, }; @@ -14,8 +15,16 @@ pub fn blocks(input: &str) -> IResult<&str, Vec, MarkdownParseError> { } pub fn block(input: &str) -> IResult<&str, Block, MarkdownParseError> { + //alt((heading_block, code_block, quote_block, paragraph_block)).parse(input) terminated( - alt((heading_block, code_block, quote_block, paragraph_block)), + alt(( + heading_block, + code_block, + quote_block, + paragraph_block, + ordered_list, + unordered_list, + )), tag("\n"), ) .parse(input) @@ -45,7 +54,7 @@ fn code_block(input: &str) -> IResult<&str, Block, MarkdownParseError> { delimited( tag("```"), (take_until("\n"), tag("\n"), take_until("```\n")), - tag("```\n"), + (tag("```"), peek(tag("\n"))), ) .parse(input) .map(|(rem, (lang, _, code))| { @@ -72,6 +81,20 @@ 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::UnorderedList { 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::OrderedList { items }) + }) +} + //|-------------------------------------------------------------------------------| //| TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS | //|-------------------------------------------------------------------------------| @@ -83,10 +106,10 @@ mod test { #[test] fn single_paragraph() { - let md = "Hello markdown!!"; + let md = "Hello markdown!!\n"; let (rem, block) = paragraph_block(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( block, Block::Paragraph { @@ -107,7 +130,7 @@ fn main() { "; let (rem, block) = code_block(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( block, Block::Code { @@ -125,7 +148,7 @@ echo \"hello world\" "; let (rem, block) = code_block(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( block, Block::Code { @@ -145,11 +168,11 @@ echo hello } #[test] - fn level_1_heading() { - let md = "## Heading2"; + fn level_2_heading() { + let md = "## Heading2\n"; let (rem, block) = heading_block(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( block, Block::Heading { @@ -163,16 +186,16 @@ echo hello #[test] fn heading_no_space() { - let md = "#heading"; + let md = "#heading\n"; assert!(heading_block(md).is_err()); } #[test] fn level_6_heading() { - let md = "###### Heading6"; + let md = "###### Heading6\n"; let (rem, block) = heading_block(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( block, Block::Heading { @@ -186,7 +209,7 @@ echo hello #[test] fn no_level_7_heading() { - let md = "####### Heading7"; + let md = "####### Heading7\n"; assert!(heading_block(md).is_err()); } @@ -233,4 +256,84 @@ 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::UnorderedList { + 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::OrderedList { + 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() + } + ] + }, + ] + } + ); + } } diff --git a/src/parser/inline.rs b/src/parser/inline.rs index 1c4941f..16c68b7 100644 --- a/src/parser/inline.rs +++ b/src/parser/inline.rs @@ -85,10 +85,10 @@ mod test { #[test] fn single_text() { - let md = "hello normal inline"; + let md = "hello normal inline\n"; let (rem, parsed) = text_inline(md).unwrap(); - assert_eq!(rem, ""); + assert_eq!(rem, "\n"); assert_eq!( parsed, Inline::Text { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1706e49..e4adcc9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -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}; pub mod block;