entry: Add Entry.process() and .get*()
[pycalendar.git] / pycalendar / entry.py
1 # Copyright
2
3 import logging as _logging
4
5 from . import text as _text
6
7
8 _LOG = _logging.getLogger(__name__)
9
10
11 class Entry (object):
12     r"""An iCalendar entry (e.g. VEVENT)
13
14     Get an entry.
15
16     >>> from .feed import Feed
17
18     >>> import os
19     >>> root_dir = os.curdir
20     >>> data_file = os.path.abspath(os.path.join(
21     ...         root_dir, 'test', 'data', 'geohash.ics'))
22     >>> url = 'file://{}'.format(data_file.replace(os.sep, '/'))
23
24     >>> feed = Feed(url=url)
25     >>> feed.fetch()
26     >>> entry = feed.pop()
27
28     Investigate the entry.
29
30     >>> print(entry)
31     BEGIN:VEVENT
32     UID:2013-06-30@geohash.invalid
33     DTSTAMP:2013-06-30T00:00:00Z
34     DTSTART;VALUE=DATE:20130630
35     DTEND;VALUE=DATE:20130701
36     SUMMARY:XKCD geohashing\, Boston graticule
37     URL:http://xkcd.com/426/
38     LOCATION:Snow Hill\, Dover\, Massachusetts
39     GEO:42.226663,-71.28676
40     END:VEVENT
41
42     >>> entry.type
43     'VEVENT'
44     >>> entry.content  # doctest: +ELLIPSIS
45     'BEGIN:VEVENT\r\nUID:...\r\nEND:VEVENT\r\n'
46
47     Use the ``.get*()`` methods to access individual fields.
48
49     >>> entry.get('LOCATION')
50     'Snow Hill\\, Dover\\, Massachusetts'
51     >>> entry.get_text('LOCATION')
52     'Snow Hill, Dover, Massachusetts'
53     """
54     def __init__(self, type, content=None):
55         super(Entry, self).__init__()
56         self.type = type
57         self.content = content
58         self.lines = None
59         if content:
60             self.process()
61
62     def __str__(self):
63         if self.content:
64             return self.content.replace('\r\n', '\n').strip()
65         return ''
66
67     def __repr__(self):
68         return '<{} type:{}>'.format(type(self).__name__, self.type)
69
70     def process(self):
71         self.unfold()
72
73     def unfold(self):
74         """Unfold wrapped lines
75
76         Following :RFC:`5545`, section 3.1 (Content Lines)
77         """
78         self.lines = []
79         semantic_line_chunks = []
80         for line in self.content.splitlines():
81             lstrip = line.lstrip()
82             if lstrip != line:
83                 if not semantic_line_chunks:
84                     raise ValueError(
85                         ('whitespace-prefixed line {!r} is not a continuation '
86                          'of a previous line').format(line))
87                 semantic_line_chunks.append(lstrip)
88             else:
89                 if semantic_line_chunks:
90                     self.lines.append(''.join(semantic_line_chunks))
91                 semantic_line_chunks = [line]
92         if semantic_line_chunks:
93             self.lines.append(''.join(semantic_line_chunks))
94
95     def get(self, key, **kwargs):
96         for k in kwargs.keys():
97             if k != 'default':
98                 raise TypeError(
99                     'get() got an unexpected keyword argument {!r}'.format(
100                         k))
101         for line in self.lines:
102             k,value = [x.strip() for x in line.split(':', 1)]
103             if k == key:
104                 return value
105         if 'default' in kwargs:
106             return kwargs['default']
107         raise KeyError(key)
108
109     def get_text(self, *args, **kwargs):
110         value = self.get(*args, **kwargs)
111         return _text.unescape(value)
112
113     def write(self, stream):
114         stream.write(self.content)