summaryrefslogtreecommitdiff
path: root/docs/src/contribs.rs
blob: 7e43249a0308745a3a95e6c12826a05179e39e44 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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<Html> {
    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::<String, Contributor>::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 += "<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,
}

#[derive(Debug, Deserialize)]
struct Response {
    commits: Vec<Commit>,
}

#[derive(Debug, Deserialize)]
struct Commit {
    author: Author,
}

#[derive(Debug, Deserialize)]
struct Author {
    login: String,
    avatar_url: String,
}