1 # Copyright (C) 2013 W. Trevor King <wking@tremily.us>
3 # This file is part of pycalender.
5 # pycalender is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
10 # pycalender is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License along with
15 # pycalender. If not, see <http://www.gnu.org/licenses/>.
17 import codecs as _codecs
18 import logging as _logging
19 import urllib.request as _urllib_request
21 from . import USER_AGENT as _USER_AGENT
22 from . import property as _property
23 from . import unfold as _unfold
24 from .component import calendar as _calendar
27 _LOG = _logging.getLogger(__name__)
30 class Feed (_calendar.Calendar):
31 r"""An iCalendar feed (:RFC:`5545`)
33 Figure out where the example feed is located, relative to the
34 directory from which you run this doctest (i.e., the project's
38 >>> root_dir = os.curdir
39 >>> data_file = os.path.abspath(os.path.join(
40 ... root_dir, 'test', 'data', 'geohash.ics'))
41 >>> url = 'file://{}'.format(data_file.replace(os.sep, '/'))
43 Create a new feed pointing to this URL.
46 >>> f # doctest: +ELLIPSIS
47 <Feed url:file://.../test/data/geohash.ics>
49 Load the feed content.
53 The ``.__str__`` method displays the feed content using Python's
56 >>> print(f) # doctest: +REPORT_UDIFF
58 PRODID:-//Example Calendar//NONSGML v1.0//EN
61 DTSTAMP:20130630T000000Z
62 UID:2013-06-30@geohash.invalid
63 DTSTART;VALUE=DATE:20130630
64 GEO:42.226663;-71.286760
65 LOCATION:Snow Hill\, Dover\, Massachusetts
66 SUMMARY:XKCD geohashing\, Boston graticule
67 URL:http://xkcd.com/426/
68 DTEND;VALUE=DATE:20130701
72 To get the CRLF line endings specified in :RFC:`5545`, use the
76 >>> stream = io.StringIO()
77 >>> f.write(stream=stream)
78 >>> stream.getvalue() # doctest: +ELLIPSIS
79 'BEGIN:VCALENDAR\r\nPRODID:...END:VCALENDAR\r\n'
81 You can also iterate through events:
83 >>> for event in f['VEVENT']:
84 ... print(repr(event))
86 ... # doctest: +ELLIPSIS, +REPORT_UDIFF
87 <Event name:VEVENT at 0x...>
89 DTSTAMP:20130630T000000Z
90 UID:2013-06-30@geohash.invalid
91 DTSTART;VALUE=DATE:20130630
92 GEO:42.226663;-71.286760
93 LOCATION:Snow Hill\, Dover\, Massachusetts
94 SUMMARY:XKCD geohashing\, Boston graticule
95 URL:http://xkcd.com/426/
96 DTEND;VALUE=DATE:20130701
99 def __init__(self, url, user_agent=None):
100 super(Feed, self).__init__(type='VCALENDAR')
102 if user_agent is None:
103 user_agent = _USER_AGENT
104 self.user_agent = user_agent
107 return '<{}.{} url:{}>'.format(
108 self.__module__, type(self).__name__, self.url)
112 request = _urllib_request.Request(
115 'User-Agent': self.user_agent,
118 with _urllib_request.urlopen(url=request) as f:
120 content_type = info.get('Content-type', None)
121 if content_type != 'text/calendar':
122 raise ValueError(content_type)
123 codec = _codecs.lookup('UTF-8')
124 with codec.streamreader(stream=f) as stream:
125 self.parse(stream=stream)
127 def parse(self, stream):
128 lines = _unfold.unfold(stream=stream)
130 prop = _property.parse(line=line)
131 if prop.name != 'BEGIN' or prop.value != self.name:
133 "stream {} must start with 'BEGIN:VCALENDAR', not {!r}".format(
135 self.read(lines=lines)