typst/crates/typst-docs/src/contribs.rs
2023-11-24 12:30:02 +01:00

87 lines
2.4 KiB
Rust

use std::cmp::Reverse;
use std::collections::HashMap;
use std::fmt::Write;
use serde::{Deserialize, Serialize};
use crate::{Html, Resolver};
/// Build HTML detailing the contributors between two tags.
pub fn contributors(resolver: &dyn Resolver, from: &str, to: &str) -> Option<Html> {
let staff = ["laurmaedje", "reknih"];
// Determine number of contributions per person.
let mut contributors = HashMap::<String, Contributor>::new();
for commit in resolver.commits(from, to) {
contributors
.entry(commit.author.login.clone())
.or_insert_with(|| Contributor {
login: commit.author.login,
avatar: commit.author.avatar_url,
contributions: 0,
})
.contributions += 1;
}
// Keep only non-staff people.
let mut contributors: Vec<_> = contributors
.into_values()
.filter(|c| !staff.contains(&c.login.as_str()))
.collect();
// Sort by highest number of commits.
contributors.sort_by_key(|c| (Reverse(c.contributions), c.login.clone()));
if contributors.is_empty() {
return None;
}
let mut html = "Thanks to everyone who contributed to this release!".to_string();
html += "<ul class=\"contribs\">";
for Contributor { login, avatar, contributions } in contributors {
let login = login.replace('\"', "&quot;").replace('&', "&amp;");
let avatar = avatar.replace("?v=", "?s=64&v=");
let s = if contributions > 1 { "s" } else { "" };
write!(
html,
r#"<li>
<a href="https://github.com/{login}" target="_blank">
<img
width="64"
height="64"
src="{avatar}"
alt="GitHub avatar of {login}"
title="@{login} made {contributions} contribution{s}"
crossorigin="anonymous"
>
</a>
</li>"#
)
.unwrap();
}
html += "</ul>";
Some(Html::new(html))
}
#[derive(Debug)]
struct Contributor {
login: String,
avatar: String,
contributions: usize,
}
/// A commit on the `typst` repository.
#[derive(Debug, Serialize, Deserialize)]
pub struct Commit {
author: Author,
}
/// A commit author.
#[derive(Debug, Serialize, Deserialize)]
pub struct Author {
login: String,
avatar_url: String,
}