diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..565727c --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,71 @@ +name: Build and Deploy Jekyll site to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python for preprocessing + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Generate NEXT_MEETINGS in README + run: | + python scripts/render_next_meetings.py + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.1" # Not needed with a .ruby-version file + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + cache-version: 0 # Increment this number if you need to re-download cached gems + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + + - name: Build with Jekyll + # Outputs to the './_site' directory by default + run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" + env: + JEKYLL_ENV: production + + - name: Upload artifact + # Automatically uploads an artifact from the './_site' directory by default + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 58964bb..ab8a1cd 100644 --- a/README.md +++ b/README.md @@ -5,34 +5,9 @@ Außerdem soll mit den Vorträgen das Wissen von und um C++ erweitert werden kö # Termine -Das nächste Treffen findet am **Mittwoch, den 3. Dezember 2025** statt. -Wir treffen uns ab **18.30 Uhr** bei [**Utimaco**](https://utimaco.com/), die Vorträge beginnen ab 19.00 Uhr. +Wir streben einen (grob) zwei-monatigen Rhythmus an. Weitere Informationen über [Meetup](https://www.meetup.com/de-DE/C-User-Gruppe-Aachen). -Geplante Vorträge: - -* **Safer integer handling with modern C++** (John Franklin Rickard) -* **ASIO & Coro - Networking Programming with C++20 Coroutines** (Peter Eisenlohr) - -Ort der Veranstaltung: - -[**Utimaco**](https://utimaco.com/) -Germanusstraße 4 -52080 Aachen - -Hinweis: Bitte den Eingang auf der Alt-Haarener Straße 36 benutzen. - -Weitere Informationen über [Meetup](https://www.meetup.com/de-DE/C-User-Gruppe-Aachen). - -## Nächste Termine - -Wir streben einen (grob) zwei-monatigen Rhythmus an, vorläufige Termine sind: - -* 4\. Februar 2026 (ModuleWorks) -* 1\. April 2026 (SLB) -* 3\. Juni 2026 (Magma) -* 5\. August 2026 (Utimaco) -* 7\. Oktober 2026 (ModuleWorks) -* 2\. Dezember 2026 (SLB) +{{ NEXT_MEETINGS }} ## Elektronischer Kalender @@ -40,7 +15,7 @@ Der jeweils aktuellste Stand der Planung befindet sich unter
[`https://github Diesen Kalender kann man sich durch Angabe der URL in der eigenen Kalender-App auf dem Mobiltelefon oder dem entsprechenden Programm (Outlook, Thunderbird) abonnieren und bleibt so immer auf dem neuesten Stand. -# Vorträge +## Vorträge Die Treffen leben von *euren* Vorträgen, meldet euch gerne mit euren Vorschlägen und Ideen. Wir möchten auch ausdrücklich darauf hinweisen, dass nicht jeder Vortrag über Template Metaprogrammierung gehen muss, auch "einfachere" Themen sind sehr willkommen und wir hatten bereits viele Vorträge die sehr gut angekommen sind. diff --git a/scripts/render_next_meetings.py b/scripts/render_next_meetings.py new file mode 100644 index 0000000..c24739e --- /dev/null +++ b/scripts/render_next_meetings.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +import json +import sys +from datetime import datetime +from zoneinfo import ZoneInfo +from pathlib import Path + +CAL_PATH = Path("calendar/cpp-aachen.jsoncal") +README_SRC = Path("README.md") +README_OUT = Path("README.md") # überschreibt im Arbeitsverzeichnis (nur im Build!) +PLACEHOLDER = "{{ NEXT_MEETINGS }}" +TZ = ZoneInfo("Europe/Berlin") + +def load_events(): + with CAL_PATH.open("r", encoding="utf-8") as f: + data = json.load(f) + events = data.get("events", []) + # parse start as aware datetime in specified timezone (if provided), otherwise naive->Berlin + norm_events = [] + for e in events: + start_str = e.get("start") + if not start_str: + continue + tzname = e.get("timezone") + tz = ZoneInfo(tzname) if tzname else TZ + try: + # ISO 8601 string -> naive dt; assign tz for proper comparison and formatting + dt = datetime.fromisoformat(start_str) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=tz) + else: + # normalize to event tz if provided + if tzname: + dt = dt.astimezone(tz) + except Exception: + continue + norm_events.append({ + "summary": e.get("summary", "C++ User Group Aachen"), + "location": e.get("location", ""), + "description": e.get("description", ""), + "url": e.get("url", ""), + "start": dt, + "tz": tz + }) + return norm_events + +def filter_future(events): + now = datetime.now(TZ) + return [e for e in events if e["start"] >= now] + +def format_markdown(events, limit=None): + if not events: + return "_Aktuell sind keine zukünftigen Termine geplant._" + # optional: limit anzeigen (z.B. Top 6) + if limit: + events = events[:limit] + lines = [] + # Markdown-Format: - Mi, 3. Dezember 2025, 18:30 — Utimaco, Aachen — Kurzbeschreibung + # inklusive Link, wenn vorhanden + for e in events: + dt = e["start"] + date_str = dt.strftime("%A, %-d. %B %Y, %H:%M") + # Einige Systeme unterstützen %-d nicht (Mac). Fallback: + if "%-d" in "%-d": + date_str = dt.strftime("%A, %d. %B %Y, %H:%M") + # führende Null optional entfernen: + if date_str[9] == "0": + date_str = date_str[:9] + date_str[10:] + + date_str = date_str.replace("Monday","Montag")\ + .replace("Tuesday","Dienstag")\ + .replace("Wednesday","Mittwoch")\ + .replace("Thursday","Donnerstag")\ + .replace("Friday","Freitag")\ + .replace("Saturday","Samstag")\ + .replace("Sunday","Sonntag") + + + parts = ['- **' + f"{date_str}" + 'Uhr**'] + + if e["location"]: + + location_text = ", [" \ + + e["location"] \ + + "](https://www.google.com/maps/search/?api=1&query=" \ + + e["location"].replace(" ", "+") \ + + ")\n" + + parts.append(location_text) + + desc = e["description"].strip() if e["description"] else "" + + if (desc != ""): + parts.append(" - ") + parts.append(desc.replace("\n", "\n - ")) + + parts.append("\n") + + lines.append("".join(parts)) + + + return "".join(lines) + +def main(): + events = load_events() + events = sorted(events, key=lambda e: e["start"]) + future = filter_future(events) + md = format_markdown(future, limit=10) + + text = README_SRC.read_text(encoding="utf-8") + if PLACEHOLDER not in text: + print(f"Warnung: Platzhalter {PLACEHOLDER} nicht in README gefunden.", file=sys.stderr) + text = text.replace(PLACEHOLDER, md) + README_OUT.write_text(text, encoding="utf-8") + +if __name__ == "__main__": + main()