Blog attempt 5
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 116 lines 3.0 kB view raw
1pub mod admin; 2pub mod fetch; 3pub mod list; 4pub mod render; 5pub mod view; 6 7use std::fs::read_to_string; 8 9use chrono::{DateTime, Datelike as _, FixedOffset, Local, NaiveDateTime, Utc}; 10use maud::{Markup, html}; 11use serde::Deserialize; 12const ISO8601_DATE: &str = "%Y-%m-%dT%H:%M"; 13 14#[derive(Deserialize, Debug)] 15pub struct Post { 16 pub bsky_uri: Option<String>, 17 pub category: Option<String>, 18 pub contents: String, 19 pub creation_datetime: DateTime<Local>, 20 pub slug: String, 21 pub subtitle: Option<String>, 22 pub title: String, 23} 24 25#[must_use] 26pub fn clock_icon() -> Markup { 27 html! { 28 span.emoji-icon aria-label="Published on" {( "🕒" )} 29 } 30} 31 32#[must_use] 33pub fn clean_empty_string(s: Option<String>) -> Option<String> { 34 match s { 35 Some(string) if string.trim().is_empty() => None, 36 other => other, 37 } 38} 39 40#[must_use] 41pub fn format_date(date: chrono::DateTime<Local>) -> String { 42 let suffix = eng_ordinal_suffix(date.day() as usize); 43 date.format(&format!("%B %d{suffix}, %Y")).to_string() 44} 45 46#[must_use] 47pub fn parse_date(datestring: &str) -> chrono::DateTime<FixedOffset> { 48 match chrono::DateTime::parse_from_rfc3339(datestring) { 49 Ok(dt) => dt, 50 Err(_) => { 51 match NaiveDateTime::parse_from_str(datestring, "%Y-%m-%dT%H:%M") 52 .map(|ndt| DateTime::<Utc>::from_naive_utc_and_offset(ndt, Utc)) 53 { 54 Ok(dt) => dt.into(), 55 Err(_) => chrono::DateTime::UNIX_EPOCH.into(), 56 } 57 } 58 } 59} 60 61#[must_use] 62#[expect( 63 clippy::expect_used, 64 reason = "We need the secret for progam execution" 65)] 66pub fn read_secret() -> String { 67 read_to_string("./.secret") 68 .expect("Failed to read secret file") 69 .trim() 70 .to_owned() 71} 72 73#[must_use] 74pub fn eng_ordinal_suffix(n: usize) -> &'static str { 75 let tens = n % 100; 76 if (11..=13).contains(&tens) { 77 return "th"; 78 } 79 80 match n % 10 { 81 1 => "st", 82 2 => "nd", 83 3 => "rd", 84 _ => "th", 85 } 86} 87 88#[cfg(test)] 89mod tests { 90 use super::*; 91 use crate::error::AppError; 92 93 #[test] 94 fn test_eng_ordinal_suffix_basic() { 95 assert_eq!(eng_ordinal_suffix(1), "st"); 96 assert_eq!(eng_ordinal_suffix(2), "nd"); 97 assert_eq!(eng_ordinal_suffix(3), "rd"); 98 assert_eq!(eng_ordinal_suffix(4), "th"); 99 assert_eq!(eng_ordinal_suffix(11), "th"); 100 assert_eq!(eng_ordinal_suffix(12), "th"); 101 assert_eq!(eng_ordinal_suffix(13), "th"); 102 assert_eq!(eng_ordinal_suffix(21), "st"); 103 assert_eq!(eng_ordinal_suffix(22), "nd"); 104 assert_eq!(eng_ordinal_suffix(23), "rd"); 105 } 106 107 #[test] 108 fn test_app_error_conversion() { 109 let db_error = rusqlite::Error::QueryReturnedNoRows; 110 let app_error: AppError = db_error.into(); 111 match app_error { 112 AppError::NotFound => {} 113 _ => panic!("Should convert to NotFound"), 114 } 115 } 116}