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