rendering md with layouts
This commit is contained in:
Generated
+67
-1
@@ -1056,8 +1056,11 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"eframe",
|
"eframe",
|
||||||
"egui_ltreeview",
|
"egui_ltreeview",
|
||||||
|
"markdown",
|
||||||
"rfd",
|
"rfd",
|
||||||
|
"serde",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1652,6 +1655,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markdown"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-id",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
@@ -2549,6 +2561,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -2815,12 +2836,36 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime 0.7.0",
|
||||||
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.27"
|
version = "0.22.27"
|
||||||
@@ -2828,10 +2873,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||||
|
dependencies = [
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
@@ -2889,6 +2949,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-id"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ eframe = "0.32.1"
|
|||||||
egui_ltreeview = "0.5.3"
|
egui_ltreeview = "0.5.3"
|
||||||
rfd = "0.15.4"
|
rfd = "0.15.4"
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
|
toml = "0.9.5"
|
||||||
|
markdown = "1.0.0"
|
||||||
|
serde = "1.0.219"
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
mod fleen_app;
|
mod fleen_app;
|
||||||
|
mod renderer;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use eframe::egui::{Color32, Context, Id, RichText};
|
use eframe::egui::{Color32, Context, Id, RichText};
|
||||||
|
|||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
use std::{default, fs};
|
||||||
|
use std::io::Error;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use eframe::egui::TextBuffer;
|
||||||
|
use markdown::message::Message;
|
||||||
|
use markdown::{CompileOptions, Constructs, ParseOptions};
|
||||||
|
use markdown::mdast::Node;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub struct RenderOutput {
|
||||||
|
pub filename: PathBuf,
|
||||||
|
pub contents: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Frontmatter {
|
||||||
|
layout: Option<String>,
|
||||||
|
title: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frontmatter {
|
||||||
|
fn apply_layout(self, content: String, root: PathBuf) -> Result<String, RenderError> {
|
||||||
|
let title = self.title.unwrap_or(String::new());
|
||||||
|
if let Some(layout) = self.layout {
|
||||||
|
let absolute_layout = root.join(layout);
|
||||||
|
let layout = fs::read_to_string(absolute_layout.clone()).map_err(|e| RenderError::FileReadError(e, absolute_layout))?;
|
||||||
|
Ok(layout.replace("$title", title.as_str()).replace("$content", content.as_str()))
|
||||||
|
} else {
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum RenderError {
|
||||||
|
#[error("Error reading {1}: {0}")]
|
||||||
|
FileReadError(Error, PathBuf),
|
||||||
|
#[error("Error parsing Markdown {1}: {0}")]
|
||||||
|
MarkdownParseError(Message, PathBuf),
|
||||||
|
#[error("Error parsing frontmatter in {1}: {0}")]
|
||||||
|
FrontmatterParseError(toml::de::Error, PathBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(source: PathBuf, root: PathBuf) -> Result<RenderOutput, RenderError> {
|
||||||
|
let contents = match source.extension().map(|o| o.to_str().unwrap()) {
|
||||||
|
Some("md") => render_as_markdown(source.clone(), root),
|
||||||
|
_ => todo!()
|
||||||
|
}?;
|
||||||
|
let filename = source.with_extension("html");
|
||||||
|
Ok(RenderOutput { filename, contents })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_as_markdown(source: PathBuf, root: PathBuf) -> Result<String, RenderError> {
|
||||||
|
let absolute_source = root.join(source.clone());
|
||||||
|
let contents = fs::read_to_string(absolute_source.clone()).map_err(|e| RenderError::FileReadError(e, source.clone()))?;
|
||||||
|
let options = markdown::Options {
|
||||||
|
parse: ParseOptions {
|
||||||
|
constructs: Constructs {
|
||||||
|
frontmatter: true,
|
||||||
|
gfm_table: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let html = markdown::to_html_with_options(contents.as_str(), &options).map_err(|e| RenderError::MarkdownParseError(e, source.clone()))?;
|
||||||
|
let ast = markdown::to_mdast(contents.as_str(), &options.parse).map_err(|e| RenderError::MarkdownParseError(e, source.clone()))?;
|
||||||
|
|
||||||
|
if let Some(frontmatter) = find_frontmatter(ast, source.clone())? {
|
||||||
|
frontmatter.apply_layout(html, root)
|
||||||
|
} else {
|
||||||
|
Ok(html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_frontmatter(node: Node, source: PathBuf) -> Result<Option<Frontmatter>, RenderError> {
|
||||||
|
if let Some(children) = node.children() {
|
||||||
|
for child in children.into_iter() {
|
||||||
|
if let Node::Toml(toml_str) = child {
|
||||||
|
let fmatter: Frontmatter = toml::from_str(toml_str.value.as_str()).map_err(|e| RenderError::FrontmatterParseError(e, source))?;
|
||||||
|
return Ok(Some(fmatter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
} else { Ok(None) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rendering_markdown() {
|
||||||
|
match render("index.md".into(), "./testdata".into()) {
|
||||||
|
Err(e) => println!("{}", e),
|
||||||
|
Ok(ro) => {
|
||||||
|
println!("File: {}", ro.filename.to_str().unwrap());
|
||||||
|
println!("Contents:\n\n{}", ro.contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
+++
|
||||||
|
layout = "layouts/post.html"
|
||||||
|
title = "Pest Toast"
|
||||||
|
+++
|
||||||
|
Test
|
||||||
|
===
|
||||||
|
|
||||||
|
This is a test. This is only a test.
|
||||||
|
~~~rust
|
||||||
|
fn main() {
|
||||||
|
println!("Here is some Rust")
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|This|is|
|
||||||
|
|----|--|
|
||||||
|
|a|table|
|
||||||
|
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>blog - $title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
$content
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user