Skip to content

Commit 4663f86

Browse files
committed
add gertrude abagal
1 parent 01807ab commit 4663f86

5 files changed

Lines changed: 187 additions & 5 deletions

File tree

_data/leadership.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@
5757
"title": "Welcoming Team"
5858
}
5959
],
60-
"Advisors": ["Benedict Koji Amofah", "Ronald Maravanyika", "Kojo Idrissa", "Carol Willing", "Jeff Triplett", "Dr. Kari L. Jordan"],
60+
"Advisors": [
61+
"Benedict Koji Amofah",
62+
"Ronald Maravanyika",
63+
"Kojo Idrissa",
64+
"Carol Willing",
65+
"Jeff Triplett",
66+
"Dr. Kari L. Jordan"
67+
],
6168
"Council": [
6269
"Dawn Wages",
6370
"Sarah Abderemane",
@@ -81,6 +88,7 @@
8188
"Lazouich Ford",
8289
"Abigail Afi Gbadago",
8390
"Israel Abiona",
84-
"Ariane Djeupang"
91+
"Ariane Djeupang",
92+
"Gertrude Abagale"
8593
]
8694
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
author:
3+
- Jay Miller
4+
date: 2026-03-22
5+
description: The BPD Council has recognized the work of Gertrude Abagale and invited
6+
them to join the Council.
7+
featured_image: /assets/images/gertrude-abagale-council.webp
8+
title: Gertrude Abagale Added to BPD Council
9+
---
10+
11+
Congratulations to [Gertrude Abagale](https://www.linkedin.com/in/gertrude-abagale/) for recognition and consideration for the BPD Council!
12+
13+
Gertrude Abagale is a Cybersecurity engineer and an active contributor to community-led open-source initiatives across Africa. She works extensively with Python to build secure backend systems and enterprise integrations. Beyond her professional work, she volunteers with PyLadies Ghana, Python Ghana, AWS User Group Accra, and Everything Open Source. Through mentoring, speaking, and community event organization, she supports women and underrepresented groups transitioning into tech, with a focus on creating accessible pathways and fostering sustainable, inclusive tech ecosystems.
14+
15+
The BPD council is the sounding board for decisions that need to be made in Black Python Devs. They are the advisory group who share how our actions can impact their communities. The council is made up of organizers, BPD leaders and those in leadership positions around the Python world.
16+
17+
Congratulations again to Gertrude!
341 KB
Loading

scripts/add_council_member.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""
2+
Script to add a new BPD Council member.
3+
4+
Steps performed:
5+
1. Adds the member's name to the Council list in _data/leadership.json
6+
2. Copies/converts the provided image to assets/images/
7+
3. Creates a blog post announcement in _posts/
8+
9+
Usage:
10+
python scripts/add_council_member.py \
11+
--name "First Last" \
12+
--image /path/to/photo.webp \
13+
--bio "Their bio text here..." \
14+
[--linkedin "https://linkedin.com/in/..."] \
15+
[--date 2026-03-22]
16+
"""
17+
18+
import argparse
19+
import json
20+
import shutil
21+
import subprocess
22+
import re
23+
from datetime import date
24+
from pathlib import Path
25+
26+
ROOT = Path(__file__).resolve().parent.parent
27+
LEADERSHIP_JSON = ROOT / "_data" / "leadership.json"
28+
POSTS_DIR = ROOT / "_posts"
29+
IMAGES_DIR = ROOT / "assets" / "images"
30+
31+
COUNCIL_BLURB = (
32+
"The BPD council is the sounding board for decisions that need to be made "
33+
"in Black Python Devs. They are the advisory group who share how our actions "
34+
"can impact their communities. The council is made up of organizers, BPD leaders "
35+
"and those in leadership positions around the Python world."
36+
)
37+
38+
39+
def slugify(name: str) -> str:
40+
return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-")
41+
42+
43+
def copy_image(source: Path, name_slug: str) -> str:
44+
"""Copy the image to assets/images as webp and return the site-relative path."""
45+
dest_name = f"{name_slug}-council.webp"
46+
dest = IMAGES_DIR / dest_name
47+
48+
if source.suffix.lower() == ".webp":
49+
shutil.copy2(source, dest)
50+
else:
51+
subprocess.run(
52+
["magick", str(source), str(dest)],
53+
check=True,
54+
)
55+
56+
return f"/assets/images/{dest_name}"
57+
58+
59+
def update_leadership(name: str) -> None:
60+
"""Add the name to the Council list in leadership.json."""
61+
data = json.loads(LEADERSHIP_JSON.read_text())
62+
63+
if name in data["Council"]:
64+
print(f"⚠ {name} is already in the Council list, skipping update.")
65+
return
66+
67+
data["Council"].append(name)
68+
LEADERSHIP_JSON.write_text(json.dumps(data, indent=2) + "\n")
69+
print(f"✓ Added {name} to leadership.json Council list.")
70+
71+
72+
def create_blog_post(
73+
name: str,
74+
bio: str,
75+
image_path: str,
76+
linkedin: str | None = None,
77+
post_date: date | None = None,
78+
) -> Path:
79+
"""Create the announcement blog post."""
80+
post_date = post_date or date.today()
81+
name_slug = slugify(name)
82+
filename = f"{post_date.isoformat()}-{name_slug}-added-to-bpd-council.md"
83+
filepath = POSTS_DIR / filename
84+
85+
# Build the name link
86+
if linkedin:
87+
name_link = f"[{name}]({linkedin})"
88+
else:
89+
name_link = name
90+
91+
first_name = name.split()[0]
92+
93+
content = f"""---
94+
author:
95+
- Jay Miller
96+
date: {post_date.isoformat()}
97+
description: The BPD Council has recognized the work of {name} and invited
98+
them to join the Council.
99+
featured_image: {image_path}
100+
title: {name} Added to BPD Council
101+
---
102+
103+
Congratulations to {name_link} for recognition and consideration for the BPD Council!
104+
105+
{bio}
106+
107+
{COUNCIL_BLURB}
108+
109+
Congratulations again to {first_name}!
110+
"""
111+
112+
filepath.write_text(content)
113+
print(f"✓ Created blog post: {filepath.relative_to(ROOT)}")
114+
return filepath
115+
116+
117+
def main():
118+
parser = argparse.ArgumentParser(
119+
description="Add a new member to the BPD Council"
120+
)
121+
parser.add_argument("--name", required=True, help="Full name of the new council member")
122+
parser.add_argument("--image", required=True, help="Path to the member's photo")
123+
parser.add_argument("--bio", required=True, help="Bio paragraph(s) for the blog post")
124+
parser.add_argument("--linkedin", default=None, help="LinkedIn profile URL (optional)")
125+
parser.add_argument(
126+
"--date",
127+
default=None,
128+
help="Post date in YYYY-MM-DD format (defaults to today)",
129+
)
130+
args = parser.parse_args()
131+
132+
post_date = date.fromisoformat(args.date) if args.date else date.today()
133+
image_source = Path(args.image).expanduser().resolve()
134+
135+
if not image_source.exists():
136+
parser.error(f"Image file not found: {image_source}")
137+
138+
name_slug = slugify(args.name)
139+
140+
# 1. Copy image
141+
image_path = copy_image(image_source, name_slug)
142+
print(f"✓ Image saved to {image_path}")
143+
144+
# 2. Update leadership.json
145+
update_leadership(args.name)
146+
147+
# 3. Create blog post
148+
create_blog_post(
149+
name=args.name,
150+
bio=args.bio,
151+
image_path=image_path,
152+
linkedin=args.linkedin,
153+
post_date=post_date,
154+
)
155+
156+
print("\nDone! Review the changes and commit when ready.")
157+
158+
159+
if __name__ == "__main__":
160+
main()

uv.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)