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)