X-Git-Url: https://git.r.bdr.sh/rbdr/blog/blobdiff_plain/2998247083406f914b3647cedd19abf5507bf2c6..edcd0e51b3e0a6d52a560f293b7e6a08999cfd0c:/src/archiver/mod.rs diff --git a/src/archiver/mod.rs b/src/archiver/mod.rs index e69de29..6f0c284 100644 --- a/src/archiver/mod.rs +++ b/src/archiver/mod.rs @@ -0,0 +1,130 @@ +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 + ] +}