diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f14ce13..bdaba01 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -79,7 +79,7 @@ jobs: # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability strategy: matrix: - msrv: [1.67.1] #version at the moment of creation + msrv: [1.80.0] # due to Option::take_if usage name: ubuntu / ${{ matrix.msrv }} steps: - uses: actions/checkout@v4 diff --git a/Cargo.toml b/Cargo.toml index b33315f..e87f0b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "yamd" -version = "0.13.3" +description = "Yet Another Markdown Document (flavour)" +version = "0.14.0" edition = "2021" license = "MIT OR Apache-2.0" -description = "Yet Another Markdown Document (flavor)" repository = "https://github.com/Lurk/yamd" readme = "README.md" keywords = ["markdown", "parser"] +[dependencies] +serde = { version = "1.0.197", features = ["derive"] } + [dev-dependencies] pretty_assertions = "1.4.0" -[dependencies] -serde = { version = "1.0.197", features = ["derive"] } -chrono = { version = "0.4.37", features = ["serde"] } -serde_yaml = "0.9.34" + diff --git a/readme.md b/readme.md index 461d11e..b2d3d7b 100644 --- a/readme.md +++ b/readme.md @@ -1,42 +1,73 @@ -# Yet another markdown document flavour (YAMD) +# yamd + [![codecov](https://codecov.io/gh/Lurk/yamd/branch/main/graph/badge.svg?token=F8KRUYI1AA)](https://codecov.io/gh/Lurk/yamd) [![crates.io](https://img.shields.io/crates/v/yamd.svg)](https://crates.io/crates/yamd) [![Released API docs](https://docs.rs/yamd/badge.svg)](https://docs.rs/yamd) -## Status + + +YAMD - Yet Another Markdown Document (flavour) + +Simplified version of [CommonMark](https://spec.commonmark.org/). + +For formatting check YAMD struct documentation. + +## Reasoning + +Simplified set of rules allows to have simpler, more efficient, parser and renderer. +YAMD does not provide render functionality, instead it is a [serde] +serializable structure that allows you to write any renderer for that structure. All HTML +equivalents in this doc are provided as an example to what it can be rendered. + +## Difference from CommonMark + +### Escaping + +Escaping done on a [lexer] level. Every symbol following the `\` symbol will be treated as a +literal. + +Example: + +| YAMD | HTML equivalent | +|-----------|-----------------| +| `\**foo**`|`
**foo**
` | + +### Escape character + +To get `\` - `\\` must be used. + +Example: -It is not ready to poke around. There is significant API changes expected. +| YAMD | HTML equivalent | +|---------------|-----------------------| +| `\\**foo**` |`\foo
` | -## Why? -Initial idea was to create human readable text format for my blog. Why not existing flavour? -Existing flavours do not have elements like image gallery, dividers, highlight, etc. +### Precedence -## Features +[CommonMark](https://spec.commonmark.org/0.31.2/#precedence) defines container blocks and leaf +blocks. And that container block indicator has higher precedence. YAMD does not discriminate by +block type, every node (block) is the same. In practice, there are no additional rules to encode +and remember. -Deserialize markdown to YAMD struct, Serialize YAMD struct to markdown. +Example: -## Example +| YAMD | HTML equivalent | +|-----------------------|-----------------------------------------------| +| ``- `one\n- two` `` | `one\n- two
**foo**
` | //! -//! Element that starts with "\`\`\`lang\n", ends with "\n```" and has code in between. +//! ## Precedence //! -//! Example: -//! ```text -//! \```rust -//! let x = 1; -//! \``` -//! ``` -//! ^ did not figured out how to escape \`\`\` in rustdoc -//! -//! ### Image -//! -//! Element that starts with "!" has image alt text in [] and followed by image url in () -//! -//! Example: -//! ```text -//! ![alt text](url) -//! ``` -//! -//! ### ImageGallery -//! -//! Element that starts with "!!!\n", ends with "\n!!!", and has image elements in between +//! [CommonMark](https://spec.commonmark.org/0.31.2/#precedence) defines container blocks and leaf +//! blocks. And that container block indicator has higher precedence. YAMD does not discriminate by +//! block type, every node (block) is the same. In practice, there are no additional rules to encode +//! and remember. //! //! Example: -//! ```text -//! !!! -//! ![alt text](url) -//! ![alt text](url) -//! !!! -//! ``` -//! -//! ### Highlight -//! -//! Element that starts with ">>>\n", followed by optional title that starts with ">> " and ends with a new line, -//! followed by optional icon specifier that starts with "> " and ends with a new line, followed by body that can -//! contain any number of paragraph elements -//! -//! Example: -//! ```text -//! >>> -//! >> Title -//! > icon -//! body -//! -//! can be multiple paragraphs long -//! >>> -//! ``` -//! no header and no icon: -//! ```text -//! >>> -//! body -//! >>> -//! ``` -//! -//! ### Divider -//! -//! Element that consist of five "-" characters in a row and ends with a new line or EOF. -//! -//! Example: ```-----``` -//! -//! ### Embed -//! -//! Element that starts with "{{" followed by embed type, followed by "|" followed by embed url, followed by "}}" -//! and ends with a new line or EOF. -//! -//! Example: ```{{youtube|https://www.youtube.com/embed/wsfdjlkjsdf}}``` -//! -//! -//! ### Paragraph -//! -//! Element that starts with any character that is not a special character and ends with a new line or EOF. -//! Can contain text, bold text, italic text, strikethrough text, anchors, and inline code. -//! -//! #### Anchor -//! -//! element that starts with "[" followed by text, followed by "]" followed by "(" followed by url, followed by ")" -//! -//! example: ```[Yamd repo](https://github.com/Lurk/yamd)``` -//! -//! #### Inline code //! -//! element that starts with "\`" followed by text and ends with "\`" +//! | YAMD | HTML equivalent | +//! |-----------------------|-----------------------------------------------| +//! | ``- `one\n- two` `` | `one\n- two
[link]
` | +/// | `[link](url with unclosed paren` | `[link](url with unclosed paren
` | +/// +#[derive(Debug, PartialEq, Serialize, Clone, Eq)] pub struct Anchor { pub text: String, pub url: String, @@ -19,82 +43,3 @@ impl Anchor { } } } - -impl Display for Anchor { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}]({})", self.text, self.url) - } -} - -impl Parse for Anchor { - fn parse(input: &str, current_position: usize) -> Option<(Self, usize)> { - if input[current_position..].starts_with('[') { - if let Some(middle) = input[current_position + 1..].find("](") { - let mut level = 1; - for (i, c) in input[current_position + middle + 3..].char_indices() { - if c == '(' { - level += 1; - } else if c == ')' { - level -= 1; - } - if level == 0 { - return Some(( - Anchor::new( - &input[current_position + 1..current_position + middle + 1], - &input[current_position + middle + 3 - ..current_position + middle + 3 + i], - ), - middle + 3 + i + 1, - )); - } - } - } - } - None - } -} - -#[cfg(test)] -mod tests { - use crate::toolkit::parser::Parse; - - use super::Anchor; - use pretty_assertions::assert_eq; - - #[test] - fn happy_path() { - let a = Anchor::new("nice link", "https://test.io"); - assert_eq!(a.text, "nice link"); - assert_eq!(a.url, "https://test.io"); - } - - #[test] - fn serialize() { - let a: String = Anchor::new("nice link", "https://test.io").to_string(); - assert_eq!(a, "[nice link](https://test.io)".to_string()); - } - - #[test] - fn parse() { - assert_eq!(Anchor::parse("[1](2)", 0), Some((Anchor::new("1", "2"), 6))); - assert_eq!(Anchor::parse("[1", 0), None); - assert_eq!(Anchor::parse("[1](2", 0), None); - } - - #[test] - fn deserilalze_with_parentesis_in_url() { - assert_eq!( - Anchor::parse( - "[the Rope data structure](https://en.wikipedia.org/wiki/Rope_(data_structure))", - 0 - ), - Some(( - Anchor::new( - "the Rope data structure", - "https://en.wikipedia.org/wiki/Rope_(data_structure)" - ), - 78 - )) - ); - } -} diff --git a/src/nodes/bold.rs b/src/nodes/bold.rs index d33860d..b126104 100644 --- a/src/nodes/bold.rs +++ b/src/nodes/bold.rs @@ -1,18 +1,13 @@ -use std::fmt::Display; - use serde::Serialize; -use crate::{ - nodes::{italic::Italic, strikethrough::Strikethrough, text::Text}, - toolkit::parser::{parse_to_consumer, parse_to_parser, Branch, Consumer, Parse, Parser}, -}; +use super::{Italic, Strikethrough}; -#[derive(Debug, PartialEq, Serialize, Clone)] -#[serde(tag = "type")] +#[derive(Debug, PartialEq, Serialize, Clone, Eq)] +#[serde(tag = "type", content = "value")] pub enum BoldNodes { Italic(Italic), Strikethrough(Strikethrough), - Text(Text), + Text(String), } impl Fromlet a = 42;
+/// ```
+
+#[derive(Debug, PartialEq, Serialize, Clone, Eq)]
pub struct Code {
pub lang: String,
pub code: String,
@@ -18,57 +39,3 @@ impl Code {
}
}
}
-
-impl Display for Code {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "```{}\n{}\n```", self.lang, self.code)
- }
-}
-
-impl Parse for Code {
- fn parse(input: &str, current_position: usize) -> Option<(Self, usize)>
- where
- Self: Sized,
- {
- if input[current_position..].starts_with("```") {
- if let Some(lang) = input[current_position + 3..].find('\n') {
- if let Some(end) = input[current_position + 3 + lang + 1..].find("\n```") {
- return Some((
- Code::new(
- &input[current_position + 3..current_position + 3 + lang],
- &input[current_position + 3 + lang + 1
- ..current_position + 3 + lang + 1 + end],
- ),
- 3 + lang + 1 + end + 4,
- ));
- }
- }
- }
- None
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::{nodes::code::Code, toolkit::parser::Parse};
- use pretty_assertions::assert_eq;
-
- #[test]
- fn serialize() {
- assert_eq!(
- Code::new("rust", "let foo:usize=1;").to_string(),
- String::from("```rust\nlet foo:usize=1;\n```")
- );
- }
-
- #[test]
- fn parser() {
- assert_eq!(
- Code::parse("```rust\nlet a=1;\n```", 0),
- Some((Code::new("rust", "let a=1;"), 20))
- );
- assert_eq!(Code::parse("```rust\nlet a=1;\n", 0), None);
- assert_eq!(Code::parse("not a code block", 0), None);
- assert_eq!(Code::parse("``````", 0), None);
- }
-}
diff --git a/src/nodes/code_span.rs b/src/nodes/code_span.rs
new file mode 100644
index 0000000..8910eb1
--- /dev/null
+++ b/src/nodes/code_span.rs
@@ -0,0 +1,28 @@
+use serde::Serialize;
+
+/// # Code span
+///
+/// Any characters except [Terminator](type@crate::lexer::TokenKind::Terminator) surrounded by a
+/// [Backtick](type@crate::lexer::TokenKind::Backtick) of length 1.
+///
+/// Example:
+///
+/// ```text
+/// `anything even EOL
+/// can be it`
+/// ```
+///
+/// HTML equivalent:
+///
+/// ```html
+/// anything even EOL
+/// can be it
+/// ```
+#[derive(Debug, PartialEq, Serialize, Clone, Eq)]
+pub struct CodeSpan(pub String);
+
+impl CodeSpan {
+ pub fn new