summaryrefslogtreecommitdiff
path: root/rem2ics
blob: adb315dcef205b52da85fa0b31b441233eca3038 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
#
# Converts output of remind to ICS format:
#
# Usage example: rem -ppp12 | rem2ics
#
# https://tools.ietf.org/html/rfc5545
import json
import re
import sys
from datetime import datetime, timedelta, timezone
from os.path import basename, splitext


def escape_text(text: str) -> str:
    # https://tools.ietf.org/html/rfc5545#section-3.3.11
    return re.sub(r"[\\;:,]", r"\\\g<0>", text)


class Event:
    def __init__(self, uid: str, event_json: dict) -> None:
        self.event_json = event_json
        self.uid = uid

    def summary(self) -> str:
        event = self.event_json
        summary = event.get("body", "")
        match = re.search(r'%"(.+)%"', summary)
        if match:
            summary = match.group(1)
        else:
            summary = self.__remove_spec(event, summary)
        return escape_text(summary)

    def description(self) -> str:
        event = self.event_json
        summary = event.get("body", "")
        if '%"' not in summary:
            return summary
        summary = summary.replace(r'%"', "")
        summary = self.__remove_spec(event, summary)
        return escape_text(summary)

    def categories(self) -> str:
        filename = basename(self.event_json["filename"].upper())
        return splitext(filename)[0]

    def dtstart(self) -> str:
        event = self.event_json
        if "eventstart" in event:
            event_datetime = datetime.strptime(event["eventstart"], "%Y-%m-%dT%H:%M")
            return self.__datetime_format(event_datetime)
        return event["date"].replace("-", "")

    def dtend(self) -> str:
        event = self.event_json
        if is_multiple_days_event(event):
            event_datetime = datetime.strptime(event["until"], "%Y-%m-%d")
            return (event_datetime + timedelta(days=1)).strftime("%Y%m%d")
        if "eventduration" in event and "eventstart" in event:
            event_datetime = datetime.strptime(event["eventstart"], "%Y-%m-%dT%H:%M")
            return self.__datetime_format(
                event_datetime + timedelta(minutes=int(event["eventduration"]))
            )
        return self.dtstart()

    @staticmethod
    def __datetime_format(event_datetime: datetime) -> str:
        return event_datetime.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ")

    def __remove_spec(self, event: dict, summary: str) -> str:
        if "time" in event:
            summary = summary.split(" ", 1)[1]
        if "passthru" in event and "COLOR" in event["passthru"]:
            summary = " ".join(summary.split(" ")[3:])
        return summary


def create_uid(event: dict) -> str:
    if is_multiple_days_event(event):
        date = event["until"]
    else:
        date = event["date"]
    filename = event["filename"]
    lineno = event["lineno"]
    return f"{basename(filename)}:{lineno}:{date}"


def is_multiple_days_event(event: dict) -> bool:
    repeat = "rep" in event
    has_until_date = "until" in event
    has_time = "time" in event
    return repeat and has_until_date and not has_time


def print_ics(events: list[Event]) -> None:
    print("BEGIN:VCALENDAR")
    print("VERSION:2.0")
    for event in events:
        print("BEGIN:VEVENT")
        print(f"UID:{event.uid}")
        print(f"DTSTART:{event.dtstart()}")
        print(f"DTEND:{event.dtend()}")
        print(f"SUMMARY:{event.summary()}")
        print(f"DESCRIPTION:{event.description()}")
        print(f"CATEGORIES:{event.categories()}")
        print("END:VEVENT")
    print("END:VCALENDAR")


def rem2ics() -> None:
    added = set()
    events = []
    data = json.load(sys.stdin)
    for month in data:
        for event_json in month["entries"]:
            uid = create_uid(event_json)
            if uid in added:
                continue
            added.add(uid)
            events.append(Event(uid, event_json))
    print_ics(events)


if __name__ == "__main__":
    rem2ics()