entry: Make Entry a subclass of dict
authorW. Trevor King <wking@tremily.us>
Sun, 30 Jun 2013 17:19:16 +0000 (13:19 -0400)
committerW. Trevor King <wking@tremily.us>
Sun, 30 Jun 2013 20:24:28 +0000 (16:24 -0400)
This gives us the familiar interface, and moves key/value processing
to a one-time-per-entry loop in ._fill_dict().

pycalendar/entry.py
pycalendar/feed.py

index 13bcea6c6cda24f0b7860bde7924c2adbc41b350..90e0bac0b060cf64c301abf86b9346fa60bc9a74 100644 (file)
@@ -8,7 +8,7 @@ from . import text as _text
 _LOG = _logging.getLogger(__name__)
 
 
-class Entry (object):
+class Entry (dict):
     r"""An iCalendar entry (e.g. VEVENT)
 
     Get an entry.
@@ -44,10 +44,19 @@ class Entry (object):
     >>> entry.content  # doctest: +ELLIPSIS
     'BEGIN:VEVENT\r\nUID:...\r\nEND:VEVENT\r\n'
 
-    Use the ``.get*()`` methods to access individual fields.
+    ``Entry`` subclasses Python's ``dict``, so you can access raw
+    field values in the usual ways.
 
+    >>> entry['LOCATION']
+    'Snow Hill\\, Dover\\, Massachusetts'
     >>> entry.get('LOCATION')
     'Snow Hill\\, Dover\\, Massachusetts'
+    >>> entry.get('missing')
+    >>> entry.get('missing', 'some default')
+    'some default'
+
+    You can also use ``get_text`` to unescape text fields.
+
     >>> entry.get_text('LOCATION')
     'Snow Hill, Dover, Massachusetts'
     """
@@ -59,6 +68,9 @@ class Entry (object):
         if content:
             self.process()
 
+    def __hash__(self):
+        return id(self)
+
     def __str__(self):
         if self.content:
             return self.content.replace('\r\n', '\n').strip()
@@ -68,7 +80,28 @@ class Entry (object):
         return '<{} type:{}>'.format(type(self).__name__, self.type)
 
     def process(self):
+        self.clear()
         self.unfold()
+        self._fill_dict()
+
+    def _fill_dict(self):
+        for index,verb,expected in [
+                [0, 'begin', 'BEGIN:{}'.format(self.type)],
+                [-1, 'end', 'END:{}'.format(self.type)],
+                ]:
+            if self.lines[index] != expected:
+                raise ValueError('entry should {} with {!r}, not {!r}'.format(
+                    verb, expected, self.lines[index]))
+        for line in self.lines[1:-1]:
+            key,value = [x.strip() for x in line.split(':', 1)]
+            if key in ['BEGIN' or 'END']:
+                raise NotImplementedError(line)
+            if key in self:
+                if type(self[key]) == str:
+                    self[key] = [self[key]]
+                self[key].append(value)
+            else:
+                self[key] = value
 
     def unfold(self):
         """Unfold wrapped lines
@@ -92,20 +125,6 @@ class Entry (object):
         if semantic_line_chunks:
             self.lines.append(''.join(semantic_line_chunks))
 
-    def get(self, key, **kwargs):
-        for k in kwargs.keys():
-            if k != 'default':
-                raise TypeError(
-                    'get() got an unexpected keyword argument {!r}'.format(
-                        k))
-        for line in self.lines:
-            k,value = [x.strip() for x in line.split(':', 1)]
-            if k == key:
-                return value
-        if 'default' in kwargs:
-            return kwargs['default']
-        raise KeyError(key)
-
     def get_text(self, *args, **kwargs):
         value = self.get(*args, **kwargs)
         return _text.unescape(value)
index 150a1749f460bcbd70a113b1d18c677bdd7a47a6..f104029419a736723ae8a64fc8b1e2ad7ce8e145 100644 (file)
@@ -127,11 +127,11 @@ class Feed (set):
                 _LOG.info('{!r}: begin {}'.format(self, _type))
                 stack.append(_type)
                 if len(stack) == 2:
-                    if entry:
+                    if entry is not None:
                         raise ValueError('double entry by line {}'.format(i))
                     entry = _entry.Entry(type=_type, content=[])
             _LOG.info(stack)
-            if entry:
+            if entry is not None:
                 entry.content.append(line)
             if line.startswith('END:'):
                 _type = line.split(':', 1)[1]