mod raw; mod gemini; mod gopher; use std::collections::HashMap; use std::fs::read_dir; use std::io::Result; use std::path::PathBuf; use time::{OffsetDateTime, format_description::FormatItem, macros::format_description}; use crate::template::{TemplateContext, TemplateValue}; const DATE_FORMAT: &[FormatItem<'_>] = format_description!("[year]-[month]-[day]"); struct ArchiveEntry { id: String, slug: String } impl ArchiveEntry { pub fn to_template_context(archive_entries: &Vec) -> TemplateContext { let mut context = HashMap::new(); let archive_entries_collection = archive_entries .iter() .map(|archive_entry| archive_entry.to_template_value()) .collect(); context.insert( "archive_length".to_string(), TemplateValue::Unsigned( archive_entries.len().try_into().unwrap() ) ); context.insert( "posts".to_string(), TemplateValue::Collection(archive_entries_collection) ); context } pub fn to_template_value(&self) -> TemplateContext { let mut context = HashMap::new(); context.insert( "id".to_string(), TemplateValue::String(self.id.clone()) ); context.insert( "slug".to_string(), TemplateValue::String(self.slug.clone()) ); if let Some(title) = self.title() { context.insert( "title".to_string(), TemplateValue::String(title) ); } context } fn title(&self) -> Option { let date = OffsetDateTime::from_unix_timestamp_nanos( (self.id.parse::().ok()? * 1_000_000).into() ).ok()?; let short_date = date.format(&DATE_FORMAT).ok()?; let title = self.slug.replace("-", " "); Some(format!("{} {}", short_date, title)) } } fn read_archive(archive_directory: &PathBuf) -> Vec { let mut archive_entries = Vec::new(); if let Ok(entries) = read_dir(&archive_directory) { for entry in entries.filter_map(Result::ok) { let entry_path = entry.path(); let post_id = entry.file_name(); if let Ok(entry_type) = entry.file_type() { if entry_type.is_dir() { if let Ok(candidates) = read_dir(&entry_path) { for candidate in candidates.filter_map(Result::ok) { let candidate_path = candidate.path(); match candidate_path.extension() { Some(extension) => { if extension == "gmi" { if let Some(slug) = candidate_path.file_stem() { if let (Some(post_id), Some(slug)) = (post_id.to_str(), slug.to_str()) { archive_entries.push(ArchiveEntry { id: post_id.to_string(), slug: slug.to_string() }) } } } }, _ => continue } } } } } } } archive_entries .sort_by(|a, b| b.id.cmp(&a.id)); archive_entries } pub fn archive(archive_directory: &PathBuf, template_directory: &PathBuf, output_directory: &PathBuf) -> Result<()> { let archivers = available_archivers(); let archive_entries = read_archive(archive_directory); let context = ArchiveEntry::to_template_context(&archive_entries); for archiver in archivers { archiver(archive_directory, template_directory, output_directory, &context)?; } return Ok(()) } fn available_archivers() -> Vec Result<()>> { vec![ raw::archive, gemini::archive, gopher::archive ] }