Compare commits
1 Commits
dev
...
24fc2deae1
| Author | SHA1 | Date | |
|---|---|---|---|
| 24fc2deae1 |
143
Cargo.lock
generated
143
Cargo.lock
generated
@@ -2,147 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.6.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is_terminal_polyfill",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "3.0.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"once_cell_polyfill",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.5.53"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.5.53"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is_terminal_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "marginal"
|
name = "marginal"
|
||||||
version = "0.0.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"clap",
|
|
||||||
"nom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "8.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "once_cell_polyfill"
|
|
||||||
version = "1.70.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.11.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.61.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -4,5 +4,3 @@ version = "0.0.1"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "4.5.53"
|
|
||||||
nom = { version = "8.0.0", features = ["std"] }
|
|
||||||
|
|||||||
132
bacon.toml
132
bacon.toml
@@ -1,132 +0,0 @@
|
|||||||
# This is a configuration file for the bacon tool
|
|
||||||
#
|
|
||||||
# Complete help on configuration: https://dystroy.org/bacon/config/
|
|
||||||
#
|
|
||||||
# You may check the current default at
|
|
||||||
# https://github.com/Canop/bacon/blob/main/defaults/default-bacon.toml
|
|
||||||
|
|
||||||
default_job = "check"
|
|
||||||
env.CARGO_TERM_COLOR = "always"
|
|
||||||
|
|
||||||
[jobs.check]
|
|
||||||
command = ["cargo", "check"]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
[jobs.check-all]
|
|
||||||
command = ["cargo", "check", "--all-targets"]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
# Run clippy on the default target
|
|
||||||
[jobs.clippy]
|
|
||||||
command = ["cargo", "clippy"]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
# Run clippy on all targets
|
|
||||||
# To disable some lints, you may change the job this way:
|
|
||||||
# [jobs.clippy-all]
|
|
||||||
# command = [
|
|
||||||
# "cargo", "clippy",
|
|
||||||
# "--all-targets",
|
|
||||||
# "--",
|
|
||||||
# "-A", "clippy::bool_to_int_with_if",
|
|
||||||
# "-A", "clippy::collapsible_if",
|
|
||||||
# "-A", "clippy::derive_partial_eq_without_eq",
|
|
||||||
# ]
|
|
||||||
# need_stdout = false
|
|
||||||
[jobs.clippy-all]
|
|
||||||
command = ["cargo", "clippy", "--all-targets"]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
# Run clippy in pedantic mode
|
|
||||||
# The 'dismiss' feature may come handy
|
|
||||||
[jobs.pedantic]
|
|
||||||
command = [
|
|
||||||
"cargo", "clippy",
|
|
||||||
"--",
|
|
||||||
"-W", "clippy::pedantic",
|
|
||||||
]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
# This job lets you run
|
|
||||||
# - all tests: bacon test
|
|
||||||
# - a specific test: bacon test -- config::test_default_files
|
|
||||||
# - the tests of a package: bacon test -- -- -p config
|
|
||||||
[jobs.test]
|
|
||||||
command = [
|
|
||||||
"cargo", "nextest", "run",
|
|
||||||
"--hide-progress-bar",
|
|
||||||
"--failure-output", "final",
|
|
||||||
"--no-fail-fast"
|
|
||||||
]
|
|
||||||
need_stdout = true
|
|
||||||
analyzer = "nextest"
|
|
||||||
|
|
||||||
[jobs.nextest]
|
|
||||||
command = [
|
|
||||||
"cargo", "nextest", "run",
|
|
||||||
"--hide-progress-bar",
|
|
||||||
"--failure-output", "final",
|
|
||||||
]
|
|
||||||
need_stdout = true
|
|
||||||
analyzer = "nextest"
|
|
||||||
|
|
||||||
[jobs.doc]
|
|
||||||
command = ["cargo", "doc", "--no-deps"]
|
|
||||||
need_stdout = false
|
|
||||||
|
|
||||||
# If the doc compiles, then it opens in your browser and bacon switches
|
|
||||||
# to the previous job
|
|
||||||
[jobs.doc-open]
|
|
||||||
command = ["cargo", "doc", "--no-deps", "--open"]
|
|
||||||
need_stdout = false
|
|
||||||
on_success = "back" # so that we don't open the browser at each change
|
|
||||||
|
|
||||||
# You can run your application and have the result displayed in bacon,
|
|
||||||
# if it makes sense for this crate.
|
|
||||||
[jobs.run]
|
|
||||||
command = [
|
|
||||||
"cargo", "run",
|
|
||||||
# put launch parameters for your program behind a `--` separator
|
|
||||||
]
|
|
||||||
need_stdout = true
|
|
||||||
allow_warnings = true
|
|
||||||
background = true
|
|
||||||
|
|
||||||
# Run your long-running application (eg server) and have the result displayed in bacon.
|
|
||||||
# For programs that never stop (eg a server), `background` is set to false
|
|
||||||
# to have the cargo run output immediately displayed instead of waiting for
|
|
||||||
# program's end.
|
|
||||||
# 'on_change_strategy' is set to `kill_then_restart` to have your program restart
|
|
||||||
# on every change (an alternative would be to use the 'F5' key manually in bacon).
|
|
||||||
# If you often use this job, it makes sense to override the 'r' key by adding
|
|
||||||
# a binding `r = job:run-long` at the end of this file .
|
|
||||||
# A custom kill command such as the one suggested below is frequently needed to kill
|
|
||||||
# long running programs (uncomment it if you need it)
|
|
||||||
[jobs.run-long]
|
|
||||||
command = [
|
|
||||||
"cargo", "run",
|
|
||||||
# put launch parameters for your program behind a `--` separator
|
|
||||||
]
|
|
||||||
need_stdout = true
|
|
||||||
allow_warnings = true
|
|
||||||
background = false
|
|
||||||
on_change_strategy = "kill_then_restart"
|
|
||||||
# kill = ["pkill", "-TERM", "-P"]
|
|
||||||
|
|
||||||
# This parameterized job runs the example of your choice, as soon
|
|
||||||
# as the code compiles.
|
|
||||||
# Call it as
|
|
||||||
# bacon ex -- my-example
|
|
||||||
[jobs.ex]
|
|
||||||
command = ["cargo", "run", "--example"]
|
|
||||||
need_stdout = true
|
|
||||||
allow_warnings = true
|
|
||||||
|
|
||||||
# You may define here keybindings that would be specific to
|
|
||||||
# a project, for example a shortcut to launch a specific job.
|
|
||||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
|
||||||
# should go in your personal global prefs.toml file instead.
|
|
||||||
[keybindings]
|
|
||||||
# alt-m = "job:my-job"
|
|
||||||
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target
|
|
||||||
p = "job:pedantic"
|
|
||||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
@@ -0,0 +1 @@
|
|||||||
|
stable
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[toolchain]
|
|
||||||
channel = "stable"
|
|
||||||
targets = [
|
|
||||||
"x86_64-unknown-linux-gnu"
|
|
||||||
]
|
|
||||||
components = [
|
|
||||||
"clippy",
|
|
||||||
"rustfmt",
|
|
||||||
"rust-analyzer"
|
|
||||||
]
|
|
||||||
34
src/ast.rs
34
src/ast.rs
@@ -1,34 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Inline {
|
|
||||||
Bold { inner: Vec<Inline> },
|
|
||||||
Italic { inner: Vec<Inline> },
|
|
||||||
Link { inner: Vec<Inline>, href: Href },
|
|
||||||
Code { content: String },
|
|
||||||
Text { content: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct Href(pub String);
|
|
||||||
|
|
||||||
impl Href {
|
|
||||||
pub fn new(href: &str) -> Self {
|
|
||||||
// can check for link correctness
|
|
||||||
Self(href.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Href {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Block {
|
|
||||||
Heading { inner: Vec<Inline>, level: u8 },
|
|
||||||
Code { content: String, lang: String },
|
|
||||||
Quote { inner: Box<Block> },
|
|
||||||
Paragraph { inner: Vec<Inline> },
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
use crate::ast::Block;
|
|
||||||
|
|
||||||
use super::ToHtml;
|
|
||||||
|
|
||||||
impl ToHtml for Vec<Block> {
|
|
||||||
fn to_html(&self) -> String {
|
|
||||||
let mut html = String::new();
|
|
||||||
for block in self.iter() {
|
|
||||||
html.push_str(&block.to_html())
|
|
||||||
}
|
|
||||||
html
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToHtml for Block {
|
|
||||||
fn to_html(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Block::Paragraph { inner } => format!("<p>{}</p><br>", inner.to_html()),
|
|
||||||
Block::Heading { inner, level } => {
|
|
||||||
format!("<h{}>{}</h{}>", level, inner.to_html(), level)
|
|
||||||
}
|
|
||||||
Block::Code { content, lang: _ } => format!("<pre><code>{content}</code></pre>"),
|
|
||||||
Block::Quote { inner } => format!("<div class=\"quote\">{}</div>", inner.to_html()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
use std::{fs, path::PathBuf};
|
|
||||||
|
|
||||||
use crate::parser::block::blocks;
|
|
||||||
|
|
||||||
use super::{GenerationError, ToHtml, TryToHtml};
|
|
||||||
|
|
||||||
impl TryToHtml for PathBuf {
|
|
||||||
fn try_to_html(&self) -> Result<String, GenerationError> {
|
|
||||||
let content_md = fs::read_to_string(self)?;
|
|
||||||
let (_rem, generated) = blocks(&content_md)?;
|
|
||||||
Ok(generated.to_html())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
use crate::ast::Inline;
|
|
||||||
|
|
||||||
use super::ToHtml;
|
|
||||||
|
|
||||||
impl ToHtml for Vec<Inline> {
|
|
||||||
fn to_html(&self) -> String {
|
|
||||||
let mut html = String::new();
|
|
||||||
for inline in self.iter() {
|
|
||||||
html.push_str(&inline.to_html())
|
|
||||||
}
|
|
||||||
html
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToHtml for Inline {
|
|
||||||
fn to_html(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Inline::Text { content } => content.to_owned(),
|
|
||||||
Inline::Bold { inner } => format!("<b>{}</b>", inner.to_html()),
|
|
||||||
Inline::Italic { inner } => format!("<i>{}</i>", inner.to_html()),
|
|
||||||
Inline::Code { content } => format!("<code>{content}</code>"),
|
|
||||||
Inline::Link { inner, href } => {
|
|
||||||
format!("<a href=\"{}\">{}</a>", href, inner.to_html())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use crate::parser::MarkdownParseError;
|
|
||||||
|
|
||||||
pub mod block;
|
|
||||||
pub mod file;
|
|
||||||
pub mod inline;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum GenerationError {
|
|
||||||
IO(std::io::Error),
|
|
||||||
Parse(nom::Err<MarkdownParseError>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for GenerationError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Generation Error: {}",
|
|
||||||
match self {
|
|
||||||
GenerationError::IO(e) => format!("IO: {e}"),
|
|
||||||
GenerationError::Parse(e) => format!("Parse: {e}"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for GenerationError {
|
|
||||||
fn from(value: std::io::Error) -> Self {
|
|
||||||
Self::IO(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<nom::Err<MarkdownParseError>> for GenerationError {
|
|
||||||
fn from(value: nom::Err<MarkdownParseError>) -> Self {
|
|
||||||
Self::Parse(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for GenerationError {}
|
|
||||||
|
|
||||||
pub trait ToHtml {
|
|
||||||
fn to_html(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TryToHtml {
|
|
||||||
fn try_to_html(&self) -> Result<String, GenerationError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
impl<T: ToHtml> TryToHtml for T {
|
|
||||||
fn try_to_html(&self) -> Result<String, GenerationError<impl std::error::Error>> {
|
|
||||||
Ok::<String, GenerationError<nom::Err<MarkdownParseError>>>(self.to_html())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@@ -1,14 +1,6 @@
|
|||||||
use crate::generator::TryToHtml;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
mod ast;
|
mod ast;
|
||||||
mod generator;
|
|
||||||
mod parser;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let infile = PathBuf::from("test.md");
|
|
||||||
|
|
||||||
let html = infile.try_to_html().unwrap();
|
|
||||||
|
|
||||||
println!("{}", html);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
use crate::{ast::Block, parser::inline::inline};
|
|
||||||
use nom::{
|
|
||||||
IResult, Parser,
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::{tag, take_until},
|
|
||||||
multi::{many_m_n, many0, many1},
|
|
||||||
sequence::{delimited, terminated},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::MarkdownParseError;
|
|
||||||
|
|
||||||
pub fn blocks(input: &str) -> IResult<&str, Vec<Block>, MarkdownParseError> {
|
|
||||||
many0(block).parse(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|
||||||
terminated(
|
|
||||||
alt((heading_block, code_block, quote_block, paragraph_block)),
|
|
||||||
tag("\n"),
|
|
||||||
)
|
|
||||||
.parse(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn paragraph_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|
||||||
(inline)
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, inl)| (rem, Block::Paragraph { inner: inl }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn heading_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|
||||||
(many_m_n(1, 6, tag("#")), many1(tag(" ")), inline)
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, (head, _, title))| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Block::Heading {
|
|
||||||
inner: title,
|
|
||||||
level: head.len() as u8,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn code_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|
||||||
delimited(
|
|
||||||
tag("```"),
|
|
||||||
(take_until("\n"), tag("\n"), take_until("```\n")),
|
|
||||||
tag("```\n"),
|
|
||||||
)
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, (lang, _, code))| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Block::Code {
|
|
||||||
content: code.to_string(),
|
|
||||||
lang: lang.to_string(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quote_block(input: &str) -> IResult<&str, Block, MarkdownParseError> {
|
|
||||||
(tag(">"), many0(tag(" ")), block)
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, (_, _, inner))| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Block::Quote {
|
|
||||||
inner: Box::new(inner),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//|-------------------------------------------------------------------------------|
|
|
||||||
//| TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS |
|
|
||||||
//|-------------------------------------------------------------------------------|
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::ast::Inline;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_paragraph() {
|
|
||||||
let md = "Hello markdown!!";
|
|
||||||
let (rem, block) = paragraph_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Paragraph {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "Hello markdown!!".to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_code_block_with_language() {
|
|
||||||
let md = "```rust
|
|
||||||
fn main() {
|
|
||||||
\tprintln!(\"Hello, World\");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
";
|
|
||||||
let (rem, block) = code_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Code {
|
|
||||||
content: "fn main() {\n\tprintln!(\"Hello, World\");\n}\n".to_string(),
|
|
||||||
lang: "rust".to_string(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_code_block_without_language() {
|
|
||||||
let md = "```
|
|
||||||
echo \"hello world\"
|
|
||||||
```
|
|
||||||
";
|
|
||||||
let (rem, block) = code_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Code {
|
|
||||||
content: "echo \"hello world\"\n".to_string(),
|
|
||||||
lang: "".to_string(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_code_block_fail() {
|
|
||||||
let md = "```abc
|
|
||||||
echo hello
|
|
||||||
```errortext
|
|
||||||
";
|
|
||||||
assert!(code_block(md).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn level_1_heading() {
|
|
||||||
let md = "## Heading2";
|
|
||||||
let (rem, block) = heading_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Heading {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "Heading2".to_string()
|
|
||||||
}],
|
|
||||||
level: 2,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn heading_no_space() {
|
|
||||||
let md = "#heading";
|
|
||||||
assert!(heading_block(md).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn level_6_heading() {
|
|
||||||
let md = "###### Heading6";
|
|
||||||
let (rem, block) = heading_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Heading {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "Heading6".to_string()
|
|
||||||
}],
|
|
||||||
level: 6,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_level_7_heading() {
|
|
||||||
let md = "####### Heading7";
|
|
||||||
assert!(heading_block(md).is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_quote_block_with_paragraph() {
|
|
||||||
let md = "> sun tzu\n";
|
|
||||||
let (rem, block) = quote_block(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
block,
|
|
||||||
Block::Quote {
|
|
||||||
inner: Box::new(Block::Paragraph {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "sun tzu".to_string()
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn heading_and_paragraph() {
|
|
||||||
let md = "## Heading
|
|
||||||
Hello MD
|
|
||||||
";
|
|
||||||
let (rem, blocks) = blocks(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
blocks,
|
|
||||||
vec![
|
|
||||||
Block::Heading {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "Heading".to_string()
|
|
||||||
}],
|
|
||||||
level: 2
|
|
||||||
},
|
|
||||||
Block::Paragraph {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "Hello MD".to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
use nom::IResult;
|
|
||||||
use nom::{
|
|
||||||
Parser,
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::{is_not, tag},
|
|
||||||
multi::many0,
|
|
||||||
sequence::delimited,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::ast::{Href, Inline};
|
|
||||||
|
|
||||||
use super::MarkdownParseError;
|
|
||||||
|
|
||||||
pub fn inline(input: &str) -> IResult<&str, Vec<Inline>, MarkdownParseError> {
|
|
||||||
many0(alt((
|
|
||||||
text_inline,
|
|
||||||
bold_inline,
|
|
||||||
italic_inline,
|
|
||||||
code_inline,
|
|
||||||
link_inline,
|
|
||||||
)))
|
|
||||||
.parse(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn text_inline(input: &str) -> IResult<&str, Inline, MarkdownParseError> {
|
|
||||||
is_not("*_`[]\n").parse(input).map(|(rem, con)| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Inline::Text {
|
|
||||||
content: con.to_string(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bold_inline(input: &str) -> IResult<&str, Inline, MarkdownParseError> {
|
|
||||||
delimited(tag("*"), inline, tag("*"))
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, inl)| (rem, Inline::Bold { inner: inl }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn italic_inline(input: &str) -> IResult<&str, Inline, MarkdownParseError> {
|
|
||||||
delimited(tag("_"), inline, tag("_"))
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, inl)| (rem, Inline::Italic { inner: inl }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn code_inline(input: &str) -> IResult<&str, Inline, MarkdownParseError> {
|
|
||||||
delimited(tag("`"), is_not("`\n"), tag("`"))
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, inl)| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Inline::Code {
|
|
||||||
content: inl.to_string(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn link_inline(input: &str) -> IResult<&str, Inline, MarkdownParseError> {
|
|
||||||
(
|
|
||||||
delimited(tag("["), inline, tag("]")),
|
|
||||||
delimited(tag("("), is_not(")\n"), tag(")")),
|
|
||||||
)
|
|
||||||
.parse(input)
|
|
||||||
.map(|(rem, (name, href))| {
|
|
||||||
(
|
|
||||||
rem,
|
|
||||||
Inline::Link {
|
|
||||||
inner: name,
|
|
||||||
href: Href::new(href),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//|-------------------------------------------------------------------------------|
|
|
||||||
//| TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS TESTS |
|
|
||||||
//|-------------------------------------------------------------------------------|
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_text() {
|
|
||||||
let md = "hello normal inline";
|
|
||||||
let (rem, parsed) = text_inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
Inline::Text {
|
|
||||||
content: "hello normal inline".to_string()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_bold() {
|
|
||||||
let md = "*bold text*";
|
|
||||||
let (rem, parsed) = bold_inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold text".to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bold_with_leftovers() {
|
|
||||||
let md = "*bold* leftover";
|
|
||||||
let (rem, parsed) = bold_inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, " leftover");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold".to_string()
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inline_normal_and_bold() {
|
|
||||||
let md = "some *bold* text";
|
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "some ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: " text".to_string()
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn multiple_normal_and_bold() {
|
|
||||||
let md = "some *bold* text and more *bold stuff*";
|
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "some ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: " text and more ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold stuff".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn normal_and_nested_bold() {
|
|
||||||
let md = "some **extra* bold* stuff";
|
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "some ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold { inner: vec![] },
|
|
||||||
Inline::Text {
|
|
||||||
content: "extra".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: " bold".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: " stuff".to_string()
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nested_bold_and_italics() {
|
|
||||||
let md = "some _nested *bold* + italics_, yeah";
|
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "some ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Italic {
|
|
||||||
inner: vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "nested ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "bold".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: " + italics".to_string()
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: ", yeah".to_string()
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn inline_code_bamboozle() {
|
|
||||||
let md = "take some `code and *bold* and _italics_` lmao";
|
|
||||||
let (rem, parsed) = inline(md).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "take some ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Code {
|
|
||||||
content: "code and *bold* and _italics_".to_string()
|
|
||||||
},
|
|
||||||
Inline::Text {
|
|
||||||
content: " lmao".to_string()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bold_link_text() {
|
|
||||||
let md = "[this link is *important*](http://example.com)";
|
|
||||||
let (rem, parsed) = link_inline(md).unwrap();
|
|
||||||
|
|
||||||
println!("{rem}");
|
|
||||||
|
|
||||||
assert_eq!(rem, "");
|
|
||||||
assert_eq!(
|
|
||||||
parsed,
|
|
||||||
Inline::Link {
|
|
||||||
inner: vec![
|
|
||||||
Inline::Text {
|
|
||||||
content: "this link is ".to_string()
|
|
||||||
},
|
|
||||||
Inline::Bold {
|
|
||||||
inner: vec![Inline::Text {
|
|
||||||
content: "important".to_string()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
href: Href("http://example.com".to_string())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use std::fmt::{Debug, Display};
|
|
||||||
|
|
||||||
pub mod block;
|
|
||||||
pub mod inline;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MarkdownParseError {
|
|
||||||
kind: nom::error::ErrorKind,
|
|
||||||
message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for MarkdownParseError {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}, {}", self.kind, self.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl nom::error::ParseError<&str> for MarkdownParseError {
|
|
||||||
fn from_error_kind(input: &str, kind: nom::error::ErrorKind) -> Self {
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
message: format!("at: {}", input),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append(input: &str, kind: nom::error::ErrorKind, other: Self) -> Self {
|
|
||||||
Self {
|
|
||||||
kind,
|
|
||||||
message: format!("{}\nat: {}", other, input),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user