-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathical-gen.py
More file actions
executable file
·117 lines (98 loc) · 3.75 KB
/
ical-gen.py
File metadata and controls
executable file
·117 lines (98 loc) · 3.75 KB
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
#!/usr/bin/env -S uv --quiet run --no-project --script --
# https://peps.python.org/pep-0723/
# https://github.com/astral-sh/uv
# /// script
# # Docopt issues SyntaxWarning in Python 3.12
# requires-python = ">=3.11,<3.12"
# dependencies = [
# "docopt >=0.6.2",
# "icalendar >=4.0.1",
# "python-dateutil >=2.7.2",
# ]
# ///
"""
Usage:
{prog} START_DT_WITH_TIMEZONE END_DT_WITH_TIMEZONE [--summary=TEXT] [--description=TEXT]
[--attachment=PATH]... [--url=URL] [--output=PATH]
{prog} --help
{prog} --version
Options:
-s, --summary=TEXT Summary
[default: Event]
-d, --description=TEXT Description
-u, --url=URL URL
-a, --attachment=PATH Path to a file to attach to the calendar event.
-o, --output=PATH Path to the file to be written.
[default: ./event.ics]
"""
import sys, os, datetime, json, mimetypes, urllib.request, base64
import icalendar
import docopt
import dateutil.parser
# TODO Use https://openflights.org/data.html to give options of entering airport code instead of timezone. Also see if it has route info and if it is possible to generate the entire calendar event by just providing flight code and departure date.
__prog__ = "ical-gen.py"
__product__ = f"The {json.dumps(__prog__)} Script"
__version__ = "0.0.0"
def main(*, args):
params = docopt.docopt(
doc=__doc__.format(
prog=__prog__,
),
argv=args,
help=True,
version=__version__,
options_first=False,
)
assert params.pop("--help") == False
assert params.pop("--version") == False
dtstart = parse_dt(params.pop("START_DT_WITH_TIMEZONE"))
dtend = parse_dt(params.pop("END_DT_WITH_TIMEZONE"))
summary = params.pop("--summary") or "Event"
description = params.pop("--description") or f"Generated By: {__product__}"
url = params.pop("--url")
attachments = params.pop("--attachment")
output_path = params.pop("--output")
assert not params, params
print("Summary: {}" .format(summary))
print("Start: {:%c %z}".format(dtstart))
print("End: {:%c %z}".format(dtend))
print("Duration: {}" .format(dtend - dtstart))
print("Description: {}" .format(description))
print(file=sys.stderr)
event = icalendar.Event()
event.add("SUMMARY", icalendar.vText(summary))
event.add("DESCRIPTION", icalendar.vText(description))
event.add("DTSTART", icalendar.vDatetime(dtstart))
event.add("DTEND", icalendar.vDatetime(dtend))
if url is not None:
event.add("URL", icalendar.vUri(url))
for path in attachments:
add_attachment_to_calendar_event(event, path)
calendar = icalendar.Calendar()
#TODO:vruyr:bugs Verify with the standard.
calendar.add("PRODID", f"-//{__product__}//vruyr.com//EN")
calendar.add("VERSION", "2.0")
calendar.add_component(event)
with open(output_path, "wb") as fo:
fo.write(calendar.to_ical())
def add_attachment_to_calendar_event(event, path):
mtype, dummy_encoding = mimetypes.MimeTypes().guess_type(urllib.request.pathname2url(path))
assert mtype is not None
with open(path, "rb") as fo:
filebytes = fo.read()
#TODO:vruyr:bugs The vBinary() seems to do a roundtrip encoding to and from unicode which
# might corrupt the data.
# See https://github.com/collective/icalendar/issues/205
b = icalendar.prop.vInline(base64.b64encode(filebytes).decode("ascii"))
b.params["VALUE"] = "BINARY"
b.params["ENCODING"] = "BASE64"
b.params["FMTTYPE"] = mtype
#TODO:vruyr:bugs What is the standard way to set the filename?
b.params["X-APPLE-FILENAME"] = os.path.basename(path)
event.add("ATTACH", b, encode=False)
def parse_dt(dt_s):
dt = dateutil.parser.parse(dt_s)
dt = dt.replace(tzinfo=datetime.timezone(dt.tzinfo.utcoffset(dt)))
return dt
if __name__ == "__main__":
sys.exit(main(args=sys.argv[1:]))