diff --git a/Cargo.lock b/Cargo.lock index 56c211f3..829543b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -212,6 +237,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -724,6 +755,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "ring" version = "0.17.14" @@ -1373,6 +1424,7 @@ dependencies = [ "handlebars", "handlebars-fluent", "percent-encoding", + "rayon", "rust_team_data", "sass-rs", "serde", diff --git a/Cargo.toml b/Cargo.toml index e1a14d95..d7b588c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ toml = "0.9" serde_json = "1.0" rust_team_data = { git = "https://github.com/rust-lang/team" } percent-encoding = "2.3.2" +rayon = "1" [dev-dependencies] time = { version = "0.3.44", features = ["parsing"] } diff --git a/locales/en-US/governance.ftl b/locales/en-US/governance.ftl index d8f8d0c3..25d32ccd 100644 --- a/locales/en-US/governance.ftl +++ b/locales/en-US/governance.ftl @@ -34,7 +34,12 @@ governance-members-header = Members governance-alumni-header = Alumni governance-alumni-thanks = We also want to thank all past members for their invaluable contributions! -## governance/all-team-members.mbs +## governance/all-team-members.hbs governance-all-team-members-title = All Rust team members governance-all-team-members-intro = This section lists the members of currently active Rust teams. governance-all-team-members-alumni-intro = This section lists our team alumni. + +## govenance/person.hbs +governance-person-title = Rust Project team member +governance-person-team-member = Team member +governance-person-team-alumni = Alumni diff --git a/src/render.rs b/src/render.rs index 8c112bd2..d65c1964 100644 --- a/src/render.rs +++ b/src/render.rs @@ -7,6 +7,8 @@ use crate::{BaseUrl, ENGLISH, LAYOUT}; use anyhow::Context; use handlebars::Handlebars; use handlebars_fluent::{Loader, SimpleLoader}; +use rayon::iter::IntoParallelRefIterator; +use rayon::iter::ParallelIterator; use serde::Serialize; use std::ffi::OsStr; use std::fs::File; @@ -180,11 +182,14 @@ pub fn render_index(render_ctx: &RenderCtx) -> anyhow::Result<()> { pub fn render_governance(render_ctx: &RenderCtx) -> anyhow::Result<()> { let data = render_ctx.teams.index_data(); + // Index page for_all_langs("governance/index.html", |dst_path, lang| { render_ctx .page("governance/index", "governance-page-title", &data, lang) .render(dst_path) })?; + + // Individual teams for team in data.teams { let data: PageData = render_ctx .teams @@ -207,6 +212,7 @@ pub fn render_governance(render_ctx: &RenderCtx) -> anyhow::Result<()> { )?; } + // Archived teams let archived_teams_data = render_ctx.teams.archived_teams(); for_all_langs("governance/archived-teams.html", |dst_path, lang| { render_ctx @@ -219,6 +225,7 @@ pub fn render_governance(render_ctx: &RenderCtx) -> anyhow::Result<()> { .render(dst_path) })?; + // Page with all team members let all_team_members_data = render_ctx.teams.all_team_members(); for_all_langs("governance/all-team-members.html", |dst_path, lang| { render_ctx @@ -231,6 +238,28 @@ pub fn render_governance(render_ctx: &RenderCtx) -> anyhow::Result<()> { .render(dst_path) })?; + // A specific page for each team member + let all_person_data = render_ctx.teams.all_person_data(); + all_person_data + .par_iter() + .map(|person| { + // Use /index.html for a nicer URL (/people/foo vs /people/foo.html). + for_all_langs( + &format!("governance/people/{}/index.html", person.github), + |dst_path, lang| { + render_ctx + .page( + "governance/person", + "governance-person-title", + &person, + lang, + ) + .render(dst_path) + }, + ) + }) + .collect::>>()?; + Ok(()) } diff --git a/src/teams.rs b/src/teams.rs index f87eb7ff..31a2209e 100644 --- a/src/teams.rs +++ b/src/teams.rs @@ -71,15 +71,11 @@ impl RustTeams { .filter(|team| team.website_data.is_some()) // On the main page, show the leadership-council and all top-level // teams. - .filter(|team| team.kind == TeamKind::Team && team.subteam_of.is_none()) + .filter(is_toplevel_team) .map(|team| IndexTeam { section: kind_to_str(team.kind), page_name: team.website_data.clone().unwrap().page, - url: format!( - "{}/{}", - kind_to_str(team.kind), - team.website_data.as_ref().unwrap().page - ), + url: get_team_relative_url(&team), team, }) .collect::>(); @@ -234,6 +230,111 @@ impl RustTeams { AllTeamMembers { active, alumni } } + + pub fn all_person_data(&self) -> Vec { + let mut people: HashMap = HashMap::new(); + + enum TeamMode { + Member, + Alumni, + MemberOfArchivedTeam, + } + + fn add_team( + people: &mut HashMap, + ctx: &RustTeams, + member: &TeamMember, + team: &Team, + mode: TeamMode, + ) { + let person = people + .entry(member.github.clone()) + .or_insert_with(move || PersonData { + name: member.name.clone(), + github: member.github.clone(), + active_teams: vec![], + alumni_teams: vec![], + }); + let teams = match mode { + TeamMode::Member => &mut person.active_teams, + TeamMode::Alumni | TeamMode::MemberOfArchivedTeam => &mut person.alumni_teams, + }; + let url = match mode { + TeamMode::Member | TeamMode::Alumni => ctx.get_toplevel_team_url(team), + TeamMode::MemberOfArchivedTeam => Some("archived-teams.html".to_string()), + }; + teams.push(PersonTeam::new(team, member, url)); + } + + for team in &self.archived_teams { + for member in team.members.iter().chain(&team.alumni) { + add_team( + &mut people, + self, + member, + team, + TeamMode::MemberOfArchivedTeam, + ); + } + } + for team in &self.teams { + if team.kind == TeamKind::MarkerTeam && team.website_data.is_none() { + continue; + } + + for member in &team.members { + add_team(&mut people, self, member, team, TeamMode::Member); + } + for member in &team.alumni { + add_team(&mut people, self, member, team, TeamMode::Alumni); + } + } + + let mut people: Vec = people.into_values().collect(); + people.sort_by(|a, b| a.github.cmp(&b.github)); + + for person in &mut people { + person + .active_teams + .sort_by(|a, b| a.webpage_name.cmp(&b.webpage_name)); + person + .alumni_teams + .sort_by(|a, b| a.webpage_name.cmp(&b.webpage_name)); + } + + people + } + + fn get_toplevel_team_url<'a>(&'a self, mut team: &'a Team) -> Option { + while !is_toplevel_team(team) { + let Some(parent) = &team.subteam_of else { + return None; + }; + let parent = self.teams.iter().find(|t| t.name == *parent)?; + team = parent; + } + + if team.website_data.is_some() { + Some(get_team_relative_url(team)) + } else { + None + } + } +} + +/// Get a relative URL of a team that should be appended to +/// Should only be used for top-level teams. +fn get_team_relative_url(team: &Team) -> String { + assert!(is_toplevel_team(team)); + format!( + "{}/{}", + kind_to_str(team.kind), + team.website_data.as_ref().unwrap().page + ) +} + +fn is_toplevel_team(team: &Team) -> bool { + team.kind == TeamKind::Team && team.subteam_of.is_none() } #[derive(Serialize)] @@ -272,6 +373,58 @@ pub struct AllTeamMembers { alumni: Vec, } +#[derive(Serialize)] +pub struct PersonTeam { + team: Team, + toplevel_url: Option, + webpage_name: String, + roles: Vec, +} + +impl PersonTeam { + fn new(team: &Team, member: &TeamMember, toplevel_url: Option) -> Self { + // Turn inside-rust-reviewers into Inside Rust Reviewers + let normalize_name = |name: &str| { + name.split("-") + .map(|p| { + p.chars() + .take(1) + .flat_map(|c| c.to_uppercase()) + .chain(p.chars().skip(1)) + .collect::() + }) + .collect::>() + .join(" ") + }; + + let webpage_name = team + .website_data + .as_ref() + .map(|w| w.name.clone()) + .unwrap_or_else(|| normalize_name(&team.name)); + + let mut roles = vec![]; + if member.is_lead { + roles.push("Lead".to_string()); + } + roles.extend(member.roles.iter().map(|r| normalize_name(r))); + Self { + team: team.clone(), + toplevel_url, + webpage_name, + roles, + } + } +} + +#[derive(Serialize)] +pub struct PersonData { + name: String, + pub github: String, + active_teams: Vec, + alumni_teams: Vec, +} + pub fn load_rust_teams() -> anyhow::Result { println!("Downloading Team API data"); diff --git a/templates/governance/all-team-members.html.hbs b/templates/governance/all-team-members.html.hbs index 40356e58..657ba480 100644 --- a/templates/governance/all-team-members.html.hbs +++ b/templates/governance/all-team-members.html.hbs @@ -1,12 +1,12 @@ {{#*inline "member"}}
- + {{member.name}}
- {{member.name}} + {{member.name}} @@ -24,7 +24,7 @@
{{#each data.active as |member|}} - {{> member member=member }} + {{> member member=member baseurl=../baseurl }} {{/each}}
@@ -38,7 +38,7 @@
{{#each data.alumni as |member|}} - {{> member member=member }} + {{> member member=member baseurl=../baseurl }} {{/each}}
diff --git a/templates/governance/archived-team.html.hbs b/templates/governance/archived-team.html.hbs index f628d8dd..08a213af 100644 --- a/templates/governance/archived-team.html.hbs +++ b/templates/governance/archived-team.html.hbs @@ -15,13 +15,13 @@
{{#each team.alumni as |member|}}
- + {{member.name}}
- {{member.name}} + {{member.name}} diff --git a/templates/governance/archived-teams.html.hbs b/templates/governance/archived-teams.html.hbs index 744d1be0..b62878e9 100644 --- a/templates/governance/archived-teams.html.hbs +++ b/templates/governance/archived-teams.html.hbs @@ -7,7 +7,7 @@ {{#each data.teams as |team|}}
- {{> governance/archived-team team=team}} + {{> governance/archived-team team=team baseurl=baseurl}}
{{/each}} diff --git a/templates/governance/group-team.html.hbs b/templates/governance/group-team.html.hbs index e8599b95..cbcce5a7 100644 --- a/templates/governance/group-team.html.hbs +++ b/templates/governance/group-team.html.hbs @@ -53,12 +53,14 @@
{{#each team.members as |member|}}
- + {{member.name}}
-

{{member.name}}

+

+ {{member.name}} +

{{#fluent "governance-user-github"}} {{#fluentparam "link"}} @@ -85,12 +87,12 @@
{{#each team.alumni as |member|}}
- + {{member.name}}
- {{member.name}} + {{member.name}} diff --git a/templates/governance/group.html.hbs b/templates/governance/group.html.hbs index 9f135ee0..6e77fd2c 100644 --- a/templates/governance/group.html.hbs +++ b/templates/governance/group.html.hbs @@ -2,34 +2,34 @@ {{#with data.team as |team|}}
- {{> governance/group-team team=team zulip_domain=data.zulip_domain}} + {{> governance/group-team team=team zulip_domain=data.zulip_domain baseurl=../baseurl}}
{{/with}} {{#if data.subteams}}
{{#each data.subteams as |team|}} - {{> governance/group-team team=team zulip_domain=data.zulip_domain}} + {{> governance/group-team team=team zulip_domain=data.zulip_domain baseurl=../baseurl}} {{/each}}
{{/if}} {{#if data.project_groups}}
{{#each data.project_groups as |team|}} - {{> governance/group-team team=team zulip_domain=data.zulip_domain}} + {{> governance/group-team team=team zulip_domain=data.zulip_domain baseurl=../baseurl}} {{/each}}
{{/if}} {{#if data.wgs}}
{{#each data.wgs as |team|}} - {{> governance/group-team team=team zulip_domain=data.zulip_domain}} + {{> governance/group-team team=team zulip_domain=data.zulip_domain baseurl=../baseurl}} {{/each}}
{{/if}} {{#if data.other_teams}}
{{#each data.other_teams as |team|}} - {{> governance/group-team team=team zulip_domain=data.zulip_domain}} + {{> governance/group-team team=team zulip_domain=data.zulip_domain baseurl=../baseurl}} {{/each}}
{{/if}} diff --git a/templates/governance/person.html.hbs b/templates/governance/person.html.hbs new file mode 100644 index 00000000..6e55a760 --- /dev/null +++ b/templates/governance/person.html.hbs @@ -0,0 +1,68 @@ +{{#*inline "teams"}} + {{#each teams as |team|}} +
  • + {{#if team.toplevel_url }} + + {{ team.webpage_name }} + {{else}} + {{ team.webpage_name }} + {{/if}} + {{#if team.roles }} + ({{~#each team.roles as |role index|~}} + {{#if (eq index 0)~}} + {{role}}{{~else~}}, {{role}} + {{~/if}} + {{~/each~}}) + {{/if}} +
  • + {{/each}} +{{/inline}} + +{{#*inline "page"}} +
    +
    +
    + + {{data.name}} + +
    + {{data.name}} +
    + GitHub: {{data.github}} +
    +
    +
    +
    +
    + + {{# if data.active_teams }} +
    +
    +
    +

    {{ fluent "governance-person-team-member" }}

    +
    +
    +
      + {{> teams teams=data.active_teams }} +
    +
    +
    + {{/if}} + + {{# if data.alumni_teams }} +
    +
    +
    +

    {{ fluent "governance-person-team-alumni" }}

    +
    +
    +
      + {{> teams teams=data.alumni_teams }} +
    +
    +
    + {{/if}} +{{/inline}} +{{~> (lookup this "parent")~}}