split metadata of files and markdown reader

This commit is contained in:
Mylloon 2024-11-10 10:55:18 +01:00
parent c7f1f912f0
commit b1c4bbdb27
Signed by: Anri
GPG key ID: A82D63DFF8D1317F
10 changed files with 202 additions and 185 deletions

View file

@ -6,7 +6,8 @@ use crate::{
config::Config, config::Config,
template::{InfosPage, NavBar}, template::{InfosPage, NavBar},
utils::{ utils::{
markdown::{File, TypeFileMetadata}, markdown::File,
metadata::TypeFileMetadata,
misc::{make_kw, read_file, Html}, misc::{make_kw, read_file, Html},
routes::blog::{build_rss, get_post, get_posts, Post, BLOG_DIR, MIME_TYPE_RSS, POST_DIR}, routes::blog::{build_rss, get_post, get_posts, Post, BLOG_DIR, MIME_TYPE_RSS, POST_DIR},
}, },

View file

@ -7,7 +7,8 @@ use crate::{
config::Config, config::Config,
template::{InfosPage, NavBar}, template::{InfosPage, NavBar},
utils::{ utils::{
markdown::{File, TypeFileMetadata}, markdown::File,
metadata::TypeFileMetadata,
misc::{make_kw, read_file, Html}, misc::{make_kw, read_file, Html},
routes::contact::{find_links, remove_paragraphs}, routes::contact::{find_links, remove_paragraphs},
}, },

View file

@ -8,7 +8,8 @@ use crate::{
config::Config, config::Config,
template::{InfosPage, NavBar}, template::{InfosPage, NavBar},
utils::{ utils::{
markdown::{File, TypeFileMetadata}, markdown::File,
metadata::TypeFileMetadata,
misc::{make_kw, read_file, Html}, misc::{make_kw, read_file, Html},
routes::cours::{excluded, get_filetree}, routes::cours::{excluded, get_filetree},
}, },

View file

@ -6,7 +6,8 @@ use crate::{
config::Config, config::Config,
template::{InfosPage, NavBar}, template::{InfosPage, NavBar},
utils::{ utils::{
markdown::{File, TypeFileMetadata}, markdown::File,
metadata::TypeFileMetadata,
misc::{make_kw, read_file, Html}, misc::{make_kw, read_file, Html},
}, },
}; };

View file

@ -7,7 +7,8 @@ use crate::{
config::Config, config::Config,
template::{InfosPage, NavBar}, template::{InfosPage, NavBar},
utils::{ utils::{
markdown::{File, TypeFileMetadata}, markdown::File,
metadata::TypeFileMetadata,
misc::{make_kw, read_file, Html}, misc::{make_kw, read_file, Html},
}, },
}; };

View file

@ -1,4 +1,3 @@
use crate::utils::date::Date;
use base64::engine::general_purpose; use base64::engine::general_purpose;
use base64::Engine; use base64::Engine;
use comrak::nodes::{AstNode, NodeValue}; 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::html_content::ContentType;
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings}; use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
use ramhorns::Content; use ramhorns::Content;
use serde::{Deserialize, Deserializer};
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
/// Metadata for blog posts use crate::utils::metadata::TypeFileMetadata;
#[derive(Content, Clone, Debug, Default, Deserialize)]
pub struct FileMetadataBlog {
pub hardbreaks: Option<bool>,
pub title: Option<String>,
pub date: Option<Date>,
pub description: Option<String>,
pub publish: Option<bool>,
pub tags: Option<Vec<Tag>>,
pub toc: Option<bool>,
}
/// A tag, related to post blog use super::metadata::{get_metadata, FileMetadata, Metadata};
#[derive(Content, Debug, Clone)]
pub struct Tag {
pub name: String,
}
impl<'a> Deserialize<'a> for Tag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<bool>,
pub user: Option<String>,
pub link: Option<String>,
pub newtab: Option<bool>,
pub description: Option<String>,
}
/// Metadata for index page
#[derive(Content, Debug, Default, Deserialize, Clone)]
pub struct FileMetadataIndex {
pub name: Option<String>,
pub pronouns: Option<String>,
pub avatar: Option<String>,
pub avatar_caption: Option<String>,
pub avatar_style: Option<String>,
}
/// Metadata for portfolio cards
#[derive(Content, Debug, Default, Deserialize, Clone)]
pub struct FileMetadataPortfolio {
pub title: Option<String>,
pub link: Option<String>,
pub description: Option<String>,
pub language: Option<String>,
}
/// 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<FileMetadataBlog>,
pub contact: Option<FileMetadataContact>,
pub index: Option<FileMetadataIndex>,
pub portfolio: Option<FileMetadataPortfolio>,
}
#[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;
}
}
/// File description /// File description
#[derive(Content, Debug, Clone)] #[derive(Content, Debug, Clone)]
@ -324,80 +222,6 @@ pub fn read_md(
} }
} }
/// Deserialize metadata based on a type
fn deserialize_metadata<T: Default + serde::de::DeserializeOwned>(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 /// Check whether mermaid diagrams are in the AST
fn check_mermaid<'a>(root: &'a AstNode<'a>, mermaid_str: &str) -> bool { fn check_mermaid<'a>(root: &'a AstNode<'a>, mermaid_str: &str) -> bool {
root.children().any(|node| match &node.data.borrow().value { root.children().any(|node| match &node.data.borrow().value {

183
src/utils/metadata.rs Normal file
View file

@ -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<bool>,
pub title: Option<String>,
pub date: Option<Date>,
pub description: Option<String>,
pub publish: Option<bool>,
pub tags: Option<Vec<Tag>>,
pub toc: Option<bool>,
}
/// A tag, related to post blog
#[derive(Content, Debug, Clone)]
pub struct Tag {
pub name: String,
}
impl<'a> Deserialize<'a> for Tag {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<bool>,
pub user: Option<String>,
pub link: Option<String>,
pub newtab: Option<bool>,
pub description: Option<String>,
}
/// Metadata for index page
#[derive(Content, Debug, Default, Deserialize, Clone)]
pub struct FileMetadataIndex {
pub name: Option<String>,
pub pronouns: Option<String>,
pub avatar: Option<String>,
pub avatar_caption: Option<String>,
pub avatar_style: Option<String>,
}
/// Metadata for portfolio cards
#[derive(Content, Debug, Default, Deserialize, Clone)]
pub struct FileMetadataPortfolio {
pub title: Option<String>,
pub link: Option<String>,
pub description: Option<String>,
pub language: Option<String>,
}
/// 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<FileMetadataBlog>,
pub contact: Option<FileMetadataContact>,
pub index: Option<FileMetadataIndex>,
pub portfolio: Option<FileMetadataPortfolio>,
}
#[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<T: Default + serde::de::DeserializeOwned>(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,
)
}

View file

@ -11,7 +11,10 @@ use reqwest::Client;
use crate::config::FileConfiguration; use crate::config::FileConfiguration;
use super::markdown::{read_md, File, FileMetadata, Metadata, TypeFileMetadata}; use super::{
markdown::{read_md, File},
metadata::{FileMetadata, Metadata, TypeFileMetadata},
};
#[cached] #[cached]
pub fn get_reqwest_client() -> Client { pub fn get_reqwest_client() -> Client {

View file

@ -1,5 +1,6 @@
pub mod date; pub mod date;
pub mod github; pub mod github;
pub mod markdown; pub mod markdown;
pub mod metadata;
pub mod misc; pub mod misc;
pub mod routes; pub mod routes;

View file

@ -18,7 +18,8 @@ use crate::{
template::InfosPage, template::InfosPage,
utils::{ utils::{
date::Date, 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}, misc::{get_url, make_kw, read_file},
}, },
}; };