use std::cmp::Reverse; use std::collections::HashMap; use std::fmt::Write; use serde::Deserialize; use super::Html; /// Build HTML detailing the contributors between two tags. pub fn contributors(from: &str, to: &str) -> Option { let staff = ["laurmaedje", "reknih"]; let url = format!("https://api.github.com/repos/typst/typst/compare/{from}...{to}"); let response: Response = ureq::get(&url) .set("X-GitHub-Api-Version", "2022-11-28") .call() .ok()? .into_json() .ok()?; // Determine number of contributions per person. let mut contributors = HashMap::::new(); for commit in response.commits { 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 += ""; Some(Html::new(html)) } #[derive(Debug)] struct Contributor { login: String, avatar: String, contributions: usize, } #[derive(Debug, Deserialize)] struct Response { commits: Vec, } #[derive(Debug, Deserialize)] struct Commit { author: Author, } #[derive(Debug, Deserialize)] struct Author { login: String, avatar_url: String, }