1// sorry clippy, we don't have a choice. askama forces this on us
2#![allow(clippy::unnecessary_wraps, clippy::trivially_copy_pass_by_ref)]
3
4use std::{
5 borrow::Borrow,
6 sync::{Arc, LazyLock},
7};
8
9use arc_swap::ArcSwap;
10use rkyv::{
11 rend::{i32_le, i64_le},
12 tuple::ArchivedTuple2,
13};
14use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
15
16// pub fn format_time(s: impl Borrow<time::OffsetDateTime>) -> Result<String, askama::Error> {
17pub fn format_time(s: impl Into<Timestamp>) -> Result<String, askama::Error> {
18 let s = s.into().0;
19
20 (*s.borrow())
21 .format(&Rfc3339)
22 .map_err(Box::from)
23 .map_err(askama::Error::Custom)
24}
25
26pub fn branch_query(branch: Option<&str>) -> String {
27 if let Some(b) = branch {
28 format!("?h={b}")
29 } else {
30 String::new()
31 }
32}
33
34pub fn timeago(s: impl Into<Timestamp>) -> Result<String, askama::Error> {
35 Ok(timeago::Formatter::new()
36 .convert((OffsetDateTime::now_utc() - s.into().0).try_into().unwrap()))
37}
38
39pub fn file_perms(s: &u16) -> Result<String, askama::Error> {
40 Ok(unix_mode::to_string(u32::from(*s)))
41}
42
43pub fn hex(s: &[u8]) -> Result<String, askama::Error> {
44 Ok(const_hex::encode(s))
45}
46
47pub fn gravatar(email: &str) -> Result<&'static str, askama::Error> {
48 static CACHE: LazyLock<ArcSwap<hashbrown::HashMap<&'static str, &'static str>>> =
49 LazyLock::new(|| ArcSwap::new(Arc::new(hashbrown::HashMap::new())));
50
51 if let Some(res) = CACHE.load().get(email).copied() {
52 return Ok(res);
53 }
54
55 let url = format!(
56 "https://www.gravatar.com/avatar/{}",
57 const_hex::encode(md5::compute(email).0)
58 );
59 let key = Box::leak(Box::from(email));
60 let url = url.leak();
61
62 CACHE.rcu(|curr| {
63 let mut r = (**curr).clone();
64 r.insert(key, url);
65 r
66 });
67
68 Ok(url)
69}
70
71pub struct Timestamp(OffsetDateTime);
72
73impl From<&ArchivedTuple2<i64_le, i32_le>> for Timestamp {
74 fn from(value: &ArchivedTuple2<i64_le, i32_le>) -> Self {
75 Self(
76 OffsetDateTime::from_unix_timestamp(value.0.to_native())
77 .unwrap()
78 .to_offset(UtcOffset::from_whole_seconds(value.1.to_native()).unwrap()),
79 )
80 }
81}
82
83impl From<(i64, i32)> for Timestamp {
84 fn from(value: (i64, i32)) -> Self {
85 Self(
86 OffsetDateTime::from_unix_timestamp(value.0)
87 .unwrap()
88 .to_offset(UtcOffset::from_whole_seconds(value.1).unwrap()),
89 )
90 }
91}
92
93impl From<&(i64, i32)> for Timestamp {
94 fn from(value: &(i64, i32)) -> Self {
95 Self(
96 OffsetDateTime::from_unix_timestamp(value.0)
97 .unwrap()
98 .to_offset(UtcOffset::from_whole_seconds(value.1).unwrap()),
99 )
100 }
101}
102
103impl From<OffsetDateTime> for Timestamp {
104 fn from(value: OffsetDateTime) -> Self {
105 Self(value)
106 }
107}