From b1c4bbdb275406e3e14220dc37814d8dabeea48a Mon Sep 17 00:00:00 2001 From: Mylloon Date: Sun, 10 Nov 2024 10:55:18 +0100 Subject: [PATCH] split metadata of files and markdown reader --- src/routes/blog.rs | 3 +- src/routes/contact.rs | 3 +- src/routes/cours.rs | 3 +- src/routes/index.rs | 3 +- src/routes/portfolio.rs | 3 +- src/utils/markdown.rs | 180 +------------------------------------- src/utils/metadata.rs | 183 +++++++++++++++++++++++++++++++++++++++ src/utils/misc.rs | 5 +- src/utils/mod.rs | 1 + src/utils/routes/blog.rs | 3 +- 10 files changed, 202 insertions(+), 185 deletions(-) create mode 100644 src/utils/metadata.rs diff --git a/src/routes/blog.rs b/src/routes/blog.rs index 1a87265..6075bdd 100644 --- a/src/routes/blog.rs +++ b/src/routes/blog.rs @@ -6,7 +6,8 @@ use crate::{ config::Config, template::{InfosPage, NavBar}, utils::{ - markdown::{File, TypeFileMetadata}, + markdown::File, + metadata::TypeFileMetadata, misc::{make_kw, read_file, Html}, routes::blog::{build_rss, get_post, get_posts, Post, BLOG_DIR, MIME_TYPE_RSS, POST_DIR}, }, diff --git a/src/routes/contact.rs b/src/routes/contact.rs index 1328212..53bd8a0 100644 --- a/src/routes/contact.rs +++ b/src/routes/contact.rs @@ -7,7 +7,8 @@ use crate::{ config::Config, template::{InfosPage, NavBar}, utils::{ - markdown::{File, TypeFileMetadata}, + markdown::File, + metadata::TypeFileMetadata, misc::{make_kw, read_file, Html}, routes::contact::{find_links, remove_paragraphs}, }, diff --git a/src/routes/cours.rs b/src/routes/cours.rs index 7dc7508..2b00df0 100644 --- a/src/routes/cours.rs +++ b/src/routes/cours.rs @@ -8,7 +8,8 @@ use crate::{ config::Config, template::{InfosPage, NavBar}, utils::{ - markdown::{File, TypeFileMetadata}, + markdown::File, + metadata::TypeFileMetadata, misc::{make_kw, read_file, Html}, routes::cours::{excluded, get_filetree}, }, diff --git a/src/routes/index.rs b/src/routes/index.rs index fd0c312..999bb6b 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -6,7 +6,8 @@ use crate::{ config::Config, template::{InfosPage, NavBar}, utils::{ - markdown::{File, TypeFileMetadata}, + markdown::File, + metadata::TypeFileMetadata, misc::{make_kw, read_file, Html}, }, }; diff --git a/src/routes/portfolio.rs b/src/routes/portfolio.rs index 8f24542..1c37665 100644 --- a/src/routes/portfolio.rs +++ b/src/routes/portfolio.rs @@ -7,7 +7,8 @@ use crate::{ config::Config, template::{InfosPage, NavBar}, utils::{ - markdown::{File, TypeFileMetadata}, + markdown::File, + metadata::TypeFileMetadata, misc::{make_kw, read_file, Html}, }, }; diff --git a/src/utils/markdown.rs b/src/utils/markdown.rs index 5c0a7dd..ce4435c 100644 --- a/src/utils/markdown.rs +++ b/src/utils/markdown.rs @@ -1,4 +1,3 @@ -use crate::utils::date::Date; use base64::engine::general_purpose; use base64::Engine; use comrak::nodes::{AstNode, NodeValue}; @@ -6,114 +5,13 @@ use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, O use lol_html::html_content::ContentType; use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings}; use ramhorns::Content; -use serde::{Deserialize, Deserializer}; use std::fmt::Debug; use std::fs; use std::path::Path; -/// Metadata for blog posts -#[derive(Content, Clone, Debug, Default, Deserialize)] -pub struct FileMetadataBlog { - pub hardbreaks: Option, - pub title: Option, - pub date: Option, - pub description: Option, - pub publish: Option, - pub tags: Option>, - pub toc: Option, -} +use crate::utils::metadata::TypeFileMetadata; -/// A tag, related to post blog -#[derive(Content, Debug, Clone)] -pub struct Tag { - pub name: String, -} - -impl<'a> Deserialize<'a> for Tag { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - match <&str>::deserialize(deserializer) { - Ok(s) => match serde_yml::from_str(s) { - Ok(tag) => Ok(Self { name: tag }), - Err(e) => Err(serde::de::Error::custom(e)), - }, - Err(e) => Err(e), - } - } -} - -/// Metadata for contact entry -#[derive(Content, Debug, Default, Deserialize, Clone)] -pub struct FileMetadataContact { - pub title: String, - pub custom: Option, - pub user: Option, - pub link: Option, - pub newtab: Option, - pub description: Option, -} - -/// Metadata for index page -#[derive(Content, Debug, Default, Deserialize, Clone)] -pub struct FileMetadataIndex { - pub name: Option, - pub pronouns: Option, - pub avatar: Option, - pub avatar_caption: Option, - pub avatar_style: Option, -} - -/// Metadata for portfolio cards -#[derive(Content, Debug, Default, Deserialize, Clone)] -pub struct FileMetadataPortfolio { - pub title: Option, - pub link: Option, - pub description: Option, - pub language: Option, -} - -/// List of available metadata types -#[derive(Hash, PartialEq, Eq, Clone, Copy)] -pub enum TypeFileMetadata { - Blog, - Contact, - Generic, - Index, - Portfolio, -} - -/// Structure who holds all the metadata the file have -/// Usually all fields are None except one -#[derive(Content, Debug, Default, Deserialize, Clone)] -pub struct FileMetadata { - pub hardbreaks: bool, - pub blog: Option, - pub contact: Option, - pub index: Option, - pub portfolio: Option, -} - -#[allow(clippy::struct_excessive_bools)] -/// Global metadata -#[derive(Content, Debug, Clone)] -pub struct Metadata { - pub info: FileMetadata, - pub math: bool, - pub mermaid: bool, - pub syntax_highlight: bool, - pub mail_obfsucated: bool, -} - -impl Metadata { - /// Update current metadata boolean fields, keeping true ones - fn merge(&mut self, other: &Self) { - self.math = self.math || other.math; - self.mermaid = self.mermaid || other.mermaid; - self.syntax_highlight = self.syntax_highlight || other.syntax_highlight; - } -} +use super::metadata::{get_metadata, FileMetadata, Metadata}; /// File description #[derive(Content, Debug, Clone)] @@ -324,80 +222,6 @@ pub fn read_md( } } -/// Deserialize metadata based on a type -fn deserialize_metadata(text: &str) -> T { - serde_yml::from_str(text.trim().trim_matches(&['-'] as &[_])).unwrap_or_default() -} - -/// Fetch metadata from AST -pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileMetadata { - root.children() - .map(|node| { - let generic = FileMetadata { - hardbreaks: true, - ..FileMetadata::default() - }; - - match &node.data.borrow().value { - // Extract metadata from frontmatter - NodeValue::FrontMatter(text) => match mtype { - TypeFileMetadata::Blog => { - let metadata: FileMetadataBlog = deserialize_metadata(text); - FileMetadata { - blog: Some(metadata.clone()), - hardbreaks: metadata.hardbreaks.unwrap_or_default(), - ..FileMetadata::default() - } - } - TypeFileMetadata::Contact => { - let mut metadata: FileMetadataContact = deserialize_metadata(text); - // Trim descriptions - if let Some(desc) = &mut metadata.description { - desc.clone_from(&desc.trim().into()); - } - FileMetadata { - contact: Some(metadata), - ..FileMetadata::default() - } - } - TypeFileMetadata::Generic => generic, - TypeFileMetadata::Index => FileMetadata { - index: Some(deserialize_metadata(text)), - ..FileMetadata::default() - }, - TypeFileMetadata::Portfolio => FileMetadata { - portfolio: Some(deserialize_metadata(text)), - ..FileMetadata::default() - }, - }, - _ => generic, - } - }) - .next() - .map_or_else( - || match mtype { - TypeFileMetadata::Blog => FileMetadata { - blog: Some(FileMetadataBlog::default()), - ..FileMetadata::default() - }, - TypeFileMetadata::Contact => FileMetadata { - contact: Some(FileMetadataContact::default()), - ..FileMetadata::default() - }, - TypeFileMetadata::Generic => FileMetadata::default(), - TypeFileMetadata::Index => FileMetadata { - index: Some(FileMetadataIndex::default()), - ..FileMetadata::default() - }, - TypeFileMetadata::Portfolio => FileMetadata { - portfolio: Some(FileMetadataPortfolio::default()), - ..FileMetadata::default() - }, - }, - |data| data, - ) -} - /// Check whether mermaid diagrams are in the AST fn check_mermaid<'a>(root: &'a AstNode<'a>, mermaid_str: &str) -> bool { root.children().any(|node| match &node.data.borrow().value { diff --git a/src/utils/metadata.rs b/src/utils/metadata.rs new file mode 100644 index 0000000..532d3d6 --- /dev/null +++ b/src/utils/metadata.rs @@ -0,0 +1,183 @@ +use crate::utils::date::Date; +use comrak::nodes::{AstNode, NodeValue}; +use ramhorns::Content; +use serde::{Deserialize, Deserializer}; +use std::fmt::Debug; + +/// Metadata for blog posts +#[derive(Content, Clone, Debug, Default, Deserialize)] +pub struct FileMetadataBlog { + pub hardbreaks: Option, + pub title: Option, + pub date: Option, + pub description: Option, + pub publish: Option, + pub tags: Option>, + pub toc: Option, +} + +/// A tag, related to post blog +#[derive(Content, Debug, Clone)] +pub struct Tag { + pub name: String, +} + +impl<'a> Deserialize<'a> for Tag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + match <&str>::deserialize(deserializer) { + Ok(s) => match serde_yml::from_str(s) { + Ok(tag) => Ok(Self { name: tag }), + Err(e) => Err(serde::de::Error::custom(e)), + }, + Err(e) => Err(e), + } + } +} + +/// Metadata for contact entry +#[derive(Content, Debug, Default, Deserialize, Clone)] +pub struct FileMetadataContact { + pub title: String, + pub custom: Option, + pub user: Option, + pub link: Option, + pub newtab: Option, + pub description: Option, +} + +/// Metadata for index page +#[derive(Content, Debug, Default, Deserialize, Clone)] +pub struct FileMetadataIndex { + pub name: Option, + pub pronouns: Option, + pub avatar: Option, + pub avatar_caption: Option, + pub avatar_style: Option, +} + +/// Metadata for portfolio cards +#[derive(Content, Debug, Default, Deserialize, Clone)] +pub struct FileMetadataPortfolio { + pub title: Option, + pub link: Option, + pub description: Option, + pub language: Option, +} + +/// List of available metadata types +#[derive(Hash, PartialEq, Eq, Clone, Copy)] +pub enum TypeFileMetadata { + Blog, + Contact, + Generic, + Index, + Portfolio, +} + +/// Structure who holds all the metadata the file have +/// Usually all fields are None except one +#[derive(Content, Debug, Default, Deserialize, Clone)] +pub struct FileMetadata { + pub hardbreaks: bool, + pub blog: Option, + pub contact: Option, + pub index: Option, + pub portfolio: Option, +} + +#[allow(clippy::struct_excessive_bools)] +/// Global metadata +#[derive(Content, Debug, Clone)] +pub struct Metadata { + pub info: FileMetadata, + pub math: bool, + pub mermaid: bool, + pub syntax_highlight: bool, + pub mail_obfsucated: bool, +} + +impl Metadata { + /// Update current metadata boolean fields, keeping true ones + pub fn merge(&mut self, other: &Self) { + self.math = self.math || other.math; + self.mermaid = self.mermaid || other.mermaid; + self.syntax_highlight = self.syntax_highlight || other.syntax_highlight; + } +} + +/// Deserialize metadata based on a type +fn deserialize_metadata(text: &str) -> T { + serde_yml::from_str(text.trim().trim_matches(&['-'] as &[_])).unwrap_or_default() +} + +/// Fetch metadata from AST +pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileMetadata { + root.children() + .map(|node| { + let generic = FileMetadata { + hardbreaks: true, + ..FileMetadata::default() + }; + + match &node.data.borrow().value { + // Extract metadata from frontmatter + NodeValue::FrontMatter(text) => match mtype { + TypeFileMetadata::Blog => { + let metadata: FileMetadataBlog = deserialize_metadata(text); + FileMetadata { + blog: Some(metadata.clone()), + hardbreaks: metadata.hardbreaks.unwrap_or_default(), + ..FileMetadata::default() + } + } + TypeFileMetadata::Contact => { + let mut metadata: FileMetadataContact = deserialize_metadata(text); + // Trim descriptions + if let Some(desc) = &mut metadata.description { + desc.clone_from(&desc.trim().into()); + } + FileMetadata { + contact: Some(metadata), + ..FileMetadata::default() + } + } + TypeFileMetadata::Generic => generic, + TypeFileMetadata::Index => FileMetadata { + index: Some(deserialize_metadata(text)), + ..FileMetadata::default() + }, + TypeFileMetadata::Portfolio => FileMetadata { + portfolio: Some(deserialize_metadata(text)), + ..FileMetadata::default() + }, + }, + _ => generic, + } + }) + .next() + .map_or_else( + || match mtype { + TypeFileMetadata::Blog => FileMetadata { + blog: Some(FileMetadataBlog::default()), + ..FileMetadata::default() + }, + TypeFileMetadata::Contact => FileMetadata { + contact: Some(FileMetadataContact::default()), + ..FileMetadata::default() + }, + TypeFileMetadata::Generic => FileMetadata::default(), + TypeFileMetadata::Index => FileMetadata { + index: Some(FileMetadataIndex::default()), + ..FileMetadata::default() + }, + TypeFileMetadata::Portfolio => FileMetadata { + portfolio: Some(FileMetadataPortfolio::default()), + ..FileMetadata::default() + }, + }, + |data| data, + ) +} diff --git a/src/utils/misc.rs b/src/utils/misc.rs index c5dc6eb..28d977a 100644 --- a/src/utils/misc.rs +++ b/src/utils/misc.rs @@ -11,7 +11,10 @@ use reqwest::Client; use crate::config::FileConfiguration; -use super::markdown::{read_md, File, FileMetadata, Metadata, TypeFileMetadata}; +use super::{ + markdown::{read_md, File}, + metadata::{FileMetadata, Metadata, TypeFileMetadata}, +}; #[cached] pub fn get_reqwest_client() -> Client { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2a288bf..a3d0fb8 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod date; pub mod github; pub mod markdown; +pub mod metadata; pub mod misc; pub mod routes; diff --git a/src/utils/routes/blog.rs b/src/utils/routes/blog.rs index 2d42359..099fbaa 100644 --- a/src/utils/routes/blog.rs +++ b/src/utils/routes/blog.rs @@ -18,7 +18,8 @@ use crate::{ template::InfosPage, utils::{ date::Date, - markdown::{get_metadata, get_options, File, FileMetadataBlog, TypeFileMetadata}, + markdown::{get_options, File}, + metadata::{get_metadata, FileMetadataBlog, TypeFileMetadata}, misc::{get_url, make_kw, read_file}, }, };