set up
This commit is contained in:
parent
2cd85a9f9d
commit
edb88a8644
|
|
@ -0,0 +1,178 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate calendar.ics and README.md from meetings.yaml
|
||||
"""
|
||||
|
||||
import yaml
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
import hashlib
|
||||
|
||||
|
||||
def load_meetings(yaml_file='meetings.yaml'):
|
||||
"""Load meetings from YAML file"""
|
||||
with open(yaml_file, 'r') as f:
|
||||
data = yaml.safe_load(f)
|
||||
return data['meetings']
|
||||
|
||||
|
||||
def filter_meetings(meetings, months=3):
|
||||
"""Filter meetings to show only those within the last/next N months"""
|
||||
now = datetime.now()
|
||||
cutoff_past = now - timedelta(days=30 * months)
|
||||
cutoff_future = now + timedelta(days=30 * months)
|
||||
|
||||
filtered = []
|
||||
for meeting in meetings:
|
||||
meeting_date = datetime.strptime(meeting['date'], '%Y-%m-%d')
|
||||
if cutoff_past <= meeting_date <= cutoff_future:
|
||||
filtered.append(meeting)
|
||||
|
||||
return sorted(filtered, key=lambda m: m['date'])
|
||||
|
||||
|
||||
def generate_ics(meetings, output_file='calendar.ics'):
|
||||
"""Generate iCalendar file from meetings"""
|
||||
lines = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//Linux Cast User Group//Events//EN',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'X-WR-CALNAME:Linux Cast User Group Meetings',
|
||||
'X-WR-TIMEZONE:America/New_York',
|
||||
'X-WR-CALDESC:Bi-monthly meetings for the Linux Cast User Group',
|
||||
''
|
||||
]
|
||||
|
||||
for meeting in meetings:
|
||||
# Parse date and time
|
||||
date_str = meeting['date']
|
||||
time_str = meeting['time']
|
||||
tz_str = meeting['timezone']
|
||||
|
||||
dt = datetime.strptime(f"{date_str} {time_str}", '%Y-%m-%d %H:%M')
|
||||
tz = ZoneInfo(tz_str)
|
||||
dt_local = dt.replace(tzinfo=tz)
|
||||
|
||||
# Convert to UTC for iCalendar
|
||||
dt_utc = dt_local.astimezone(ZoneInfo('UTC'))
|
||||
dt_end_utc = dt_utc + timedelta(hours=1)
|
||||
|
||||
# Generate unique ID
|
||||
uid_base = f"{date_str}-{meeting['title']}"
|
||||
uid = hashlib.md5(uid_base.encode()).hexdigest()
|
||||
|
||||
# Format timestamps
|
||||
dtstamp = datetime.now(ZoneInfo('UTC')).strftime('%Y%m%dT%H%M%SZ')
|
||||
dtstart = dt_utc.strftime('%Y%m%dT%H%M%SZ')
|
||||
dtend = dt_end_utc.strftime('%Y%m%dT%H%M%SZ')
|
||||
|
||||
lines.extend([
|
||||
'BEGIN:VEVENT',
|
||||
f'UID:{uid}@thelinuxcast.org',
|
||||
f'DTSTAMP:{dtstamp}',
|
||||
f'DTSTART:{dtstart}',
|
||||
f'DTEND:{dtend}',
|
||||
f'SUMMARY:Linux Cast User Group: {meeting["title"]}',
|
||||
f'DESCRIPTION:{meeting["title"]}\\n\\n{meeting["description"]}',
|
||||
f'LOCATION:{meeting.get("location", "Online")}',
|
||||
'STATUS:CONFIRMED',
|
||||
'END:VEVENT',
|
||||
''
|
||||
])
|
||||
|
||||
lines.append('END:VCALENDAR')
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
print(f"Generated {output_file}")
|
||||
|
||||
|
||||
def generate_readme(meetings, output_file='README.md'):
|
||||
"""Generate README.md from meetings"""
|
||||
now = datetime.now()
|
||||
|
||||
# Separate past and upcoming meetings
|
||||
past_meetings = []
|
||||
upcoming_meetings = []
|
||||
|
||||
for meeting in meetings:
|
||||
meeting_date = datetime.strptime(meeting['date'], '%Y-%m-%d')
|
||||
if meeting_date < now:
|
||||
past_meetings.append(meeting)
|
||||
else:
|
||||
upcoming_meetings.append(meeting)
|
||||
|
||||
# Sort
|
||||
past_meetings = sorted(past_meetings, key=lambda m: m['date'], reverse=True)
|
||||
upcoming_meetings = sorted(upcoming_meetings, key=lambda m: m['date'])
|
||||
|
||||
lines = [
|
||||
'# Linux Cast User Group Meetings',
|
||||
'',
|
||||
'**[Subscribe to Calendar](calendar.ics)** | **[Download ICS](calendar.ics)**',
|
||||
'',
|
||||
]
|
||||
|
||||
# Upcoming events
|
||||
if upcoming_meetings:
|
||||
lines.append('## Upcoming Events')
|
||||
lines.append('')
|
||||
|
||||
for meeting in upcoming_meetings:
|
||||
dt = datetime.strptime(f"{meeting['date']} {meeting['time']}", '%Y-%m-%d %H:%M')
|
||||
formatted_date = dt.strftime('%B %d, %Y at %I:%M %p')
|
||||
|
||||
lines.extend([
|
||||
f"### {meeting['title']}",
|
||||
f"- **Date:** {formatted_date} EST",
|
||||
f"- **Location:** {meeting.get('location', 'Online')}",
|
||||
f"- **Description:** {meeting['description']}",
|
||||
''
|
||||
])
|
||||
|
||||
# Recent past events
|
||||
if past_meetings:
|
||||
lines.append('## Recent Past Events')
|
||||
lines.append('')
|
||||
|
||||
for meeting in past_meetings:
|
||||
dt = datetime.strptime(f"{meeting['date']} {meeting['time']}", '%Y-%m-%d %H:%M')
|
||||
formatted_date = dt.strftime('%B %d, %Y at %I:%M %p')
|
||||
|
||||
lines.extend([
|
||||
f"### {meeting['title']}",
|
||||
f"- **Date:** {formatted_date} EST",
|
||||
f"- **Location:** {meeting.get('location', 'Online')}",
|
||||
f"- **Description:** {meeting['description']}",
|
||||
''
|
||||
])
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
print(f"Generated {output_file}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
print("Loading meetings from meetings.yaml...")
|
||||
meetings = load_meetings()
|
||||
|
||||
print(f"Found {len(meetings)} total meetings")
|
||||
|
||||
# Filter to 3 months
|
||||
filtered_meetings = filter_meetings(meetings, months=3)
|
||||
print(f"Filtered to {len(filtered_meetings)} meetings within 3 months")
|
||||
|
||||
# Generate files
|
||||
generate_ics(filtered_meetings)
|
||||
generate_readme(filtered_meetings)
|
||||
|
||||
print("Done!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
meetings:
|
||||
- date: "2025-10-09"
|
||||
time: "11:15"
|
||||
timezone: "America/Detroit"
|
||||
title: "EARLY LUG"
|
||||
description: "Join us for the first Linux User Group of the Month"
|
||||
location: "Online"
|
||||
|
||||
- date: "2025-11-23"
|
||||
time: "20:15"
|
||||
timezone: "America/Detroit"
|
||||
title: "LATE LUG"
|
||||
description: "Join us for the second Linux User Group of the Month"
|
||||
location: "Online"
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
name: Update Calendar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'meetings.yaml'
|
||||
- 'generate_calendar.py'
|
||||
- '.gitea/workflows/update-calendar.yaml'
|
||||
schedule:
|
||||
# Run daily at 00:00 UTC
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-calendar:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install pyyaml
|
||||
|
||||
- name: Generate calendar files
|
||||
run: |
|
||||
python generate_calendar.py
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
git diff --quiet calendar.ics README.md || echo "changed=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.check_changes.outputs.changed == 'true'
|
||||
run: |
|
||||
git config --local user.email "noreply@thelinuxcast.org"
|
||||
git config --local user.name "Calendar Bot"
|
||||
git add calendar.ics README.md
|
||||
git commit -m "Auto-update calendar files
|
||||
|
||||
🤖 Generated with automation"
|
||||
git push
|
||||
Loading…
Reference in New Issue