aggregator: Add an initial Aggregator implementation
authorW. Trevor King <wking@tremily.us>
Sun, 30 Jun 2013 14:27:44 +0000 (10:27 -0400)
committerW. Trevor King <wking@tremily.us>
Sun, 30 Jun 2013 16:26:18 +0000 (12:26 -0400)
Currently broken pending support for `for entry in feed`.  The version
and product identifier fields are required, as specified in RFC 5545,
sections 3.7.3 (Product Identifier) and 3.7.4 (Version).  They'll also
need to have special characters (e.g. commas) escaped, but I'll get to
that later.

pycalendar/aggregator.py [new file with mode: 0644]

diff --git a/pycalendar/aggregator.py b/pycalendar/aggregator.py
new file mode 100644 (file)
index 0000000..c54b122
--- /dev/null
@@ -0,0 +1,70 @@
+# Copyright
+
+class Aggregator (list):
+    r"""An iCalendar feed aggregator
+
+    Figure out where the example feeds are located, relative to the
+    directory from which you run this doctest (i.e., the project's
+    root directory).
+
+    >>> import os
+    >>> root_dir = os.curdir
+    >>> data_dir = os.path.abspath(os.path.join(root_dir, 'test', 'data'))
+    >>> base_url = 'file://{}'.format(data_dir.replace(os.sep, '/'))
+
+    >>> from .feed import Feed
+
+    >>> a = Aggregator(
+    ...     prodid='-//pycalendar//NONSGML testing//EN',
+    ...     feeds=[
+    ...         Feed(url='{}/{}'.format(base_url, name))
+    ...         for name in ['geohash.ics',]],
+    ...     )
+    >>> a  # doctest: +ELLIPSIS
+    [<Feed url:file://.../test/data/geohash.ics>]
+    >>> a.fetch()
+
+    Generate aggregate calendars with the ``.write`` method.
+
+    >>> import io
+    >>> stream = io.StringIO()
+    >>> a.write(stream=stream)
+    >>> value = stream.getvalue()
+    >>> value  # doctest: +ELLIPSIS
+    'BEGIN:VCALENDAR\r\nVERSION:2.0\r\n...END:VCALENDAR\r\n'
+    >>> print(value.replace('\r\n', '\n'))
+    BEGIN:VCALENDAR
+    VERSION:2.0
+    PRODID:-//pycalendar//NONSGML testing//EN
+    BEGIN:VEVENT
+    UID:2013-06-30@geohash.invalid
+    DTSTAMP:2013-06-30T00:00:00Z
+    DTSTART;VALUE=DATE:20130630
+    DTEND;VALUE=DATE:20130701
+    SUMMARY:XKCD geohashing\, Boston graticule
+    URL:http://xkcd.com/426/
+    LOCATION:Snow Hill\, Dover\, Massachusetts
+    GEO:42.226663,-71.28676
+    END:VEVENT
+    END:VCALENDAR
+    <BLANKLINE>
+    """
+    def __init__(self, prodid, version='2.0', feeds=None):
+        super(Aggregator, self).__init__()
+        self.prodid = prodid
+        self.version = version
+        if feeds:
+            self.extend(feeds)
+
+    def fetch(self):
+        for feed in self:
+            feed.fetch()
+
+    def write(self, stream):
+        stream.write('BEGIN:VCALENDAR\r\n')
+        stream.write('VERSION:{}\r\n'.format(self.version))
+        stream.write('PRODID:{}\r\n'.format(self.prodid))
+        for feed in self:
+            for entry in feed:
+                entry.write(stream=stream)
+        stream.write('END:VCALENDAR\r\n')