use { crate::{ modules::blog::config::Blog, response::success, route::track_mount, url::ROOT_GEMINI_URL, xml::{Item as XmlItem, Writer as XmlWriter}, }, std::{ collections::HashMap, fs::{self, read_dir}, io::Read, }, }; #[allow(clippy::too_many_lines)] pub fn module(router: &mut windmark::router::Router) { let paths = read_dir("content/blogs").unwrap(); let mut blogs: HashMap> = HashMap::new(); for path in paths { let blog = path.unwrap().path().display().to_string(); let blog_paths = read_dir(&blog).unwrap(); let mut entries: HashMap<_, String> = HashMap::new(); blog_paths.map(|e| e.unwrap().path().display().to_string()).for_each( |file| { let mut contents = String::new(); fs::File::open(&file).unwrap().read_to_string(&mut contents).unwrap(); entries.insert( file.replace(&blog, "").replace(".gmi", "").replace( { #[cfg(windows)] { '\\' } #[cfg(unix)] { '/' } }, "", ), contents, ); }, ); blogs.insert( blog .replace( { #[cfg(windows)] { "content/blogs\\" } #[cfg(unix)] { "content/blogs/" } }, "", ) .split('_') .map(|s| { // https://gist.github.com/jpastuszek/2704f3c5a3864b05c48ee688d0fd21d7 let mut c = s.chars(); c.next().map_or_else(String::new, |f| { f.to_uppercase().chain(c.flat_map(char::to_lowercase)).collect() }) }) .collect::>() .join(" "), entries, ); } let blog_clone = blogs.clone(); track_mount(router, "/blog", "Fuwn's blogs", move |context| { success( &format!( "# Blogs ({})\n\n{}", blog_clone.len(), blog_clone .iter() .map(|(title, entries)| (title.clone(), entries.clone())) .collect::>() .into_iter() .map(|(title, entries)| { let config: Option = entries .get("blog.json") .and_then(|content| Blog::from_string(content).ok()); let name = config .clone() .unwrap_or_default() .name() .clone() .unwrap_or_else(|| title.clone()); let description = config.unwrap_or_default().description().clone(); // .unwrap_or_else(|| "One of Fuwn's blogs".to_string()); format!( "=> {} {}{}", format_args!("/blog/{}", name.replace(' ', "_").to_lowercase(),), name, if description.is_some() { format!(" ― {}", description.unwrap()) } else { String::new() } ) }) .collect::>() .join("\n") ), &context, ) }); for (blog, mut entries) in blogs { let fixed_blog_name = blog.replace(' ', "_").to_lowercase(); let fixed_blog_name_clone = fixed_blog_name.clone(); let fixed_blog_name_clone_2 = fixed_blog_name.clone(); let config: Option = entries .remove_entry("blog.json") .and_then(|(_, content)| Blog::from_string(&content).ok()); let entries_clone = entries.clone(); let name = config .clone() .unwrap_or_default() .name() .clone() .unwrap_or_else(|| blog.clone()); let description = config.clone().unwrap_or_default().description().clone(); // .unwrap_or_else(|| "One of Fuwn's blogs".to_string()); let config_clone = config.clone(); let mut xml = XmlWriter::builder(); xml.add_field("title", &name); xml.add_field("link", &format!("{ROOT_GEMINI_URL}/blog/{fixed_blog_name}")); if description.is_some() { xml.add_field("description", &description.clone().unwrap()); } xml.add_field("generator", "locus"); xml.add_field("lastBuildDate", &chrono::Local::now().to_rfc2822()); xml.add_link(&format!("{ROOT_GEMINI_URL}/blog/{fixed_blog_name}.xml")); track_mount( router, &format!("/blog/{fixed_blog_name}"), &format!( "{name}{}", if description.clone().is_some() { format!(" ― {}", description.clone().unwrap()) } else { String::new() } ), move |context| { let fixed_blog_name = fixed_blog_name_clone.clone(); success( &format!( "# {} ({})\n\n{}\n\n{}\n\n## Really Simple Syndication\n\nAccess \ {0}'s RSS feed\n\n=> {} here!", blog, entries_clone.len(), description.clone().unwrap_or_default(), entries_clone .keys() .map(Clone::clone) .collect::>() .into_iter() .map(|title| { let postish = config_clone .clone() .unwrap_or_default() .posts() .clone() .and_then(|posts| posts.get(&title).cloned()) .unwrap_or_default(); format!( "=> {} {}{}", format_args!( "/blog/{}/{}", fixed_blog_name, title.to_lowercase() ), { postish.name().clone().unwrap_or_else(|| title.clone()) }, { let post = postish.description().clone().unwrap_or_default(); if post.is_empty() { String::new() } else { format!(" ― {post}") } } ) }) .collect::>() .join("\n"), format_args!("/blog/{}.xml", fixed_blog_name), ), &context, ) }, ); for (title, contents) in entries { let header = construct_header(&config, &title) .unwrap_or_else(|()| (String::new(), String::new())); let fixed_blog_name = fixed_blog_name_clone_2.clone(); let mut real_title = "Unknown"; xml.add_item(&{ let mut builder = XmlItem::builder(); builder.add_field( "link", &format!( "{ROOT_GEMINI_URL}/blog/{}/{}", fixed_blog_name, title.to_lowercase() ), ); builder.add_field("description", &contents); builder.add_field( "guid", &format!( "{ROOT_GEMINI_URL}/blog/{}/{}", fixed_blog_name, title.to_lowercase() ), ); if let Some(configuration) = &config { if let Some(posts) = configuration.posts() { if let Some(post) = posts.get(&title) { if let Some(name) = post.name() { real_title = name; } if let Some(date) = post.created() { builder.add_field("pubDate", date); } } } } builder.add_field("title", real_title); builder }); track_mount( router, &format!("/blog/{}/{}", fixed_blog_name, title.to_lowercase()), &format!( "{}, {} ― {}", name, real_title, if header.1.is_empty() { "An entry to one of Fuwn's blogs".to_string() } else { header.1 } ), move |context| { success(&format!("{}\n{}", header.0, contents,), &context) }, ); } track_mount( router, &format!("/blog/{fixed_blog_name}.xml"), &format!("Really Simple Syndication for the {name} blog"), move |_| { windmark::response::Response::success(xml.to_string()) .with_mime("text/rss+xml") .clone() }, ); } } fn construct_header( config: &Option, name: &str, ) -> Result<(String, String), ()> { let post = if let Some(posts) = config.clone().unwrap_or_default().posts().clone() { if let Some(post) = posts.get(name) { post.clone() } else { return Err(()); } } else { return Err(()); }; macro_rules! field { ($getter:ident, $format:literal) => { if post.$getter().is_some() { format!($format, post.$getter().clone().unwrap()) } else { "".to_string() } }; } Ok(( format!( "# {}\n\n{}{}{}{}", post.name().clone().unwrap_or_else(|| name.to_string()), field!(author, "Author: {}\n"), field!(created, "Created: {}\n"), field!(last_modified, "Last Modified: {}\n"), field!(description, "\n{}\n"), ), post.description().clone().unwrap_or_default(), )) }