From 4d18d3e43e6a191289bf2ef69d05dee533ea047e Mon Sep 17 00:00:00 2001 From: Seun Akanni Date: Mon, 8 Jun 2026 15:58:42 +0100 Subject: [PATCH] refactor(wall): revert to simpler 7-col layout with contributor badge Reverts the rendering logic to the e6006d6 state (the post-denylist, pre-stylise-bundle commit which the user preferred), then layers two small additions on top: - Replace the "N people have contributed" prose line with a shields.io contributors badge: ![Contributors](https://img.shields.io/badge/contributors-N-brightgreen) Empty state uses lightgrey color and shows 0. - Italicise "Last updated YYYY-MM-DD" at the bottom Drops all the intermediate visual polish (centered wrapper, hr separator, colgroup, valign-top, fixed cell widths, NAME_MAX_LEN truncation, @login title attribute, 12-column shrink, 6-column shrink). The wall is back to plain 7-column x 100px-avatar layout. Wall is 22 rows tall for 151 contributors. Will likely still produce a horizontal scroll on narrow viewports (table is ~700px wide); user explicitly chose this state knowing the trade-off. --- profile/README.md | 674 ++++++++++++++--------------- scripts/generate_wall_of_honour.py | 93 ++-- scripts/tests/test_generate.py | 129 +----- 3 files changed, 372 insertions(+), 524 deletions(-) diff --git a/profile/README.md b/profile/README.md index ae80610..73c04e0 100644 --- a/profile/README.md +++ b/profile/README.md @@ -21,535 +21,509 @@ We also have a space for questions and discussions: https://github.com/orgs/BHoM Please let us know how can we improve it! Please raise GitHub Issues in the [Documentation repo](https://github.com/BHoM/documentation/issues) or start a [Discussion](https://github.com/orgs/BHoM/discussions). +## Wall of Honour ---- +![Contributors](https://img.shields.io/badge/contributors-151-brightgreen) -
- -Contributors who have supported and advanced the BHoM. - - - - - - - - - - +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - + - + - - - - - - + - - - - - - - - - - - - - - - - - - -
-
AaronWagner
-
-
Adam Sobies…
-
-
Agnes Lindb…
-
-
Ahmad Sater
-
-
Ahmed Elnag…
+
+
AaronWagner
-
Al Fisher
+
+
Adam Sobieski
-
Alaa Alfaka…
-
-
Albin Bergh
+
+
Agnes Lindblom
-
Alessio Lom…
+
+
Ahmad Sater
-
Alex Radne
+
+
Ahmed Elnagar
-
Alexis Sant…
+
+
Al Fisher
-
Andreas Bak
+
+
Alaa Alfakara
-
Andrew Engh
+
+
Albin Bergh
-
Anna Stigen…
+
+
Alessio Lombardi
-
Anthony Mor…
+
+
Alex Radne
-
Antigoni Ka…
+
+
Alexis Santella
-
Arnaud Decl…
+
+
Andreas Bak
-
Arne Marten…
-
-
Arvid Sigur…
+
+
Andrew Engh
-
Aurora Jens…
-
-
Bing Wang
-
-
Carmen Fan
-
-
Charlie Bro…
-
-
Chris Self
+
+
Anna Stigenberg
-
Christopher…
-
-
Clemens Pre…
-
-
Conor Hubert
+
+
Anthony Morehouse
-
Courtney Bo…
-
-
Daniel Imms
-
-
Daniel Jons…
-
-
DarienC
+
+
Antigoni Karaiskou
-
Deesha Tech
+
+
Arnaud Declercq
-
Desai Wang
+
+
Arne Martensen
-
DiellzaElsh…
+
+
Arvid Sigurdsson
-
Dimitrie St…
+
+
Aurora Jensen
-
Djamila Mam…
+
+
Bing Wang
-
Dmitry Zele…
+
+
Carmen Fan
-
Eddy Dalton
+
+
Charlie Brooker
-
Eduardo Pig…
+
+
Chris Self
-
Einar Perss…
+
+
Christopher Short
-
Elina Usman…
+
+
Clemens Preisinger
-
Ellie Adebo…
+
+
Conor Hubert
+
+
Courtney Boulter
-
Emil Svedjer
+
+
Daniel Imms
+
+
Daniel Jonsson
-
Emma Sander
+
+
DarienC
-
EnricoAntol…
+
+
Deesha Tech
-
Erida Bendo
+
+
Desai Wang
-
Erik Forsbe…
+
+
DiellzaElshani
-
Erik Narhi
+
+
Dimitrie Stefanescu
-
Evangelos P…
+
+
Djamila Mamedova
+
+
Dmitry Zelenkin
-
felipebande…
+
+
Eddy Dalton
-
Felix Malli…
+
+
Eduardo Pignatelli
-
Fraser Gree…
+
+
Einar Persson
-
Frédéric Bo…
+
+
Elina Usmanova
-
Geir Soderin
+
+
Ellie Adebowale
-
Gideon Tamir
+
+
Emil Svedjer
-
Giorgio Car…
+
+
Emma Sander
-
Habiba Moub…
+
+
EnricoAntolini
-
Haiping
+
+
Erida Bendo
-
Hedvig Kjel…
+
+
Erik Forsberg
-
Isak Larborn
+
+
Erik Narhi
+
+
Evangelos Pantazis
-
Isak Näslund
+
+
felipebandeiramm
+
+
Felix Mallinder
-
izzy lys
+
+
Fraser Greenroyd
-
Jack Trainor
+
+
Frédéric Bourgeon
-
Jaco Bekker
+
+
Geir Soderin
-
James Ramsd…
+
+
Gideon Tamir
-
Johanna Isa…
+
+
Giorgio Carlo Roberto Albieri
-
Johanna Olin
+
+
Habiba Moubarak
+
+
Haiping
-
John Sloane
+
+
Hedvig Kjellander
-
Jonathan Ni…
+
+
Isak Larborn
-
Jose Castro…
+
+
Isak Näslund
-
JS
+
+
izzy lys
-
jukkasus
+
+
Jack Trainor
-
Junjie
+
+
Jaco Bekker
-
Kai Chelliah
+
+
James Ramsden
-
Kalle Edstr…
+
+
Johanna Isaksson
-
Kalle Thors…
+
+
Johanna Olin
-
Kalle Thors…
+
+
John Sloane
-
Karolina Pr…
+
+
Jonathan Nillius
+
+
Jose Castro Aguilar
-
Kartheek My…
+
+
JS
+
+
jukkasus
-
Kasia Wodzi…
+
+
Junjie
-
Kayleigh Ho…
+
+
Kai Chelliah
-
Kenton Phil…
+
+
Kalle Edstroem
-
Kenton Phil…
+
+
Kalle Thorsager
-
Konrad Stol…
+
+
Kalle Thorsager
-
Lina Erikss…
+
+
Karolina Prusicka
+
+
Kartheek Mylavarapu
-
linhnam-ngu…
+
+
Kasia Wodzisz
-
Liv Anderss…
+
+
Kayleigh Houde
-
livand
+
+
Kenton Phillips
-
Ludvig Borg…
+
+
Kenton Phillips
-
Luis E. Fra…
+
+
Konrad Stolarski
-
Madelene Sa…
+
+
Lina Eriksson
-
Maja Lindro…
+
+
linhnam-nguyen
-
Manuel Chaf…
+
+
Liv Andersson
-
Maria Glima…
+
+
livand
-
Maria Roman
+
+
Ludvig Borgenstam
-
Maria Tjader
+
+
Luis E. Fraguada
+
+
Madelene Sandmer
-
Mark Cunlif…
+
+
Maja Lindroth
+
+
Manuel Chafart
-
Martin B. H…
+
+
Maria Glimaker
-
Marvin Suen
+
+
Maria Roman
-
Maryanne Wa…
+
+
Maria Tjader
-
Maryanne Wa…
+
+
Mark Cunliffe
-
Matilda Sve…
+
+
Martin B. Henriksen
-
Matteo Comi…
+
+
Marvin Suen
+
+
Maryanne Wachter
-
Matthew Swa…
+
+
Maryanne Wachter
-
Maya Arora-…
+
+
Matilda Svensson
-
Meinrad Rec…
+
+
Matteo Cominetti
-
Michael Hoe…
+
+
Matthew Swaidan
-
Michal Deng…
+
+
Maya Arora-Jonsson
-
Michal Peka…
+
+
Meinrad Recheis
-
MILeach
+
+
Michael Hoehn
-
Milo Gillot
+
+
Michal Dengusiak
-
Mish Nuh
+
+
Michal Pekacki
-
Mozhgan Kab…
+
+
MILeach
-
Nathan Brown
+
+
Milo Gillot
+
+
Mish Nuh
-
Nazeefa Bus…
+
+
Mozhgan Kabiri Chimeh
+
+
Nathan Brown
-
Nicola Smit…
+
+
Nazeefa Bushra
-
non-manifold
+
+
Nicola Smithies
-
Ofek Bruhis
+
+
non-manifold
-
Oliver Stai…
+
+
Ofek Bruhis
-
Oscar Borgs…
+
+
Oliver Stainton
-
Paul Poinet
+
+
Oscar Borgström
+
+
Paul Poinet
-
Paul Richmo…
+
+
Paul Richmond
-
Pawel Baran
+
+
Pawel Baran
-
Peter Harman
+
+
Peter Harman
-
Peter Heywo…
+
+
Peter Heywood
-
Peter Nugent
+
+
Peter Nugent
-
PiotrBuda
+
+
PiotrBuda
-
Puria Safar…
+
+
Puria Safari Hesari
-
Rasmus Canf…
+
+
Rasmus Canfjorden
-
Rene Schulte
+
+
Rene Schulte
-
Richard Whi…
+
+
Richard Whitfield
-
Rob May
+
+
Rob May
+
+
Robert Chisholm
-
Robert Chis…
+
+
Robin Flyman
+
+
Roly Hudson
-
Robin Flyman
+
+
Ross Boulton
-
Roly Hudson
+
+
Sally Pickard
-
Ross Boulton
+
+
Sam Vazquez
-
Sally Picka…
+
+
Samuel Basimalla
-
Sam Vazquez
+
+
Sofia Malmsten
-
Samuel Basi…
+
+
StephennipBH
+
+
Thomas Edward Kingstone
-
Sofia Malms…
+
+
Tiago Del Grossi
-
StephennipBH
+
+
Tom Svilans
-
Thomas Edwa…
+
+
Toste Skanberg Dahlstedt
-
Tiago Del G…
+
+
Tristan Gerrish
-
Tom Svilans
+
+
Valerie Green
-
Toste Skanb…
+
+
Viet Le
-
Tristan Ger…
+
+
Will Pearson
-
Valerie Gre…
+
+
wojciech buda
-
Viet Le
-
-
Will Pearson
-
-
wojciech bu…
+
+
Łukasz Markowski
-
Łukasz Mark…
-
-151 contributors across 109 repositories. Last updated 2026-06-08. - -
+_Last updated: 2026-06-08_ diff --git a/scripts/generate_wall_of_honour.py b/scripts/generate_wall_of_honour.py index ad4c3e6..ac421fc 100644 --- a/scripts/generate_wall_of_honour.py +++ b/scripts/generate_wall_of_honour.py @@ -1,7 +1,6 @@ """Generate BHoM Wall of Honour and splice into profile/README.md.""" from __future__ import annotations -import html import os import re import sys @@ -73,47 +72,34 @@ def enrich_display_names( session: requests.Session, contributors: dict[str, dict[str, Any]], ) -> dict[str, dict[str, Any]]: - """Fetch each contributor's display name; fall back to login when blank or unavailable. - - Handles 404s gracefully (e.g. deleted GitHub accounts) by using the login as the name. - """ + """Fetch each contributor's display name; fall back to login when blank.""" for login, info in contributors.items(): - try: - user = get_one(session, f"{GITHUB_API}/users/{login}") - name = (user.get("name") or "").strip() - except requests.HTTPError: - name = "" + user = get_one(session, f"{GITHUB_API}/users/{login}") + name = (user.get("name") or "").strip() info["name"] = name if name else login return contributors -GRID_COLS = 6 -AVATAR_SIZE = 45 -CELL_WIDTH = 55 -NAME_MAX_LEN = 12 # truncate longer display names with an ellipsis; full name in title attr - - -def _pluralize(n: int, singular: str, plural: str) -> str: - return f"{n} {singular if n == 1 else plural}" +GRID_COLS = 7 +AVATAR_SIZE = 100 -INTRO_TEXT = "Contributors who have supported and advanced the BHoM." +def _contributors_badge(count: int) -> str: + color = "lightgrey" if count == 0 else "brightgreen" + return f"![Contributors](https://img.shields.io/badge/contributors-{count}-{color})" -def render_wall( - contributors: dict[str, dict[str, Any]], - last_updated: str, - repo_count: int | None = None, -) -> str: +def render_wall(contributors: dict[str, dict[str, Any]], last_updated: str) -> str: """Render the markdown block (markers included) for the wall.""" + badge = _contributors_badge(len(contributors)) + if not contributors: return ( - "\n\n" - "---\n\n" - "
\n\n" - f"{INTRO_TEXT}\n\n" - f"_(No contributors yet. Last updated {last_updated}.)_\n\n" - "
\n" + "\n" + "## Wall of Honour\n\n" + f"{badge}\n\n" + "Wall coming soon. No contributors yet.\n\n" + f"_Last updated: {last_updated}_\n" "" ) @@ -123,56 +109,31 @@ def render_wall( ) rows: list[str] = [] - empty_cell = f" " for row_start in range(0, len(sorted_logins), GRID_COLS): row_logins = sorted_logins[row_start:row_start + GRID_COLS] cells = [_render_cell(login, contributors[login]) for login in row_logins] - while len(cells) < GRID_COLS: - cells.append(empty_cell) rows.append(" \n" + "\n".join(cells) + "\n ") - contributors_part = _pluralize(len(contributors), "contributor", "contributors") - if repo_count is not None: - repos_part = _pluralize(repo_count, "repository", "repositories") - stats = f"{contributors_part} across {repos_part}. Last updated {last_updated}." - else: - stats = f"{contributors_part}. Last updated {last_updated}." - - colgroup_lines = "\n".join(f" " for _ in range(GRID_COLS)) - colgroup = " \n" + colgroup_lines + "\n " - return ( - "\n\n" - "---\n\n" - "
\n\n" - f"{INTRO_TEXT}\n\n" - "\n" - + colgroup + "\n" + "\n" + "## Wall of Honour\n\n" + f"{badge}\n\n" + "
\n" + "\n".join(rows) + "\n
\n\n" - f"{stats}\n\n" - "
\n" + f"_Last updated: {last_updated}_\n" "" ) -def _truncate_name(name: str, max_len: int = NAME_MAX_LEN) -> str: - """Trim to max_len visible characters, appending an ellipsis if shortened.""" - if len(name) <= max_len: - return name - return name[: max_len - 1].rstrip() + "…" - - def _render_cell(login: str, info: dict[str, Any]) -> str: - full_name = info["name"] - display = html.escape(_truncate_name(full_name)) + name = info["name"] return ( - f" \n" - f" " + " \n" + f" " f"
" - f"{display}
\n" + f"width=\"{AVATAR_SIZE}\" height=\"{AVATAR_SIZE}\" />
" + f"{name}\n" " " ) @@ -242,7 +203,7 @@ def main() -> int: enriched = enrich_display_names(session, aggregated) today = date.today().isoformat() - wall_md = render_wall(enriched, today, repo_count=len(repos)) + wall_md = render_wall(enriched, today) changed = splice_into_readme(readme_path, wall_md) if changed: diff --git a/scripts/tests/test_generate.py b/scripts/tests/test_generate.py index 9c65784..608cbf6 100644 --- a/scripts/tests/test_generate.py +++ b/scripts/tests/test_generate.py @@ -140,13 +140,13 @@ def test_enrich_display_names_uses_real_name(): responses.add( responses.GET, "https://api.github.com/users/alice", - json={"login": "alice", "name": "Alice"}, + json={"login": "alice", "name": "Alice Example"}, status=200, ) session = make_session("fake-token") contributors = {"alice": {"avatar_url": "https://x/a", "contributions": 10}} result = enrich_display_names(session, contributors) - assert result["alice"]["name"] == "Alice" + assert result["alice"]["name"] == "Alice Example" @responses.activate @@ -177,129 +177,42 @@ def test_enrich_display_names_falls_back_to_login_when_empty_string(): assert result["carol"]["name"] == "carol" -@responses.activate -def test_enrich_display_names_falls_back_on_404(): - # Deleted GitHub account: users/{login} returns 404; we should not crash. - responses.add( - responses.GET, - "https://api.github.com/users/deleted_user", - status=404, - ) - session = make_session("fake-token") - contributors = {"deleted_user": {"avatar_url": "https://x/d", "contributions": 1}} - result = enrich_display_names(session, contributors) - assert result["deleted_user"]["name"] == "deleted_user" - - from scripts.generate_wall_of_honour import render_wall def test_render_wall_empty(): md = render_wall({}, "2026-06-08") - assert "supported and advanced the BHoM" in md + assert "Wall coming soon" in md assert "" in md assert "" in md - assert "
" in md - # Horizontal rule separates the wall from preceding README content - assert "\n---\n" in md - - -def test_render_wall_includes_hr_separator(): - contributors = { - "alice": {"avatar_url": "x", "contributions": 1, "name": "Alice"}, - } - md = render_wall(contributors, "2026-06-08") - # The hr appears between the start marker and the centered content - start_idx = md.index("") - div_idx = md.index("
") - hr_idx = md.index("---") - assert start_idx < hr_idx < div_idx + # Badge present even when empty (shows 0) + assert "img.shields.io/badge/contributors-0-" in md + # Last updated is italicised + assert "_Last updated: 2026-06-08_" in md def test_render_wall_single_row(): contributors = { - "alice": {"avatar_url": "https://x/a", "contributions": 10, "name": "Alice"}, - "bob": {"avatar_url": "https://x/b", "contributions": 5, "name": "Bob"}, + "alice": {"avatar_url": "https://x/a", "contributions": 10, "name": "Alice Example"}, + "bob": {"avatar_url": "https://x/b", "contributions": 5, "name": "Bob Sample"}, } md = render_wall(contributors, "2026-06-08") - assert "2 contributors" in md - assert md.count("") == 1 # 2 cells fit in 1 row of GRID_COLS - assert "Alice" in md - assert "Bob" in md + # Contributor count exposed via shields.io badge instead of inline text + assert "img.shields.io/badge/contributors-2-brightgreen" in md + assert md.count("") == 1 # 2 cells fit in 1 row of 7 + assert "Alice Example" in md + assert "Bob Sample" in md # Alphabetical: Alice before Bob - assert md.index("Alice") < md.index("Bob") - # Rounded avatars - assert "border-radius: 50%" in md - # Centered wrapper - assert "
" in md - # Empty padding cells fill row to GRID_COLS width - assert md.count("") == 4 - - -def test_render_wall_stats_with_repo_count(): - contributors = { - "alice": {"avatar_url": "https://x/a", "contributions": 10, "name": "Alice"}, - "bob": {"avatar_url": "https://x/b", "contributions": 5, "name": "Bob"}, - } - md = render_wall(contributors, "2026-06-08", repo_count=42) - assert "2 contributors across 42 repositories" in md - - -def test_render_wall_pluralization_singular(): - contributors = { - "solo": {"avatar_url": "https://x/s", "contributions": 1, "name": "Solo Dev"}, - } - md = render_wall(contributors, "2026-06-08", repo_count=1) - assert "1 contributor across 1 repository" in md - assert "1 contributors" not in md - assert "1 repositories" not in md + assert md.index("Alice Example") < md.index("Bob Sample") + # Last updated is italicised + assert "_Last updated: 2026-06-08_" in md def test_render_wall_two_rows_with_remainder(): contributors = {f"user{i}": {"avatar_url": f"https://x/{i}", "contributions": 1, "name": f"User {i}"} for i in range(8)} md = render_wall(contributors, "2026-06-08") assert md.count("") == 2 - # 8 populated cells (links present) + 4 empty padding cells in the second row - assert md.count("") == 4 - - -def test_render_wall_truncates_long_names_with_ellipsis(): - contributors = { - "mocklongname": {"avatar_url": "https://x/m", "contributions": 1, "name": "A Very Long Display Name"}, - } - md = render_wall(contributors, "2026-06-08") - # Display in is truncated - assert "A Very Long Display Name" not in md - assert "…" in md - # GitHub login (with @ prefix) is exposed via the title attribute for hover - assert "title=\"@mocklongname\"" in md - assert "title=\"A Very Long Display Name\"" not in md - - -def test_render_wall_keeps_short_names_intact(): - contributors = { - "alice": {"avatar_url": "https://x/a", "contributions": 1, "name": "Alice"}, - } - md = render_wall(contributors, "2026-06-08") - assert "Alice" in md - assert "…" not in md # no ellipsis anywhere - - -def test_render_wall_uses_valign_top(): - # valign="top" keeps avatars aligned even when names wrap to two lines - contributors = {"alice": {"avatar_url": "x", "contributions": 1, "name": "Alice"}} - md = render_wall(contributors, "2026-06-08") - assert "valign=\"top\"" in md - - -def test_render_wall_uses_colgroup_for_uniform_column_widths(): - # colgroup gives GitHub's table renderer authoritative column widths, - # preventing content-based auto-sizing that produces uneven cell widths. - contributors = {"alice": {"avatar_url": "x", "contributions": 1, "name": "Alice"}} - md = render_wall(contributors, "2026-06-08") - assert "" in md - assert md.count("") == 6 # one per GRID_COLS + assert md.count("