chore: refactor backend around domain types (#26)
Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
mod pubkey;
|
||||
mod repo_url;
|
||||
mod text;
|
||||
mod time;
|
||||
|
||||
pub use pubkey::shorten_pubkey;
|
||||
pub use time::{relative_time, relative_time_secs};
|
||||
pub use repo_url::same_repo_url;
|
||||
pub use text::{flatten_whitespace, middle_truncate};
|
||||
pub use time::{latest, relative_time, relative_time_secs, sort_newest_first, sort_oldest_first};
|
||||
|
||||
@@ -1,49 +1,14 @@
|
||||
use nostr::prelude::*;
|
||||
|
||||
/// Shorten a [`PublicKey`] to `npub1abc...wxyz` form.
|
||||
use crate::text::middle_truncate;
|
||||
|
||||
const HEAD_CHARS: usize = 9;
|
||||
const TAIL_CHARS: usize = 4;
|
||||
|
||||
pub fn shorten_pubkey(public_key: PublicKey) -> String {
|
||||
let encoded = public_key
|
||||
.to_bech32()
|
||||
.unwrap_or_else(|_| public_key.to_hex());
|
||||
|
||||
truncate_middle(&encoded)
|
||||
}
|
||||
|
||||
fn truncate_middle(value: &str) -> String {
|
||||
const HEAD_CHARS: usize = 9;
|
||||
const TAIL_CHARS: usize = 4;
|
||||
|
||||
let length = value.chars().count();
|
||||
if length <= HEAD_CHARS + TAIL_CHARS + 3 {
|
||||
return value.to_owned();
|
||||
}
|
||||
|
||||
let head: String = value.chars().take(HEAD_CHARS).collect();
|
||||
let tail: String = value.chars().skip(length - TAIL_CHARS).collect();
|
||||
|
||||
format!("{head}...{tail}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PUBLIC_KEY_HEX: &str = "68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272";
|
||||
|
||||
#[test]
|
||||
fn shortens_a_valid_pubkey() {
|
||||
let public_key = PublicKey::from_hex(PUBLIC_KEY_HEX).expect("valid pubkey");
|
||||
let npub = public_key.to_bech32().expect("valid pubkey encodes");
|
||||
|
||||
assert_eq!(
|
||||
shorten_pubkey(public_key),
|
||||
format!("{}...{}", &npub[..9], &npub[npub.len() - 4..])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_short_values_intact() {
|
||||
assert_eq!(truncate_middle("npub1short"), "npub1short");
|
||||
assert_eq!(truncate_middle("thirteenchars"), "thirteenchars");
|
||||
}
|
||||
middle_truncate(&encoded, HEAD_CHARS, TAIL_CHARS)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use nostr::prelude::*;
|
||||
|
||||
/// Compare git URLs ignoring scheme, trailing `.git` and path slashes, so a
|
||||
/// grasp announce URL matches its https origin. Unparseable values compare literally.
|
||||
pub fn same_repo_url(a: &str, b: &str) -> bool {
|
||||
match (url_identity(a), url_identity(b)) {
|
||||
(Some(a), Some(b)) => a == b,
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
fn url_identity(url: &str) -> Option<(String, Option<u16>, String)> {
|
||||
let parsed = Url::parse(url).ok()?;
|
||||
let host = parsed.host_str()?.to_ascii_lowercase();
|
||||
let mut path = parsed.path().trim_matches('/').to_owned();
|
||||
if let Some(stripped) = path.strip_suffix(".git") {
|
||||
path = stripped.to_owned();
|
||||
}
|
||||
Some((host, parsed.port(), path))
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
pub fn middle_truncate(value: &str, head: usize, tail: usize) -> String {
|
||||
let len = value.chars().count();
|
||||
if len <= head + tail + 3 {
|
||||
return value.to_string();
|
||||
}
|
||||
let head: String = value.chars().take(head).collect();
|
||||
let tail: String = value.chars().skip(len - tail).collect();
|
||||
format!("{head}...{tail}")
|
||||
}
|
||||
|
||||
pub fn flatten_whitespace(text: &str) -> String {
|
||||
const MAX_CHARS: usize = 200;
|
||||
let flat = text.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if flat.chars().count() <= MAX_CHARS {
|
||||
flat
|
||||
} else {
|
||||
let mut clipped: String = flat.chars().take(MAX_CHARS).collect();
|
||||
clipped.push('…');
|
||||
clipped
|
||||
}
|
||||
}
|
||||
+19
-20
@@ -1,6 +1,24 @@
|
||||
use nostr::prelude::*;
|
||||
|
||||
/// Format a timestamp as a short relative time, e.g. `3h ago`.
|
||||
pub fn sort_newest_first(events: &mut [Event]) {
|
||||
events.sort_by(|a, b| {
|
||||
b.created_at
|
||||
.cmp(&a.created_at)
|
||||
.then_with(|| b.id.to_hex().cmp(&a.id.to_hex()))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn sort_oldest_first(events: &mut [Event]) {
|
||||
events.sort_by_key(|e| e.created_at);
|
||||
}
|
||||
|
||||
pub fn latest<I>(events: I) -> Option<Event>
|
||||
where
|
||||
I: IntoIterator<Item = Event>,
|
||||
{
|
||||
events.into_iter().max_by_key(|e| e.created_at)
|
||||
}
|
||||
|
||||
pub fn relative_time(timestamp: Timestamp) -> String {
|
||||
let now = Timestamp::now().as_secs();
|
||||
let secs = now.saturating_sub(timestamp.as_secs());
|
||||
@@ -20,25 +38,6 @@ pub fn relative_time(timestamp: Timestamp) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a unix timestamp in seconds as a short relative time, e.g. `3h ago`.
|
||||
pub fn relative_time_secs(secs: i64) -> String {
|
||||
relative_time(Timestamp::from_secs(secs.max(0) as u64))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn formats_relative_time() {
|
||||
let now = Timestamp::now();
|
||||
|
||||
assert_eq!(relative_time(now), "just now");
|
||||
assert_eq!(relative_time(now - 300), "5m ago");
|
||||
assert_eq!(relative_time(now - 7_200), "2h ago");
|
||||
assert_eq!(relative_time(now - 3 * 86_400), "3d ago");
|
||||
assert_eq!(relative_time(now - 60 * 86_400), "2mo ago");
|
||||
assert_eq!(relative_time(now - 800 * 86_400), "2y ago");
|
||||
assert_eq!(relative_time(now + 600), "just now");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user