Browse Source

metti quel che serve

boyska 2 years ago
commit
47a2307e87
1 changed files with 171 additions and 0 deletions
  1. 171 0
      ics2mdwn.py

+ 171 - 0
ics2mdwn.py

@@ -0,0 +1,171 @@
+#!/usr/bin/python3
+
+import argparse
+import os.path
+from typing import Iterable
+from pathlib import Path
+import yaml
+from datetime import timedelta
+
+from icalendar import Calendar, Event
+
+
+def get_parser():
+    p = argparse.ArgumentParser()
+    p.add_argument("files", nargs="+", type=str)
+    p.add_argument(
+        "--out-md-dir",
+        help="Output directory for markdown files",
+        default="content/talks/",
+        type=Path,
+    )
+    p.add_argument(
+        "--out-schedule",
+        help="Output schedule.yml file",
+        default="data/schedule.yml",
+        type=Path,
+    )
+    p.add_argument('--night-threshold', metavar='HOUR', default=5, type=int)
+    p.add_argument("--mode", choices=["hugo"], default="hugo")
+    return p
+
+
+def round_down(num, divisor):
+    """
+    >>> round_down(1000, 10)
+    1000
+    >>> round_down(1001, 10)
+    1000
+    >>> round_down(1009, 10)
+    1000
+    """
+    return num - (num % divisor)
+
+
+class Converter:
+    """
+    This class takes care of everything converter-related.
+
+    Objects are used to enable multiple output formats to be added pretty easily by subclassing
+    """
+
+    def __init__(self, args):
+        self.args = args
+        self.rooms = []
+        self.talks = {}
+        self.talk_room = {}  # map talk uid to room name
+        self.changed_files = []
+
+    def _fname_to_room(self, fpath: str) -> str:
+        return os.path.splitext(os.path.basename(fpath))[0]
+
+    def get_vevents_from_calendar(self, cal: Calendar) -> Iterable[Event]:
+        for subc in cal.subcomponents:
+            if type(subc) is Event:
+                yield subc
+
+    def load_input(self):
+        for fpath in self.args.files:
+            room = self._fname_to_room(fpath)
+            with open(fpath) as buf:
+                file_content = buf.read()
+            cal = Calendar.from_ical(file_content, multiple=True)
+            for subcal in cal:
+                for ev in self.get_vevents_from_calendar(subcal):
+                    uid: str = ev.decoded("uid").decode("ascii")
+                    self.talks[uid] = ev
+                    self.talk_room[uid] = room
+
+    def run(self):
+        self.rooms = [self._fname_to_room(fpath) for fpath in self.args.files]
+        self.load_input()
+        self.output()
+        for fpath in self.changed_files:
+            print(fpath)
+
+
+class HugoConverter(Converter):
+    """
+    add relevant output features to the base converter
+    """
+
+    def output_markdown(self):
+        for uid, talk in self.talks.items():
+            fname = "%s.md" % uid
+            fpath = self.args.out_md_dir / fname
+            self.changed_files.append(fpath)
+            with open(fpath, "w") as buf:
+                # preamble
+                buf.write("---\n")
+                yaml.safe_dump(
+                    dict(
+                        key=uid,
+                        title=talk.decoded("SUMMARY").decode("utf8"),
+                        format="conference",
+                        tags=[],
+                    ),
+                    buf,
+                )
+                buf.write("---\n\n")
+                if "DESCRIPTION" in talk:
+                    buf.write(talk.decoded("DESCRIPTION").decode("utf8"))
+
+    def output_schedule(self):
+        days = {}
+        for uid, talk in self.talks.items():
+            # TODO: talk just after midnight should belong to the preceding day
+            dt = talk.decoded("dtstart")
+            after_midnight = dt.time().hour < self.args.night_threshold
+            if after_midnight:
+                dt = dt - timedelta(days=1)
+            day = dt.strftime("%Y-%m-%d")
+
+            hour = talk.decoded("dtstart").time().hour
+            minute = talk.decoded("dtstart").time().minute
+            if after_midnight:
+                hour += 24
+
+            start = '%02d:%02d' % (hour, minute)
+            if day not in days:
+                days[day] = dict(day=day, start=start, rooms={})
+            if days[day]['start'] > start:
+                days[day]['start'] = start
+
+            room = self.talk_room[uid]
+            days[day]["rooms"].setdefault(room, dict(room=room, slots=[]))
+
+            talkstart = round_down(hour * 100 + minute, 10)
+            duration = talk.decoded("dtend") - talk.decoded("dtstart")
+            duration_minutes = int(duration.total_seconds() // 60)
+            duration_minutes = round_down(duration_minutes, 30)
+            slot = "%04d-%dmin" % (talkstart, duration_minutes)
+            days[day]["rooms"][room]["slots"].append(dict(slot=slot, talk=uid))
+
+        # convert from our intermediate format to the correct one
+        for d in days:
+            # vanity: let's sort
+            for room in days[d]["rooms"]:
+                days[d]["rooms"][room]["slots"].sort(key=lambda x: x["slot"])
+            # convert dict to list
+            days[d]["rooms"] = list(days[d]["rooms"].values())
+        out = {"schedule": list(days.values())}
+
+        # dump, finally
+        with open(self.args.out_schedule, "w") as buf:
+            yaml.safe_dump(out, buf)
+        self.changed_files.append(self.args.out_schedule)
+
+    def output(self):
+        self.output_markdown()
+        self.output_schedule()
+
+
+def main():
+    converter_register = {"hugo": HugoConverter}
+    args = get_parser().parse_args()
+    c = converter_register[args.mode](args)
+    c.run()
+
+
+if __name__ == "__main__":
+    main()