use super::posts::parse::Tag; use super::util::slugify::slugify; use super::util::templates::{filters, TemplateCommon}; use super::util::{from_frontmatter, output_rendered_template, word_count}; use super::{content_path, markdown}; use askama::Template; use chrono::{DateTime, FixedOffset}; use serde::Deserialize; use std::{fs, io::Read, path::PathBuf}; pub fn generate() { let series = vec![ generate_series("forge-modding-1102", "Forge Mods for 1.10.2"), generate_series("forge-modding-1112", "Forge Mods for 1.11.2"), generate_series("forge-modding-112", "Forge Mods for 1.12"), ]; generate_index(&series); } fn generate_index(series: &[TutorialSeries]) { output_rendered_template( &TutorialsIndexTemplate { all_series: series }, "/tutorials/index.html", ) .unwrap(); } fn generate_series(slug: &'static str, name: &'static str) -> TutorialSeries { let mut path = content_path("tutorials"); path.push(slug); let posts = fs::read_dir(path) .unwrap() .map(|entry| { let entry = entry.unwrap(); assert!(entry.file_type().unwrap().is_file()); parse_post(entry.path(), slug, name) }) .collect::>(); let series = TutorialSeries { slug, name, posts }; for post in series.posts.iter() { generate_post(&series, post); } generate_series_index(&series); series } fn generate_post(series: &TutorialSeries, post: &TutorialPost) { output_rendered_template( &TutorialPostTemplate { series, post }, format!("/tutorials/{}/{}/index.html", series.slug, post.slug), ) .unwrap(); } fn generate_series_index(series: &TutorialSeries) { output_rendered_template( &TutorialSeriesTemplate { series }, format!("/tutorials/{}/index.html", series.slug), ) .unwrap(); } fn parse_post(path: PathBuf, series_slug: &str, series_name: &str) -> TutorialPost { let mut f = fs::File::open(&path).unwrap(); let mut buffer = String::new(); f.read_to_string(&mut buffer).unwrap(); TutorialPost::new(path, &buffer, series_slug, series_name) } struct TutorialSeries { slug: &'static str, name: &'static str, posts: Vec, } impl TutorialSeries { fn last_updated(&self) -> &DateTime { &self .posts .iter() .max_by_key(|p| p.metadata.date) .unwrap() .metadata .date } } struct TutorialPost { content: String, slug: String, word_count: Option, metadata: TutorialPostMetadata, } impl TutorialPost { fn new(path: PathBuf, contents: &str, series_slug: &str, series_name: &str) -> Self { let (mut metadata, rest_contents) = from_frontmatter::<'_, TutorialPostMetadata>(contents).unwrap(); metadata.tags = Some(vec![Tag { name: series_name.to_owned(), slug: format!("tutorials/{}", series_slug), }]); let slug = metadata .slug .clone() .unwrap_or_else(|| slugify(&metadata.title)); assert!(path.extension().unwrap().eq_ignore_ascii_case("md")); let wc = word_count::markdown(rest_contents); let mut buf = vec![]; markdown::render(rest_contents, &mut buf); let html = String::from_utf8(buf).unwrap(); TutorialPost { content: html, slug, word_count: Some(wc), metadata, } } } #[derive(Deserialize)] struct TutorialPostMetadata { title: String, date: DateTime, slug: Option, tags: Option>, } #[derive(Template)] #[template(path = "layout/tutorial.html")] struct TutorialPostTemplate<'a> { series: &'a TutorialSeries, post: &'a TutorialPost, } impl<'a> TemplateCommon for TutorialPostTemplate<'a> {} impl<'a> TutorialPostTemplate<'a> { fn permalink(&self) -> String { format!("/tutorials/{}/{}/", self.series.slug, self.post.slug) } } #[derive(Template)] #[template(path = "layout/tutorial-series.html")] struct TutorialSeriesTemplate<'a> { series: &'a TutorialSeries, } impl<'a> TemplateCommon for TutorialSeriesTemplate<'a> {} impl<'a> TutorialSeriesTemplate<'a> { fn permalink(&self) -> String { format!("/tutorials/{}/", self.series.slug) } } #[derive(Template)] #[template(path = "tutorials.html")] struct TutorialsIndexTemplate<'a> { all_series: &'a [TutorialSeries], } impl<'a> TemplateCommon for TutorialsIndexTemplate<'a> {} impl<'a> TutorialsIndexTemplate<'a> { fn permalink(&self) -> &'static str { "/tutorials/" } }