You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
5.6 KiB
199 lines
5.6 KiB
from datetime import datetime, timedelta |
|
from typing import Iterable |
|
|
|
import click |
|
import pyperclip |
|
import requests |
|
|
|
# typing |
|
RawEvent = dict[str : tuple[str | datetime, str]] |
|
Event = dict[str:str] |
|
TableMetaData = dict[str:int] |
|
|
|
ICAL_URL = "https://bwsyncandshare.kit.edu/remote.php/dav/public-calendars/69o9HEM9aeZnER8S/?export" |
|
|
|
ICS_DATE_FORMAT = "%Y%m%dT%H%M%S" |
|
GERMAN_DATE_FORMAT = "%d.%m.%Y %H:%M" |
|
|
|
EARLIEST = datetime.now() - timedelta(days=30 * 3) |
|
LATEST = datetime.now() + timedelta(days=30 * 6) |
|
|
|
TABLE_COLUMN_ORDER = ("date", "location", "presenter") |
|
|
|
# for mail sending |
|
MAIL_RECIPIENTS = ",".join( |
|
[ |
|
"cpistaff@imtek.de", |
|
"andrealayedra@hotmail.com", |
|
"jaro.homburger@biologie.uni-freiburg.de", |
|
"kubilay.acar@continental.com", |
|
"Mario.diehlmann@basf.com", |
|
"paul.fernandez@fmf.uni-freiburg.de", |
|
"sunil.shetty@uniklinik-freiburg.de", |
|
"sofia.genoves94@gmail.com", |
|
"sofia.genoves@yahoo.com.ar", |
|
] |
|
) |
|
MAIL_SUBJECT = "Updated Group Meeting Schedule" |
|
MAIL_START = """ |
|
Dear All, |
|
|
|
There is an updated group-seminar schedule available: |
|
""" |
|
MAIL_END = """ |
|
If (and only if) you can't make it in person, you can join the presentations via zoom: https://uni-freiburg.zoom-x.de/j/62063730956?pwd=6poraREYWvuHkStuYDRvYhO4vhmTbt.1 |
|
|
|
Meeting-ID: 620 6373 0956 |
|
Code: Lm5xaDXPH |
|
|
|
You can have a look in our group meeting calendar at: https://bwsyncandshare.kit.edu/apps/calendar/p/69o9HEM9aeZnER8S |
|
|
|
Or on our wiki: https://wiki.cpi.imtek.uni-freiburg.de/SeminarSchedule |
|
|
|
If you want to add the schedule to your calendar app of choice, please use this subscribe link: https://bwsyncandshare.kit.edu/remote.php/dav/public-calendars/69o9HEM9aeZnER8S/?export |
|
|
|
Best, |
|
Holgi |
|
""" |
|
|
|
# parsers |
|
|
|
|
|
def _parse_text(ics_text: str) -> str: |
|
text = ics_text.strip() |
|
return text.replace("\\,", ",") |
|
|
|
|
|
def _parse_presenter(ics_text: str) -> str: |
|
text = _parse_text(ics_text) |
|
first, sep, last = text.partition(":") |
|
return first if not sep else last.strip() |
|
|
|
|
|
def _parse_datetime(ics_text: str) -> str: |
|
return datetime.strptime(ics_text.strip(), ICS_DATE_FORMAT) |
|
|
|
|
|
def _format_datetime(date: datetime) -> str: |
|
return datetime.strftime(date, GERMAN_DATE_FORMAT) |
|
|
|
|
|
class Transformer: |
|
def __init__(self, name, parser, formater): |
|
self.name = name |
|
self.parser = parser |
|
self.formater = formater |
|
|
|
|
|
vevent_transformer = { |
|
"DTSTART": Transformer( |
|
"date", parser=_parse_datetime, formater=_format_datetime |
|
), |
|
"LOCATION": Transformer("location", parser=_parse_text, formater=str), |
|
"SUMMARY": Transformer("presenter", parser=_parse_presenter, formater=str), |
|
} |
|
|
|
|
|
def get_ics(url: str) -> str: |
|
try: |
|
response = requests.get(url) |
|
except IndexError: |
|
msg = f"Could not load calender from url" |
|
raise IOError(msg) |
|
return response.text |
|
|
|
|
|
def extract_event_info(response: str) -> Iterable[Event]: |
|
events = ( |
|
event |
|
for event in _extract_event_info(response) |
|
if EARLIEST < event["date"][0] < LATEST |
|
) |
|
for event in sorted(events, key=lambda d: d["date"]): |
|
yield {key: values[1] for key, values in event.items()} |
|
|
|
|
|
def _extract_event_info(response: str) -> Iterable[RawEvent]: |
|
handle = (line for line in response.splitlines()) |
|
for line in handle: |
|
if line.upper().startswith("BEGIN:VEVENT"): |
|
yield extract_vevent(handle) |
|
|
|
|
|
def extract_vevent(handle: Iterable[str]) -> RawEvent: |
|
event = {} |
|
for line in handle: |
|
if line.upper().startswith("END:VEVENT"): |
|
break |
|
raw_key, _, raw_value = line.partition(":") |
|
key, _, _ = raw_key.partition(";") |
|
if t := vevent_transformer.get(key): |
|
value = t.parser(raw_value) |
|
text = t.formater(value) |
|
event[t.name] = (value, text) |
|
return event |
|
|
|
|
|
def create_wiki_table(events: Iterable[Event]) -> str: |
|
table_meta_data = _get_table_meta_data(events) |
|
headers = {key: f"'''{key.capitalize()}'''" for key in TABLE_COLUMN_ORDER} |
|
lines = [ |
|
_create_table_line(headers, table_meta_data, sep="||", bracket=True), |
|
] |
|
lines.extend( |
|
_create_table_body(events, table_meta_data, sep="||", bracket=True) |
|
) |
|
return "\n".join(lines) |
|
|
|
|
|
def create_email_table(events: Iterable[Event]) -> str: |
|
table_meta_data = _get_table_meta_data(events) |
|
lines = _create_table_body(events, table_meta_data, sep="|", bracket=False) |
|
return "\n".join(lines) |
|
|
|
|
|
def _create_table_body( |
|
events: Iterable[Event], meta: TableMetaData, sep: str, *, bracket: bool |
|
) -> list[str]: |
|
return [_create_table_line(event, meta, sep=sep, bracket=bracket) for event in events] |
|
|
|
|
|
def _create_table_line( |
|
event: Event, meta: TableMetaData, sep: str, *, bracket: bool |
|
) -> str: |
|
columns = [event[key].ljust(meta[key]) for key in TABLE_COLUMN_ORDER] |
|
inner = f" {sep} ".join(columns) |
|
return f"{sep} {inner} {sep}" if bracket else inner |
|
|
|
|
|
def _get_table_meta_data(events: Iterable[Event]) -> TableMetaData: |
|
meta = {} |
|
for event in events: |
|
for key, value in event.items(): |
|
meta[key] = max(meta.get(key, 0), len(value)) |
|
return meta |
|
|
|
|
|
@click.command() |
|
def group_schedule(): |
|
ics_file = get_ics(ICAL_URL) |
|
events = list(extract_event_info(ics_file)) |
|
|
|
wiki_table = create_wiki_table(events) |
|
email_table = create_email_table(events) |
|
|
|
components = [ |
|
wiki_table, |
|
"", |
|
"", |
|
"", |
|
MAIL_RECIPIENTS, |
|
"", |
|
MAIL_SUBJECT, |
|
MAIL_START, |
|
email_table, |
|
MAIL_END, |
|
] |
|
text = "\n".join(components) |
|
print(text) |
|
pyperclip.copy(text)
|
|
|