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 { let staff = ["laurmaedje", "reknih"]; let bots = ["dependabot[bot]"]; // Determine number of contributions per person. let mut contributors = HashMap::::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| { let login = c.login.as_str(); !staff.contains(&login) && !bots.contains(&login) }) .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, } /// 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, }